summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Boddie <paul@boddie.org.uk>2014-08-06 15:48:26 (GMT)
committerPaul Boddie <paul@boddie.org.uk>2014-08-06 15:48:26 (GMT)
commitfee17e6f7a5fa995f7d77c7822e3ed1f2f8bffb5 (patch)
treeb401d3b6c748c09895ec4f0a887f3f2f0cdb6e0d
parente0e89b980b8671eabf682cd83efc603775048228 (diff)
parenta8555e3e8789fd02b7d5749ea4fc51b84e57285f (diff)
downloadpykolab-dev/boddie.tar.gz
Merge branch 'master' of git://git.kolab.org/git/pykolab into dev/boddiedev/boddie
Conflicts: conf/kolab.conf po/POTFILES.in pykolab/auth/ldap/__init__.py pykolab/auth/ldap/cache.py pykolab/cli/cmd_rename_mailbox.py pykolab/imap/__init__.py pykolab/imap/cyrus.py pykolab/wap_client/__init__.py pykolab/xml/event.py
-rw-r--r--conf/kolab.conf9
-rw-r--r--po/POTFILES.in12
-rw-r--r--po/POTFILES.skip282
-rw-r--r--po/de.po2252
-rw-r--r--po/de_DE.po2230
-rw-r--r--po/pykolab.pot917
-rw-r--r--pykolab/Makefile.am4
-rw-r--r--pykolab/auth/__init__.py5
-rw-r--r--pykolab/auth/ldap/__init__.py113
-rw-r--r--pykolab/auth/ldap/cache.py35
-rw-r--r--pykolab/cli/cmd_count_domain_mailboxes.py6
-rw-r--r--pykolab/cli/cmd_delete_mailbox.py5
-rw-r--r--pykolab/cli/cmd_list_ous.py39
-rw-r--r--pykolab/cli/cmd_list_users.py39
-rw-r--r--pykolab/cli/cmd_rename_mailbox.py2
-rw-r--r--pykolab/cli/commands.py1
-rw-r--r--pykolab/imap/__init__.py15
-rw-r--r--pykolab/imap/cyrus.py12
-rw-r--r--pykolab/itip/__init__.py287
-rw-r--r--pykolab/translate.py18
-rw-r--r--pykolab/wap_client/__init__.py287
-rw-r--r--pykolab/xml/__init__.py4
-rw-r--r--pykolab/xml/attendee.py81
-rw-r--r--pykolab/xml/contact.py4
-rw-r--r--pykolab/xml/contact_reference.py21
-rw-r--r--pykolab/xml/event.py418
-rw-r--r--pykolab/xml/recurrence_rule.py118
-rw-r--r--tests/functional/resource_func.py4
-rw-r--r--tests/functional/test_wallace/test_005_resource_add.py17
-rw-r--r--tests/functional/test_wallace/test_005_resource_invitation.py265
-rw-r--r--tests/functional/test_wallace/test_007_invitationpolicy.py737
-rw-r--r--tests/functional/user_add.py4
-rw-r--r--tests/unit/test-002-attendee.py27
-rw-r--r--tests/unit/test-003-event.py391
-rw-r--r--tests/unit/test-011-itip.py412
-rw-r--r--tests/unit/test-011-wallace_resources.py208
-rw-r--r--tests/unit/test-012-wallace_invitationpolicy.py161
-rw-r--r--tests/unit/test-015-translate.py23
-rw-r--r--wallace/module_invitationpolicy.py1015
-rw-r--r--wallace/module_resources.py580
40 files changed, 8796 insertions, 2264 deletions
diff --git a/conf/kolab.conf b/conf/kolab.conf
index 6bf2125..627b23f 100644
--- a/conf/kolab.conf
+++ b/conf/kolab.conf
@@ -235,6 +235,10 @@ domain_rootdn_attribute = inetdomainbasedn
; The attribute that holds the quota.
quota_attribute = mailquota
+
+; The format of the modifytimestamp attribute values
+modifytimestamp_format = %Y%m%d%H%M%SZ
+
; A unique attribute that can be used to identify the entry beyond renames and
; moves. Note that 'nsuniqueid' is specific to all Netscape-based directory
; services.
@@ -374,7 +378,7 @@ mail_gid = vmail
mail_location = mbox:/var/mail/vmail/%%u
[wallace]
-modules = resources, footer
+modules = resources, invitationpolicy, footer
footer_text = /etc/kolab/footer.text
footer_html = /etc/kolab/footer.html
@@ -386,6 +390,9 @@ bind_proxy_uri =
; This will normally be localhost.
host =
+; default settings for kolabInvitationPolicy
+kolab_invitation_policy = ACT_ACCEPT_IF_NO_CONFLICT:example.org, ACT_MANUAL
+
; This is a domain name space specific section, that enables us to override
; all settings, for example, the LDAP URI, base and bind DNs, scopes, filters,
; etc. Note that overriding the LDAP settings for the primary domain name space
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 361bce9..5a5bc37 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -12,10 +12,10 @@ ext/python/Tools/freeze/makefreeze.py
ext/python/Tools/freeze/makemakefile.py
ext/python/Tools/freeze/parsesetup.py
ext/python/Tools/freeze/winmakemakefile.py
+kolab-cli.py
kolabd/__init__.py
kolabd/process.py
kolabd.py
-kolab-cli.py
pykolab/auth/__init__.py
pykolab/auth/ldap/auth_cache.py
pykolab/auth/ldap/cache.py
@@ -83,11 +83,13 @@ pykolab/imap/cyrus.py
pykolab/imap/__init__.py
pykolab/imap_utf7.py
pykolab/__init__.py
+pykolab/itip/__init__.py
pykolab/logger.py
pykolab/plugins/defaultfolders/__init__.py
pykolab/plugins/dynamicquota/__init__.py
pykolab/plugins/__init__.py
pykolab/plugins/recipientpolicy/__init__.py
+pykolab/plugins/roundcubedb/__init__.py
pykolab/plugins/sievemgmt/__init__.py
pykolab/setup/components.py
pykolab/setup/__init__.py
@@ -136,6 +138,8 @@ tests/functional/test_wallace/test_003_nonascii_subject.py
tests/functional/test_wallace/test_004_nonascii_addresses.py
tests/functional/test_wallace/test_005_resource_add.py
tests/functional/test_wallace/test_005_resource_invitation.py
+tests/functional/test_wallace/test_006_resource_performance.py
+tests/functional/test_wallace/test_007_invitationpolicy.py
tests/functional/test_wap_client/__init__.py
tests/functional/test_wap_client/test_001_connect.py
tests/functional/test_wap_client/test_002_user_add.py
@@ -159,12 +163,18 @@ tests/unit/test-007-ldap_syncrepl.py
tests/unit/test-008-sievelib.py
tests/unit/test-009-parse_ldap_uri.py
tests/unit/test-010-transliterate.py
+tests/unit/test-011-itip.py
tests/unit/test-011-wallace_resources.py
+tests/unit/test-012-wallace_invitationpolicy.py
+tests/unit/test-014-conf-and-raw.py
+tests/unit/test-015-translate.py
test-wallace.py
ucs/kolab_sieve.py
ucs/listener.py
wallace/__init__.py
wallace/module_footer.py
+wallace/module_gpgencrypt.py
+wallace/module_invitationpolicy.py
wallace/module_optout.py
wallace/module_resources.py
wallace/modules.py
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index b2ac963..1966fae 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -1,214 +1,70 @@
-activate_domain.py
-bin/test_parse_policy.py
-bin/test-read-input.py
-bonnie/__init__.py
-bonnie.py
-demo.py
-kwap/data/templates/home/kanarip/devel/src/kolab/pykolab.git/kwap/kwap/templates/about.mak.py
-kwap/data/templates/home/kanarip/devel/src/kolab/pykolab.git/kwap/kwap/templates/authentication.mak.py
-kwap/data/templates/home/kanarip/devel/src/kolab/pykolab.git/kwap/kwap/templates/data.mak.py
-kwap/data/templates/home/kanarip/devel/src/kolab/pykolab.git/kwap/kwap/templates/environ.mak.py
-kwap/data/templates/home/kanarip/devel/src/kolab/pykolab.git/kwap/kwap/templates/error.mak.py
-kwap/data/templates/home/kanarip/devel/src/kolab/pykolab.git/kwap/kwap/templates/index.mak.py
-kwap/data/templates/home/kanarip/devel/src/kolab/pykolab.git/kwap/kwap/templates/login.mak.py
-kwap/data/templates/home/kanarip/devel/src/kolab/pykolab.git/kwap/kwap/templates/master.mak.py
-kwap/ez_setup/__init__.py
-kwap/kwap/config/app_cfg.py
-kwap/kwap/config/environment.py
-kwap/kwap/config/__init__.py
-kwap/kwap/config/middleware.py
-kwap/kwap/controllers/domain.py
-kwap/kwap/controllers/error.py
-kwap/kwap/controllers/__init__.py
-kwap/kwap/controllers/root.py
-kwap/kwap/controllers/secure.py
-kwap/kwap/controllers/template.py
-kwap/kwap/grids/domains.py
-kwap/kwap/grids/__init__.py
-kwap/kwap/__init__.py
-kwap/kwap/lib/app_globals.py
-kwap/kwap/lib/base.py
-kwap/kwap/lib/helpers.py
-kwap/kwap/lib/__init__.py
-kwap/kwap/model/auth.py
-kwap/kwap/model/__init__.py
-kwap/kwap/templates/domain/__init__.py
-kwap/kwap/templates/__init__.py
-kwap/kwap/tests/functional/__init__.py
-kwap/kwap/tests/functional/test_authentication.py
-kwap/kwap/tests/functional/test_root.py
-kwap/kwap/tests/__init__.py
-kwap/kwap/tests/models/__init__.py
-kwap/kwap/tests/models/test_auth.py
-kwap/kwap/websetup/bootstrap.py
-kwap/kwap/websetup/__init__.py
-kwap/kwap/websetup/schema.py
-kwap/setup.py
-munich_demo.py
-play/anon-imap/anon-imap.py
-play/augeas-insert.py
-play/base_64_decode.py
-play/brepr_vs_unicode.py
-play/cleanup_acls.py
-play/cliconfmgmt.py
-play/conf-add-file-to-service.py
-play/conf-add-setting.py
-play/conf-list-config-files.py
-play/confmgmt/augeas.py
-play/confmgmt/db.py
-play/confmgmt/__init__.py
-play/confmgmt/model.py
-play/conf.py
-play/conf-settings-from-file.py
-play/conf-update-file.py
-play/detect-object-type.py
-play/dttz.py
-play/effectiverights.py
-play/flawed_zpush_testing_create_folders.py
-play/fork.py
-play/get_uid.py
-play/imap_annotations_test.py
-play/kolab-sap/kolab_smtp_access_policy.py
-play/libkolabxml/contact.py
-play/libkolabxml/event_imap.py
-play/libkolabxml/event.py
-play/libkolabxml/event_rfc822.py
-play/libkolabxml/todo.py
-play/load_test.py
-play/migrate_lowercase_uid.py
-play/migrate_mail_to_uid_prod.py
-play/migrate_mail_to_uid.py
-play/migrate_uid_to_mail_prod.py
-play/migrate_uid_to_mail.py
-play/noheaderini.py
-play/not-an-itip-message.py
-play/openssl/license.py
-play/parallel_persistent_searches.py
-play/parse_policy.py
-play/persistent_search_kolab_23.py
-play/persistent_search.py
-play/pooling.py
-play/purge_users_roundcube.py
-play/push_contacts.py
-play/pygpgme/sign.py
-play/pygpgme/verify.py
-play/regexps.py
-play/rolequota/__init__.py
-play/roundcube_database/identities.py
-play/roundcube_database/__init__.py
-play/roundcube_database.py
-play/roundcube_database/users.py
-play/split_message_file.py
-play/sqlalchemy_schemadisplay.py
-play/strip_many_headers.py
-play/sync_client.py
-play/sync_repl_kolab_23.py
-play/test_augeas_load.py
-play/test_cal_spread.py
-play/test-entitlements.py
-play/test_filter.py
-play/test_folders.py
-play/test-icalendar-attendee.py
-play/test_imapd.py
-play/test_imap.py
-play/test-kolab-smtp-access-policy-load.py
-play/test_kolabxml.py
-play/test-login-as.py
-play/test-namespace.py
-play/test-output.py
-play/test_page_control.py
-play/test-parse_ldap_uri.py
-play/test.py
-play/test_sk.ks.c.py
-play/test_socket_client.py
-play/test_socket.py
-play/test_submission.py
-play/test_undelete.py
-play/translit.py
-play/unicode_test.py
-play/unicode-to-ascii.py
-play/wap/domain.info.py
-play/wap/domains.capabilities.py
-play/wap/domains.list.py
-play/wap/form_value.generate_cn.py
-play/wap/form_value.generate_displayname.py
-play/wap/form_value.generate_mail.py
-play/wap/form_value.generate_password.py
-play/wap/form_value.generate_uid.py
-play/wap/form_value.list_options-c.py
-play/wap/form_value.select_options-ou.py
-play/wap/form_value.select_options-preferredlanguage.py
-play/wap/group.add.py
-play/wap/group.info.py
-play/wap/group.members_list.py
-play/wap/groups.list.py
-play/wap/group_types.list.py
-play/wap/role.add.py
-play/wap/role.capabilities.py
-play/wap/role.delete.py
-play/wap/role.find_by_attribute.py
-play/wap/role.info.py
-play/wap/roles.list.py
-play/wap/scu.py
-play/wap/system.capabilities.py
-play/wap/system.select_domain.py
-play/wap/user.add.py
-play/wap/user.delete.py
-play/wap/user.edit.py
-play/wap/user.info.py
-play/wap/users.list.py
-play/wap/user_types.list.py
-play/xmlevent.py
-play/xmlformat.py
-play/xmlfromical.py
-play/zorbadb/test.py
-pykolab/auth/ldap/fds/__init__.py
-pykolab/auth/ldap/msds/__init__.py
-pykolab/auth/ldap/openldap/__init__.py
-pykolab/auth/ldap/rhds/__init__.py
-pykolab/auth/ldap/sunds/__init__.py
-pykolab/auth/sql/__init__.py
-pykolab/cli/cmd_examine_message.py
-pykolab/cli/cmd_list_contacts.py
-pykolab/cli/cmd_list_events.py
-pykolab/cli/cmd_rebalance_mailboxes.py
-pykolab/cli/cmd_role_info.py
-pykolab/cli/cmd_set_quota.py
-pykolab/cli/cmd_summarize_quota_allocation.py
-pykolab/confmgmt/augeas.py
-pykolab/conf/parser.py
+kolabd/.___init__.py
+pykolab/auth/.___init__.py
+pykolab/auth/ldap/._cache.py
+pykolab/auth/ldap/.___init__.py
+pykolab/._base.py
+pykolab/cli/._cmd_create_mailbox.py
+pykolab/cli/._cmd_list_mailbox_metadata.py
+pykolab/cli/._cmd_list_quota.py
+pykolab/cli/._cmd_set_language.py
+pykolab/cli/._cmd_set_mailbox_acl.py
+pykolab/cli/._cmd_set_mail.py
+pykolab/conf/._defaults.py
+pykolab/conf/.___init__.py
+pykolab/._constants.py
pykolab/constants.py
-pykolab/ical/itip.py
-pykolab/imap/dovecot.py
-pykolab/plugins/roundcube/__init__.py
-pykolab/wap_client.old/__init__.py
-pykolab/xml/task.py
-sievelib-0.5/setup.py
-sievelib-0.5/sievelib/commands.py
-sievelib-0.5/sievelib/digest_md5.py
-sievelib-0.5/sievelib/factory.py
-sievelib-0.5/sievelib/__init__.py
-sievelib-0.5/sievelib/managesieve.py
-sievelib-0.5/sievelib/parser.py
-testaci.py
-test-ask_menu.py
-test-search.py
-test-send-mail-kolab_smtp_access_policy.py
-test-send-mail-kolab_smtp_access_policy-relay.py
-tests/functional/test_kolabd/test_004_many_aliases.py
-test-subscribe-address-to-ml.py
-tests/unit/test-011-base64_encoded_contact.py
-tests/unit/test-012-utf7.py
-test-urllib.py
-test-wallace-loadmsg.py
-test-wallace-resource.py
-test-wallace-send.py
-wallace/future_module_bcc.py
-wallace/future_module_conversations.py
-wallace/future_module_correctsentdate.py
-wallace/future_module_dlp.py
-wallace/future_module_footer.py
-wallace/future_module_freebusy.py
-wallace/future_module_googletranslate.py
-wallace/future_module_statistics.py
-wap_raw.py
+pykolab/._constants.py.in
+pykolab/imap/._cyrus.py
+pykolab/imap/.___init__.py
+pykolab/.___init__.py
+pykolab/itip/.___init__.py
+pykolab/._logger.py
+pykolab/plugins/dynamicquota/.___init__.py
+pykolab/plugins/.___init__.py
+pykolab/plugins/roundcubedb/.___init__.py
+pykolab/setup/.___init__.py
+pykolab/setup/._setup_freebusy.py
+pykolab/setup/._setup_ldap.py
+pykolab/setup/._setup_mta.py
+pykolab/setup/._setup_roundcube.py
+pykolab/._translate.py
+pykolab/._translit.py
+pykolab/._utils.py
+pykolab/wap_client/.___init__.py
+pykolab/xml/._attendee.py
+pykolab/xml/._event.py
+pykolab/xml/.___init__.py
+pykolab/xml/._utils.py
+tests/functional/._purge_users.py
+tests/functional/._resource_func.py
+tests/functional/._synchronize.py
+tests/functional/test_auth/.___init__.py
+tests/functional/test_auth/._test_001_ldap.py
+tests/functional/test_auth/._test_002_sql.py
+tests/functional/test_auth/._test_003_pam.py
+tests/functional/test_auth/._test_004_saslauthd.py
+tests/functional/test_kolabd/._test_001_user_sync.py
+tests/functional/test_wallace/._test_001_user_add.py
+tests/functional/test_wallace/._test_002_footer.py
+tests/functional/test_wallace/._test_005_resource_add.py
+tests/functional/test_wallace/._test_005_resource_invitation.py
+tests/functional/test_wallace/._test_006_resource_performance.py
+tests/functional/test_wallace/._test_007_invitationpolicy.py
+tests/functional/test_wap_client/._test_002_user_add.py
+tests/functional/._user_add.py
+tests/unit/._test-000-imports.py
+tests/unit/._test-002-attendee.py
+tests/unit/._test-003-event.py
+tests/unit/._test-006-ldap_psearch.py
+tests/unit/._test-007-ldap_syncrepl.py
+tests/unit/._test-010-transliterate.py
+tests/unit/._test-011-itip.py
+tests/unit/._test-011-wallace_resources.py
+tests/unit/._test-012-wallace_invitationpolicy.py
+tests/unit/._test-014-conf-and-raw.py
+tests/unit/._test-015-translate.py
+wallace/.___init__.py
+wallace/._module_gpgencrypt.py
+wallace/._module_invitationpolicy.py
+wallace/._module_resources.py
+wallace/._modules.py
diff --git a/po/de.po b/po/de.po
index ff7abe2..46f34fc 100644
--- a/po/de.po
+++ b/po/de.po
@@ -3,331 +3,362 @@
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
-# Christoph Wickert <cwickert@fedoraproject.org>, 2011.
-# <grote@kolabsys.com>, 2012.
+# Christoph Wickert <christoph.wickert@gmail.com>, 2011
+# Grote <grote@kolabsys.com>, 2012
+# balin <johannes_graumann@web.de>, 2012
+# Jo <jo@caj-augsburg.de>, 2012
+# bitnukl <robert@proemper.net>, 2014
+# Thomas Brüderli <roundcube@gmail.com>, 2014
+# Till Savekoul <till@koul.de>, 2012
msgid ""
msgstr ""
"Project-Id-Version: Kolab Groupware Solution\n"
-"Report-Msgid-Bugs-To: https://isues.kolab.org/\n"
-"POT-Creation-Date: 2012-08-14 12:22+0100\n"
-"PO-Revision-Date: 2012-08-14 11:13+0000\n"
-"Last-Translator: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com>\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-07-10 07:21-0400\n"
+"PO-Revision-Date: 2014-07-22 13:01+0000\n"
+"Last-Translator: Thomas Brüderli <roundcube@gmail.com>\n"
"Language-Team: German (http://www.transifex.com/projects/p/kolab/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
-"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: ../bin/kolab_smtp_access_policy.py:206
+#: ../bin/kolab_smtp_access_policy.py:209
#, python-format
msgid "Adding policy request to instance %s"
msgstr "Füge Richtlinien-Anfrage zu Instanz %s hinzu"
-#: ../bin/kolab_smtp_access_policy.py:446
+#: ../bin/kolab_smtp_access_policy.py:479
msgid "Unauthorized access not allowed"
msgstr "Unberechtigter Zugriff nicht erlaubt"
-#: ../bin/kolab_smtp_access_policy.py:467
-#: ../bin/kolab_smtp_access_policy.py:657
+#: ../bin/kolab_smtp_access_policy.py:508
+#: ../bin/kolab_smtp_access_policy.py:689
msgid "Could not find recipient"
msgstr "Konnte den Empfänger nicht finden"
-#: ../bin/kolab_smtp_access_policy.py:486
-#: ../bin/kolab_smtp_access_policy.py:586
+#: ../bin/kolab_smtp_access_policy.py:527
#, python-format
-msgid "Could not find envelope sender user %s"
-msgstr "Konnte den Absender-Umschlag für den Benutzer %s nicht finden"
+msgid "Could not find envelope sender user %s (511)"
+msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:529
+#: ../bin/kolab_smtp_access_policy.py:570
#, python-format
msgid "Obtained authenticated user details for %r: %r"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:608
+#: ../bin/kolab_smtp_access_policy.py:627
+#, python-format
+msgid "Could not find envelope sender user %s"
+msgstr "Konnte den Absender-Umschlag für den Benutzer %s nicht finden"
+
+#: ../bin/kolab_smtp_access_policy.py:649
#, python-format
msgid "%s is unauthorized to send on behalf of %s"
msgstr "Benutzer %s ist nicht berechtigt als Benutzer %s zu senden"
-#: ../bin/kolab_smtp_access_policy.py:618
+#: ../bin/kolab_smtp_access_policy.py:659
#, python-format
msgid ""
"User %s attempted to use envelope sender address %s without authorization"
-msgstr ""
+msgstr "Benutzer %s versuchte die Absendeadresse %s ohne Berechtigung zu verwenden"
-#: ../bin/kolab_smtp_access_policy.py:681
-#: ../bin/kolab_smtp_access_policy.py:692
+#: ../bin/kolab_smtp_access_policy.py:713
+#: ../bin/kolab_smtp_access_policy.py:724
#, python-format
msgid "Found user %s to be a delegate user of %s"
msgstr "Benutzer %s ist ein delegierter Benutzer von %s"
-#: ../bin/kolab_smtp_access_policy.py:716
+#: ../bin/kolab_smtp_access_policy.py:748
#, python-format
msgid ""
"Verifying authenticated sender '%(sender)s' with sasl_username "
"'%(sasl_username)s' for recipient '%(recipient)s'"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:719
+#: ../bin/kolab_smtp_access_policy.py:751
#, python-format
msgid ""
"Verifying unauthenticated sender '%(sender)s' for recipient '%(recipient)s'"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:735
+#: ../bin/kolab_smtp_access_policy.py:767
#, python-format
msgid "Reproducing verify_recipient(%s, %s) from cache"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:753
+#: ../bin/kolab_smtp_access_policy.py:804
#, python-format
msgid "Using authentication domain %s instead of %s"
msgstr "Benutze Authentisierungsdomain %s anstelle von %s"
-#: ../bin/kolab_smtp_access_policy.py:763
+#: ../bin/kolab_smtp_access_policy.py:814
#, python-format
msgid "Domain %s is a primary domain"
msgstr "Die Domain %s ist die primäre Domain"
-#: ../bin/kolab_smtp_access_policy.py:771
+#: ../bin/kolab_smtp_access_policy.py:822
#, python-format
msgid ""
"Checking the recipient for domain %s that is not ours. This is probably a "
"configuration error."
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:786
+#: ../bin/kolab_smtp_access_policy.py:837
msgid ""
"This recipient address is related to multiple object entries and the SMTP "
"Access Policy can therefore not restrict message flow"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:793
+#: ../bin/kolab_smtp_access_policy.py:854
#, python-format
msgid ""
"Recipient address %r not found. Allowing since the MTA was configured to "
"accept the recipient."
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:820
+#: ../bin/kolab_smtp_access_policy.py:890
msgid "Invalid recipient"
msgstr "Ungültiger Empfänger"
-#: ../bin/kolab_smtp_access_policy.py:831
+#: ../bin/kolab_smtp_access_policy.py:901
msgid "Could not find this user, accepting"
msgstr "Konnte keine Einschränkung für diesen Benutzer finden, akzeptiere Nachricht"
-#: ../bin/kolab_smtp_access_policy.py:894
-#: ../bin/kolab_smtp_access_policy.py:945
+#: ../bin/kolab_smtp_access_policy.py:974
+#: ../bin/kolab_smtp_access_policy.py:1050
#, python-format
msgid "Sender %s is not allowed to send to recipient %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:933
+#: ../bin/kolab_smtp_access_policy.py:1038
#, python-format
msgid "Reproducing verify_sender(%r) from cache"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:960
+#: ../bin/kolab_smtp_access_policy.py:1055
+msgid "Unverifiable sender."
+msgstr ""
+
+#: ../bin/kolab_smtp_access_policy.py:1060
+msgid "Sender is not using an alias"
+msgstr ""
+
+#: ../bin/kolab_smtp_access_policy.py:1068
msgid "Sender uses unauthorized envelope sender address"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:977
+#: ../bin/kolab_smtp_access_policy.py:1085
msgid "Could not verify sender"
msgstr "Konnte den Absender nicht verifizieren"
-#: ../bin/kolab_smtp_access_policy.py:984
+#: ../bin/kolab_smtp_access_policy.py:1092
msgid ""
"Verifying whether sender is allowed to send to recipient using sender policy"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:997
+#: ../bin/kolab_smtp_access_policy.py:1105
#, python-format
msgid "Result is %r"
msgstr "Das Ergebnis ist %r"
-#: ../bin/kolab_smtp_access_policy.py:1002
+#: ../bin/kolab_smtp_access_policy.py:1110
msgid "No recipient policy restrictions exist for this sender"
msgstr "Es existiert keine Empfängerrichtlinie für diesen Absender"
-#: ../bin/kolab_smtp_access_policy.py:1011
+#: ../bin/kolab_smtp_access_policy.py:1119
msgid "Found a recipient policy to apply for this sender."
msgstr "Empfänger-Richtlinie für diesen Benutzer gefunden"
-#: ../bin/kolab_smtp_access_policy.py:1026
+#: ../bin/kolab_smtp_access_policy.py:1134
#, python-format
msgid "Sender %s not allowed to send to recipient %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1056
+#: ../bin/kolab_smtp_access_policy.py:1155
msgid "Cleaning up the cache"
msgstr "Aufräumen des Cache"
-#: ../bin/kolab_smtp_access_policy.py:1093
+#: ../bin/kolab_smtp_access_policy.py:1177
+msgid ""
+"The 'uri' setting in the kolab_smtp_access_policy section is soon going to "
+"be deprecated in favor of 'cache_uri'"
+msgstr ""
+
+#: ../bin/kolab_smtp_access_policy.py:1193
#, python-format
msgid "Operational Error in caching: %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1144
+#: ../bin/kolab_smtp_access_policy.py:1245
#, python-format
msgid "Caching the policy result with timestamp %d"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1221
+#: ../bin/kolab_smtp_access_policy.py:1319
#, python-format
msgid "Returning action DEFER_IF_PERMIT: %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1226
+#: ../bin/kolab_smtp_access_policy.py:1324
#, python-format
msgid "Returning action DUNNO: %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1231
+#: ../bin/kolab_smtp_access_policy.py:1329
#, python-format
msgid "Returning action HOLD: %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1236
+#: ../bin/kolab_smtp_access_policy.py:1334
#, python-format
msgid "Returning action PERMIT: %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1241
+#: ../bin/kolab_smtp_access_policy.py:1459
#, python-format
msgid "Returning action REJECT: %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1287
+#: ../bin/kolab_smtp_access_policy.py:1505
msgid "Starting to loop for new request"
msgstr "Starte Schleife für neue Anfrage"
-#: ../bin/kolab_smtp_access_policy.py:1294
+#: ../bin/kolab_smtp_access_policy.py:1512
msgid "Timeout for policy request reading exceeded"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1300
+#: ../bin/kolab_smtp_access_policy.py:1518
msgid "End of current request"
msgstr "Ende der aktuellen Anfrage"
-#: ../bin/kolab_smtp_access_policy.py:1304
+#: ../bin/kolab_smtp_access_policy.py:1522
#, python-format
msgid "Getting line: %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1308
+#: ../bin/kolab_smtp_access_policy.py:1526
msgid "Returning request"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1334
+#: ../bin/kolab_smtp_access_policy.py:1555
msgid "Access Policy Options"
msgstr "Zugriffsrichtlinien-Einstellungen"
-#: ../bin/kolab_smtp_access_policy.py:1341
+#: ../bin/kolab_smtp_access_policy.py:1562
msgid "SMTP Policy request timeout."
msgstr "Zeitüberschreitung der SMTP Richtlinien-Anfrage"
-#: ../bin/kolab_smtp_access_policy.py:1347
+#: ../bin/kolab_smtp_access_policy.py:1568
msgid "Verify the recipient access policy."
msgstr "Verifiziere die Empfänger-Zugriffs-Richtlinie."
-#: ../bin/kolab_smtp_access_policy.py:1353
+#: ../bin/kolab_smtp_access_policy.py:1574
msgid "Verify the sender access policy."
msgstr "Verifiziere die Sender-Zugriffs-Richtlinie."
-#: ../bin/kolab_smtp_access_policy.py:1359
+#: ../bin/kolab_smtp_access_policy.py:1580
msgid "Allow unauthenticated senders."
msgstr "Erlaube nicht authentisierte Sender."
-#: ../bin/kolab_smtp_access_policy.py:1373
+#: ../bin/kolab_smtp_access_policy.py:1594
#, python-format
msgid "Got request instance %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1382
+#: ../bin/kolab_smtp_access_policy.py:1603
#, python-format
msgid "Request instance %s is in state %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1390
+#: ../bin/kolab_smtp_access_policy.py:1611
#, python-format
msgid "Request instance %s is not yet in DATA state"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1402
+#: ../bin/kolab_smtp_access_policy.py:1623
#, python-format
msgid "Request instance %s reached DATA state"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1420
+#: ../bin/kolab_smtp_access_policy.py:1643
+#, python-format
+msgid "Unhandled exception caught: %r"
+msgstr ""
+
+#: ../bin/kolab_smtp_access_policy.py:1647
msgid "Sender access denied"
msgstr "Sender Zugriff verweigert"
-#: ../bin/kolab_smtp_access_policy.py:1422
+#: ../bin/kolab_smtp_access_policy.py:1649
msgid "Recipient access denied"
msgstr "Empfänger Zugriff verweigert"
-#: ../bin/kolab_smtp_access_policy.py:1424
+#: ../bin/kolab_smtp_access_policy.py:1651
msgid "No objections"
msgstr "Keine Einwände"
-#: ../conf.py:37 ../kolab.py:34 ../saslauthd.py:33
+#: ../conf.py:37 ../kolab-cli.py:34 ../saslauthd.py:33
msgid "Cannot load pykolab/logger.py:"
-msgstr ""
+msgstr "Laden von pykolab/logger.py nicht möglich:"
-#: ../kolabd/__init__.py:49 ../saslauthd/__init__.py:48
-#: ../wallace/__init__.py:65
+#: ../kolabd/__init__.py:49 ../saslauthd/__init__.py:51
+#: ../wallace/__init__.py:85
msgid "Daemon Options"
msgstr "Daemon Optionen"
-#: ../kolabd/__init__.py:56 ../saslauthd/__init__.py:55
-#: ../wallace/__init__.py:72
+#: ../kolabd/__init__.py:56 ../saslauthd/__init__.py:58
+#: ../wallace/__init__.py:92
msgid "Fork to the background."
msgstr "In den Hintergrund abtauchen"
-#: ../kolabd/__init__.py:65 ../saslauthd/__init__.py:64
-#: ../wallace/__init__.py:98
+#: ../kolabd/__init__.py:65 ../saslauthd/__init__.py:67
+#: ../wallace/__init__.py:118
msgid "Path to the PID file to use."
msgstr "Pfad zur PID-Datei"
-#: ../kolabd/__init__.py:74 ../saslauthd/__init__.py:73
-#: ../wallace/__init__.py:115
+#: ../kolabd/__init__.py:74 ../saslauthd/__init__.py:76
+#: ../wallace/__init__.py:135
msgid "Run as user USERNAME"
-msgstr ""
+msgstr "Als Benutzer USERNAME ausführen"
-#: ../kolabd/__init__.py:84 ../saslauthd/__init__.py:83
-#: ../wallace/__init__.py:89
+#: ../kolabd/__init__.py:84 ../saslauthd/__init__.py:86
+#: ../wallace/__init__.py:109
msgid "Run as group GROUPNAME"
-msgstr ""
+msgstr "Als Gruppe GROUPNAME ausführen"
-#: ../kolabd/__init__.py:122 ../pykolab/utils.py:151
-#: ../wallace/__init__.py:288
+#: ../kolabd/__init__.py:122 ../pykolab/logger.py:139 ../pykolab/utils.py:234
+#: ../saslauthd/__init__.py:292 ../wallace/__init__.py:329
#, python-format
msgid "Group %s does not exist"
-msgstr ""
+msgstr "Gruppe %s exisitert nicht"
-#: ../kolabd/__init__.py:131 ../wallace/__init__.py:297
+#: ../kolabd/__init__.py:131 ../saslauthd/__init__.py:301
+#: ../wallace/__init__.py:338
#, python-format
msgid "Switching real and effective group id to %d"
msgstr ""
-#: ../kolabd/__init__.py:153 ../pykolab/utils.py:175
-#: ../wallace/__init__.py:319
+#: ../kolabd/__init__.py:153 ../pykolab/logger.py:159 ../pykolab/utils.py:258
+#: ../saslauthd/__init__.py:323 ../wallace/__init__.py:360
#, python-format
msgid "User %s does not exist"
msgstr "Benutzer %s existiert nicht"
-#: ../kolabd/__init__.py:163 ../wallace/__init__.py:329
+#: ../kolabd/__init__.py:163 ../saslauthd/__init__.py:333
+#: ../wallace/__init__.py:370
#, python-format
msgid "Switching real and effective user id to %d"
msgstr ""
-#: ../kolabd/__init__.py:172 ../wallace/__init__.py:338
+#: ../kolabd/__init__.py:172 ../saslauthd/__init__.py:342
+#: ../wallace/__init__.py:379
msgid "Could not change real and effective uid and/or gid"
msgstr ""
-#: ../kolabd/__init__.py:192 ../saslauthd/__init__.py:122
-#: ../wallace/__init__.py:358
+#: ../kolabd/__init__.py:192 ../saslauthd/__init__.py:133
+#: ../wallace/__init__.py:399
msgid "Interrupted by user"
msgstr "Vom Benutzer unterbrochen"
@@ -335,328 +366,492 @@ msgstr "Vom Benutzer unterbrochen"
msgid "Traceback occurred, please report a "
msgstr ""
-#: ../kolabd/__init__.py:203 ../saslauthd/__init__.py:130
-#: ../wallace/__init__.py:367
+#: ../kolabd/__init__.py:203 ../saslauthd/__init__.py:141
+#: ../wallace/__init__.py:408
#, python-format
msgid "Type Error: %s"
msgstr "Typ-Fehler: %s"
-#: ../kolabd/__init__.py:223 ../pykolab/auth/ldap/__init__.py:1591
+#: ../kolabd/__init__.py:230
+msgid "Could not connect to LDAP, is it running?"
+msgstr ""
+
+#: ../kolabd/__init__.py:233 ../pykolab/auth/ldap/__init__.py:2137
#: ../pykolab/cli/cmd_sync.py:36
msgid "Listing domains..."
msgstr "Liste Domains auf..."
-#: ../kolabd/__init__.py:256
+#: ../kolabd/__init__.py:244
+msgid "No domains. Not syncing"
+msgstr ""
+
+#: ../kolabd/__init__.py:275
#, python-format
msgid "added domains: %r, removed domains: %r"
msgstr ""
+#: ../kolabd/process.py:33
+#, python-format
+msgid "Process created for domain %s"
+msgstr ""
+
+#: ../kolabd/process.py:42
+#, python-format
+msgid "Synchronizing for domain %s"
+msgstr ""
+
+#: ../kolabd/process.py:59
+#, python-format
+msgid ""
+"Error in process %r, terminating:\n"
+"\t%r"
+msgstr ""
+
#: ../kolabd.py:31 ../setup-kolab.py:36 ../wallace.py:31
msgid "Cannot load pykolab/constants.py:"
msgstr "Konnte nicht pykolab/constants.py laden:"
-#: ../pykolab/auth/__init__.py:94
+#: ../pykolab/auth/__init__.py:89
#, python-format
msgid "Called for domain %r"
msgstr ""
-#: ../pykolab/auth/__init__.py:107 ../pykolab/auth/__init__.py:116
+#: ../pykolab/auth/__init__.py:106 ../pykolab/auth/__init__.py:115
#, python-format
msgid "Using section %s and domain %s"
msgstr ""
-#: ../pykolab/auth/__init__.py:121
+#: ../pykolab/auth/__init__.py:120
#, python-format
msgid "Connecting to Authentication backend for domain %s"
msgstr ""
-#: ../pykolab/auth/__init__.py:132
+#: ../pykolab/auth/__init__.py:131
#, python-format
msgid "Section %s has no option 'auth_mechanism'"
msgstr ""
-#: ../pykolab/auth/__init__.py:139
+#: ../pykolab/auth/__init__.py:138
#, python-format
msgid "Section %s has auth_mechanism: %r"
msgstr ""
-#: ../pykolab/auth/__init__.py:148 ../pykolab/auth/__init__.py:157
+#: ../pykolab/auth/__init__.py:147 ../pykolab/auth/__init__.py:156
msgid "Starting LDAP..."
msgstr "Starte LDAP..."
-#: ../pykolab/auth/ldap/cache.py:109
+#: ../pykolab/auth/ldap/cache.py:126
#, python-format
msgid "Inserting cache entry %r"
msgstr ""
-#: ../pykolab/auth/ldap/cache.py:122
+#: ../pykolab/auth/ldap/cache.py:147
#, python-format
msgid "Updating timestamp for cache entry %r"
msgstr ""
-#: ../pykolab/auth/ldap/cache.py:129
+#: ../pykolab/auth/ldap/cache.py:155
#, python-format
msgid "Updating result_attribute for cache entry %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:51
+#: ../pykolab/auth/ldap/__init__.py:52
msgid "Python LDAP library does not support persistent search"
msgstr "Die Python LDAP Bibliothek unterstützt keine persistente Suche"
-#: ../pykolab/auth/ldap/__init__.py:142
+#: ../pykolab/auth/ldap/__init__.py:143
#, python-format
msgid "Attempting to authenticate user %s in realm %s"
msgstr "Versuche Benutzer %s in Bereich %s zu authentisieren"
-#: ../pykolab/auth/ldap/__init__.py:184
+#: ../pykolab/auth/ldap/__init__.py:175 ../pykolab/auth/ldap/__init__.py:226
+#, python-format
+msgid "Authentication cache failed: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:216 ../pykolab/auth/ldap/__init__.py:240
#, python-format
msgid "Binding with user_dn %s and password %s"
msgstr "Binde mit user_dn %s und Passwort %s"
-#: ../pykolab/auth/ldap/__init__.py:194
+#: ../pykolab/auth/ldap/__init__.py:231 ../pykolab/auth/ldap/__init__.py:263
#, python-format
msgid "Failed to authenticate as user %s"
+msgstr "Autorisation als Benutzer %s gescheitert"
+
+#: ../pykolab/auth/ldap/__init__.py:249
+#, python-format
+msgid "Error occured, there is no such object: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:254
+msgid "Authentication cache failed to clear entry"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:260
+#, python-format
+msgid "Exception occured: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:211
+#: ../pykolab/auth/ldap/__init__.py:280
msgid "Connecting to LDAP..."
msgstr "Verbinde zum LDAP..."
-#: ../pykolab/auth/ldap/__init__.py:215
+#: ../pykolab/auth/ldap/__init__.py:284
#, python-format
msgid "Attempting to use LDAP URI %s"
msgstr "Versuche LDAP URI %s zu benutzen"
-#: ../pykolab/auth/ldap/__init__.py:357
+#: ../pykolab/auth/ldap/__init__.py:371
+#, python-format
+msgid "Entry ID: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:373
+#, python-format
+msgid "Entry DN: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:376
+#, python-format
+msgid ""
+"ldap search: (%r, %r, filterstr='(objectclass=*)', attrlist=[ 'dn' ] + %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:453
#, python-format
msgid "Finding recipient with filter %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:425
+#: ../pykolab/auth/ldap/__init__.py:529
#, python-format
msgid "Finding resource with filter %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:448
+#: ../pykolab/auth/ldap/__init__.py:560
#, python-format
msgid "Using timestamp %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:480
+#: ../pykolab/auth/ldap/__init__.py:595
+msgid "Applying recipient policy disabled through configuration"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:600
#, python-format
msgid "Applying recipient policy to %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:497
+#: ../pykolab/auth/ldap/__init__.py:617
#, python-format
msgid "Using mail attributes: %r, with primary %r and "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:508
+#: ../pykolab/auth/ldap/__init__.py:628
#, python-format
msgid "key %r not in entry"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:510
+#: ../pykolab/auth/ldap/__init__.py:630
#, python-format
msgid "key %r is the prim. mail attr."
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:512
+#: ../pykolab/auth/ldap/__init__.py:632
msgid "prim. mail pol. is not empty"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:515
+#: ../pykolab/auth/ldap/__init__.py:635
#, python-format
msgid "key %r is the sec. mail attr."
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:517
+#: ../pykolab/auth/ldap/__init__.py:637
msgid "sec. mail pol. is not empty"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:520 ../pykolab/auth/ldap/__init__.py:533
+#: ../pykolab/auth/ldap/__init__.py:641 ../pykolab/auth/ldap/__init__.py:655
#, python-format
msgid "Attributes %r are not yet available for entry %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:574
+#: ../pykolab/auth/ldap/__init__.py:694
#, python-format
msgid "No results for mail address %s found"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:585
+#: ../pykolab/auth/ldap/__init__.py:705
#, python-format
msgid "1 result for address %s found, verifying"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:595
+#: ../pykolab/auth/ldap/__init__.py:715
#, python-format
msgid "Too bad, primary email address %s "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:606 ../pykolab/auth/ldap/__init__.py:695
+#: ../pykolab/auth/ldap/__init__.py:726 ../pykolab/auth/ldap/__init__.py:815
msgid "Address assigned to us"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:661
+#: ../pykolab/auth/ldap/__init__.py:781
#, python-format
msgid "No results for address %s found"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:672
+#: ../pykolab/auth/ldap/__init__.py:792
#, python-format
msgid "1 result for address %s found, "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:683
+#: ../pykolab/auth/ldap/__init__.py:803
msgid "Too bad, secondary email "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:710
+#: ../pykolab/auth/ldap/__init__.py:830
msgid "Recipient policy composed the following set of secondary "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:716
+#: ../pykolab/auth/ldap/__init__.py:841
#, python-format
msgid "Secondary mail addresses that we want is not None: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:727
+#: ../pykolab/auth/ldap/__init__.py:852
msgid "Avoiding the duplication of the primary mail "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:738
+#: ../pykolab/auth/ldap/__init__.py:863
#, python-format
msgid "Entry is getting secondary mail addresses: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:746
+#: ../pykolab/auth/ldap/__init__.py:871
msgid "Entry did not have any secondary mail "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:772
+#: ../pykolab/auth/ldap/__init__.py:888 ../pykolab/auth/ldap/__init__.py:894
+#, python-format
+msgid "secondary_mail_addresses: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:889 ../pykolab/auth/ldap/__init__.py:895
+#, python-format
+msgid "entry[%s]: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:906
#, python-format
msgid "Entry modifications list: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:792
+#: ../pykolab/auth/ldap/__init__.py:934
#, python-format
msgid "Setting entry attribute %r to %r for %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:828
+#: ../pykolab/auth/ldap/__init__.py:970
#, python-format
-msgid "Could not update dn %r"
+msgid ""
+"Could not update dn %r:\n"
+"%r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:841
+#: ../pykolab/auth/ldap/__init__.py:983
#, python-format
msgid "Using filter %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:879
+#: ../pykolab/auth/ldap/__init__.py:998
+#, python-format
+msgid "Synchronization is searching against base DN: %s"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:1044
#, python-format
msgid "About to consider the user quota for %r (used: %r, "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:945
+#: ../pykolab/auth/ldap/__init__.py:1115
msgid "Invalid DN, username and/or password."
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1229
+#: ../pykolab/auth/ldap/__init__.py:1236 ../pykolab/auth/ldap/__init__.py:1249
+#: ../pykolab/auth/ldap/__init__.py:1614 ../pykolab/auth/ldap/__init__.py:1627
+#, python-format
+msgid "Found a subject %r with access %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:1356
+#, python-format
+msgid "Entry %s attribute value: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:1364
#, python-format
-msgid "Current changelog entry %s with %s"
+msgid "imap.user_mailbox_server(%r) result: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1242
+#: ../pykolab/auth/ldap/__init__.py:1684 ../pykolab/auth/ldap/__init__.py:1853
#, python-format
msgid "Result from recipient policy: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1385
+#: ../pykolab/auth/ldap/__init__.py:1908
#, python-format
msgid "Kolab user %s does not have a result attribute %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1527
+#: ../pykolab/auth/ldap/__init__.py:2067
#, python-format
msgid "Finding domain root dn for domain %s"
-msgstr ""
+msgstr "Suche root dn für die Domain %s"
-#: ../pykolab/auth/ldap/__init__.py:1615
+#: ../pykolab/auth/ldap/__init__.py:2164
msgid "Authentication database DOWN"
msgstr "Authentisierungsdatenbank UNTEN"
-#: ../pykolab/auth/ldap/__init__.py:1699 ../pykolab/auth/ldap/__init__.py:1734
+#: ../pykolab/auth/ldap/__init__.py:2248 ../pykolab/auth/ldap/__init__.py:2296
#, python-format
msgid "Entry type: %s"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1822
+#: ../pykolab/auth/ldap/__init__.py:2321
+#, python-format
+msgid "Done with _synchronize_callback() for entry %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:2393
msgid "LDAP Search Result Data Entry:"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1838
+#: ../pykolab/auth/ldap/__init__.py:2409
msgid "Entry Change Notification attributes:"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1843
+#: ../pykolab/auth/ldap/__init__.py:2414
#, python-format
msgid "Change Type: %r (%r)"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1851
+#: ../pykolab/auth/ldap/__init__.py:2422
#, python-format
msgid "Previous DN: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1906
+#: ../pykolab/auth/ldap/__init__.py:2477
#, python-format
msgid "Object %s searched no longer exists"
msgstr "Das gesuchte Objekt %s existiert nicht mehr"
-#: ../pykolab/auth/ldap/__init__.py:1916
+#: ../pykolab/auth/ldap/__init__.py:2487
#, python-format
msgid "%d results..."
msgstr "%d Ergebnisse..."
-#: ../pykolab/auth/ldap/__init__.py:2014
+#: ../pykolab/auth/ldap/__init__.py:2590
#, python-format
msgid "Searching with filter %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2058
+#: ../pykolab/auth/ldap/__init__.py:2642
#, python-format
msgid "Checking for support for %s on %s"
msgstr ""
-#: ../pykolab/cli/cmd_add_domain.py:36 ../pykolab/cli/cmd_create_mailbox.py:36
+#: ../pykolab/auth/ldap/__init__.py:2661
+#, python-format
+msgid "Found support for %s"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:2706
+#, python-format
+msgid "An error occured using %s: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/syncrepl.py:46
+msgid "The name of the persistent, unique attribute "
+msgstr ""
+
+#: ../pykolab/cli/cmd_acl_cleanup.py:34
+msgid "Clean up ACLs that use identifiers that no longer exist"
+msgstr ""
+
+#: ../pykolab/cli/cmd_acl_cleanup.py:56
+#, python-format
+msgid "Deleting ACL %s for subject %s on folder %s"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:42
+msgid "Specify the (new) alias address"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:45
+msgid "Specify the existing recipient address"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:66 ../pykolab/cli/cmd_add_alias.py:70
+#, python-format
+msgid "Domain %r is not a local domain"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:75
+msgid "Primary and secondary domain do not have the same parent domain"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:81
+#, python-format
+msgid "No such recipient %r"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:87
+#, python-format
+msgid "Recipient for alias %r already exists"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:97
+msgid "Environment is not configured for "
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:105
+#, python-format
+msgid "Recipient %r is not the primary recipient for address %r"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_domain.py:36
+#: ../pykolab/cli/cmd_count_domain_mailboxes.py:38
+#: ../pykolab/cli/cmd_create_mailbox.py:36
#: ../pykolab/cli/cmd_export_mailbox.py:33
-#: ../pykolab/cli/cmd_list_mailboxes.py:39
-#: ../pykolab/cli/cmd_list_user_subscriptions.py:35
+#: ../pykolab/cli/cmd_list_deleted_mailboxes.py:38
+#: ../pykolab/cli/cmd_list_domain_mailboxes.py:36
+#: ../pykolab/cli/cmd_list_mailboxes.py:40
+#: ../pykolab/cli/cmd_list_mailbox_metadata.py:37
+#: ../pykolab/cli/cmd_list_messages.py:37 ../pykolab/cli/cmd_list_quota.py:36
+#: ../pykolab/cli/cmd_list_user_subscriptions.py:36
+#: ../pykolab/cli/cmd_server_info.py:34
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:38
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:39
+#: ../pykolab/cli/cmd_undelete_mailbox.py:34
msgid "CLI Options"
msgstr "Kommandozeilen-Parameter"
#: ../pykolab/cli/cmd_add_domain.py:42
-msgid "Add domain as alias for DOMAIN"
+msgid "Add alias domain."
msgstr ""
#: ../pykolab/cli/cmd_add_domain.py:47
-msgid "Add a new domain or domain alias."
-msgstr "Füge eine neue Domain oder einen neuen Domain Alias hinzu."
-
-#: ../pykolab/cli/cmd_add_domain.py:55
-msgid "Could not find credentials with sufficient permissions"
+msgid "Add a new domain."
msgstr ""
-#: ../pykolab/cli/cmd_add_domain.py:80 ../pykolab/wap_client/__init__.py:113
-msgid "Invalid parent domain"
+#: ../pykolab/cli/cmd_add_domain.py:55 ../pykolab/cli/cmd_delete_domain.py:44
+#: ../pykolab/cli/cmd_find_domain.py:44
+msgid "Could not find credentials with sufficient permissions"
msgstr ""
-#: ../pykolab/cli/cmd_add_domain.py:86
+#: ../pykolab/cli/cmd_add_domain.py:67 ../pykolab/cli/cmd_delete_domain.py:56
+#: ../pykolab/cli/cmd_find_domain.py:56
msgid "Domain name"
-msgstr ""
+msgstr "Name der Domain"
#: ../pykolab/cli/cmd_add_user_subscription.py:37
msgid "Subscribe a user to a folder."
@@ -670,7 +865,7 @@ msgid "Folder pattern"
msgstr ""
#: ../pykolab/cli/cmd_add_user_subscription.py:50
-#: ../pykolab/cli/cmd_list_user_subscriptions.py:56
+#: ../pykolab/cli/cmd_list_user_subscriptions.py:63
#: ../pykolab/cli/cmd_remove_user_subscription.py:50
msgid "User ID"
msgstr "Benutzer ID"
@@ -682,30 +877,39 @@ msgid "Cannot subscribe user to folder %r:"
msgstr ""
#: ../pykolab/cli/cmd_add_user_subscription.py:73
+#: ../pykolab/cli/cmd_delete_message.py:61
+#: ../pykolab/cli/cmd_list_messages.py:67
#: ../pykolab/cli/cmd_remove_user_subscription.py:73
msgid "No such folder"
-msgstr ""
+msgstr "Dieser Ordner ist nicht vorhanden"
-#: ../pykolab/cli/cmd_add_user_subscription.py:86
-#, python-format
-msgid "Successfully subscribed user %s to the following folders:"
-msgstr ""
-
-#: ../pykolab/cli/cmd_add_user_subscription.py:92
-#, python-format
-msgid "User %s not subscribed to any folders."
+#: ../pykolab/cli/cmd_count_domain_mailboxes.py:44
+#: ../pykolab/cli/cmd_list_deleted_mailboxes.py:50
+#: ../pykolab/cli/cmd_list_domain_mailboxes.py:48
+#: ../pykolab/cli/cmd_list_mailboxes.py:52 ../pykolab/cli/cmd_list_quota.py:42
+#: ../pykolab/cli/cmd_server_info.py:40
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:57
+msgid "List mailboxes on server SERVER only."
msgstr ""
-#: ../pykolab/cli/cmd_create_mailbox.py:41
+#: ../pykolab/cli/cmd_create_mailbox.py:42
msgid "Set metadata for folder to ANNOTATION=VALUE"
msgstr ""
#: ../pykolab/cli/cmd_create_mailbox.py:50
-msgid "Invalid argument"
+msgid "Create folder on PARTITION."
msgstr ""
-#: ../pykolab/cli/cmd_create_mailbox.py:58
+#: ../pykolab/cli/cmd_create_mailbox.py:60
+msgid "Invalid argument"
+msgstr "Ungültiges Argument"
+
+#: ../pykolab/cli/cmd_create_mailbox.py:68
msgid "Invalid argument for metadata"
+msgstr "Ungültiges Argument für die Metadaten"
+
+#: ../pykolab/cli/cmd_delete_domain.py:36
+msgid "Delete a domain."
msgstr ""
#: ../pykolab/cli/cmd_delete_mailbox_acl.py:45
@@ -716,26 +920,48 @@ msgid "ACI Subject"
msgstr ""
#: ../pykolab/cli/cmd_delete_mailbox_acl.py:48
-#: ../pykolab/cli/cmd_list_mailbox_acls.py:41
-#: ../pykolab/cli/cmd_list_mailbox_metadata.py:41
+#: ../pykolab/cli/cmd_list_mailbox_acls.py:43
+#: ../pykolab/cli/cmd_list_mailbox_metadata.py:54
#: ../pykolab/cli/cmd_set_mailbox_acl.py:54
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:54
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:66
+#: ../pykolab/cli/cmd_set_quota.py:46 ../tests/unit/test-015-translate.py:12
+#: ../tests/unit/test-015-translate.py:16
+#: ../tests/unit/test-015-translate.py:18
+#: ../tests/unit/test-015-translate.py:20
msgid "Folder name"
-msgstr ""
+msgstr "Ordnername"
#: ../pykolab/cli/cmd_delete_mailbox_acl.py:60
-#: ../pykolab/cli/cmd_list_mailbox_acls.py:52
-#: ../pykolab/cli/cmd_list_mailbox_metadata.py:52
+#: ../pykolab/cli/cmd_list_mailbox_acls.py:54
+#: ../pykolab/cli/cmd_list_mailbox_metadata.py:80
#: ../pykolab/cli/cmd_set_mailbox_acl.py:67
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:67
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:94
+#: ../pykolab/cli/cmd_set_quota.py:58
#, python-format
msgid "No such folder %r"
-msgstr ""
+msgstr "Kein Ordner %r verfügbar"
#: ../pykolab/cli/cmd_delete_mailbox.py:46
msgid "No mailbox specified"
msgstr "Keine Mailbox angegeben"
+#: ../pykolab/cli/cmd_delete_mailbox.py:56
+msgid "No such folder(s)"
+msgstr ""
+
+#: ../pykolab/cli/cmd_delete_message.py:36
+msgid "Delete a message from a folder"
+msgstr ""
+
+#: ../pykolab/cli/cmd_delete_message.py:49
+msgid "Specify a UID"
+msgstr ""
+
+#: ../pykolab/cli/cmd_delete_message.py:52
+#: ../pykolab/cli/cmd_list_messages.py:58
+msgid "Specify a folder"
+msgstr ""
+
#: ../pykolab/cli/cmd_export_mailbox.py:38
msgid "All folders this user has access to"
msgstr ""
@@ -743,40 +969,77 @@ msgstr ""
#: ../pykolab/cli/cmd_export_mailbox.py:108
#, python-format
msgid "%s is not a directory"
-msgstr ""
+msgstr "%s ist kein Verzeichnis"
#: ../pykolab/cli/cmd_export_mailbox.py:118
#, python-format
msgid "ZIP file at %s.zip"
-msgstr ""
+msgstr "ZIP-Datei unter %s.zip"
#: ../pykolab/cli/cmd_export_mailbox.py:120
#, python-format
msgid "No directories found for user %s"
msgstr ""
-#: ../pykolab/cli/cmd_list_mailboxes.py:44
+#: ../pykolab/cli/cmd_find_domain.py:36
+msgid "Find a domain."
+msgstr ""
+
+#: ../pykolab/cli/cmd_list_deleted_mailboxes.py:43
+#: ../pykolab/cli/cmd_list_domain_mailboxes.py:41
+#: ../pykolab/cli/cmd_list_mailboxes.py:45
+#: ../pykolab/cli/cmd_list_user_subscriptions.py:41
msgid "Display raw IMAP UTF-7 folder names"
msgstr ""
-#: ../pykolab/cli/cmd_list_mailboxes.py:75
+#: ../pykolab/cli/cmd_list_domain_mailboxes.py:58
+msgid "Domain"
+msgstr "Domäne"
+
+#: ../pykolab/cli/cmd_list_mailboxes.py:87
#, python-format
msgid "Appending folder search for %r"
msgstr ""
-#: ../pykolab/cli/cmd_list_user_subscriptions.py:40
+#: ../pykolab/cli/cmd_list_mailbox_metadata.py:44
+msgid "List annotations as user USER"
+msgstr ""
+
+#: ../pykolab/cli/cmd_list_messages.py:43
+msgid "Include messages flagged as \\Deleted"
+msgstr ""
+
+#: ../pykolab/cli/cmd_list_messages.py:47
+msgid "List messages in a folder"
+msgstr ""
+
+#: ../pykolab/cli/cmd_list_quota.py:73 ../pykolab/cli/cmd_list_quota.py:89
+#, python-format
+msgid "The quota for folder %s is set to literally allow 0KB of storage."
+msgstr ""
+
+#: ../pykolab/cli/cmd_list_user_subscriptions.py:47
msgid "List unsubscribed folders"
msgstr "Liste nicht abonnierte Ordner"
-#: ../pykolab/cli/cmd_list_user_subscriptions.py:43
+#: ../pykolab/cli/cmd_list_user_subscriptions.py:50
msgid "List the folders a user is subscribed to."
msgstr "Liste die Ordner, die ein Benutzer abonniert hat."
-#: ../pykolab/cli/cmd_list_user_subscriptions.py:88
+#: ../pykolab/cli/cmd_list_user_subscriptions.py:98
#, python-format
msgid "No unsubscribed folders for user %s"
msgstr ""
+#: ../pykolab/cli/cmd_mailbox_cleanup.py:37
+msgid "Clean up mailboxes that do no longer have an owner."
+msgstr ""
+
+#: ../pykolab/cli/cmd_mailbox_cleanup.py:61
+#, python-format
+msgid "Deleting folder 'user/%s'"
+msgstr ""
+
#: ../pykolab/cli/cmd_remove_mailaddress.py:49
msgid "Invalid or unqualified email address."
msgstr ""
@@ -811,23 +1074,23 @@ msgstr ""
#: ../pykolab/cli/cmd_remove_user_subscription.py:92
#, python-format
-msgid "User %s not be unsubscribed from any folders."
+msgid "User %s was not unsubscribed from any folders."
msgstr ""
-#: ../pykolab/cli/cmd_rename_mailbox.py:48
+#: ../pykolab/cli/cmd_rename_mailbox.py:52
msgid "No target mailbox name specified"
msgstr ""
-#: ../pykolab/cli/cmd_rename_mailbox.py:50
+#: ../pykolab/cli/cmd_rename_mailbox.py:54
msgid "No source mailbox name specified"
msgstr ""
-#: ../pykolab/cli/cmd_rename_mailbox.py:62
+#: ../pykolab/cli/cmd_rename_mailbox.py:66
#, python-format
msgid "Source folder %r does not exist"
msgstr ""
-#: ../pykolab/cli/cmd_rename_mailbox.py:66
+#: ../pykolab/cli/cmd_rename_mailbox.py:70
#, python-format
msgid "Target folder %r already exists"
msgstr ""
@@ -838,17 +1101,75 @@ msgstr ""
msgid "ACI Permissions"
msgstr ""
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:47
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:51
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:56
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:45
+msgid "Set annotation as user USER"
+msgstr ""
+
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:59
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:63
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:68
msgid "Metadata value"
msgstr ""
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:50
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:55
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:62
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:67
msgid "Metadata path"
msgstr ""
+#: ../pykolab/cli/cmd_set_quota.py:43 ../pykolab/cli/cmd_set_quota.py:47
+msgid "New quota"
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:44
+msgid "Delete mailboxes for recipients that do not appear to exist in LDAP."
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:50
+msgid "Display changes, do not apply them."
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:88
+#, python-format
+msgid "Domains in IMAP not in LDAP: %r"
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:101
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:142
+#, python-format
+msgid ""
+"No recipients for '%s' (would have deleted the mailbox if not for --dry-"
+"run)!"
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:106
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:147
+#, python-format
+msgid "Deleting mailbox '%s' because it has no recipients"
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:110
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:151
+#, python-format
+msgid "An error occurred removing mailbox %r: %r"
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:112
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:153
+#, python-format
+msgid "Not automatically deleting shared folder '%s'"
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:114
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:155
+#, python-format
+msgid "No recipients for '%s' (use --delete to delete)!"
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:136
+#, python-format
+msgid "Multiple recipients for '%s'!"
+msgstr ""
+
#: ../pykolab/cli/cmd_sync.py:41
#, python-format
msgid "Found %d domains in %d seconds"
@@ -859,15 +1180,23 @@ msgstr "%d Domains in %d Sekunden gefunden"
msgid "Running for domain %s"
msgstr "Starte für Domain %s"
-#: ../pykolab/cli/cmd_sync.py:57
+#: ../pykolab/cli/cmd_sync.py:58
#, python-format
msgid "Synchronizing users for %s took %d seconds"
msgstr "Benutzer für %s zu synchronisieren dauerte %d Sekunden"
-#: ../pykolab/cli/cmd_undelete_mailbox.py:33
+#: ../pykolab/cli/cmd_undelete_mailbox.py:39
+msgid "Do not actually execute, but state what would have been executed."
+msgstr ""
+
+#: ../pykolab/cli/cmd_undelete_mailbox.py:42
msgid "Recover mailboxes previously deleted."
msgstr ""
+#: ../pykolab/cli/cmd_user_info.py:39
+msgid "Email address"
+msgstr "E-Mail-Adresse"
+
#. This is a nested command
#. This is a nested component
#: ../pykolab/cli/commands.py:98 ../pykolab/setup/components.py:90
@@ -875,25 +1204,102 @@ msgstr ""
msgid "Command Group: %s"
msgstr ""
-#: ../pykolab/cli/commands.py:109 ../pykolab/cli/commands.py:114
+#: ../pykolab/cli/commands.py:113 ../pykolab/cli/commands.py:118
msgid "No such command."
msgstr "Dieses Kommando existiert nicht."
-#: ../pykolab/cli/commands.py:165 ../pykolab/setup/components.py:231
+#: ../pykolab/cli/commands.py:168 ../pykolab/setup/components.py:231
#, python-format
msgid "Command '%s' already registered"
msgstr ""
-#: ../pykolab/cli/commands.py:190 ../pykolab/setup/components.py:257
-#: ../wallace/modules.py:338
+#: ../pykolab/cli/commands.py:193 ../pykolab/setup/components.py:257
+#: ../wallace/modules.py:369
#, python-format
msgid "Alias for %s"
msgstr "Alias für %s"
-#: ../pykolab/cli/commands.py:198 ../pykolab/setup/components.py:265
+#: ../pykolab/cli/commands.py:201 ../pykolab/setup/components.py:265
msgid "Not yet implemented"
msgstr "Diese Funktion ist noch nicht implementiert"
+#: ../pykolab/cli/sieve/cmd_list.py:43 ../pykolab/cli/sieve/cmd_put.py:42
+#: ../pykolab/cli/sieve/cmd_refresh.py:44 ../pykolab/cli/sieve/cmd_test.py:43
+msgid "Email Address"
+msgstr "E-mail-Adresse"
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:99
+#: ../pykolab/plugins/sievemgmt/__init__.py:111
+#, python-format
+msgid "Found the following scripts for user %s: %s"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:100
+#: ../pykolab/plugins/sievemgmt/__init__.py:112
+#, python-format
+msgid "And the following script is active for user %s: %s"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:178
+#: ../pykolab/plugins/sievemgmt/__init__.py:190
+#, python-format
+msgid ""
+"Delivery to folder active, but no folder name attribute available for user "
+"%r"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:181
+#: ../pykolab/plugins/sievemgmt/__init__.py:193
+msgid "Delivery to folder active, but no folder name attribute configured"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:359
+#, python-format
+msgid "MANAGEMENT script for user %s contents: %r"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:364
+#: ../pykolab/plugins/sievemgmt/__init__.py:374
+#, python-format
+msgid "Uploading script MANAGEMENT failed for user %s"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:366
+#: ../pykolab/plugins/sievemgmt/__init__.py:376
+#, python-format
+msgid "Uploading script MANAGEMENT for user %s succeeded"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:377
+#: ../pykolab/plugins/sievemgmt/__init__.py:387
+#, python-format
+msgid "Including script %s in USER (for user %s)"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:386
+#: ../pykolab/plugins/sievemgmt/__init__.py:396
+#, python-format
+msgid "Uploading script USER failed for user %s"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:388
+#: ../pykolab/plugins/sievemgmt/__init__.py:398
+#, python-format
+msgid "Uploading script USER for user %s succeeded"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:416
+#: ../pykolab/plugins/sievemgmt/__init__.py:426
+#, python-format
+msgid "Uploading script MASTER failed for user %s"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:418
+#: ../pykolab/plugins/sievemgmt/__init__.py:428
+#, python-format
+msgid "Uploading script MASTER for user %s succeeded"
+msgstr ""
+
#: ../pykolab/cli/telemetry/cmd_examine_command_issue.py:40
msgid "Unspecified command issue identifier"
msgstr ""
@@ -958,27 +1364,27 @@ msgstr ""
#: ../pykolab/conf/__init__.py:87
#, python-format
msgid "Setting %s to %r (from defaults)"
-msgstr ""
+msgstr "Setze %s auf %r (aus den Standardeinstellungen)"
#: ../pykolab/conf/__init__.py:106
#, python-format
msgid "Setting %s to %r (from CLI, verified)"
-msgstr ""
+msgstr "Setze %s auf %r (von der Kommandozeile, überprüft)"
#: ../pykolab/conf/__init__.py:109
#, python-format
msgid "Setting %s to %r (from CLI, not checked)"
-msgstr ""
+msgstr "Setze %s auf %r (von der Kommandozeile, nicht überprüft)"
#: ../pykolab/conf/__init__.py:150 ../pykolab/conf/__init__.py:207
#, python-format
msgid "Setting %s_%s to '****' (from configuration file)"
-msgstr ""
+msgstr "Setze %s_%s auf '****' (aus Konfigurationsdatei)"
#: ../pykolab/conf/__init__.py:152 ../pykolab/conf/__init__.py:209
#, python-format
msgid "Setting %s_%s to %r (from configuration file)"
-msgstr ""
+msgstr "Setze %s_%s auf %r (aus Konfigurationsdatei)"
#: ../pykolab/conf/__init__.py:162
msgid "Setting options from configuration file"
@@ -1039,76 +1445,76 @@ msgstr "Ja auf alle Fragen."
msgid "No command supplied"
msgstr "Kein Befehl angegeben"
-#: ../pykolab/conf/__init__.py:411
+#: ../pykolab/conf/__init__.py:416
msgid "Insufficient options. Need section, key and value -in that order."
msgstr "Unzureichende Optionen. Brauche Sektion, Schlüssel und Wert in dieser Reihenfolge."
-#: ../pykolab/conf/__init__.py:414
+#: ../pykolab/conf/__init__.py:419
#, python-format
msgid "No section '%s' exists."
msgstr "Es existiert keine Sektion '%s'."
-#: ../pykolab/conf/__init__.py:445
+#: ../pykolab/conf/__init__.py:461
#, python-format
msgid "Setting %s to %r (from the default values for CLI options)"
-msgstr ""
+msgstr "Setze %s auf %r (aus den Standardwerten für Kommandozeilenoptionen)"
-#: ../pykolab/conf/__init__.py:514
+#: ../pykolab/conf/__init__.py:534
#, python-format
msgid "Could not execute configuration function: %s"
-msgstr ""
+msgstr "Konnte die Konfigurationsfunktion nicht ausführen: %s"
-#: ../pykolab/conf/__init__.py:522
+#: ../pykolab/conf/__init__.py:542
#, python-format
msgid "Option %s/%s does not exist in config file %s, pulling from defaults"
-msgstr ""
+msgstr "Die Option %s/%s existiert in der Konfigurationsdatei %s nicht, sie wird aus den Standardeinstellungen geholt"
-#: ../pykolab/conf/__init__.py:530 ../pykolab/conf/__init__.py:533
+#: ../pykolab/conf/__init__.py:550 ../pykolab/conf/__init__.py:553
msgid "Option does not exist in defaults."
msgstr "Diese Option hat keinen Standardwert."
-#: ../pykolab/conf/__init__.py:543
+#: ../pykolab/conf/__init__.py:563
#, python-format
msgid "Configuration file %s not readable."
msgstr "Konfigurationsdatei %s ist nicht lesbar."
-#: ../pykolab/conf/__init__.py:546
+#: ../pykolab/conf/__init__.py:566
#, python-format
msgid "Configuration file %s does not exist."
msgstr "Konfigurationsdatei %s existiert nicht."
-#: ../pykolab/conf/__init__.py:551
+#: ../pykolab/conf/__init__.py:571
msgid ""
"WARNING: A negative debug level value does not make this program be any more"
" silent."
msgstr "Warnung: Eine negative Fehlerprotokollierungszahl macht dieses Programm nicht noch stiller."
-#: ../pykolab/conf/__init__.py:557
+#: ../pykolab/conf/__init__.py:577
msgid "This program has 9 levels of verbosity. Using the maximum of 9."
msgstr "Dieses Programm hat 9 Ebenen der Detailliertheit. Benutze das Maximum 9."
-#: ../pykolab/conf/__init__.py:565 ../pykolab/conf/__init__.py:571
+#: ../pykolab/conf/__init__.py:585 ../pykolab/conf/__init__.py:591
msgid "Cannot start SASL authentication daemon"
msgstr "Konnte SASL Authentisierungsdaemon nicht starten"
-#: ../pykolab/conf/__init__.py:582
+#: ../pykolab/conf/__init__.py:602
msgid "No imaplib library found."
-msgstr ""
+msgstr "Keine imaplib-Bibliothek gefunden."
-#: ../pykolab/conf/__init__.py:592
+#: ../pykolab/conf/__init__.py:612
msgid "No LMTP class found in the smtplib library."
-msgstr ""
+msgstr "Keine Klasse namens LMTP in der smtplib-Bibliothek gefunden."
-#: ../pykolab/conf/__init__.py:602
+#: ../pykolab/conf/__init__.py:622
msgid "No SMTP class found in the smtplib library."
-msgstr ""
+msgstr "Keine Klasse namens SMTP in der smtplib-Bibliothek gefunden."
-#: ../pykolab/conf/__init__.py:616
+#: ../pykolab/conf/__init__.py:636
#, python-format
msgid "Found you specified a specific set of items to test: %s"
msgstr ""
-#: ../pykolab/conf/__init__.py:624
+#: ../pykolab/conf/__init__.py:644
#, python-format
msgid "Selectively selecting: %s"
msgstr ""
@@ -1137,263 +1543,383 @@ msgstr "389 Verzeichnisserver oder Red Hat Verzeichnisserver"
msgid "OpenLDAP or compatible"
msgstr "OpenLDAP oder kompatibel"
-#: ../pykolab/imap/cyrus.py:79
+#: ../pykolab/imap/cyrus.py:80
#, python-format
msgid "Could not connect to Cyrus IMAP server %r"
-msgstr ""
+msgstr "Verbindung zum Cyrus IMAP-Server %r nicht möglich"
-#: ../pykolab/imap/cyrus.py:134
+#: ../pykolab/imap/cyrus.py:137
#, python-format
msgid "Continuing with separator: %r"
msgstr ""
-#: ../pykolab/imap/cyrus.py:139
+#: ../pykolab/imap/cyrus.py:142
msgid "Detected we are running in a Murder topology"
msgstr ""
-#: ../pykolab/imap/cyrus.py:143
+#: ../pykolab/imap/cyrus.py:146
msgid "This system is not part of a murder topology"
msgstr ""
-#: ../pykolab/imap/cyrus.py:164
+#: ../pykolab/imap/cyrus.py:167
#, python-format
msgid "Checking actual backend server for folder %s through annotations"
msgstr ""
-#: ../pykolab/imap/cyrus.py:179
+#: ../pykolab/imap/cyrus.py:172
+msgid "Possibly reproducing the find "
+msgstr ""
+
+#: ../pykolab/imap/cyrus.py:195
#, python-format
msgid "Could not get the annotations after %s tries."
msgstr ""
-#: ../pykolab/imap/cyrus.py:183
+#: ../pykolab/imap/cyrus.py:199
#, python-format
msgid "No annotations for %s: %r"
msgstr ""
-#: ../pykolab/imap/cyrus.py:190
+#: ../pykolab/imap/cyrus.py:206
#, python-format
msgid "Server for INBOX folder %s is %s"
msgstr ""
-#: ../pykolab/imap/cyrus.py:202
+#: ../pykolab/imap/cyrus.py:226
#, python-format
msgid "Setting quota for folder %s to %s"
msgstr ""
-#: ../pykolab/imap/cyrus.py:206
+#: ../pykolab/imap/cyrus.py:230
#, python-format
msgid "Could not set quota for mailfolder %s"
msgstr ""
-#: ../pykolab/imap/cyrus.py:215
+#: ../pykolab/imap/cyrus.py:239
#, python-format
msgid "Moving INBOX folder %s to %s"
-msgstr ""
+msgstr "Verschiebe Eingangsordner %s nach %s"
-#: ../pykolab/imap/cyrus.py:227
+#: ../pykolab/imap/cyrus.py:254
#, python-format
msgid "Setting annotation %s on folder %s"
msgstr ""
-#: ../pykolab/imap/cyrus.py:234
+#: ../pykolab/imap/cyrus.py:259
#, python-format
msgid "Could not set annotation %r on mail folder %r: %r"
msgstr ""
-#: ../pykolab/imap/cyrus.py:238
+#: ../pykolab/imap/cyrus.py:263
#, python-format
msgid "Transferring folder %s from %s to %s"
msgstr ""
-#: ../pykolab/imap/cyrus.py:298
+#: ../pykolab/imap/cyrus.py:323
#, python-format
msgid "Undeleting %s to %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:45
+#: ../pykolab/imap/cyrus.py:334
+#, python-format
+msgid "Would have transfered %s from %s to %s"
+msgstr ""
+
+#: ../pykolab/imap/cyrus.py:336
+#, python-format
+msgid "Would have renamed %s to %s"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:46
#, python-format
msgid "Cleaning up ACL entries for %s across all folders"
msgstr ""
-#: ../pykolab/imap/__init__.py:60
+#: ../pykolab/imap/__init__.py:61
#, python-format
msgid "Cleaning up ACL entries referring to identifier %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:69
+#: ../pykolab/imap/__init__.py:70
#, python-format
msgid "Iterating over %d folders"
msgstr ""
#. Set the ACL to '' (effectively deleting the ACL entry)
-#: ../pykolab/imap/__init__.py:82
+#: ../pykolab/imap/__init__.py:83
#, python-format
msgid "Removing acl %r for subject %r from folder %r"
msgstr ""
-#: ../pykolab/imap/__init__.py:143
+#: ../pykolab/imap/__init__.py:145
+msgid "No administrator password is available."
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:153
#, python-format
msgid "Logging on to Cyrus IMAP server %s"
-msgstr ""
+msgstr "Anmeldung am Cyrus IMAP Server %s"
-#: ../pykolab/imap/__init__.py:152
+#: ../pykolab/imap/__init__.py:162
#, python-format
msgid "Logging on to Dovecot IMAP server %s"
-msgstr ""
+msgstr "Anmeldung am Dovecot IMAP Server %s"
-#: ../pykolab/imap/__init__.py:161
+#: ../pykolab/imap/__init__.py:171
#, python-format
msgid "Logging on to generic IMAP server %s"
-msgstr ""
+msgstr "Anmeldung am generischen IMAP Server %s"
-#: ../pykolab/imap/__init__.py:179
+#: ../pykolab/imap/__init__.py:189
#, python-format
msgid "Reusing existing IMAP server connection to %s"
msgstr "Benutze Verbindung zum IMAP Server %s wieder"
-#: ../pykolab/imap/__init__.py:181
+#: ../pykolab/imap/__init__.py:191
#, python-format
msgid "Reconnecting to IMAP server %s"
msgstr "Verbinde nochmal zum IMAP Server %s"
-#: ../pykolab/imap/__init__.py:197
+#: ../pykolab/imap/__init__.py:208
msgid "Called imap.disconnect() on a server that we had no connection to."
msgstr ""
-#: ../pykolab/imap/__init__.py:212
+#: ../pykolab/imap/__init__.py:222 ../pykolab/imap/__init__.py:234
#, python-format
-msgid "%r has no attribute %s"
-msgstr "%r hat kein Attribut %s"
+msgid "Could not create folder %r"
+msgstr ""
-#: ../pykolab/imap/__init__.py:285
-msgid "Private annotations need to be set using the appropriate user account."
+#: ../pykolab/imap/__init__.py:223
+#, python-format
+msgid " on server %r"
msgstr ""
-#: ../pykolab/imap/__init__.py:318 ../pykolab/imap/__init__.py:353
+#: ../pykolab/imap/__init__.py:244 ../pykolab/imap/__init__.py:246
+#, python-format
+msgid "%r has no attribute %s"
+msgstr "%r hat kein Attribut %s"
+
+#: ../pykolab/imap/__init__.py:393 ../pykolab/imap/__init__.py:428
#, python-format
msgid "Creating new shared folder %s"
msgstr "Erzeuge einen neuen geteilten Ordner %s"
-#: ../pykolab/imap/__init__.py:375
+#: ../pykolab/imap/__init__.py:453 ../pykolab/imap/__init__.py:675
+#, python-format
+msgid "Downcasing mailbox name %r"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:457
#, python-format
msgid "Creating new mailbox for user %s"
msgstr "Erzeuge eine neue Mailbox für Benutzer %s"
-#: ../pykolab/imap/__init__.py:404
+#: ../pykolab/imap/__init__.py:470
+msgid "Waiting for the Cyrus IMAP Murder to settle..."
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:516
#, python-format
msgid "Creating additional folders for user %s"
msgstr "Erzeuge weitere Order für Benutzer %s"
-#: ../pykolab/imap/__init__.py:428
+#: ../pykolab/imap/__init__.py:535
+#, python-format
+msgid "Waiting for the Cyrus murder to settle... %r"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:547
+#, python-format
+msgid "Correcting additional folder name from %r to %r"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:553
#, python-format
msgid "Mailbox already exists: %s"
msgstr "Mailbox existiert bereits: %s"
-#: ../pykolab/imap/__init__.py:471
+#: ../pykolab/imap/__init__.py:593
msgid "Subscribing user to the additional folders"
msgstr "Abonniere weitere Ordner für den Benutzer"
-#: ../pykolab/imap/__init__.py:531 ../pykolab/imap/__init__.py:605
+#: ../pykolab/imap/__init__.py:607
+msgid "Using the following tests for folder subscriptions:"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:609
+#, python-format
+msgid " %r"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:612
+#, python-format
+msgid "Folder %s"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:624
+#, python-format
+msgid "Subscribing %s to folder %s"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:628
+#, python-format
+msgid "Subscribing %s to folder %s failed: %r"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:658
+#, python-format
+msgid "Could not rename %s to reside on partition %s"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:691
+#, python-format
+msgid "INBOX folder to rename (%s) does not exist"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:694 ../pykolab/imap/__init__.py:770
#, python-format
msgid "Renaming INBOX from %s to %s"
msgstr "Benenne INBOX Ordner von %s in %s um"
-#: ../pykolab/imap/__init__.py:535
+#: ../pykolab/imap/__init__.py:698
#, python-format
msgid "Could not rename INBOX folder %s to %s"
msgstr "Konnte INBOX Ordner nicht von %s in %s umbenennen"
-#: ../pykolab/imap/__init__.py:537 ../pykolab/imap/__init__.py:609
+#: ../pykolab/imap/__init__.py:700 ../pykolab/imap/__init__.py:774
#, python-format
msgid ""
"Moving INBOX folder %s won't succeed as target folder %s already exists"
+msgstr "Der INBOX-Ordner %s kann nicht verschoben werden, weil der Zielordner %s bereits existiert"
+
+#: ../pykolab/imap/__init__.py:704
+#, python-format
+msgid "Server for mailbox %r is %r"
msgstr ""
-#: ../pykolab/imap/__init__.py:547
+#: ../pykolab/imap/__init__.py:712
#, python-format
msgid "Looking for folder '%s', we found folders: %r"
-msgstr ""
+msgstr "Auf der Suche nach dem Ordner '%s' haben wir diese Ordner gefunden: %r"
-#: ../pykolab/imap/__init__.py:570
+#: ../pykolab/imap/__init__.py:735
#, python-format
msgid "Setting ACL rights %s for subject %s on folder "
msgstr "Richte ACL Rechte %s für Subjekt %s des Ordners ein"
-#: ../pykolab/imap/__init__.py:581
+#: ../pykolab/imap/__init__.py:746
#, python-format
msgid "Removing ACL rights %s for subject %s on folder "
msgstr ""
-#: ../pykolab/imap/__init__.py:602
+#: ../pykolab/imap/__init__.py:767
#, python-format
msgid "Found old INBOX folder %s"
-msgstr ""
+msgstr "Alter INBOX-Ordner %s gefunden"
-#: ../pykolab/imap/__init__.py:611
+#: ../pykolab/imap/__init__.py:776
#, python-format
msgid "Did not find old folder user/%s to rename"
msgstr "Konnte alten Ordner user/%s nicht zum umbenennen finden."
-#: ../pykolab/imap/__init__.py:613
+#: ../pykolab/imap/__init__.py:778
msgid "Value for user is not a dictionary"
msgstr "Der Wert für user ist kein dictionary"
#. TODO: Go in fact correct the quota.
-#: ../pykolab/imap/__init__.py:673
+#: ../pykolab/imap/__init__.py:846
#, python-format
msgid "Cannot get current IMAP quota for folder %s"
msgstr "Kann aktuelles IMAP Kontingent für den Ordner %s nicht bekommen"
-#: ../pykolab/imap/__init__.py:686
+#: ../pykolab/imap/__init__.py:859
#, python-format
msgid "Quota for %s currently is %s"
msgstr "Kontingent für %s ist aktuell %s"
-#: ../pykolab/imap/__init__.py:692
+#: ../pykolab/imap/__init__.py:865
#, python-format
msgid "Adjusting authentication database quota for folder %s to %d"
msgstr ""
-#: ../pykolab/imap/__init__.py:697
+#: ../pykolab/imap/__init__.py:870
#, python-format
msgid "Correcting quota for %s to %s (currently %s)"
msgstr ""
-#: ../pykolab/imap/__init__.py:774
+#: ../pykolab/imap/__init__.py:947
#, python-format
msgid "Checking folder: %s"
msgstr "Überprüfe Ordner: %s"
-#: ../pykolab/imap/__init__.py:779
+#: ../pykolab/imap/__init__.py:952
#, python-format
msgid "Folder has no corresponding user (1): %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:782
+#: ../pykolab/imap/__init__.py:955
#, python-format
msgid "Folder has no corresponding user (2): %s"
msgstr ""
#. We got user identifier only
-#: ../pykolab/imap/__init__.py:797
+#: ../pykolab/imap/__init__.py:970
msgid "Please don't give us just a user identifier"
msgstr ""
-#: ../pykolab/imap/__init__.py:800
+#: ../pykolab/imap/__init__.py:973
#, python-format
msgid "Deleting folder %s"
-msgstr ""
+msgstr "Lösche Verzeichnis %s"
#: ../pykolab/__init__.py:50
msgid "Returning thread local configuration"
msgstr ""
-#: ../pykolab/logger.py:105
+#: ../pykolab/itip/__init__.py:43
+#, python-format
+msgid "Method %r not really interesting for us."
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:49
#, python-format
-msgid "Could not change the ownership of log file %s"
+msgid "Raw iTip payload: %s"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:59
+msgid "Could not read iTip from message."
msgstr ""
-#: ../pykolab/logger.py:121
+#: ../pykolab/itip/__init__.py:67
+#, python-format
+msgid "Duplicate iTip object: %s"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:90
+msgid "iTip event without a start"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:132
+msgid "Message is not an iTip message (non-multipart message)"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:225
+#, python-format
+msgid "Failed to compose iTip reply message: %r"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:236 ../wallace/module_invitationpolicy.py:936
+#: ../wallace/module_resources.py:964
+#, python-format
+msgid "SMTP sendmail error: %r"
+msgstr ""
+
+#: ../pykolab/logger.py:173 ../pykolab/logger.py:179
+#, python-format
+msgid "Could not change permissions on %s: %r"
+msgstr ""
+
+#: ../pykolab/logger.py:196
#, python-format
msgid "Cannot log to file %s: %s"
msgstr ""
@@ -1425,12 +1951,12 @@ msgstr ""
#: ../pykolab/plugins/__init__.py:74
#, python-format
msgid "RuntimeError for plugin %s: %s"
-msgstr ""
+msgstr "Laufzeitfehler von Plugin %s: %s"
#: ../pykolab/plugins/__init__.py:78
#, python-format
msgid "Plugin %s failed to load (%s: %s)"
-msgstr ""
+msgstr "Plugin %s konnte nicht geladen werden (%s: %s)"
#: ../pykolab/plugins/__init__.py:116 ../pykolab/plugins/__init__.py:118
#, python-format
@@ -1470,7 +1996,7 @@ msgstr ""
#: ../pykolab/plugins/__init__.py:187
#, python-format
msgid "Cannot check options for plugin %s: %s"
-msgstr ""
+msgstr "Kein Zugriff auf Einstellungen des Plugins %s: %s"
#: ../pykolab/plugins/__init__.py:189
#, python-format
@@ -1491,16 +2017,51 @@ msgstr ""
msgid "Attribute substitution for 'mail' failed in Recipient Policy"
msgstr ""
-#: ../pykolab/plugins/recipientpolicy/__init__.py:115
+#: ../pykolab/plugins/recipientpolicy/__init__.py:116
msgid "Could not parse the alternative mail routines"
msgstr ""
+#: ../pykolab/plugins/recipientpolicy/__init__.py:120
+#, python-format
+msgid "Alternative mail routines: %r"
+msgstr ""
+
#: ../pykolab/plugins/recipientpolicy/__init__.py:127
-#: ../pykolab/plugins/recipientpolicy/__init__.py:137
+#, python-format
+msgid ""
+"An error occurred in composing the secondary mail attribute for entry %r"
+msgstr ""
+
+#: ../pykolab/plugins/recipientpolicy/__init__.py:138
+#: ../pykolab/plugins/recipientpolicy/__init__.py:153
+#, python-format
+msgid "Appending additional mail address: %s"
+msgstr ""
+
+#: ../pykolab/plugins/recipientpolicy/__init__.py:142
+#, python-format
+msgid "Policy for secondary email address failed: %r"
+msgstr ""
+
+#: ../pykolab/plugins/recipientpolicy/__init__.py:157
msgid ""
"Attribute substitution for 'alternative_mail' failed in Recipient Policy"
msgstr ""
+#: ../pykolab/plugins/roundcubedb/__init__.py:48
+#, python-format
+msgid "user_delete: %r"
+msgstr ""
+
+#: ../pykolab/plugins/roundcubedb/__init__.py:55
+#: ../pykolab/setup/setup_roundcube.py:160
+msgid "Roundcube installation path not found."
+msgstr ""
+
+#: ../pykolab/plugins/sievemgmt/__init__.py:51
+msgid "Wrong number of arguments for sieve management plugin"
+msgstr ""
+
#: ../pykolab/setup/components.py:58
msgid "Display this help."
msgstr "Zeige diese Hilfe."
@@ -1521,47 +2082,27 @@ msgstr ""
msgid "Free/Busy is not installed on this system"
msgstr ""
-#: ../pykolab/setup/setup_freebusy.py:55
-msgid ""
-"\n"
-" Please supply the MySQL password for the 'roundcube'\n"
-" user. You have supplied this password earlier, and it is\n"
-" available from the database URI setting in\n"
-" /etc/roundcubemail/db.inc.php.\n"
-" "
-msgstr ""
-
-#: ../pykolab/setup/setup_freebusy.py:64
-#: ../pykolab/setup/setup_roundcube.py:56
-msgid "MySQL roundcube password"
-msgstr "MySQL roundcube Passwort"
+#: ../pykolab/setup/setup_imap.py:45
+msgid "Setup IMAP."
+msgstr "Richte IMAP ein."
-#: ../pykolab/setup/setup_freebusy.py:92
-#: ../pykolab/setup/setup_roundcube.py:116 ../pykolab/setup/setup_zpush.py:71
-#, python-format
-msgid "Using template file %r"
+#: ../pykolab/setup/setup_imap.py:89
+msgid "Could not write out Cyrus IMAP configuration file /etc/imapd.conf"
msgstr ""
-#: ../pykolab/setup/setup_freebusy.py:99
-#: ../pykolab/setup/setup_roundcube.py:123 ../pykolab/setup/setup_zpush.py:78
-#, python-format
-msgid "Successfully compiled template %r, writing out to %r"
+#: ../pykolab/setup/setup_imap.py:114
+msgid "Could not write out Cyrus IMAP configuration file /etc/cyrus.conf"
msgstr ""
-#: ../pykolab/setup/setup_freebusy.py:119 ../pykolab/setup/setup_imap.py:143
-#: ../pykolab/setup/setup_ldap.py:288 ../pykolab/setup/setup_ldap.py:521
-#: ../pykolab/setup/setup_mta.py:309 ../pykolab/setup/setup_mysql.py:49
-#: ../pykolab/setup/setup_roundcube.py:191
-#: ../pykolab/setup/setup_syncroton.py:66 ../pykolab/setup/setup_zpush.py:98
-msgid "Could not start and configure to start on boot, the "
+#: ../pykolab/setup/setup_imap.py:158
+msgid "Could not start the cyrus-imapd and kolab-saslauthd services."
msgstr ""
-#: ../pykolab/setup/setup_imap.py:44
-msgid "Setup IMAP."
-msgstr "Richte IMAP ein."
-
-#: ../pykolab/setup/setup_imap.py:88 ../pykolab/setup/setup_imap.py:113
-msgid "Could not write out Cyrus IMAP configuration file /etc/imapd.conf"
+#: ../pykolab/setup/setup_imap.py:173 ../pykolab/setup/setup_kolabd.py:81
+#: ../pykolab/setup/setup_ldap.py:426 ../pykolab/setup/setup_mta.py:455
+#: ../pykolab/setup/setup_mysql.py:58 ../pykolab/setup/setup_roundcube.py:237
+#: ../pykolab/setup/setup_syncroton.py:102
+msgid "Could not configure to start on boot, the "
msgstr ""
#: ../pykolab/setup/setup_kolabd.py:43
@@ -1577,23 +2118,62 @@ msgid ""
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:43
+#: ../pykolab/setup/setup_kolabd.py:72
+msgid "Could not start the kolab server service."
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:45
msgid "LDAP Options"
msgstr "LDAP Optionen"
-#: ../pykolab/setup/setup_ldap.py:50
+#: ../pykolab/setup/setup_ldap.py:52
msgid "Specify FQDN (overriding defaults)."
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:58
+#: ../pykolab/setup/setup_ldap.py:60
msgid "Allow anonymous binds (default: no)."
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:62
+#: ../pykolab/setup/setup_ldap.py:68
+msgid "Skip setting up the LDAP server."
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:76
+msgid "Setup configuration for OpenLDAP compatibility."
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:84
+msgid "Setup configuration for Active Directory compatibility."
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:88
msgid "Setup LDAP."
msgstr "LDAP Einrichten"
-#: ../pykolab/setup/setup_ldap.py:74
+#: ../pykolab/setup/setup_ldap.py:97
+msgid "Skipping setup of LDAP, as specified"
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:126
+msgid ""
+"\n"
+" You can not configure Kolab to run against OpenLDAP\n"
+" and Active Directory simultaneously.\n"
+" "
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:139
+msgid ""
+"\n"
+" It seems 389 Directory Server has an existing\n"
+" instance configured. This setup script does not\n"
+" intend to destroy or overwrite your data. Please\n"
+" make sure /etc/dirsrv/ and /var/lib/dirsrv/ are\n"
+" clean so that this setup does not have to worry.\n"
+" "
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:154
msgid ""
"\n"
" Please supply a password for the LDAP administrator user\n"
@@ -1602,11 +2182,11 @@ msgid ""
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:82
+#: ../pykolab/setup/setup_ldap.py:162
msgid "Administrator password"
msgstr "Administrator Passwort"
-#: ../pykolab/setup/setup_ldap.py:89
+#: ../pykolab/setup/setup_ldap.py:169
msgid ""
"\n"
" Please supply a password for the LDAP Directory Manager\n"
@@ -1616,11 +2196,11 @@ msgid ""
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:98
+#: ../pykolab/setup/setup_ldap.py:178
msgid "Directory Manager password"
msgstr "Verzeichnismanager Passwort"
-#: ../pykolab/setup/setup_ldap.py:105
+#: ../pykolab/setup/setup_ldap.py:185
msgid ""
"\n"
" Please choose the system user and group the service\n"
@@ -1629,15 +2209,15 @@ msgid ""
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:112
+#: ../pykolab/setup/setup_ldap.py:195 ../pykolab/setup/setup_ldap.py:198
msgid "User"
msgstr "Benutzer"
-#: ../pykolab/setup/setup_ldap.py:113
+#: ../pykolab/setup/setup_ldap.py:196 ../pykolab/setup/setup_ldap.py:199
msgid "Group"
msgstr "Gruppe"
-#: ../pykolab/setup/setup_ldap.py:143
+#: ../pykolab/setup/setup_ldap.py:234
msgid ""
"\n"
" This setup procedure plans to set up Kolab Groupware for\n"
@@ -1648,18 +2228,18 @@ msgid ""
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:157
+#: ../pykolab/setup/setup_ldap.py:248
msgid "Domain name to use"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:162 ../pykolab/setup/setup_ldap.py:187
+#: ../pykolab/setup/setup_ldap.py:253 ../pykolab/setup/setup_ldap.py:278
msgid ""
"\n"
" Invalid input. Please try again.\n"
" "
-msgstr ""
+msgstr "\n Ungültige Eingabe. Bitte nochmals versuchen.\n "
-#: ../pykolab/setup/setup_ldap.py:171
+#: ../pykolab/setup/setup_ldap.py:262
msgid ""
"\n"
" The standard root dn we composed for you follows. Please\n"
@@ -1667,11 +2247,15 @@ msgid ""
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:182
+#: ../pykolab/setup/setup_ldap.py:273
msgid "Root DN to use"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:234
+#: ../pykolab/setup/setup_ldap.py:325
+msgid "No directory server setup tool available."
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:337
msgid ""
"\n"
" Setup is now going to set up the 389 Directory Server. This\n"
@@ -1680,24 +2264,42 @@ msgid ""
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:241
+#: ../pykolab/setup/setup_ldap.py:344
msgid "Setting up 389 Directory Server"
msgstr "Richte 389 Verzeichnisserver ein"
-#. TODO: Get the return code and display output if not successful.
-#: ../pykolab/setup/setup_ldap.py:253
+#: ../pykolab/setup/setup_ldap.py:356
+msgid ""
+"\n"
+" An error was detected in the setup procedure for 389\n"
+" Directory Server. This setup will write out stderr and\n"
+" stdout to /var/log/kolab/setup.error.log and\n"
+" /var/log/kolab/setup.out.log respectively, before it\n"
+" exits.\n"
+" "
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:373
msgid "Setup DS stdout:"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:256
+#: ../pykolab/setup/setup_ldap.py:376
msgid "Setup DS stderr:"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:279 ../pykolab/setup/setup_mysql.py:129
-msgid "Could not find the Kolab schema file"
-msgstr "Konnte Kolab Schema Datei nicht finden"
+#: ../pykolab/setup/setup_ldap.py:402
+msgid "Could not copy the LDAP extensions for Kolab"
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:405
+msgid "Could not find the ldap Kolab schema file"
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:417
+msgid "Could not start the directory server service."
+msgstr ""
-#: ../pykolab/setup/setup_ldap.py:293
+#: ../pykolab/setup/setup_ldap.py:431
msgid ""
"\n"
" Please supply a Cyrus Administrator password. This\n"
@@ -1708,11 +2310,11 @@ msgid ""
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:303
+#: ../pykolab/setup/setup_ldap.py:441
msgid "Cyrus Administrator password"
msgstr "Cyrus Administrator Passwort"
-#: ../pykolab/setup/setup_ldap.py:310
+#: ../pykolab/setup/setup_ldap.py:448
msgid ""
"\n"
" Please supply a Kolab Service account password. This\n"
@@ -1722,98 +2324,127 @@ msgid ""
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:319
+#: ../pykolab/setup/setup_ldap.py:457
msgid "Kolab Service password"
msgstr "Kolab-Dienst-Passwort"
-#: ../pykolab/setup/setup_ldap.py:329
+#: ../pykolab/setup/setup_ldap.py:467
msgid "Writing out configuration to kolab.conf"
msgstr "Schreibe Einstellungen in kolab.conf"
-#: ../pykolab/setup/setup_ldap.py:343
+#: ../pykolab/setup/setup_ldap.py:481
msgid "Inserting service users into LDAP."
msgstr "Füge Service-Benutzer ins LDAP ein."
-#: ../pykolab/setup/setup_ldap.py:417
+#: ../pykolab/setup/setup_ldap.py:555
msgid "Writing out cn=kolab,cn=config"
msgstr "Schreibe cn=kolab,cn=config"
#. TODO: Add kolab-admin role
#. TODO: Assign kolab-admin admin ACLs
-#: ../pykolab/setup/setup_ldap.py:441
+#: ../pykolab/setup/setup_ldap.py:579
#, python-format
msgid "Adding domain %s to list of domains for this deployment"
msgstr "Füge Domain %s zu dieser Installation hinzu"
-#: ../pykolab/setup/setup_ldap.py:457
+#: ../pykolab/setup/setup_ldap.py:607
msgid "Disabling anonymous binds"
msgstr "Stelle anonymes Binden ab"
#. TODO: Ensure the uid attribute is unique
#. TODO^2: Consider renaming the general "attribute uniqueness to "uid
#. attribute uniqueness"
-#: ../pykolab/setup/setup_ldap.py:465
+#: ../pykolab/setup/setup_ldap.py:615
msgid "Enabling attribute uniqueness plugin"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:471
+#: ../pykolab/setup/setup_ldap.py:621
msgid "Enabling referential integrity plugin"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:477
+#: ../pykolab/setup/setup_ldap.py:627
msgid "Enabling and configuring account policy plugin"
msgstr ""
#. TODO: Add kolab-admin role
-#: ../pykolab/setup/setup_ldap.py:492
+#: ../pykolab/setup/setup_ldap.py:642
msgid "Adding the kolab-admin role"
msgstr "Füge Kolab-Admin Rolle hinzu"
#. TODO: User writeable attributes on root_dn
-#: ../pykolab/setup/setup_ldap.py:503
+#: ../pykolab/setup/setup_ldap.py:653
#, python-format
msgid "Setting access control to %s"
msgstr ""
-#: ../pykolab/setup/setup_mta.py:40
+#: ../pykolab/setup/setup_ldap.py:679
+msgid "Could not start and configure to start on boot, the "
+msgstr ""
+
+#: ../pykolab/setup/setup_mta.py:41
msgid "Setup MTA."
msgstr "Richte MTA ein."
-#: ../pykolab/setup/setup_mta.py:224 ../pykolab/setup/setup_php.py:80
+#: ../pykolab/setup/setup_mta.py:317 ../pykolab/setup/setup_php.py:106
#, python-format
msgid "Setting key %r to %r"
msgstr ""
-#: ../pykolab/setup/setup_mta.py:252
+#: ../pykolab/setup/setup_mta.py:350
msgid "Could not write out Postfix configuration file /etc/postfix/master.cf"
msgstr ""
-#: ../pykolab/setup/setup_mta.py:287
-msgid ""
-"Could not write out Amavis configuration file /etc/amavisd/amavisd.conf"
+#: ../pykolab/setup/setup_mta.py:397
+msgid "Could not write out Amavis configuration file amavisd.conf"
+msgstr ""
+
+#: ../pykolab/setup/setup_mta.py:405
+msgid "Not writing out any configuration for Amavis."
+msgstr ""
+
+#: ../pykolab/setup/setup_mta.py:437
+msgid "Could not start the postfix, clamav and amavisd services services."
msgstr ""
#: ../pykolab/setup/setup_mysql.py:39
msgid "Setup MySQL."
msgstr "Richte MySQL ein."
-#: ../pykolab/setup/setup_mysql.py:53
+#: ../pykolab/setup/setup_mysql.py:49
+msgid "Could not start the MySQL database service."
+msgstr ""
+
+#: ../pykolab/setup/setup_mysql.py:71
+msgid "What MySQL server are we setting up?"
+msgstr ""
+
+#: ../pykolab/setup/setup_mysql.py:75
msgid ""
"\n"
-" Please supply a root password for MySQL. This password will\n"
-" be the administrative user for this MySQL server, and it\n"
-" should be kept a secret. After this setup process has\n"
-" completed, Kolab is going to discard and forget about this\n"
-" password, but you will need it for administrative tasks in\n"
-" MySQL.\n"
-" "
+" Please supply the root password for MySQL, so we can set\n"
+" up user accounts for other components that use MySQL.\n"
+" "
msgstr ""
-#: ../pykolab/setup/setup_mysql.py:64
+#: ../pykolab/setup/setup_mysql.py:82 ../pykolab/setup/setup_mysql.py:99
+#: ../pykolab/setup/setup_roundcube.py:183
+#: ../pykolab/setup/setup_syncroton.py:63
msgid "MySQL root password"
+msgstr "MySQL root Password"
+
+#: ../pykolab/setup/setup_mysql.py:88
+msgid ""
+"\n"
+" Please supply a root password for MySQL. This password\n"
+" will be the administrative user for this MySQL server,\n"
+" and it should be kept a secret. After this setup process\n"
+" has completed, Kolab is going to discard and forget\n"
+" about this password, but you will need it for\n"
+" administrative tasks in MySQL.\n"
+" "
msgstr ""
-#: ../pykolab/setup/setup_mysql.py:103
+#: ../pykolab/setup/setup_mysql.py:139
msgid ""
"\n"
" Please supply a password for the MySQL user 'kolab'.\n"
@@ -1822,8 +2453,12 @@ msgid ""
" "
msgstr ""
-#: ../pykolab/setup/setup_mysql.py:111
+#: ../pykolab/setup/setup_mysql.py:147
msgid "MySQL kolab password"
+msgstr "MySQL-kolab Password"
+
+#: ../pykolab/setup/setup_mysql.py:165
+msgid "Could not find the MySQL Kolab schema file"
msgstr ""
#: ../pykolab/setup/setup_php.py:42
@@ -1832,23 +2467,38 @@ msgstr "PHP Optionen"
#: ../pykolab/setup/setup_php.py:49
msgid "Specify the timezone for PHP."
+msgstr "Zeitzone für PHP definieren."
+
+#: ../pykolab/setup/setup_php.py:57
+msgid "Specify the path to the php.ini file used with the webserver."
msgstr ""
-#: ../pykolab/setup/setup_php.py:53
+#: ../pykolab/setup/setup_php.py:61
msgid "Setup PHP."
msgstr "Richte PHP ein."
-#: ../pykolab/setup/setup_php.py:58
+#: ../pykolab/setup/setup_php.py:66
msgid ""
"\n"
" Please supply the timezone PHP should be using.\n"
+" You have to use a Continent or Country / City locality name\n"
+" like 'Europe/Berlin', but not just 'CEST'.\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_php.py:64
+#: ../pykolab/setup/setup_php.py:74
msgid "Timezone ID"
msgstr "Zeitzonen ID"
+#: ../pykolab/setup/setup_php.py:80
+#, python-format
+msgid "Cannot configure PHP through %r (No such file or directory)"
+msgstr ""
+
+#: ../pykolab/setup/setup_php.py:91
+msgid "Could not find PHP configuration file php.ini"
+msgstr ""
+
#: ../pykolab/setup/setup_roundcube.py:44
msgid "Setup Roundcube."
msgstr "Stelle Roundcube ein."
@@ -1862,18 +2512,29 @@ msgid ""
" "
msgstr ""
-#: ../pykolab/setup/setup_syncroton.py:40
-msgid "Setup Syncroton."
-msgstr "Richte Syncroton ein."
+#: ../pykolab/setup/setup_roundcube.py:56
+msgid "MySQL roundcube password"
+msgstr "MySQL roundcube Passwort"
-#: ../pykolab/setup/setup_zpush.py:41
-msgid "Setup zpush."
-msgstr "zpush einrichten."
+#: ../pykolab/setup/setup_roundcube.py:120
+#, python-format
+msgid "Using template file %r"
+msgstr ""
-#: ../pykolab/setup/setup_zpush.py:45
-msgid "Z-Push is not installed on this system"
+#: ../pykolab/setup/setup_roundcube.py:127
+#, python-format
+msgid "Successfully compiled template %r, writing out to %r"
+msgstr ""
+
+#: ../pykolab/setup/setup_roundcube.py:228
+#: ../pykolab/setup/setup_syncroton.py:93
+msgid "Could not start the webserver server service."
msgstr ""
+#: ../pykolab/setup/setup_syncroton.py:40
+msgid "Setup Syncroton."
+msgstr "Richte Syncroton ein."
+
#. start_max = (int)(time.time())
#: ../pykolab/telemetry.py:588
#, python-format
@@ -1899,111 +2560,181 @@ msgstr ""
msgid "No database available"
msgstr "Keine Datenbank verfügbar"
-#: ../pykolab/utils.py:57 ../pykolab/utils.py:59
+#: ../pykolab/utils.py:62 ../pykolab/utils.py:64
#, python-format
msgid "Confirm %s: "
-msgstr ""
+msgstr "Bestätige %s:"
-#: ../pykolab/utils.py:62
+#: ../pykolab/utils.py:67
msgid "Incorrect confirmation. "
-msgstr ""
+msgstr "Ungültige Bestätigung."
-#: ../pykolab/utils.py:67 ../pykolab/utils.py:72
+#: ../pykolab/utils.py:72 ../pykolab/utils.py:77
#, python-format
msgid "%s: "
msgstr "%s:"
-#: ../pykolab/utils.py:69 ../pykolab/utils.py:74
+#: ../pykolab/utils.py:74 ../pykolab/utils.py:79
#, python-format
msgid "%s [%s]: "
msgstr "%s [%s]: "
-#: ../pykolab/utils.py:119
+#: ../pykolab/utils.py:124
msgid "Please answer 'yes' or 'no'."
msgstr "Bitte antworten Sie mit 'yes' (ja) oder 'no' (nein)."
-#: ../pykolab/utils.py:185
+#: ../pykolab/utils.py:164
+msgid "Choice"
+msgstr ""
+
+#: ../pykolab/utils.py:167
+msgid "Choice (type '?' for options)"
+msgstr ""
+
+#: ../pykolab/utils.py:268
#, python-format
msgid "Could not change the permissions on %s"
msgstr ""
-#: ../pykolab/wap_client/__init__.py:257
+#: ../pykolab/utils.py:479
+#, python-format
+msgid "Transliterating string %r with locale %r"
+msgstr ""
+
+#: ../pykolab/utils.py:487
+msgid "Attempting to set locale"
+msgstr ""
+
+#: ../pykolab/utils.py:489
+msgid "Success setting locale"
+msgstr ""
+
+#: ../pykolab/utils.py:491
+msgid "Failure to set locale"
+msgstr ""
+
+#: ../pykolab/utils.py:499
+#, python-format
+msgid "Executing '%s | %s'"
+msgstr ""
+
+#: ../pykolab/utils.py:510
+#, python-format
+msgid "Could not translate %s using locale %s"
+msgstr ""
+
+#: ../pykolab/wap_client/__init__.py:320
#, python-format
msgid "Requesting %r with params %r"
msgstr ""
-#: ../pykolab/wap_client/__init__.py:263
+#: ../pykolab/wap_client/__init__.py:328
#, python-format
msgid "Got response: %r"
msgstr ""
#. Some data is not JSON
-#: ../pykolab/wap_client/__init__.py:268
+#: ../pykolab/wap_client/__init__.py:334
msgid "Response data is not JSON"
msgstr ""
-#: ../pykolab/xml/attendee.py:79 ../pykolab/xml/attendee.py:99
+#. support integer values, too
+#: ../pykolab/xml/attendee.py:9 ../pykolab/xml/attendee.py:17
+msgid "Needs Action"
+msgstr ""
+
+#: ../pykolab/xml/attendee.py:10 ../pykolab/xml/attendee.py:18
+msgid "Accepted"
+msgstr "Akzeptiert"
+
+#: ../pykolab/xml/attendee.py:11 ../pykolab/xml/attendee.py:19
+msgid "Declined"
+msgstr "Abgelehnt"
+
+#: ../pykolab/xml/attendee.py:12 ../pykolab/xml/attendee.py:20
+msgid "Tentatively Accepted"
+msgstr "Provisorisch Akzeptiert"
+
+#: ../pykolab/xml/attendee.py:13 ../pykolab/xml/attendee.py:21
+msgid "Delegated"
+msgstr "Delegiert"
+
+#: ../pykolab/xml/attendee.py:14
+msgid "Completed"
+msgstr ""
+
+#: ../pykolab/xml/attendee.py:15
+msgid "In Process"
+msgstr ""
+
+#: ../pykolab/xml/attendee.py:108 ../pykolab/xml/attendee.py:130
msgid "Not a valid attendee"
msgstr ""
-#: ../pykolab/xml/attendee.py:84
+#: ../pykolab/xml/attendee.py:115
msgid "No valid delegator references found"
msgstr ""
-#: ../pykolab/xml/attendee.py:104
+#: ../pykolab/xml/attendee.py:135
msgid "No valid delegatee references found"
msgstr ""
-#: ../pykolab/xml/attendee.py:140
+#: ../pykolab/xml/attendee.py:180
#, python-format
msgid "Invalid cutype %r"
msgstr ""
-#: ../pykolab/xml/attendee.py:151
+#: ../pykolab/xml/attendee.py:192
#, python-format
msgid "Invalid participant status %r"
msgstr ""
-#: ../pykolab/xml/attendee.py:159
+#: ../pykolab/xml/attendee.py:200
#, python-format
msgid "Invalid role %r"
msgstr ""
-#: ../pykolab/xml/event.py:172
+#: ../pykolab/xml/event.py:100 ../pykolab/xml/event.py:708
+#: ../pykolab/xml/event.py:751
+msgid "Event start needs datetime.date or datetime.datetime instance"
+msgstr ""
+
+#: ../pykolab/xml/event.py:241
#, python-format
msgid "No attendee with email or name %r"
msgstr ""
-#: ../pykolab/xml/event.py:180
+#: ../pykolab/xml/event.py:249
#, python-format
msgid "Invalid argument value attendee %r, must be basestring or Attendee"
msgstr ""
-#: ../pykolab/xml/event.py:186
+#: ../pykolab/xml/event.py:255
#, python-format
msgid "No attendee with email %r"
msgstr ""
-#: ../pykolab/xml/event.py:192
+#: ../pykolab/xml/event.py:261
#, python-format
msgid "No attendee with name %r"
msgstr ""
-#: ../pykolab/xml/event.py:338
+#: ../pykolab/xml/event.py:426
msgid "Invalid participant status"
msgstr ""
-#: ../pykolab/xml/event.py:538
-msgid "Event end needs datetime.date or datetime.datetime instance"
+#: ../pykolab/xml/event.py:542
+#, python-format
+msgid "Invalid status %r"
msgstr ""
-#: ../pykolab/xml/event.py:654
+#: ../pykolab/xml/event.py:550
#, python-format
-msgid "Invalid status %r"
+msgid "Invalid classification %r"
msgstr ""
-#: ../pykolab/xml/event.py:675 ../pykolab/xml/event.py:725
-msgid "Event start needs datetime.date or datetime.datetime instance"
+#: ../pykolab/xml/event.py:577
+msgid "Event end needs datetime.date or datetime.datetime instance"
msgstr ""
#: ../pykolab/xml/event.py:761
@@ -2011,62 +2742,434 @@ msgstr ""
msgid "Invalid status set: %r"
msgstr ""
-#: ../pykolab/xml/event.py:879
+#: ../pykolab/xml/event.py:923
msgid "No sender specified"
msgstr ""
-#: ../saslauthd/__init__.py:126 ../saslauthd/__init__.py:134
-#: ../wallace/__init__.py:362 ../wallace/__init__.py:371
+#: ../pykolab/xml/event.py:932
+#, python-format
+msgid "Invitation for %s was %s"
+msgstr ""
+
+#: ../pykolab/xml/event.py:937
+msgid "This is an automated response to one of your event requests."
+msgstr ""
+
+#: ../saslauthd/__init__.py:99
+#, python-format
+msgid "Could not create %r: %r"
+msgstr ""
+
+#: ../saslauthd/__init__.py:137 ../saslauthd/__init__.py:145
+#: ../wallace/__init__.py:403 ../wallace/__init__.py:412
msgid ""
"Traceback occurred, please report a bug at http://bugzilla.kolabsys.com"
msgstr "Ein Fehler mit Traceback trat auf, bitte legen Sie einen Bericht auf http://bugzilla.kolabsys.com an"
-#: ../wallace/__init__.py:61
+#: ../saslauthd/__init__.py:185
+msgid "kolab-saslauthd could not accept "
+msgstr ""
+
+#: ../saslauthd/__init__.py:190
+msgid "Maximum tries exceeded, exiting"
+msgstr ""
+
+#: ../tests/functional/test_wallace/test_005_resource_invitation.py:190
+#: ../wallace/module_resources.py:879
+#, python-format
+msgid "Reservation Request for %(summary)s was %(status)s"
+msgstr ""
+
+#. check notification message sent to resource owner (jane)
+#: ../tests/functional/test_wallace/test_005_resource_invitation.py:605
+#: ../tests/functional/test_wallace/test_005_resource_invitation.py:621
+#: ../wallace/module_resources.py:954
+#, python-format
+msgid "Booking for %s has been %s"
+msgstr "Buchung für %s wurde %s"
+
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:146
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:720
+#: ../wallace/module_invitationpolicy.py:374
+#, python-format
+msgid "\"%(summary)s\" has been %(status)s"
+msgstr "\"%(summary)s\" wurde %(status)s"
+
+#. check for notification message
+#. this notification should be suppressed until mark has replied, too
+#. this triggers an additional notification
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:616
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:622
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:635
+#: ../wallace/module_invitationpolicy.py:925
+#, python-format
+msgid "\"%s\" has been updated"
+msgstr "\"%s\" wurde aktualisiert"
+
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:627
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:639
+msgid "PENDING"
+msgstr ""
+
+#: ../wallace/__init__.py:57
+#, python-format
+msgid "Wallace modules: %r"
+msgstr ""
+
+#: ../wallace/__init__.py:69
+#, python-format
+msgid "Module %s.execute() failed on message %r with error: %s"
+msgstr ""
+
+#: ../wallace/__init__.py:78
#, python-format
msgid "Worker process %s initializing"
msgstr ""
-#: ../wallace/__init__.py:80
+#: ../wallace/__init__.py:100
msgid "Bind address for Wallace."
-msgstr ""
+msgstr "Bind-Adresse für Wallace."
-#: ../wallace/__init__.py:106
+#: ../wallace/__init__.py:126
msgid "Port that Wallace is supposed to use."
msgstr "Port, den Wallace benutzen soll."
-#: ../wallace/__init__.py:157
+#: ../wallace/__init__.py:177
#, python-format
msgid "Could not bind to socket on port %d on bind "
msgstr ""
-#: ../wallace/__init__.py:169
+#: ../wallace/__init__.py:189
msgid "Could not shut down socket"
-msgstr ""
+msgstr "Konnte Socket nicht schließen"
-#: ../wallace/__init__.py:226
+#: ../wallace/__init__.py:253
msgid "Accepted connection"
msgstr "Verbindung akzeptiert"
-#: ../wallace/__init__.py:387
+#: ../wallace/__init__.py:428
#, python-format
msgid "Could not write pid file %s"
msgstr ""
-#: ../wallace/module_optout.py:61 ../wallace/module_resources.py:94
+#: ../wallace/module_footer.py:60 ../wallace/module_gpgencrypt.py:60
+#: ../wallace/module_invitationpolicy.py:168 ../wallace/module_optout.py:61
+#: ../wallace/module_resources.py:120
#, python-format
msgid "Issuing callback after processing to stage %s"
msgstr ""
-#: ../wallace/module_optout.py:62 ../wallace/module_resources.py:100
+#: ../wallace/module_footer.py:61 ../wallace/module_gpgencrypt.py:61
+#: ../wallace/module_invitationpolicy.py:170 ../wallace/module_optout.py:62
+#: ../wallace/module_resources.py:126
#, python-format
msgid "Testing cb_action_%s()"
msgstr ""
-#: ../wallace/module_optout.py:64 ../wallace/module_resources.py:103
+#: ../wallace/module_footer.py:63 ../wallace/module_gpgencrypt.py:63
+#: ../wallace/module_invitationpolicy.py:172 ../wallace/module_optout.py:64
+#: ../wallace/module_resources.py:129
#, python-format
msgid "Attempting to execute cb_action_%s()"
msgstr ""
+#: ../wallace/module_footer.py:67
+#, python-format
+msgid "Executing module footer for %r, %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:66
+#, python-format
+msgid "Executing module gpgencrypt for %r, %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:98
+msgid "Message is already encrypted (app/pgp-enc content-type)"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:102
+msgid "Message already encrypted by main content-type header"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:131
+msgid ""
+"Configured to encrypt to a key not configured, and strict policy enabled. "
+"Bailing out."
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:134
+msgid ""
+"Configured to encrypt to a key not configured, but continuing anyway (see "
+"'gpgencrypt_strict')."
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:171
+#, python-format
+msgid "Recipients: %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:183
+#, python-format
+msgid "Current keys: %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:188
+#, python-format
+msgid "Retrieving key for recipient: %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:192 ../wallace/module_gpgencrypt.py:208
+#, python-format
+msgid "Found matching address %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:200
+#, python-format
+msgid "Found matching address %r in remote keys"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:232
+#, python-format
+msgid "An error occurred: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:154
+#, python-format
+msgid "Invitation policy called for %r, %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:211
+#: ../wallace/module_resources.py:169
+#, python-format
+msgid "Failed to parse iTip events from message: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:215
+msgid ""
+"Message is not an iTip message or does not contain any (valid) iTip events."
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:219
+#, python-format
+msgid ""
+"iTip events attached to this message contain the following information: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:232
+#, python-format
+msgid "No itips, no users, pass along %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:235
+#, python-format
+msgid "iTips, but no users, pass along %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:255
+#, python-format
+msgid "No user attendee matching envelope recipient %s, skip message"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:259
+#, python-format
+msgid "Receiving user: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:284
+#, python-format
+msgid "Apply invitation policy %r for domain %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:295
+#, python-format
+msgid "Ignoring '%s' iTip method"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:299
+#, python-format
+msgid "iTip message %r consumed by the invitationpolicy module"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:315
+msgid "Pass invitation for manual processing"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:320
+#, python-format
+msgid "Receiving Attendee: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:339
+#, python-format
+msgid "Existing event: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:350
+#, python-format
+msgid "Precondition for event %r fulfilled: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:386
+#, python-format
+msgid "No RSVP for recipient %r requested"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:412
+msgid "Pass reply for manual processing"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:419
+#, python-format
+msgid "Sender Attendee: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:431
+#, python-format
+msgid ""
+"The iTip reply sequence (%r) doesn't match the referred event version (%r). "
+"Forwarding to Inbox."
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:437
+#, python-format
+msgid "Auto-updating event %r on iTip REPLY"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:459
+#: ../wallace/module_invitationpolicy.py:488
+msgid ""
+"The event referred by this reply was not found in the user's calendars. "
+"Forwarding to Inbox."
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:472
+msgid "Pass cancellation for manual processing"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:517
+#, python-format
+msgid "Checking if email address %r belongs to a local user"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:522
+#, python-format
+msgid "User DN: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:524
+#, python-format
+msgid "No user record(s) found for %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:577
+#, python-format
+msgid "User record doesn't have the mailbox attribute %r set"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:590
+#, python-format
+msgid "IMAP proxy authentication failed: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:612
+#, python-format
+msgid "List calendar folders for user %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:628
+#, python-format
+msgid "IMAP metadata for %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:658
+#, python-format
+msgid "Searching folder %r for event %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:670
+#: ../wallace/module_invitationpolicy.py:709
+#: ../wallace/module_resources.py:486
+#, python-format
+msgid "Failed to parse event from message %s/%s: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:696
+#, python-format
+msgid "Listing events from folder %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:715
+#, python-format
+msgid "Existing event %r conflicts with invitation %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:722
+#: ../wallace/module_resources.py:344
+#, python-format
+msgid "start: %r, end: %r, total: %r, messages: %d"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:748
+#, python-format
+msgid "%r is locked, waiting..."
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:811
+#, python-format
+msgid "Failed to save event: no calendar folder found for user %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:814
+#, python-format
+msgid "Save event %r to user calendar %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:827
+#, python-format
+msgid "Failed to save event to user calendar at %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:843
+#, python-format
+msgid "Delete event %r in %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:863
+#, python-format
+msgid "Compose participation status summary for event %r to user %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:901
+#, python-format
+msgid ""
+"Waiting for more automated replies (got %d of %d); skipping notification"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:998
+#, python-format
+msgid "Updated %s's copy of %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:1001
+#, python-format
+msgid "Attendee %s's copy of %r not found"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:1004
+#, python-format
+msgid "Attendee %r not found in LDAP"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:1008
+#, python-format
+msgid ""
+"\n"
+" %(name)s has %(status)s your invitation for %(summary)s.\n"
+"\n"
+" *** This is an automated response sent by the Kolab Invitation system ***\n"
+" "
+msgstr ""
+
#. modules.next_module('optout')
#: ../wallace/module_optout.py:70
#, python-format
@@ -2088,210 +3191,295 @@ msgstr ""
msgid "Could not send request to optout_url %s"
msgstr ""
-#: ../wallace/module_resources.py:81
+#: ../wallace/module_resources.py:110
#, python-format
msgid "Resource Management called for %r, %r"
msgstr ""
-#: ../wallace/module_resources.py:139
+#: ../wallace/module_resources.py:174
msgid "Message is not an iTip message or does not contain any "
msgstr ""
-#: ../wallace/module_resources.py:147
+#: ../wallace/module_resources.py:182
msgid "iTip events attached to this message contain the "
msgstr ""
-#: ../wallace/module_resources.py:174
+#: ../wallace/module_resources.py:205
msgid "Not an iTip message, but sent to resource nonetheless. Reject message"
msgstr ""
-#: ../wallace/module_resources.py:182
-msgid "No itips, no resources, pass along"
+#: ../wallace/module_resources.py:213
+#, python-format
+msgid "No itips, no resources, pass along %r"
msgstr ""
-#: ../wallace/module_resources.py:186
-msgid "iTips, but no resources, pass along"
+#: ../wallace/module_resources.py:216
+#, python-format
+msgid "iTips, but no resources, pass along %r"
msgstr ""
-#: ../wallace/module_resources.py:218
+#: ../wallace/module_resources.py:225
#, python-format
-msgid "Resources: %r"
+msgid "No resource attendees matching envelope recipient %s, Reject message"
msgstr ""
-#: ../wallace/module_resources.py:236
+#: ../wallace/module_resources.py:234
#, python-format
-msgid "Checking events in resource folder %r"
+msgid "Resources: %r; %r"
msgstr ""
-#: ../wallace/module_resources.py:243
+#: ../wallace/module_resources.py:244
#, python-format
-msgid "Mailbox for resource %r doesn't exist"
+msgid "Receiving Resource: %r; %r"
msgstr ""
-#: ../wallace/module_resources.py:256
+#: ../wallace/module_resources.py:252
#, python-format
-msgid "Fetching message UID %r from folder %r"
+msgid "Recipient %r is non-participant, ignoring message"
msgstr ""
-#: ../wallace/module_resources.py:295
+#: ../wallace/module_resources.py:279
#, python-format
-msgid "Event %r conflicts with event "
+msgid "Accept invitation for individual resource %r / %r"
msgstr ""
#: ../wallace/module_resources.py:308
#, python-format
-msgid "start: %r, end: %r, total: %r, messages: %r"
+msgid "Delegate invitation for resource collection %r to %r"
msgstr ""
-#: ../wallace/module_resources.py:315
+#: ../wallace/module_resources.py:340
+#, python-format
+msgid "Failed to read resource calendar for %r: %r"
+msgstr ""
+
+#: ../wallace/module_resources.py:350
#, python-format
msgid "Polling for resource %r"
msgstr ""
-#: ../wallace/module_resources.py:319
+#: ../wallace/module_resources.py:353
#, python-format
msgid "Resource %r has been popped from the list"
msgstr ""
-#: ../wallace/module_resources.py:326
+#: ../wallace/module_resources.py:357
msgid "Resource is a collection"
msgstr ""
-#: ../wallace/module_resources.py:374 ../wallace/module_resources.py:424
+#: ../wallace/module_resources.py:368
#, python-format
-msgid "Adding event to %r"
+msgid "Removed conflicting resources from %r: (%r) => %r"
msgstr ""
-#: ../wallace/module_resources.py:473
+#: ../wallace/module_resources.py:380
#, python-format
-msgid "Method %r not really interesting for us."
+msgid "Conflicting events: %r for resource %r"
msgstr ""
-#: ../wallace/module_resources.py:481
+#: ../wallace/module_resources.py:397
#, python-format
-msgid "Raw iTip payload: %s"
+msgid "Delegate to another resource collection member: %r to %r"
msgstr ""
-#: ../wallace/module_resources.py:491
-msgid "Could not read iTip from message."
+#: ../wallace/module_resources.py:459
+#, python-format
+msgid "Checking events in resource folder %r"
msgstr ""
-#: ../wallace/module_resources.py:513
-msgid "iTip event without a start"
+#: ../wallace/module_resources.py:475
+#, python-format
+msgid "Fetching message UID %r from folder %r"
msgstr ""
-#. end if c.name == "VEVENT"
-#. end for c in cal.walk()
-#. end if part.get_content_type() == "text/calendar"
-#. end for part in message.walk()
-#. if message.is_multipart()
-#: ../wallace/module_resources.py:543
-msgid "Message is not an iTip message (non-multipart message)"
+#: ../wallace/module_resources.py:498
+#, python-format
+msgid "Event %r conflicts with event %r"
msgstr ""
-#: ../wallace/module_resources.py:564
+#: ../wallace/module_resources.py:525
#, python-format
-msgid "Checking if email address %r belongs to a resource (collection)"
+msgid "Adding event to %r: %r"
msgstr ""
-#: ../wallace/module_resources.py:575 ../wallace/module_resources.py:649
-#: ../wallace/module_resources.py:699
+#: ../wallace/module_resources.py:573
#, python-format
-msgid "No resource (collection) records found for %r"
+msgid "Failed to save event to resource calendar at %r: %r"
+msgstr ""
+
+#: ../wallace/module_resources.py:590
+#, python-format
+msgid "Delete resource calendar object %r in %r: %r"
msgstr ""
-#: ../wallace/module_resources.py:583 ../wallace/module_resources.py:657
-#: ../wallace/module_resources.py:707
+#: ../wallace/module_resources.py:633
+#, python-format
+msgid "Checking if email address %r belongs to a resource (collection)"
+msgstr ""
+
+#: ../wallace/module_resources.py:641 ../wallace/module_resources.py:709
+#: ../wallace/module_resources.py:743
#, python-format
msgid "Resource record(s): %r"
msgstr ""
-#: ../wallace/module_resources.py:589 ../wallace/module_resources.py:664
-#: ../wallace/module_resources.py:714
+#: ../wallace/module_resources.py:643 ../wallace/module_resources.py:711
+#: ../wallace/module_resources.py:746
+#, python-format
+msgid "No resource (collection) records found for %r"
+msgstr ""
+
+#: ../wallace/module_resources.py:647 ../wallace/module_resources.py:715
+#: ../wallace/module_resources.py:750
#, python-format
msgid "Resource record: %r"
msgstr ""
-#: ../wallace/module_resources.py:608
+#: ../wallace/module_resources.py:667
#, python-format
msgid "Raw itip_events: %r"
msgstr ""
-#: ../wallace/module_resources.py:616
+#: ../wallace/module_resources.py:675
#, python-format
msgid "Raw set of attendees: %r"
msgstr ""
-#: ../wallace/module_resources.py:624
+#: ../wallace/module_resources.py:683
#, python-format
msgid "Raw set of resources: %r"
msgstr ""
-#: ../wallace/module_resources.py:638
+#: ../wallace/module_resources.py:702
#, python-format
msgid "Checking if attendee %r is a resource (collection)"
msgstr ""
-#: ../wallace/module_resources.py:671 ../wallace/module_resources.py:717
+#: ../wallace/module_resources.py:718 ../wallace/module_resources.py:752
msgid "Resource reservation made but no resource records found"
msgstr ""
-#: ../wallace/module_resources.py:689
+#: ../wallace/module_resources.py:737
#, python-format
msgid "Checking if resource %r is a resource (collection)"
msgstr ""
-#: ../wallace/module_resources.py:721
+#: ../wallace/module_resources.py:755
msgid "The following resources are being referred to in the "
msgstr ""
+#: ../wallace/module_resources.py:894
+#, python-format
+msgid ""
+"\n"
+" *** This is an automated response, please do not reply! ***\n"
+"\n"
+" Your reservation was delegated to \"%s\" which is available for the requested time.\n"
+" "
+msgstr ""
+
+#: ../wallace/module_resources.py:905
+#, python-format
+msgid ""
+"\n"
+" *** This is an automated response, please do not reply! ***\n"
+" \n"
+" We hereby inform you that your reservation was %s.\n"
+" "
+msgstr ""
+
+#: ../wallace/module_resources.py:912
+#, python-format
+msgid ""
+"\n"
+" If you have questions about this reservation, please contact\n"
+" %s <%s> %s\n"
+" "
+msgstr ""
+
+#: ../wallace/module_resources.py:941
+#, python-format
+msgid "Sending booking notification for event %r to %r from %r"
+msgstr ""
+
+#: ../wallace/module_resources.py:954
+msgid "failed"
+msgstr ""
+
+#: ../wallace/module_resources.py:973
+#, python-format
+msgid ""
+"\n"
+" The resource booking for %(resource)s by %(orgname)s <%(orgemail)s> has been %(status)s for %(date)s.\n"
+"\n"
+" *** This is an automated message, sent to you as the resource owner. ***\n"
+" "
+msgstr ""
+
+#: ../wallace/module_resources.py:979
+#, python-format
+msgid ""
+"\n"
+" A reservation request for %(resource)s could not be processed automatically.\n"
+" Please contact %(orgname)s <%(orgemail)s> who requested this resource for %(date)s. Subject: %(summary)s.\n"
+"\n"
+" *** This is an automated message, sent to you as the resource owner. ***\n"
+" "
+msgstr ""
+
#. This is a nested module
-#: ../wallace/modules.py:96
+#: ../wallace/modules.py:97
#, python-format
msgid "Module Group: %s"
+msgstr "Modulgruppe: %s"
+
+#: ../wallace/modules.py:108
+#, python-format
+msgid "No such module %r in modules %r (1)."
msgstr ""
-#: ../wallace/modules.py:107 ../wallace/modules.py:112
-msgid "No such module."
+#: ../wallace/modules.py:113
+#, python-format
+msgid "No such module %r in modules %r (2)."
msgstr ""
-#: ../wallace/modules.py:118
+#: ../wallace/modules.py:119
#, python-format
msgid "Holding message in queue for manual review (%s by %s)"
-msgstr ""
+msgstr "Behalte Nachricht zur manuellen Prüfung in der Warteliste (%s von %s)"
-#: ../wallace/modules.py:121
+#: ../wallace/modules.py:122
#, python-format
msgid "Deferring message in %s (by module %s)"
msgstr ""
-#: ../wallace/modules.py:131
+#: ../wallace/modules.py:134
#, python-format
msgid "The time when the message was sent: %r"
-msgstr ""
+msgstr "Zeitpunkt des Versandts: %r"
-#: ../wallace/modules.py:132
+#: ../wallace/modules.py:135
#, python-format
msgid "The time now: %r"
msgstr "Die Zeit ist jetzt: %r"
-#: ../wallace/modules.py:133
+#: ../wallace/modules.py:136
#, python-format
msgid "The time delta: %r"
-msgstr ""
+msgstr "Die Zeitdifferenz: %r"
#. TODO: Send NDR back to user
-#: ../wallace/modules.py:137
+#: ../wallace/modules.py:140
#, python-format
msgid "Message in file %s older then 5 days, deleting"
msgstr ""
-#: ../wallace/modules.py:162
+#: ../wallace/modules.py:165
#, python-format
msgid "Rejecting message in %s (by module %s)"
msgstr ""
-#: ../wallace/modules.py:180
+#: ../wallace/modules.py:186
#, python-format
msgid ""
"This is the email system Wallace at %s.\n"
@@ -2304,21 +3492,31 @@ msgid ""
"Your message is being delivered to any other recipients you may have\n"
"sent your message to. There is no need to resend the message to those\n"
"recipients.\n"
-msgstr ""
+msgstr "Ich bin das E-Mail-System Wallace auf %s.\n\nMit Bedauern muß ich Sie informieren, daß die angehängte Nachricht\nnicht an die folgenden Empfänger zugestellt werden konnte:\n\n- %s\n\nFalls Sie noch andere Empfänger angegeben haben wurde die Nachricht\nan diese zugestellt. An diese Empfänger müssen sie die Nachricht\nnicht erneut senden.\n"
-#: ../wallace/modules.py:195
+#: ../wallace/modules.py:201
#, python-format
msgid ""
"X-Wallace-Module: %s\n"
"X-Wallace-Result: REJECT\n"
msgstr ""
-#: ../wallace/modules.py:248
+#: ../wallace/modules.py:260
#, python-format
msgid "Accepting message in %s (by module %s)"
msgstr ""
-#: ../wallace/modules.py:316
+#: ../wallace/modules.py:262
#, python-format
-msgid "Module '%s' already registered"
+msgid "Accepting message in: %r"
msgstr ""
+
+#: ../wallace/modules.py:269
+#, python-format
+msgid "recipients: %r"
+msgstr ""
+
+#: ../wallace/modules.py:347
+#, python-format
+msgid "Module '%s' already registered"
+msgstr "Modul '%s' ist bereits registriert"
diff --git a/po/de_DE.po b/po/de_DE.po
index 03f16f2..b8bcc6f 100644
--- a/po/de_DE.po
+++ b/po/de_DE.po
@@ -1,678 +1,913 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
-#
+#
# Translators:
-# <grote@kolabsys.com>, 2012.
+# Christoph Wickert <christoph.wickert@gmail.com>, 2012
+# Grote <grote@kolabsys.com>, 2012
+# Thomas Brüderli <roundcube@gmail.com>, 2014
msgid ""
msgstr ""
"Project-Id-Version: Kolab Groupware Solution\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-01-12 11:17+0000\n"
-"PO-Revision-Date: 2012-08-14 11:13+0000\n"
-"Last-Translator: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com>\n"
-"Language-Team: German (Germany) (http://www.transifex.com/projects/p/kolab/"
-"language/de_DE/)\n"
-"Language: de_DE\n"
+"POT-Creation-Date: 2014-07-10 07:21-0400\n"
+"PO-Revision-Date: 2014-07-22 13:04+0000\n"
+"Last-Translator: Thomas Brüderli <roundcube@gmail.com>\n"
+"Language-Team: German (Germany) (http://www.transifex.com/projects/p/kolab/language/de_DE/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"Language: de_DE\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: ../bin/kolab_smtp_access_policy.py:206
+#: ../bin/kolab_smtp_access_policy.py:209
#, python-format
msgid "Adding policy request to instance %s"
msgstr "Füge Richtlinien-Anfrage zu Instanz %s hinzu"
-#: ../bin/kolab_smtp_access_policy.py:446
+#: ../bin/kolab_smtp_access_policy.py:479
msgid "Unauthorized access not allowed"
msgstr "Unberechtigter Zugriff nicht erlaubt"
-#: ../bin/kolab_smtp_access_policy.py:475
-#: ../bin/kolab_smtp_access_policy.py:657
+#: ../bin/kolab_smtp_access_policy.py:508
+#: ../bin/kolab_smtp_access_policy.py:689
msgid "Could not find recipient"
msgstr "Konnte den Empfänger nicht finden"
-#: ../bin/kolab_smtp_access_policy.py:494
-#: ../bin/kolab_smtp_access_policy.py:594
+#: ../bin/kolab_smtp_access_policy.py:527
#, python-format
-msgid "Could not find envelope sender user %s"
-msgstr "Konnte den Absender-Umschlag für den Benutzer %s nicht finden"
+msgid "Could not find envelope sender user %s (511)"
+msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:537
+#: ../bin/kolab_smtp_access_policy.py:570
#, python-format
msgid "Obtained authenticated user details for %r: %r"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:616
+#: ../bin/kolab_smtp_access_policy.py:627
+#, python-format
+msgid "Could not find envelope sender user %s"
+msgstr "Konnte den Absender-Umschlag für den Benutzer %s nicht finden"
+
+#: ../bin/kolab_smtp_access_policy.py:649
#, python-format
msgid "%s is unauthorized to send on behalf of %s"
msgstr "Benutzer %s ist nicht berechtigt als Benutzer %s zu senden"
-#: ../bin/kolab_smtp_access_policy.py:626
+#: ../bin/kolab_smtp_access_policy.py:659
#, python-format
-msgid "User %s attempted to use envelope sender address %s "
+msgid ""
+"User %s attempted to use envelope sender address %s without authorization"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:681
-#: ../bin/kolab_smtp_access_policy.py:692
+#: ../bin/kolab_smtp_access_policy.py:713
+#: ../bin/kolab_smtp_access_policy.py:724
#, python-format
msgid "Found user %s to be a delegate user of %s"
msgstr "Benutzer %s ist ein delegierter Benutzer von %s"
-#: ../bin/kolab_smtp_access_policy.py:716
+#: ../bin/kolab_smtp_access_policy.py:748
#, python-format
-msgid "Verifying authenticated sender '%(sender)s' with "
+msgid ""
+"Verifying authenticated sender '%(sender)s' with sasl_username "
+"'%(sasl_username)s' for recipient '%(recipient)s'"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:721
+#: ../bin/kolab_smtp_access_policy.py:751
#, python-format
-msgid "Verifying unauthenticated sender '%(sender)s' "
+msgid ""
+"Verifying unauthenticated sender '%(sender)s' for recipient '%(recipient)s'"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:738
+#: ../bin/kolab_smtp_access_policy.py:767
#, python-format
-msgid "Reproducing verify_recipient(%s, %s) from "
+msgid "Reproducing verify_recipient(%s, %s) from cache"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:760
+#: ../bin/kolab_smtp_access_policy.py:804
#, python-format
msgid "Using authentication domain %s instead of %s"
msgstr "Benutze Authentisierungsdomain %s anstelle von %s"
-#: ../bin/kolab_smtp_access_policy.py:770
+#: ../bin/kolab_smtp_access_policy.py:814
#, python-format
msgid "Domain %s is a primary domain"
msgstr "Die Domain %s ist die primäre Domain"
-#: ../bin/kolab_smtp_access_policy.py:778
+#: ../bin/kolab_smtp_access_policy.py:822
#, python-format
-msgid "Checking the recipient for domain %s that is not "
+msgid ""
+"Checking the recipient for domain %s that is not ours. This is probably a "
+"configuration error."
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:794
-msgid "This recipient address is related to multiple "
+#: ../bin/kolab_smtp_access_policy.py:837
+msgid ""
+"This recipient address is related to multiple object entries and the SMTP "
+"Access Policy can therefore not restrict message flow"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:803
+#: ../bin/kolab_smtp_access_policy.py:854
#, python-format
-msgid "Recipient address %r not found. Allowing since "
+msgid ""
+"Recipient address %r not found. Allowing since the MTA was configured to "
+"accept the recipient."
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:831
+#: ../bin/kolab_smtp_access_policy.py:890
msgid "Invalid recipient"
msgstr "Ungültiger Empfänger"
-#: ../bin/kolab_smtp_access_policy.py:842
+#: ../bin/kolab_smtp_access_policy.py:901
msgid "Could not find this user, accepting"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:905
-#: ../bin/kolab_smtp_access_policy.py:958
+#: ../bin/kolab_smtp_access_policy.py:974
+#: ../bin/kolab_smtp_access_policy.py:1050
#, python-format
-msgid "Sender %s is not allowed to send to "
+msgid "Sender %s is not allowed to send to recipient %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:945
+#: ../bin/kolab_smtp_access_policy.py:1038
#, python-format
-msgid "Reproducing verify_sender(%r) from cache, "
+msgid "Reproducing verify_sender(%r) from cache"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:974
+#: ../bin/kolab_smtp_access_policy.py:1055
+msgid "Unverifiable sender."
+msgstr ""
+
+#: ../bin/kolab_smtp_access_policy.py:1060
+msgid "Sender is not using an alias"
+msgstr ""
+
+#: ../bin/kolab_smtp_access_policy.py:1068
msgid "Sender uses unauthorized envelope sender address"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:991
+#: ../bin/kolab_smtp_access_policy.py:1085
msgid "Could not verify sender"
msgstr "Konnte den Absender nicht verifizieren"
-#: ../bin/kolab_smtp_access_policy.py:998
-msgid "Verifying whether sender is allowed to send to "
+#: ../bin/kolab_smtp_access_policy.py:1092
+msgid ""
+"Verifying whether sender is allowed to send to recipient using sender policy"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1012
+#: ../bin/kolab_smtp_access_policy.py:1105
#, python-format
msgid "Result is %r"
msgstr "Das Ergebnis ist %r"
-#: ../bin/kolab_smtp_access_policy.py:1017
+#: ../bin/kolab_smtp_access_policy.py:1110
msgid "No recipient policy restrictions exist for this sender"
msgstr "Es existiert keine Empfängerrichtlinie für diesen Absender"
-#: ../bin/kolab_smtp_access_policy.py:1026
+#: ../bin/kolab_smtp_access_policy.py:1119
msgid "Found a recipient policy to apply for this sender."
msgstr "Empfänger-Richtlinie für diesen Benutzer gefunden"
-#: ../bin/kolab_smtp_access_policy.py:1041
+#: ../bin/kolab_smtp_access_policy.py:1134
#, python-format
-msgid "Sender %s not allowed to send to recipient "
+msgid "Sender %s not allowed to send to recipient %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1063
+#: ../bin/kolab_smtp_access_policy.py:1155
msgid "Cleaning up the cache"
msgstr "Aufräumen des Cache"
-#: ../bin/kolab_smtp_access_policy.py:1085
+#: ../bin/kolab_smtp_access_policy.py:1177
msgid ""
"The 'uri' setting in the kolab_smtp_access_policy section is soon going to "
"be deprecated in favor of 'cache_uri'"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1101
+#: ../bin/kolab_smtp_access_policy.py:1193
#, python-format
msgid "Operational Error in caching: %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1152
+#: ../bin/kolab_smtp_access_policy.py:1245
#, python-format
msgid "Caching the policy result with timestamp %d"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1229
+#: ../bin/kolab_smtp_access_policy.py:1319
#, python-format
msgid "Returning action DEFER_IF_PERMIT: %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1234
+#: ../bin/kolab_smtp_access_policy.py:1324
#, python-format
msgid "Returning action DUNNO: %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1239
+#: ../bin/kolab_smtp_access_policy.py:1329
#, python-format
msgid "Returning action HOLD: %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1244
+#: ../bin/kolab_smtp_access_policy.py:1334
#, python-format
msgid "Returning action PERMIT: %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1249
+#: ../bin/kolab_smtp_access_policy.py:1459
#, python-format
msgid "Returning action REJECT: %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1298
+#: ../bin/kolab_smtp_access_policy.py:1505
msgid "Starting to loop for new request"
msgstr "Starte Schleife für neue Anfrage"
-#: ../bin/kolab_smtp_access_policy.py:1305
+#: ../bin/kolab_smtp_access_policy.py:1512
msgid "Timeout for policy request reading exceeded"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1311
+#: ../bin/kolab_smtp_access_policy.py:1518
msgid "End of current request"
msgstr "Ende der aktuellen Anfrage"
-#: ../bin/kolab_smtp_access_policy.py:1315
+#: ../bin/kolab_smtp_access_policy.py:1522
#, python-format
msgid "Getting line: %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1319
+#: ../bin/kolab_smtp_access_policy.py:1526
msgid "Returning request"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1348
+#: ../bin/kolab_smtp_access_policy.py:1555
msgid "Access Policy Options"
msgstr "Zugriffsrichtlinien-Einstellungen"
-#: ../bin/kolab_smtp_access_policy.py:1355
+#: ../bin/kolab_smtp_access_policy.py:1562
msgid "SMTP Policy request timeout."
msgstr "Zeitüberschreitung der SMTP Richtlinien-Anfrage"
-#: ../bin/kolab_smtp_access_policy.py:1361
+#: ../bin/kolab_smtp_access_policy.py:1568
msgid "Verify the recipient access policy."
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1367
+#: ../bin/kolab_smtp_access_policy.py:1574
msgid "Verify the sender access policy."
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1373
+#: ../bin/kolab_smtp_access_policy.py:1580
msgid "Allow unauthenticated senders."
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1385
+#: ../bin/kolab_smtp_access_policy.py:1594
#, python-format
msgid "Got request instance %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1394
+#: ../bin/kolab_smtp_access_policy.py:1603
#, python-format
msgid "Request instance %s is in state %s"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1402
+#: ../bin/kolab_smtp_access_policy.py:1611
#, python-format
msgid "Request instance %s is not yet in DATA state"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1414
+#: ../bin/kolab_smtp_access_policy.py:1623
#, python-format
msgid "Request instance %s reached DATA state"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1432
+#: ../bin/kolab_smtp_access_policy.py:1643
+#, python-format
+msgid "Unhandled exception caught: %r"
+msgstr ""
+
+#: ../bin/kolab_smtp_access_policy.py:1647
msgid "Sender access denied"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1434
+#: ../bin/kolab_smtp_access_policy.py:1649
msgid "Recipient access denied"
msgstr ""
-#: ../bin/kolab_smtp_access_policy.py:1436
+#: ../bin/kolab_smtp_access_policy.py:1651
msgid "No objections"
msgstr ""
-#: ../conf.py:37 ../kolab.py:34 ../saslauthd.py:33
+#: ../conf.py:37 ../kolab-cli.py:34 ../saslauthd.py:33
msgid "Cannot load pykolab/logger.py:"
msgstr ""
-#: ../kolabd/__init__.py:49 ../saslauthd/__init__.py:48
-#: ../wallace/__init__.py:66
+#: ../kolabd/__init__.py:49 ../saslauthd/__init__.py:51
+#: ../wallace/__init__.py:85
msgid "Daemon Options"
msgstr ""
-#: ../kolabd/__init__.py:56 ../saslauthd/__init__.py:55
-#: ../wallace/__init__.py:73
+#: ../kolabd/__init__.py:56 ../saslauthd/__init__.py:58
+#: ../wallace/__init__.py:92
msgid "Fork to the background."
msgstr ""
-#: ../kolabd/__init__.py:65 ../saslauthd/__init__.py:64
-#: ../wallace/__init__.py:99
+#: ../kolabd/__init__.py:65 ../saslauthd/__init__.py:67
+#: ../wallace/__init__.py:118
msgid "Path to the PID file to use."
msgstr ""
-#: ../kolabd/__init__.py:74 ../saslauthd/__init__.py:73
-#: ../wallace/__init__.py:116
+#: ../kolabd/__init__.py:74 ../saslauthd/__init__.py:76
+#: ../wallace/__init__.py:135
msgid "Run as user USERNAME"
msgstr ""
-#: ../kolabd/__init__.py:84 ../saslauthd/__init__.py:83
-#: ../wallace/__init__.py:90
+#: ../kolabd/__init__.py:84 ../saslauthd/__init__.py:86
+#: ../wallace/__init__.py:109
msgid "Run as group GROUPNAME"
msgstr ""
-#: ../kolabd/__init__.py:122 ../pykolab/utils.py:180
-#: ../wallace/__init__.py:297
+#: ../kolabd/__init__.py:122 ../pykolab/logger.py:139 ../pykolab/utils.py:234
+#: ../saslauthd/__init__.py:292 ../wallace/__init__.py:329
#, python-format
msgid "Group %s does not exist"
msgstr ""
-#: ../kolabd/__init__.py:131 ../wallace/__init__.py:306
+#: ../kolabd/__init__.py:131 ../saslauthd/__init__.py:301
+#: ../wallace/__init__.py:338
#, python-format
msgid "Switching real and effective group id to %d"
msgstr ""
-#: ../kolabd/__init__.py:153 ../pykolab/utils.py:204
-#: ../wallace/__init__.py:328
+#: ../kolabd/__init__.py:153 ../pykolab/logger.py:159 ../pykolab/utils.py:258
+#: ../saslauthd/__init__.py:323 ../wallace/__init__.py:360
#, python-format
msgid "User %s does not exist"
msgstr ""
-#: ../kolabd/__init__.py:163 ../wallace/__init__.py:338
+#: ../kolabd/__init__.py:163 ../saslauthd/__init__.py:333
+#: ../wallace/__init__.py:370
#, python-format
msgid "Switching real and effective user id to %d"
msgstr ""
-#: ../kolabd/__init__.py:172 ../wallace/__init__.py:347
+#: ../kolabd/__init__.py:172 ../saslauthd/__init__.py:342
+#: ../wallace/__init__.py:379
msgid "Could not change real and effective uid and/or gid"
msgstr ""
-#: ../kolabd/__init__.py:192 ../saslauthd/__init__.py:122
-#: ../wallace/__init__.py:367
+#: ../kolabd/__init__.py:192 ../saslauthd/__init__.py:133
+#: ../wallace/__init__.py:399
msgid "Interrupted by user"
msgstr ""
#: ../kolabd/__init__.py:197 ../kolabd/__init__.py:208
-#: ../wallace/__init__.py:371 ../wallace/__init__.py:381
msgid "Traceback occurred, please report a "
msgstr ""
-#: ../kolabd/__init__.py:203 ../saslauthd/__init__.py:130
-#: ../wallace/__init__.py:377
+#: ../kolabd/__init__.py:203 ../saslauthd/__init__.py:141
+#: ../wallace/__init__.py:408
#, python-format
msgid "Type Error: %s"
msgstr ""
-#: ../kolabd/__init__.py:223 ../pykolab/auth/ldap/__init__.py:1623
+#: ../kolabd/__init__.py:230
+msgid "Could not connect to LDAP, is it running?"
+msgstr ""
+
+#: ../kolabd/__init__.py:233 ../pykolab/auth/ldap/__init__.py:2137
#: ../pykolab/cli/cmd_sync.py:36
msgid "Listing domains..."
+msgstr "Domänen werden geladen…"
+
+#: ../kolabd/__init__.py:244
+msgid "No domains. Not syncing"
msgstr ""
-#: ../kolabd/__init__.py:260
+#: ../kolabd/__init__.py:275
#, python-format
msgid "added domains: %r, removed domains: %r"
msgstr ""
-#: ../kolabd/process.py:48
+#: ../kolabd/process.py:33
+#, python-format
+msgid "Process created for domain %s"
+msgstr ""
+
+#: ../kolabd/process.py:42
#, python-format
-msgid "Error in process %r, terminating: %r"
+msgid "Synchronizing for domain %s"
+msgstr ""
+
+#: ../kolabd/process.py:59
+#, python-format
+msgid ""
+"Error in process %r, terminating:\n"
+"\t%r"
msgstr ""
#: ../kolabd.py:31 ../setup-kolab.py:36 ../wallace.py:31
msgid "Cannot load pykolab/constants.py:"
msgstr ""
-#: ../pykolab/auth/__init__.py:94
+#: ../pykolab/auth/__init__.py:89
#, python-format
msgid "Called for domain %r"
msgstr ""
-#: ../pykolab/auth/__init__.py:107 ../pykolab/auth/__init__.py:116
+#: ../pykolab/auth/__init__.py:106 ../pykolab/auth/__init__.py:115
#, python-format
msgid "Using section %s and domain %s"
msgstr ""
-#: ../pykolab/auth/__init__.py:121
+#: ../pykolab/auth/__init__.py:120
#, python-format
msgid "Connecting to Authentication backend for domain %s"
msgstr ""
-#: ../pykolab/auth/__init__.py:132
+#: ../pykolab/auth/__init__.py:131
#, python-format
msgid "Section %s has no option 'auth_mechanism'"
msgstr ""
-#: ../pykolab/auth/__init__.py:139
+#: ../pykolab/auth/__init__.py:138
#, python-format
msgid "Section %s has auth_mechanism: %r"
msgstr ""
-#: ../pykolab/auth/__init__.py:148 ../pykolab/auth/__init__.py:157
+#: ../pykolab/auth/__init__.py:147 ../pykolab/auth/__init__.py:156
msgid "Starting LDAP..."
msgstr ""
-#: ../pykolab/auth/ldap/cache.py:112
+#: ../pykolab/auth/ldap/cache.py:126
#, python-format
msgid "Inserting cache entry %r"
msgstr ""
-#: ../pykolab/auth/ldap/cache.py:129
+#: ../pykolab/auth/ldap/cache.py:147
#, python-format
msgid "Updating timestamp for cache entry %r"
msgstr ""
-#: ../pykolab/auth/ldap/cache.py:136
+#: ../pykolab/auth/ldap/cache.py:155
#, python-format
msgid "Updating result_attribute for cache entry %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:51
+#: ../pykolab/auth/ldap/__init__.py:52
msgid "Python LDAP library does not support persistent search"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:142
+#: ../pykolab/auth/ldap/__init__.py:143
#, python-format
msgid "Attempting to authenticate user %s in realm %s"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:184
+#: ../pykolab/auth/ldap/__init__.py:175 ../pykolab/auth/ldap/__init__.py:226
+#, python-format
+msgid "Authentication cache failed: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:216 ../pykolab/auth/ldap/__init__.py:240
#, python-format
msgid "Binding with user_dn %s and password %s"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:194
+#: ../pykolab/auth/ldap/__init__.py:231 ../pykolab/auth/ldap/__init__.py:263
#, python-format
msgid "Failed to authenticate as user %s"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:211
-msgid "Connecting to LDAP..."
+#: ../pykolab/auth/ldap/__init__.py:249
+#, python-format
+msgid "Error occured, there is no such object: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:254
+msgid "Authentication cache failed to clear entry"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:215
+#: ../pykolab/auth/ldap/__init__.py:260
+#, python-format
+msgid "Exception occured: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:280
+msgid "Connecting to LDAP..."
+msgstr "Zum LDAP verbinden…"
+
+#: ../pykolab/auth/ldap/__init__.py:284
#, python-format
msgid "Attempting to use LDAP URI %s"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:363
+#: ../pykolab/auth/ldap/__init__.py:371
+#, python-format
+msgid "Entry ID: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:373
+#, python-format
+msgid "Entry DN: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:376
+#, python-format
+msgid ""
+"ldap search: (%r, %r, filterstr='(objectclass=*)', attrlist=[ 'dn' ] + %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:453
#, python-format
msgid "Finding recipient with filter %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:431
+#: ../pykolab/auth/ldap/__init__.py:529
#, python-format
msgid "Finding resource with filter %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:454
+#: ../pykolab/auth/ldap/__init__.py:560
#, python-format
msgid "Using timestamp %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:486
+#: ../pykolab/auth/ldap/__init__.py:595
+msgid "Applying recipient policy disabled through configuration"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:600
#, python-format
msgid "Applying recipient policy to %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:503
+#: ../pykolab/auth/ldap/__init__.py:617
#, python-format
msgid "Using mail attributes: %r, with primary %r and "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:514
+#: ../pykolab/auth/ldap/__init__.py:628
#, python-format
msgid "key %r not in entry"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:516
+#: ../pykolab/auth/ldap/__init__.py:630
#, python-format
msgid "key %r is the prim. mail attr."
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:518
+#: ../pykolab/auth/ldap/__init__.py:632
msgid "prim. mail pol. is not empty"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:521
+#: ../pykolab/auth/ldap/__init__.py:635
#, python-format
msgid "key %r is the sec. mail attr."
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:523
+#: ../pykolab/auth/ldap/__init__.py:637
msgid "sec. mail pol. is not empty"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:527 ../pykolab/auth/ldap/__init__.py:541
+#: ../pykolab/auth/ldap/__init__.py:641 ../pykolab/auth/ldap/__init__.py:655
#, python-format
msgid "Attributes %r are not yet available for entry %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:577
+#: ../pykolab/auth/ldap/__init__.py:694
#, python-format
msgid "No results for mail address %s found"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:588
+#: ../pykolab/auth/ldap/__init__.py:705
#, python-format
msgid "1 result for address %s found, verifying"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:598
+#: ../pykolab/auth/ldap/__init__.py:715
#, python-format
msgid "Too bad, primary email address %s "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:609 ../pykolab/auth/ldap/__init__.py:698
+#: ../pykolab/auth/ldap/__init__.py:726 ../pykolab/auth/ldap/__init__.py:815
msgid "Address assigned to us"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:664
+#: ../pykolab/auth/ldap/__init__.py:781
#, python-format
msgid "No results for address %s found"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:675
+#: ../pykolab/auth/ldap/__init__.py:792
#, python-format
msgid "1 result for address %s found, "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:686
+#: ../pykolab/auth/ldap/__init__.py:803
msgid "Too bad, secondary email "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:713
+#: ../pykolab/auth/ldap/__init__.py:830
msgid "Recipient policy composed the following set of secondary "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:724
+#: ../pykolab/auth/ldap/__init__.py:841
#, python-format
msgid "Secondary mail addresses that we want is not None: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:735
+#: ../pykolab/auth/ldap/__init__.py:852
msgid "Avoiding the duplication of the primary mail "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:746
+#: ../pykolab/auth/ldap/__init__.py:863
#, python-format
msgid "Entry is getting secondary mail addresses: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:754
+#: ../pykolab/auth/ldap/__init__.py:871
msgid "Entry did not have any secondary mail "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:780
+#: ../pykolab/auth/ldap/__init__.py:888 ../pykolab/auth/ldap/__init__.py:894
+#, python-format
+msgid "secondary_mail_addresses: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:889 ../pykolab/auth/ldap/__init__.py:895
+#, python-format
+msgid "entry[%s]: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:906
#, python-format
msgid "Entry modifications list: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:800
+#: ../pykolab/auth/ldap/__init__.py:934
#, python-format
msgid "Setting entry attribute %r to %r for %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:836
+#: ../pykolab/auth/ldap/__init__.py:970
#, python-format
-msgid "Could not update dn %r"
+msgid ""
+"Could not update dn %r:\n"
+"%r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:849
+#: ../pykolab/auth/ldap/__init__.py:983
#, python-format
msgid "Using filter %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:887
+#: ../pykolab/auth/ldap/__init__.py:998
+#, python-format
+msgid "Synchronization is searching against base DN: %s"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:1044
#, python-format
msgid "About to consider the user quota for %r (used: %r, "
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:953
-#, fuzzy
-msgid "Invalid bind credentials"
-msgstr "Ungültiger Empfänger"
+#: ../pykolab/auth/ldap/__init__.py:1115
+msgid "Invalid DN, username and/or password."
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:1236 ../pykolab/auth/ldap/__init__.py:1249
+#: ../pykolab/auth/ldap/__init__.py:1614 ../pykolab/auth/ldap/__init__.py:1627
+#, python-format
+msgid "Found a subject %r with access %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:1356
+#, python-format
+msgid "Entry %s attribute value: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:1364
+#, python-format
+msgid "imap.user_mailbox_server(%r) result: %r"
+msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1248 ../pykolab/auth/ldap/__init__.py:1372
+#: ../pykolab/auth/ldap/__init__.py:1684 ../pykolab/auth/ldap/__init__.py:1853
#, python-format
msgid "Result from recipient policy: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1417
+#: ../pykolab/auth/ldap/__init__.py:1908
#, python-format
msgid "Kolab user %s does not have a result attribute %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1559
+#: ../pykolab/auth/ldap/__init__.py:2067
#, python-format
msgid "Finding domain root dn for domain %s"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1647
+#: ../pykolab/auth/ldap/__init__.py:2164
msgid "Authentication database DOWN"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1731 ../pykolab/auth/ldap/__init__.py:1766
+#: ../pykolab/auth/ldap/__init__.py:2248 ../pykolab/auth/ldap/__init__.py:2296
#, python-format
msgid "Entry type: %s"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1854
+#: ../pykolab/auth/ldap/__init__.py:2321
+#, python-format
+msgid "Done with _synchronize_callback() for entry %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/__init__.py:2393
msgid "LDAP Search Result Data Entry:"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1870
+#: ../pykolab/auth/ldap/__init__.py:2409
msgid "Entry Change Notification attributes:"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1875
+#: ../pykolab/auth/ldap/__init__.py:2414
#, python-format
msgid "Change Type: %r (%r)"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1883
+#: ../pykolab/auth/ldap/__init__.py:2422
#, python-format
msgid "Previous DN: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1938
+#: ../pykolab/auth/ldap/__init__.py:2477
#, python-format
msgid "Object %s searched no longer exists"
-msgstr ""
+msgstr "Das gesuchte Objekt %s existiert nicht mehr"
-#: ../pykolab/auth/ldap/__init__.py:1948
+#: ../pykolab/auth/ldap/__init__.py:2487
#, python-format
msgid "%d results..."
-msgstr ""
+msgstr "%d Ergebnisse…"
-#: ../pykolab/auth/ldap/__init__.py:2051
+#: ../pykolab/auth/ldap/__init__.py:2590
#, python-format
msgid "Searching with filter %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2095
+#: ../pykolab/auth/ldap/__init__.py:2642
#, python-format
msgid "Checking for support for %s on %s"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2114
+#: ../pykolab/auth/ldap/__init__.py:2661
#, python-format
msgid "Found support for %s"
msgstr ""
-#: ../pykolab/cli/cmd_add_domain.py:36 ../pykolab/cli/cmd_create_mailbox.py:36
+#: ../pykolab/auth/ldap/__init__.py:2706
+#, python-format
+msgid "An error occured using %s: %r"
+msgstr ""
+
+#: ../pykolab/auth/ldap/syncrepl.py:46
+msgid "The name of the persistent, unique attribute "
+msgstr ""
+
+#: ../pykolab/cli/cmd_acl_cleanup.py:34
+msgid "Clean up ACLs that use identifiers that no longer exist"
+msgstr ""
+
+#: ../pykolab/cli/cmd_acl_cleanup.py:56
+#, python-format
+msgid "Deleting ACL %s for subject %s on folder %s"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:42
+msgid "Specify the (new) alias address"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:45
+msgid "Specify the existing recipient address"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:66 ../pykolab/cli/cmd_add_alias.py:70
+#, python-format
+msgid "Domain %r is not a local domain"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:75
+msgid "Primary and secondary domain do not have the same parent domain"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:81
+#, python-format
+msgid "No such recipient %r"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:87
+#, python-format
+msgid "Recipient for alias %r already exists"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:97
+msgid "Environment is not configured for "
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_alias.py:105
+#, python-format
+msgid "Recipient %r is not the primary recipient for address %r"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_domain.py:36
+#: ../pykolab/cli/cmd_count_domain_mailboxes.py:38
+#: ../pykolab/cli/cmd_create_mailbox.py:36
#: ../pykolab/cli/cmd_export_mailbox.py:33
-#: ../pykolab/cli/cmd_list_mailboxes.py:39
+#: ../pykolab/cli/cmd_list_deleted_mailboxes.py:38
+#: ../pykolab/cli/cmd_list_domain_mailboxes.py:36
+#: ../pykolab/cli/cmd_list_mailboxes.py:40
#: ../pykolab/cli/cmd_list_mailbox_metadata.py:37
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:37
+#: ../pykolab/cli/cmd_list_messages.py:37 ../pykolab/cli/cmd_list_quota.py:36
+#: ../pykolab/cli/cmd_list_user_subscriptions.py:36
+#: ../pykolab/cli/cmd_server_info.py:34
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:38
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:39
+#: ../pykolab/cli/cmd_undelete_mailbox.py:34
msgid "CLI Options"
-msgstr ""
+msgstr "Kommandozeilenoptionen"
#: ../pykolab/cli/cmd_add_domain.py:42
-msgid "Add domain as alias for DOMAIN"
+msgid "Add alias domain."
msgstr ""
#: ../pykolab/cli/cmd_add_domain.py:47
-msgid "Add a new domain or domain alias."
+msgid "Add a new domain."
msgstr ""
-#: ../pykolab/cli/cmd_add_domain.py:55
+#: ../pykolab/cli/cmd_add_domain.py:55 ../pykolab/cli/cmd_delete_domain.py:44
+#: ../pykolab/cli/cmd_find_domain.py:44
msgid "Could not find credentials with sufficient permissions"
msgstr ""
-#: ../pykolab/cli/cmd_add_domain.py:80 ../pykolab/wap_client/__init__.py:113
-msgid "Invalid parent domain"
+#: ../pykolab/cli/cmd_add_domain.py:67 ../pykolab/cli/cmd_delete_domain.py:56
+#: ../pykolab/cli/cmd_find_domain.py:56
+msgid "Domain name"
msgstr ""
-#: ../pykolab/cli/cmd_add_domain.py:86
-msgid "Domain name"
+#: ../pykolab/cli/cmd_add_user_subscription.py:37
+msgid "Subscribe a user to a folder."
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_user_subscription.py:47
+#: ../pykolab/cli/cmd_add_user_subscription.py:51
+#: ../pykolab/cli/cmd_remove_user_subscription.py:47
+#: ../pykolab/cli/cmd_remove_user_subscription.py:51
+msgid "Folder pattern"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_user_subscription.py:50
+#: ../pykolab/cli/cmd_list_user_subscriptions.py:63
+#: ../pykolab/cli/cmd_remove_user_subscription.py:50
+msgid "User ID"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_user_subscription.py:72
+#: ../pykolab/cli/cmd_remove_user_subscription.py:72
+#, python-format
+msgid "Cannot subscribe user to folder %r:"
+msgstr ""
+
+#: ../pykolab/cli/cmd_add_user_subscription.py:73
+#: ../pykolab/cli/cmd_delete_message.py:61
+#: ../pykolab/cli/cmd_list_messages.py:67
+#: ../pykolab/cli/cmd_remove_user_subscription.py:73
+msgid "No such folder"
+msgstr ""
+
+#: ../pykolab/cli/cmd_count_domain_mailboxes.py:44
+#: ../pykolab/cli/cmd_list_deleted_mailboxes.py:50
+#: ../pykolab/cli/cmd_list_domain_mailboxes.py:48
+#: ../pykolab/cli/cmd_list_mailboxes.py:52 ../pykolab/cli/cmd_list_quota.py:42
+#: ../pykolab/cli/cmd_server_info.py:40
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:57
+msgid "List mailboxes on server SERVER only."
msgstr ""
#: ../pykolab/cli/cmd_create_mailbox.py:42
msgid "Set metadata for folder to ANNOTATION=VALUE"
msgstr ""
-#: ../pykolab/cli/cmd_create_mailbox.py:52
-msgid "Invalid argument"
+#: ../pykolab/cli/cmd_create_mailbox.py:50
+msgid "Create folder on PARTITION."
msgstr ""
#: ../pykolab/cli/cmd_create_mailbox.py:60
+msgid "Invalid argument"
+msgstr ""
+
+#: ../pykolab/cli/cmd_create_mailbox.py:68
msgid "Invalid argument for metadata"
msgstr ""
+#: ../pykolab/cli/cmd_delete_domain.py:36
+msgid "Delete a domain."
+msgstr ""
+
#: ../pykolab/cli/cmd_delete_mailbox_acl.py:45
#: ../pykolab/cli/cmd_delete_mailbox_acl.py:49
#: ../pykolab/cli/cmd_set_mailbox_acl.py:50
@@ -684,7 +919,11 @@ msgstr ""
#: ../pykolab/cli/cmd_list_mailbox_acls.py:43
#: ../pykolab/cli/cmd_list_mailbox_metadata.py:54
#: ../pykolab/cli/cmd_set_mailbox_acl.py:54
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:65
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:66
+#: ../pykolab/cli/cmd_set_quota.py:46 ../tests/unit/test-015-translate.py:12
+#: ../tests/unit/test-015-translate.py:16
+#: ../tests/unit/test-015-translate.py:18
+#: ../tests/unit/test-015-translate.py:20
msgid "Folder name"
msgstr ""
@@ -692,7 +931,8 @@ msgstr ""
#: ../pykolab/cli/cmd_list_mailbox_acls.py:54
#: ../pykolab/cli/cmd_list_mailbox_metadata.py:80
#: ../pykolab/cli/cmd_set_mailbox_acl.py:67
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:93
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:94
+#: ../pykolab/cli/cmd_set_quota.py:58
#, python-format
msgid "No such folder %r"
msgstr ""
@@ -701,6 +941,23 @@ msgstr ""
msgid "No mailbox specified"
msgstr ""
+#: ../pykolab/cli/cmd_delete_mailbox.py:56
+msgid "No such folder(s)"
+msgstr ""
+
+#: ../pykolab/cli/cmd_delete_message.py:36
+msgid "Delete a message from a folder"
+msgstr ""
+
+#: ../pykolab/cli/cmd_delete_message.py:49
+msgid "Specify a UID"
+msgstr ""
+
+#: ../pykolab/cli/cmd_delete_message.py:52
+#: ../pykolab/cli/cmd_list_messages.py:58
+msgid "Specify a folder"
+msgstr ""
+
#: ../pykolab/cli/cmd_export_mailbox.py:38
msgid "All folders this user has access to"
msgstr ""
@@ -720,11 +977,22 @@ msgstr ""
msgid "No directories found for user %s"
msgstr ""
-#: ../pykolab/cli/cmd_list_mailboxes.py:44
+#: ../pykolab/cli/cmd_find_domain.py:36
+msgid "Find a domain."
+msgstr ""
+
+#: ../pykolab/cli/cmd_list_deleted_mailboxes.py:43
+#: ../pykolab/cli/cmd_list_domain_mailboxes.py:41
+#: ../pykolab/cli/cmd_list_mailboxes.py:45
+#: ../pykolab/cli/cmd_list_user_subscriptions.py:41
msgid "Display raw IMAP UTF-7 folder names"
msgstr ""
-#: ../pykolab/cli/cmd_list_mailboxes.py:75
+#: ../pykolab/cli/cmd_list_domain_mailboxes.py:58
+msgid "Domain"
+msgstr "Domäne"
+
+#: ../pykolab/cli/cmd_list_mailboxes.py:87
#, python-format
msgid "Appending folder search for %r"
msgstr ""
@@ -733,11 +1001,41 @@ msgstr ""
msgid "List annotations as user USER"
msgstr ""
-#: ../pykolab/cli/cmd_list_quota.py:59 ../pykolab/cli/cmd_list_quota.py:71
+#: ../pykolab/cli/cmd_list_messages.py:43
+msgid "Include messages flagged as \\Deleted"
+msgstr ""
+
+#: ../pykolab/cli/cmd_list_messages.py:47
+msgid "List messages in a folder"
+msgstr ""
+
+#: ../pykolab/cli/cmd_list_quota.py:73 ../pykolab/cli/cmd_list_quota.py:89
#, python-format
msgid "The quota for folder %s is set to literally allow 0KB of storage."
msgstr ""
+#: ../pykolab/cli/cmd_list_user_subscriptions.py:47
+msgid "List unsubscribed folders"
+msgstr ""
+
+#: ../pykolab/cli/cmd_list_user_subscriptions.py:50
+msgid "List the folders a user is subscribed to."
+msgstr ""
+
+#: ../pykolab/cli/cmd_list_user_subscriptions.py:98
+#, python-format
+msgid "No unsubscribed folders for user %s"
+msgstr ""
+
+#: ../pykolab/cli/cmd_mailbox_cleanup.py:37
+msgid "Clean up mailboxes that do no longer have an owner."
+msgstr ""
+
+#: ../pykolab/cli/cmd_mailbox_cleanup.py:61
+#, python-format
+msgid "Deleting folder 'user/%s'"
+msgstr ""
+
#: ../pykolab/cli/cmd_remove_mailaddress.py:49
msgid "Invalid or unqualified email address."
msgstr ""
@@ -761,20 +1059,34 @@ msgstr ""
msgid "Found the following recipients:"
msgstr ""
-#: ../pykolab/cli/cmd_rename_mailbox.py:48
+#: ../pykolab/cli/cmd_remove_user_subscription.py:37
+msgid "Unsubscribe a user from a folder."
+msgstr ""
+
+#: ../pykolab/cli/cmd_remove_user_subscription.py:86
+#, python-format
+msgid "Successfully unsubscribed user %s from the following folders:"
+msgstr ""
+
+#: ../pykolab/cli/cmd_remove_user_subscription.py:92
+#, python-format
+msgid "User %s was not unsubscribed from any folders."
+msgstr ""
+
+#: ../pykolab/cli/cmd_rename_mailbox.py:52
msgid "No target mailbox name specified"
msgstr ""
-#: ../pykolab/cli/cmd_rename_mailbox.py:50
+#: ../pykolab/cli/cmd_rename_mailbox.py:54
msgid "No source mailbox name specified"
msgstr ""
-#: ../pykolab/cli/cmd_rename_mailbox.py:62
+#: ../pykolab/cli/cmd_rename_mailbox.py:66
#, python-format
msgid "Source folder %r does not exist"
msgstr ""
-#: ../pykolab/cli/cmd_rename_mailbox.py:66
+#: ../pykolab/cli/cmd_rename_mailbox.py:70
#, python-format
msgid "Target folder %r already exists"
msgstr ""
@@ -785,21 +1097,75 @@ msgstr ""
msgid "ACI Permissions"
msgstr ""
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:44
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:45
msgid "Set annotation as user USER"
msgstr ""
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:58
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:62
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:67
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:59
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:63
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:68
msgid "Metadata value"
msgstr ""
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:61
-#: ../pykolab/cli/cmd_set_mailbox_metadata.py:66
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:62
+#: ../pykolab/cli/cmd_set_mailbox_metadata.py:67
msgid "Metadata path"
msgstr ""
+#: ../pykolab/cli/cmd_set_quota.py:43 ../pykolab/cli/cmd_set_quota.py:47
+msgid "New quota"
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:44
+msgid "Delete mailboxes for recipients that do not appear to exist in LDAP."
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:50
+msgid "Display changes, do not apply them."
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:88
+#, python-format
+msgid "Domains in IMAP not in LDAP: %r"
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:101
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:142
+#, python-format
+msgid ""
+"No recipients for '%s' (would have deleted the mailbox if not for --dry-"
+"run)!"
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:106
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:147
+#, python-format
+msgid "Deleting mailbox '%s' because it has no recipients"
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:110
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:151
+#, python-format
+msgid "An error occurred removing mailbox %r: %r"
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:112
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:153
+#, python-format
+msgid "Not automatically deleting shared folder '%s'"
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:114
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:155
+#, python-format
+msgid "No recipients for '%s' (use --delete to delete)!"
+msgstr ""
+
+#: ../pykolab/cli/cmd_sync_mailhost_attrs.py:136
+#, python-format
+msgid "Multiple recipients for '%s'!"
+msgstr ""
+
#: ../pykolab/cli/cmd_sync.py:41
#, python-format
msgid "Found %d domains in %d seconds"
@@ -810,37 +1176,126 @@ msgstr ""
msgid "Running for domain %s"
msgstr ""
-#: ../pykolab/cli/cmd_sync.py:57
+#: ../pykolab/cli/cmd_sync.py:58
#, python-format
msgid "Synchronizing users for %s took %d seconds"
msgstr ""
+#: ../pykolab/cli/cmd_undelete_mailbox.py:39
+msgid "Do not actually execute, but state what would have been executed."
+msgstr ""
+
+#: ../pykolab/cli/cmd_undelete_mailbox.py:42
+msgid "Recover mailboxes previously deleted."
+msgstr ""
+
+#: ../pykolab/cli/cmd_user_info.py:39
+msgid "Email address"
+msgstr ""
+
#. This is a nested command
#. This is a nested component
-#: ../pykolab/cli/commands.py:101 ../pykolab/setup/components.py:90
+#: ../pykolab/cli/commands.py:98 ../pykolab/setup/components.py:90
#, python-format
msgid "Command Group: %s"
msgstr ""
-#: ../pykolab/cli/commands.py:116 ../pykolab/cli/commands.py:121
+#: ../pykolab/cli/commands.py:113 ../pykolab/cli/commands.py:118
msgid "No such command."
msgstr ""
-#: ../pykolab/cli/commands.py:171 ../pykolab/setup/components.py:231
+#: ../pykolab/cli/commands.py:168 ../pykolab/setup/components.py:231
#, python-format
msgid "Command '%s' already registered"
msgstr ""
-#: ../pykolab/cli/commands.py:196 ../pykolab/setup/components.py:257
-#: ../wallace/modules.py:348
+#: ../pykolab/cli/commands.py:193 ../pykolab/setup/components.py:257
+#: ../wallace/modules.py:369
#, python-format
msgid "Alias for %s"
msgstr ""
-#: ../pykolab/cli/commands.py:204 ../pykolab/setup/components.py:265
+#: ../pykolab/cli/commands.py:201 ../pykolab/setup/components.py:265
msgid "Not yet implemented"
msgstr ""
+#: ../pykolab/cli/sieve/cmd_list.py:43 ../pykolab/cli/sieve/cmd_put.py:42
+#: ../pykolab/cli/sieve/cmd_refresh.py:44 ../pykolab/cli/sieve/cmd_test.py:43
+msgid "Email Address"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:99
+#: ../pykolab/plugins/sievemgmt/__init__.py:111
+#, python-format
+msgid "Found the following scripts for user %s: %s"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:100
+#: ../pykolab/plugins/sievemgmt/__init__.py:112
+#, python-format
+msgid "And the following script is active for user %s: %s"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:178
+#: ../pykolab/plugins/sievemgmt/__init__.py:190
+#, python-format
+msgid ""
+"Delivery to folder active, but no folder name attribute available for user "
+"%r"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:181
+#: ../pykolab/plugins/sievemgmt/__init__.py:193
+msgid "Delivery to folder active, but no folder name attribute configured"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:359
+#, python-format
+msgid "MANAGEMENT script for user %s contents: %r"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:364
+#: ../pykolab/plugins/sievemgmt/__init__.py:374
+#, python-format
+msgid "Uploading script MANAGEMENT failed for user %s"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:366
+#: ../pykolab/plugins/sievemgmt/__init__.py:376
+#, python-format
+msgid "Uploading script MANAGEMENT for user %s succeeded"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:377
+#: ../pykolab/plugins/sievemgmt/__init__.py:387
+#, python-format
+msgid "Including script %s in USER (for user %s)"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:386
+#: ../pykolab/plugins/sievemgmt/__init__.py:396
+#, python-format
+msgid "Uploading script USER failed for user %s"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:388
+#: ../pykolab/plugins/sievemgmt/__init__.py:398
+#, python-format
+msgid "Uploading script USER for user %s succeeded"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:416
+#: ../pykolab/plugins/sievemgmt/__init__.py:426
+#, python-format
+msgid "Uploading script MASTER failed for user %s"
+msgstr ""
+
+#: ../pykolab/cli/sieve/cmd_refresh.py:418
+#: ../pykolab/plugins/sievemgmt/__init__.py:428
+#, python-format
+msgid "Uploading script MASTER for user %s succeeded"
+msgstr ""
+
#: ../pykolab/cli/telemetry/cmd_examine_command_issue.py:40
msgid "Unspecified command issue identifier"
msgstr ""
@@ -986,357 +1441,429 @@ msgstr ""
msgid "No command supplied"
msgstr ""
-#: ../pykolab/conf/__init__.py:411
+#: ../pykolab/conf/__init__.py:416
msgid "Insufficient options. Need section, key and value -in that order."
msgstr ""
-#: ../pykolab/conf/__init__.py:414
+#: ../pykolab/conf/__init__.py:419
#, python-format
msgid "No section '%s' exists."
msgstr ""
-#: ../pykolab/conf/__init__.py:445
+#: ../pykolab/conf/__init__.py:461
#, python-format
msgid "Setting %s to %r (from the default values for CLI options)"
msgstr ""
-#: ../pykolab/conf/__init__.py:514
+#: ../pykolab/conf/__init__.py:534
#, python-format
msgid "Could not execute configuration function: %s"
msgstr ""
-#: ../pykolab/conf/__init__.py:522
+#: ../pykolab/conf/__init__.py:542
#, python-format
msgid "Option %s/%s does not exist in config file %s, pulling from defaults"
msgstr ""
-#: ../pykolab/conf/__init__.py:530 ../pykolab/conf/__init__.py:533
+#: ../pykolab/conf/__init__.py:550 ../pykolab/conf/__init__.py:553
msgid "Option does not exist in defaults."
msgstr ""
-#: ../pykolab/conf/__init__.py:543
+#: ../pykolab/conf/__init__.py:563
#, python-format
msgid "Configuration file %s not readable."
msgstr ""
-#: ../pykolab/conf/__init__.py:546
+#: ../pykolab/conf/__init__.py:566
#, python-format
msgid "Configuration file %s does not exist."
msgstr ""
-#: ../pykolab/conf/__init__.py:551
+#: ../pykolab/conf/__init__.py:571
msgid ""
-"WARNING: A negative debug level value does not make this program be any more "
-"silent."
+"WARNING: A negative debug level value does not make this program be any more"
+" silent."
msgstr ""
-#: ../pykolab/conf/__init__.py:557
+#: ../pykolab/conf/__init__.py:577
msgid "This program has 9 levels of verbosity. Using the maximum of 9."
msgstr ""
-#: ../pykolab/conf/__init__.py:565 ../pykolab/conf/__init__.py:571
+#: ../pykolab/conf/__init__.py:585 ../pykolab/conf/__init__.py:591
msgid "Cannot start SASL authentication daemon"
msgstr ""
-#: ../pykolab/conf/__init__.py:582
+#: ../pykolab/conf/__init__.py:602
msgid "No imaplib library found."
msgstr ""
-#: ../pykolab/conf/__init__.py:592
+#: ../pykolab/conf/__init__.py:612
msgid "No LMTP class found in the smtplib library."
msgstr ""
-#: ../pykolab/conf/__init__.py:602
+#: ../pykolab/conf/__init__.py:622
msgid "No SMTP class found in the smtplib library."
msgstr ""
-#: ../pykolab/conf/__init__.py:616
+#: ../pykolab/conf/__init__.py:636
#, python-format
msgid "Found you specified a specific set of items to test: %s"
msgstr ""
-#: ../pykolab/conf/__init__.py:624
+#: ../pykolab/conf/__init__.py:644
#, python-format
msgid "Selectively selecting: %s"
msgstr ""
#: ../pykolab/constants.py.in:40
-msgid "PyKolab is a Kolab Systems product. For more information "
+msgid ""
+"PyKolab is a Kolab Systems product. For more information about Kolab or "
+"PyKolab, visit http://www.kolabsys.com"
msgstr ""
-#: ../pykolab/constants.py.in:54
+#: ../pykolab/constants.py.in:53
msgid "WARNING"
msgstr ""
-#: ../pykolab/constants.py.in:54
-msgid "The Fully Qualified "
+#: ../pykolab/constants.py.in:53
+msgid ""
+"The Fully Qualified Domain Name or FQDN for this system is incorrect. "
+"Falling back to 'localdomain'."
msgstr ""
-#: ../pykolab/constants.py.in:75
+#: ../pykolab/constants.py.in:72
msgid "389 Directory Server or Red Hat Directory Server"
msgstr ""
-#: ../pykolab/constants.py.in:79 ../pykolab/constants.py.in:83
+#: ../pykolab/constants.py.in:76 ../pykolab/constants.py.in:80
msgid "OpenLDAP or compatible"
msgstr ""
-#: ../pykolab/imap/cyrus.py:79
+#: ../pykolab/imap/cyrus.py:80
#, python-format
msgid "Could not connect to Cyrus IMAP server %r"
msgstr ""
-#: ../pykolab/imap/cyrus.py:136
+#: ../pykolab/imap/cyrus.py:137
#, python-format
msgid "Continuing with separator: %r"
msgstr ""
-#: ../pykolab/imap/cyrus.py:141
+#: ../pykolab/imap/cyrus.py:142
msgid "Detected we are running in a Murder topology"
msgstr ""
-#: ../pykolab/imap/cyrus.py:145
+#: ../pykolab/imap/cyrus.py:146
msgid "This system is not part of a murder topology"
msgstr ""
-#: ../pykolab/imap/cyrus.py:166
+#: ../pykolab/imap/cyrus.py:167
#, python-format
msgid "Checking actual backend server for folder %s through annotations"
msgstr ""
-#: ../pykolab/imap/cyrus.py:181
+#: ../pykolab/imap/cyrus.py:172
+msgid "Possibly reproducing the find "
+msgstr ""
+
+#: ../pykolab/imap/cyrus.py:195
#, python-format
msgid "Could not get the annotations after %s tries."
msgstr ""
-#: ../pykolab/imap/cyrus.py:185
+#: ../pykolab/imap/cyrus.py:199
#, python-format
msgid "No annotations for %s: %r"
msgstr ""
-#: ../pykolab/imap/cyrus.py:192
+#: ../pykolab/imap/cyrus.py:206
#, python-format
msgid "Server for INBOX folder %s is %s"
msgstr ""
-#: ../pykolab/imap/cyrus.py:204
+#: ../pykolab/imap/cyrus.py:226
#, python-format
msgid "Setting quota for folder %s to %s"
msgstr ""
-#: ../pykolab/imap/cyrus.py:208
+#: ../pykolab/imap/cyrus.py:230
#, python-format
msgid "Could not set quota for mailfolder %s"
msgstr ""
-#: ../pykolab/imap/cyrus.py:217
+#: ../pykolab/imap/cyrus.py:239
#, python-format
msgid "Moving INBOX folder %s to %s"
msgstr ""
-#: ../pykolab/imap/cyrus.py:232
+#: ../pykolab/imap/cyrus.py:254
#, python-format
msgid "Setting annotation %s on folder %s"
msgstr ""
-#: ../pykolab/imap/cyrus.py:237
+#: ../pykolab/imap/cyrus.py:259
#, python-format
msgid "Could not set annotation %r on mail folder %r: %r"
msgstr ""
-#: ../pykolab/imap/cyrus.py:241
+#: ../pykolab/imap/cyrus.py:263
#, python-format
msgid "Transferring folder %s from %s to %s"
msgstr ""
-#: ../pykolab/imap/cyrus.py:301
+#: ../pykolab/imap/cyrus.py:323
#, python-format
msgid "Undeleting %s to %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:45
+#: ../pykolab/imap/cyrus.py:334
+#, python-format
+msgid "Would have transfered %s from %s to %s"
+msgstr ""
+
+#: ../pykolab/imap/cyrus.py:336
+#, python-format
+msgid "Would have renamed %s to %s"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:46
#, python-format
msgid "Cleaning up ACL entries for %s across all folders"
msgstr ""
-#: ../pykolab/imap/__init__.py:60
+#: ../pykolab/imap/__init__.py:61
#, python-format
msgid "Cleaning up ACL entries referring to identifier %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:69
+#: ../pykolab/imap/__init__.py:70
#, python-format
msgid "Iterating over %d folders"
msgstr ""
#. Set the ACL to '' (effectively deleting the ACL entry)
-#: ../pykolab/imap/__init__.py:82
+#: ../pykolab/imap/__init__.py:83
#, python-format
msgid "Removing acl %r for subject %r from folder %r"
msgstr ""
-#: ../pykolab/imap/__init__.py:143
+#: ../pykolab/imap/__init__.py:145
+msgid "No administrator password is available."
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:153
#, python-format
msgid "Logging on to Cyrus IMAP server %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:152
+#: ../pykolab/imap/__init__.py:162
#, python-format
msgid "Logging on to Dovecot IMAP server %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:161
+#: ../pykolab/imap/__init__.py:171
#, python-format
msgid "Logging on to generic IMAP server %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:179
+#: ../pykolab/imap/__init__.py:189
#, python-format
msgid "Reusing existing IMAP server connection to %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:181
+#: ../pykolab/imap/__init__.py:191
#, python-format
msgid "Reconnecting to IMAP server %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:197
+#: ../pykolab/imap/__init__.py:208
msgid "Called imap.disconnect() on a server that we had no connection to."
msgstr ""
-#: ../pykolab/imap/__init__.py:212
+#: ../pykolab/imap/__init__.py:222 ../pykolab/imap/__init__.py:234
#, python-format
-msgid "%r has no attribute %s"
+msgid "Could not create folder %r"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:223
+#, python-format
+msgid " on server %r"
msgstr ""
-#: ../pykolab/imap/__init__.py:279
-msgid "Private annotations need to be set using the appropriate user account."
+#: ../pykolab/imap/__init__.py:244 ../pykolab/imap/__init__.py:246
+#, python-format
+msgid "%r has no attribute %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:294 ../pykolab/imap/__init__.py:329
+#: ../pykolab/imap/__init__.py:393 ../pykolab/imap/__init__.py:428
#, python-format
msgid "Creating new shared folder %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:354 ../pykolab/imap/__init__.py:504
+#: ../pykolab/imap/__init__.py:453 ../pykolab/imap/__init__.py:675
#, python-format
msgid "Downcasing mailbox name %r"
msgstr ""
-#: ../pykolab/imap/__init__.py:358
+#: ../pykolab/imap/__init__.py:457
#, python-format
msgid "Creating new mailbox for user %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:387
+#: ../pykolab/imap/__init__.py:470
+msgid "Waiting for the Cyrus IMAP Murder to settle..."
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:516
#, python-format
msgid "Creating additional folders for user %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:407
+#: ../pykolab/imap/__init__.py:535
+#, python-format
+msgid "Waiting for the Cyrus murder to settle... %r"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:547
+#, python-format
+msgid "Correcting additional folder name from %r to %r"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:553
#, python-format
msgid "Mailbox already exists: %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:443
+#: ../pykolab/imap/__init__.py:593
msgid "Subscribing user to the additional folders"
msgstr ""
-#: ../pykolab/imap/__init__.py:458
+#: ../pykolab/imap/__init__.py:607
+msgid "Using the following tests for folder subscriptions:"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:609
+#, python-format
+msgid " %r"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:612
#, python-format
msgid "Folder %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:470
+#: ../pykolab/imap/__init__.py:624
#, python-format
msgid "Subscribing %s to folder %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:520 ../pykolab/imap/__init__.py:594
+#: ../pykolab/imap/__init__.py:628
+#, python-format
+msgid "Subscribing %s to folder %s failed: %r"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:658
+#, python-format
+msgid "Could not rename %s to reside on partition %s"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:691
+#, python-format
+msgid "INBOX folder to rename (%s) does not exist"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:694 ../pykolab/imap/__init__.py:770
#, python-format
msgid "Renaming INBOX from %s to %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:524
+#: ../pykolab/imap/__init__.py:698
#, python-format
msgid "Could not rename INBOX folder %s to %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:526 ../pykolab/imap/__init__.py:598
+#: ../pykolab/imap/__init__.py:700 ../pykolab/imap/__init__.py:774
#, python-format
-msgid "Moving INBOX folder %s won't succeed as target folder %s already exists"
+msgid ""
+"Moving INBOX folder %s won't succeed as target folder %s already exists"
msgstr ""
-#: ../pykolab/imap/__init__.py:536
+#: ../pykolab/imap/__init__.py:704
+#, python-format
+msgid "Server for mailbox %r is %r"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:712
#, python-format
msgid "Looking for folder '%s', we found folders: %r"
msgstr ""
-#: ../pykolab/imap/__init__.py:559
+#: ../pykolab/imap/__init__.py:735
#, python-format
msgid "Setting ACL rights %s for subject %s on folder "
msgstr ""
-#: ../pykolab/imap/__init__.py:570
+#: ../pykolab/imap/__init__.py:746
#, python-format
msgid "Removing ACL rights %s for subject %s on folder "
msgstr ""
-#: ../pykolab/imap/__init__.py:591
+#: ../pykolab/imap/__init__.py:767
#, python-format
msgid "Found old INBOX folder %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:600
+#: ../pykolab/imap/__init__.py:776
#, python-format
msgid "Did not find old folder user/%s to rename"
msgstr ""
-#: ../pykolab/imap/__init__.py:602
+#: ../pykolab/imap/__init__.py:778
msgid "Value for user is not a dictionary"
msgstr ""
#. TODO: Go in fact correct the quota.
-#: ../pykolab/imap/__init__.py:662
+#: ../pykolab/imap/__init__.py:846
#, python-format
msgid "Cannot get current IMAP quota for folder %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:675
+#: ../pykolab/imap/__init__.py:859
#, python-format
msgid "Quota for %s currently is %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:681
+#: ../pykolab/imap/__init__.py:865
#, python-format
msgid "Adjusting authentication database quota for folder %s to %d"
msgstr ""
-#: ../pykolab/imap/__init__.py:686
+#: ../pykolab/imap/__init__.py:870
#, python-format
msgid "Correcting quota for %s to %s (currently %s)"
msgstr ""
-#: ../pykolab/imap/__init__.py:763
+#: ../pykolab/imap/__init__.py:947
#, python-format
msgid "Checking folder: %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:768
+#: ../pykolab/imap/__init__.py:952
#, python-format
msgid "Folder has no corresponding user (1): %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:771
+#: ../pykolab/imap/__init__.py:955
#, python-format
msgid "Folder has no corresponding user (2): %s"
msgstr ""
#. We got user identifier only
-#: ../pykolab/imap/__init__.py:786
+#: ../pykolab/imap/__init__.py:970
msgid "Please don't give us just a user identifier"
msgstr ""
-#: ../pykolab/imap/__init__.py:789
+#: ../pykolab/imap/__init__.py:973
#, python-format
msgid "Deleting folder %s"
msgstr ""
@@ -1345,12 +1872,50 @@ msgstr ""
msgid "Returning thread local configuration"
msgstr ""
-#: ../pykolab/logger.py:106
+#: ../pykolab/itip/__init__.py:43
+#, python-format
+msgid "Method %r not really interesting for us."
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:49
+#, python-format
+msgid "Raw iTip payload: %s"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:59
+msgid "Could not read iTip from message."
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:67
+#, python-format
+msgid "Duplicate iTip object: %s"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:90
+msgid "iTip event without a start"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:132
+msgid "Message is not an iTip message (non-multipart message)"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:225
+#, python-format
+msgid "Failed to compose iTip reply message: %r"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:236 ../wallace/module_invitationpolicy.py:936
+#: ../wallace/module_resources.py:964
+#, python-format
+msgid "SMTP sendmail error: %r"
+msgstr ""
+
+#: ../pykolab/logger.py:173 ../pykolab/logger.py:179
#, python-format
-msgid "Could not change the ownership of log file %s"
+msgid "Could not change permissions on %s: %r"
msgstr ""
-#: ../pykolab/logger.py:122
+#: ../pykolab/logger.py:196
#, python-format
msgid "Cannot log to file %s: %s"
msgstr ""
@@ -1448,27 +2013,51 @@ msgstr ""
msgid "Attribute substitution for 'mail' failed in Recipient Policy"
msgstr ""
-#: ../pykolab/plugins/recipientpolicy/__init__.py:115
+#: ../pykolab/plugins/recipientpolicy/__init__.py:116
msgid "Could not parse the alternative mail routines"
msgstr ""
-#: ../pykolab/plugins/recipientpolicy/__init__.py:119
+#: ../pykolab/plugins/recipientpolicy/__init__.py:120
#, python-format
msgid "Alternative mail routines: %r"
msgstr ""
-#: ../pykolab/plugins/recipientpolicy/__init__.py:130
-#: ../pykolab/plugins/recipientpolicy/__init__.py:141
+#: ../pykolab/plugins/recipientpolicy/__init__.py:127
+#, python-format
+msgid ""
+"An error occurred in composing the secondary mail attribute for entry %r"
+msgstr ""
+
+#: ../pykolab/plugins/recipientpolicy/__init__.py:138
+#: ../pykolab/plugins/recipientpolicy/__init__.py:153
#, python-format
msgid "Appending additional mail address: %s"
msgstr ""
-#: ../pykolab/plugins/recipientpolicy/__init__.py:134
-#: ../pykolab/plugins/recipientpolicy/__init__.py:145
+#: ../pykolab/plugins/recipientpolicy/__init__.py:142
+#, python-format
+msgid "Policy for secondary email address failed: %r"
+msgstr ""
+
+#: ../pykolab/plugins/recipientpolicy/__init__.py:157
msgid ""
"Attribute substitution for 'alternative_mail' failed in Recipient Policy"
msgstr ""
+#: ../pykolab/plugins/roundcubedb/__init__.py:48
+#, python-format
+msgid "user_delete: %r"
+msgstr ""
+
+#: ../pykolab/plugins/roundcubedb/__init__.py:55
+#: ../pykolab/setup/setup_roundcube.py:160
+msgid "Roundcube installation path not found."
+msgstr ""
+
+#: ../pykolab/plugins/sievemgmt/__init__.py:51
+msgid "Wrong number of arguments for sieve management plugin"
+msgstr ""
+
#: ../pykolab/setup/components.py:58
msgid "Display this help."
msgstr ""
@@ -1489,61 +2078,29 @@ msgstr ""
msgid "Free/Busy is not installed on this system"
msgstr ""
-#: ../pykolab/setup/setup_freebusy.py:55
-msgid ""
-"\n"
-" Please supply the MySQL password for the "
-"'roundcube'\n"
-" user. You have supplied this password earlier, and "
-"it is\n"
-" available from the database URI setting in\n"
-" /etc/roundcubemail/db.inc.php.\n"
-" "
-msgstr ""
-
-#: ../pykolab/setup/setup_freebusy.py:64
-#: ../pykolab/setup/setup_roundcube.py:56
-msgid "MySQL roundcube password"
+#: ../pykolab/setup/setup_imap.py:45
+msgid "Setup IMAP."
msgstr ""
-#: ../pykolab/setup/setup_freebusy.py:92
-#: ../pykolab/setup/setup_roundcube.py:115 ../pykolab/setup/setup_zpush.py:71
-#, python-format
-msgid "Using template file %r"
+#: ../pykolab/setup/setup_imap.py:89
+msgid "Could not write out Cyrus IMAP configuration file /etc/imapd.conf"
msgstr ""
-#: ../pykolab/setup/setup_freebusy.py:99
-#: ../pykolab/setup/setup_roundcube.py:122 ../pykolab/setup/setup_zpush.py:78
-#, python-format
-msgid "Successfully compiled template %r, writing out to %r"
+#: ../pykolab/setup/setup_imap.py:114
+msgid "Could not write out Cyrus IMAP configuration file /etc/cyrus.conf"
msgstr ""
-#: ../pykolab/setup/setup_freebusy.py:119
-#: ../pykolab/setup/setup_roundcube.py:193
-#: ../pykolab/setup/setup_syncroton.py:66 ../pykolab/setup/setup_zpush.py:98
-msgid "Could not start the webserver server service."
+#: ../pykolab/setup/setup_imap.py:158
+msgid "Could not start the cyrus-imapd and kolab-saslauthd services."
msgstr ""
-#: ../pykolab/setup/setup_freebusy.py:128 ../pykolab/setup/setup_imap.py:169
-#: ../pykolab/setup/setup_kolabd.py:81 ../pykolab/setup/setup_ldap.py:327
-#: ../pykolab/setup/setup_mta.py:378 ../pykolab/setup/setup_mysql.py:58
-#: ../pykolab/setup/setup_roundcube.py:202
-#: ../pykolab/setup/setup_syncroton.py:75 ../pykolab/setup/setup_zpush.py:107
+#: ../pykolab/setup/setup_imap.py:173 ../pykolab/setup/setup_kolabd.py:81
+#: ../pykolab/setup/setup_ldap.py:426 ../pykolab/setup/setup_mta.py:455
+#: ../pykolab/setup/setup_mysql.py:58 ../pykolab/setup/setup_roundcube.py:237
+#: ../pykolab/setup/setup_syncroton.py:102
msgid "Could not configure to start on boot, the "
msgstr ""
-#: ../pykolab/setup/setup_imap.py:45
-msgid "Setup IMAP."
-msgstr ""
-
-#: ../pykolab/setup/setup_imap.py:89 ../pykolab/setup/setup_imap.py:114
-msgid "Could not write out Cyrus IMAP configuration file /etc/imapd.conf"
-msgstr ""
-
-#: ../pykolab/setup/setup_imap.py:154
-msgid "Could not start the cyrus-imapd and kolab-saslauthd services."
-msgstr ""
-
#: ../pykolab/setup/setup_kolabd.py:43
msgid "Setup the Kolab daemon."
msgstr ""
@@ -1552,8 +2109,7 @@ msgstr ""
#, python-format
msgid ""
"\n"
-" Copying the configuration section for 'example."
-"org' over to\n"
+" Copying the configuration section for 'example.org' over to\n"
" a section applicable to your domain '%s'.\n"
" "
msgstr ""
@@ -1574,220 +2130,250 @@ msgstr ""
msgid "Allow anonymous binds (default: no)."
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:64
-msgid "Setup LDAP."
+#: ../pykolab/setup/setup_ldap.py:68
+msgid "Skip setting up the LDAP server."
msgstr ""
#: ../pykolab/setup/setup_ldap.py:76
+msgid "Setup configuration for OpenLDAP compatibility."
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:84
+msgid "Setup configuration for Active Directory compatibility."
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:88
+msgid "Setup LDAP."
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:97
+msgid "Skipping setup of LDAP, as specified"
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:126
+msgid ""
+"\n"
+" You can not configure Kolab to run against OpenLDAP\n"
+" and Active Directory simultaneously.\n"
+" "
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:139
msgid ""
"\n"
-" Please supply a password for the LDAP administrator "
-"user\n"
-" 'admin', used to login to the graphical console of "
-"389\n"
+" It seems 389 Directory Server has an existing\n"
+" instance configured. This setup script does not\n"
+" intend to destroy or overwrite your data. Please\n"
+" make sure /etc/dirsrv/ and /var/lib/dirsrv/ are\n"
+" clean so that this setup does not have to worry.\n"
+" "
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:154
+msgid ""
+"\n"
+" Please supply a password for the LDAP administrator user\n"
+" 'admin', used to login to the graphical console of 389\n"
" Directory server.\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:84
+#: ../pykolab/setup/setup_ldap.py:162
msgid "Administrator password"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:91
+#: ../pykolab/setup/setup_ldap.py:169
msgid ""
"\n"
-" Please supply a password for the LDAP Directory "
-"Manager\n"
-" user, which is the administrator user you will be "
-"using\n"
-" to at least initially log in to the Web Admin, and "
-"that\n"
+" Please supply a password for the LDAP Directory Manager\n"
+" user, which is the administrator user you will be using\n"
+" to at least initially log in to the Web Admin, and that\n"
" Kolab uses to perform administrative tasks.\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:100
+#: ../pykolab/setup/setup_ldap.py:178
msgid "Directory Manager password"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:107
+#: ../pykolab/setup/setup_ldap.py:185
msgid ""
"\n"
" Please choose the system user and group the service\n"
" should use to run under. These should be existing,\n"
-" unprivileged, local system POSIX accounts with no "
-"shell.\n"
+" unprivileged, local system POSIX accounts with no shell.\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:117 ../pykolab/setup/setup_ldap.py:120
+#: ../pykolab/setup/setup_ldap.py:195 ../pykolab/setup/setup_ldap.py:198
msgid "User"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:118 ../pykolab/setup/setup_ldap.py:121
+#: ../pykolab/setup/setup_ldap.py:196 ../pykolab/setup/setup_ldap.py:199
msgid "Group"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:157
+#: ../pykolab/setup/setup_ldap.py:234
msgid ""
"\n"
-" This setup procedure plans to set up Kolab Groupware "
-"for\n"
-" the following domain name space. This domain name "
-"is\n"
+" This setup procedure plans to set up Kolab Groupware for\n"
+" the following domain name space. This domain name is\n"
" obtained from the reverse DNS entry on your network\n"
-" interface. Please confirm this is the appropriate "
-"domain\n"
+" interface. Please confirm this is the appropriate domain\n"
" name space.\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:171
+#: ../pykolab/setup/setup_ldap.py:248
msgid "Domain name to use"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:176 ../pykolab/setup/setup_ldap.py:201
+#: ../pykolab/setup/setup_ldap.py:253 ../pykolab/setup/setup_ldap.py:278
msgid ""
"\n"
" Invalid input. Please try again.\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:185
+#: ../pykolab/setup/setup_ldap.py:262
msgid ""
"\n"
-" The standard root dn we composed for you follows. "
-"Please\n"
+" The standard root dn we composed for you follows. Please\n"
" confirm this is the root dn you wish to use.\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:196
+#: ../pykolab/setup/setup_ldap.py:273
msgid "Root DN to use"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:244
+#: ../pykolab/setup/setup_ldap.py:325
msgid "No directory server setup tool available."
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:255
+#: ../pykolab/setup/setup_ldap.py:337
msgid ""
"\n"
-" Setup is now going to set up the 389 Directory Server. "
-"This\n"
-" may take a little while (during which period there is "
-"no\n"
+" Setup is now going to set up the 389 Directory Server. This\n"
+" may take a little while (during which period there is no\n"
" output and no progress indication).\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:262
+#: ../pykolab/setup/setup_ldap.py:344
msgid "Setting up 389 Directory Server"
msgstr ""
-#. TODO: Get the return code and display output if not successful.
-#: ../pykolab/setup/setup_ldap.py:274
+#: ../pykolab/setup/setup_ldap.py:356
+msgid ""
+"\n"
+" An error was detected in the setup procedure for 389\n"
+" Directory Server. This setup will write out stderr and\n"
+" stdout to /var/log/kolab/setup.error.log and\n"
+" /var/log/kolab/setup.out.log respectively, before it\n"
+" exits.\n"
+" "
+msgstr ""
+
+#: ../pykolab/setup/setup_ldap.py:373
msgid "Setup DS stdout:"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:277
+#: ../pykolab/setup/setup_ldap.py:376
msgid "Setup DS stderr:"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:303
+#: ../pykolab/setup/setup_ldap.py:402
msgid "Could not copy the LDAP extensions for Kolab"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:306
+#: ../pykolab/setup/setup_ldap.py:405
msgid "Could not find the ldap Kolab schema file"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:318
+#: ../pykolab/setup/setup_ldap.py:417
msgid "Could not start the directory server service."
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:332
+#: ../pykolab/setup/setup_ldap.py:431
msgid ""
"\n"
" Please supply a Cyrus Administrator password. This\n"
" password is used by Kolab to execute administrative\n"
" tasks in Cyrus IMAP. You may also need the password\n"
" yourself to troubleshoot Cyrus IMAP and/or perform\n"
-" other administrative tasks against Cyrus IMAP "
-"directly.\n"
+" other administrative tasks against Cyrus IMAP directly.\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:342
+#: ../pykolab/setup/setup_ldap.py:441
msgid "Cyrus Administrator password"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:349
+#: ../pykolab/setup/setup_ldap.py:448
msgid ""
"\n"
-" Please supply a Kolab Service account password. "
-"This\n"
-" account is used by various services such as "
-"Postfix,\n"
-" and Roundcube, as anonymous binds to the LDAP "
-"server\n"
+" Please supply a Kolab Service account password. This\n"
+" account is used by various services such as Postfix,\n"
+" and Roundcube, as anonymous binds to the LDAP server\n"
" will not be allowed.\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:358
+#: ../pykolab/setup/setup_ldap.py:457
msgid "Kolab Service password"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:368
+#: ../pykolab/setup/setup_ldap.py:467
msgid "Writing out configuration to kolab.conf"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:382
+#: ../pykolab/setup/setup_ldap.py:481
msgid "Inserting service users into LDAP."
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:456
+#: ../pykolab/setup/setup_ldap.py:555
msgid "Writing out cn=kolab,cn=config"
msgstr ""
#. TODO: Add kolab-admin role
#. TODO: Assign kolab-admin admin ACLs
-#: ../pykolab/setup/setup_ldap.py:480
+#: ../pykolab/setup/setup_ldap.py:579
#, python-format
msgid "Adding domain %s to list of domains for this deployment"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:497
+#: ../pykolab/setup/setup_ldap.py:607
msgid "Disabling anonymous binds"
msgstr ""
#. TODO: Ensure the uid attribute is unique
-#. TODO^2: Consider renaming the general "attribute uniqueness to "uid attribute uniqueness"
-#: ../pykolab/setup/setup_ldap.py:505
+#. TODO^2: Consider renaming the general "attribute uniqueness to "uid
+#. attribute uniqueness"
+#: ../pykolab/setup/setup_ldap.py:615
msgid "Enabling attribute uniqueness plugin"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:511
+#: ../pykolab/setup/setup_ldap.py:621
msgid "Enabling referential integrity plugin"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:517
+#: ../pykolab/setup/setup_ldap.py:627
msgid "Enabling and configuring account policy plugin"
msgstr ""
#. TODO: Add kolab-admin role
-#: ../pykolab/setup/setup_ldap.py:532
+#: ../pykolab/setup/setup_ldap.py:642
msgid "Adding the kolab-admin role"
msgstr ""
#. TODO: User writeable attributes on root_dn
-#: ../pykolab/setup/setup_ldap.py:543
+#: ../pykolab/setup/setup_ldap.py:653
#, python-format
msgid "Setting access control to %s"
msgstr ""
-#: ../pykolab/setup/setup_ldap.py:568
+#: ../pykolab/setup/setup_ldap.py:679
msgid "Could not start and configure to start on boot, the "
msgstr ""
@@ -1795,24 +2381,24 @@ msgstr ""
msgid "Setup MTA."
msgstr ""
-#: ../pykolab/setup/setup_mta.py:245 ../pykolab/setup/setup_php.py:104
+#: ../pykolab/setup/setup_mta.py:317 ../pykolab/setup/setup_php.py:106
#, python-format
msgid "Setting key %r to %r"
msgstr ""
-#: ../pykolab/setup/setup_mta.py:278
+#: ../pykolab/setup/setup_mta.py:350
msgid "Could not write out Postfix configuration file /etc/postfix/master.cf"
msgstr ""
-#: ../pykolab/setup/setup_mta.py:321
-msgid "Could not write out Amavis configuration file /etc/amavisd/amavisd.conf"
+#: ../pykolab/setup/setup_mta.py:397
+msgid "Could not write out Amavis configuration file amavisd.conf"
msgstr ""
-#: ../pykolab/setup/setup_mta.py:329
+#: ../pykolab/setup/setup_mta.py:405
msgid "Not writing out any configuration for Amavis."
msgstr ""
-#: ../pykolab/setup/setup_mta.py:360
+#: ../pykolab/setup/setup_mta.py:437
msgid "Could not start the postfix, clamav and amavisd services services."
msgstr ""
@@ -1824,55 +2410,50 @@ msgstr ""
msgid "Could not start the MySQL database service."
msgstr ""
-#: ../pykolab/setup/setup_mysql.py:68
+#: ../pykolab/setup/setup_mysql.py:71
msgid "What MySQL server are we setting up?"
msgstr ""
-#: ../pykolab/setup/setup_mysql.py:72
+#: ../pykolab/setup/setup_mysql.py:75
msgid ""
"\n"
-" Please supply the root password for MySQL, so we can "
-"set\n"
-" up user accounts for other components that use "
-"MySQL.\n"
+" Please supply the root password for MySQL, so we can set\n"
+" up user accounts for other components that use MySQL.\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_mysql.py:79 ../pykolab/setup/setup_mysql.py:96
+#: ../pykolab/setup/setup_mysql.py:82 ../pykolab/setup/setup_mysql.py:99
+#: ../pykolab/setup/setup_roundcube.py:183
+#: ../pykolab/setup/setup_syncroton.py:63
msgid "MySQL root password"
msgstr ""
-#: ../pykolab/setup/setup_mysql.py:85
+#: ../pykolab/setup/setup_mysql.py:88
msgid ""
"\n"
-" Please supply a root password for MySQL. This "
-"password\n"
-" will be the administrative user for this MySQL "
-"server,\n"
-" and it should be kept a secret. After this setup "
-"process\n"
+" Please supply a root password for MySQL. This password\n"
+" will be the administrative user for this MySQL server,\n"
+" and it should be kept a secret. After this setup process\n"
" has completed, Kolab is going to discard and forget\n"
" about this password, but you will need it for\n"
" administrative tasks in MySQL.\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_mysql.py:136
+#: ../pykolab/setup/setup_mysql.py:139
msgid ""
"\n"
-" Please supply a password for the MySQL user "
-"'kolab'.\n"
-" This password will be used by Kolab services, such "
-"as\n"
+" Please supply a password for the MySQL user 'kolab'.\n"
+" This password will be used by Kolab services, such as\n"
" the Web Administration Panel.\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_mysql.py:144
+#: ../pykolab/setup/setup_mysql.py:147
msgid "MySQL kolab password"
msgstr ""
-#: ../pykolab/setup/setup_mysql.py:162
+#: ../pykolab/setup/setup_mysql.py:165
msgid "Could not find the MySQL Kolab schema file"
msgstr ""
@@ -1896,19 +2477,21 @@ msgstr ""
msgid ""
"\n"
" Please supply the timezone PHP should be using.\n"
+" You have to use a Continent or Country / City locality name\n"
+" like 'Europe/Berlin', but not just 'CEST'.\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_php.py:72
+#: ../pykolab/setup/setup_php.py:74
msgid "Timezone ID"
msgstr ""
-#: ../pykolab/setup/setup_php.py:78
+#: ../pykolab/setup/setup_php.py:80
#, python-format
msgid "Cannot configure PHP through %r (No such file or directory)"
msgstr ""
-#: ../pykolab/setup/setup_php.py:89
+#: ../pykolab/setup/setup_php.py:91
msgid "Could not find PHP configuration file php.ini"
msgstr ""
@@ -1919,23 +2502,33 @@ msgstr ""
#: ../pykolab/setup/setup_roundcube.py:48
msgid ""
"\n"
-" Please supply a password for the MySQL user "
-"'roundcube'.\n"
+" Please supply a password for the MySQL user 'roundcube'.\n"
" This password will be used by the Roundcube webmail\n"
" interface.\n"
" "
msgstr ""
-#: ../pykolab/setup/setup_syncroton.py:40
-msgid "Setup Syncroton."
+#: ../pykolab/setup/setup_roundcube.py:56
+msgid "MySQL roundcube password"
+msgstr ""
+
+#: ../pykolab/setup/setup_roundcube.py:120
+#, python-format
+msgid "Using template file %r"
msgstr ""
-#: ../pykolab/setup/setup_zpush.py:41
-msgid "Setup zpush."
+#: ../pykolab/setup/setup_roundcube.py:127
+#, python-format
+msgid "Successfully compiled template %r, writing out to %r"
+msgstr ""
+
+#: ../pykolab/setup/setup_roundcube.py:228
+#: ../pykolab/setup/setup_syncroton.py:93
+msgid "Could not start the webserver server service."
msgstr ""
-#: ../pykolab/setup/setup_zpush.py:45
-msgid "Z-Push is not installed on this system"
+#: ../pykolab/setup/setup_syncroton.py:40
+msgid "Setup Syncroton."
msgstr ""
#. start_max = (int)(time.time())
@@ -1963,198 +2556,616 @@ msgstr ""
msgid "No database available"
msgstr ""
-#: ../pykolab/utils.py:60 ../pykolab/utils.py:62
+#: ../pykolab/utils.py:62 ../pykolab/utils.py:64
#, python-format
msgid "Confirm %s: "
msgstr ""
-#: ../pykolab/utils.py:65
+#: ../pykolab/utils.py:67
msgid "Incorrect confirmation. "
msgstr ""
-#: ../pykolab/utils.py:70 ../pykolab/utils.py:75
+#: ../pykolab/utils.py:72 ../pykolab/utils.py:77
#, python-format
msgid "%s: "
msgstr ""
-#: ../pykolab/utils.py:72 ../pykolab/utils.py:77
+#: ../pykolab/utils.py:74 ../pykolab/utils.py:79
#, python-format
msgid "%s [%s]: "
msgstr ""
-#: ../pykolab/utils.py:122
+#: ../pykolab/utils.py:124
msgid "Please answer 'yes' or 'no'."
msgstr ""
-#: ../pykolab/utils.py:148
+#: ../pykolab/utils.py:164
msgid "Choice"
msgstr ""
-#: ../pykolab/utils.py:214
+#: ../pykolab/utils.py:167
+msgid "Choice (type '?' for options)"
+msgstr ""
+
+#: ../pykolab/utils.py:268
#, python-format
msgid "Could not change the permissions on %s"
msgstr ""
-#: ../pykolab/utils.py:395
+#: ../pykolab/utils.py:479
#, python-format
msgid "Transliterating string %r with locale %r"
msgstr ""
-#: ../pykolab/utils.py:403
+#: ../pykolab/utils.py:487
msgid "Attempting to set locale"
msgstr ""
-#: ../pykolab/utils.py:405
+#: ../pykolab/utils.py:489
msgid "Success setting locale"
msgstr ""
-#: ../pykolab/utils.py:407
+#: ../pykolab/utils.py:491
msgid "Failure to set locale"
msgstr ""
-#: ../pykolab/utils.py:415
+#: ../pykolab/utils.py:499
#, python-format
msgid "Executing '%s | %s'"
msgstr ""
-#: ../pykolab/wap_client/__init__.py:257
+#: ../pykolab/utils.py:510
+#, python-format
+msgid "Could not translate %s using locale %s"
+msgstr ""
+
+#: ../pykolab/wap_client/__init__.py:320
#, python-format
msgid "Requesting %r with params %r"
msgstr ""
-#: ../pykolab/wap_client/__init__.py:263
+#: ../pykolab/wap_client/__init__.py:328
#, python-format
msgid "Got response: %r"
msgstr ""
#. Some data is not JSON
-#: ../pykolab/wap_client/__init__.py:268
+#: ../pykolab/wap_client/__init__.py:334
msgid "Response data is not JSON"
msgstr ""
-#: ../pykolab/xml/attendee.py:79 ../pykolab/xml/attendee.py:99
+#. support integer values, too
+#: ../pykolab/xml/attendee.py:9 ../pykolab/xml/attendee.py:17
+msgid "Needs Action"
+msgstr ""
+
+#: ../pykolab/xml/attendee.py:10 ../pykolab/xml/attendee.py:18
+msgid "Accepted"
+msgstr "Akzeptiert"
+
+#: ../pykolab/xml/attendee.py:11 ../pykolab/xml/attendee.py:19
+msgid "Declined"
+msgstr "Abgelehnt"
+
+#: ../pykolab/xml/attendee.py:12 ../pykolab/xml/attendee.py:20
+msgid "Tentatively Accepted"
+msgstr "Provisorisch Akzeptiert"
+
+#: ../pykolab/xml/attendee.py:13 ../pykolab/xml/attendee.py:21
+msgid "Delegated"
+msgstr "Delegiert"
+
+#: ../pykolab/xml/attendee.py:14
+msgid "Completed"
+msgstr ""
+
+#: ../pykolab/xml/attendee.py:15
+msgid "In Process"
+msgstr ""
+
+#: ../pykolab/xml/attendee.py:108 ../pykolab/xml/attendee.py:130
msgid "Not a valid attendee"
msgstr ""
-#: ../pykolab/xml/attendee.py:84
+#: ../pykolab/xml/attendee.py:115
msgid "No valid delegator references found"
msgstr ""
-#: ../pykolab/xml/attendee.py:104
+#: ../pykolab/xml/attendee.py:135
msgid "No valid delegatee references found"
msgstr ""
-#: ../pykolab/xml/attendee.py:140
+#: ../pykolab/xml/attendee.py:180
#, python-format
msgid "Invalid cutype %r"
msgstr ""
-#: ../pykolab/xml/attendee.py:151
+#: ../pykolab/xml/attendee.py:192
#, python-format
msgid "Invalid participant status %r"
msgstr ""
-#: ../pykolab/xml/attendee.py:159
+#: ../pykolab/xml/attendee.py:200
#, python-format
msgid "Invalid role %r"
msgstr ""
-#: ../pykolab/xml/event.py:172
+#: ../pykolab/xml/event.py:100 ../pykolab/xml/event.py:708
+#: ../pykolab/xml/event.py:751
+msgid "Event start needs datetime.date or datetime.datetime instance"
+msgstr ""
+
+#: ../pykolab/xml/event.py:241
#, python-format
msgid "No attendee with email or name %r"
msgstr ""
-#: ../pykolab/xml/event.py:180
+#: ../pykolab/xml/event.py:249
#, python-format
msgid "Invalid argument value attendee %r, must be basestring or Attendee"
msgstr ""
-#: ../pykolab/xml/event.py:186
+#: ../pykolab/xml/event.py:255
#, python-format
msgid "No attendee with email %r"
msgstr ""
-#: ../pykolab/xml/event.py:192
+#: ../pykolab/xml/event.py:261
#, python-format
msgid "No attendee with name %r"
msgstr ""
-#: ../pykolab/xml/event.py:338
+#: ../pykolab/xml/event.py:426
msgid "Invalid participant status"
msgstr ""
-#: ../pykolab/xml/event.py:538
-msgid "Event end needs datetime.date or datetime.datetime instance"
+#: ../pykolab/xml/event.py:542
+#, python-format
+msgid "Invalid status %r"
msgstr ""
-#: ../pykolab/xml/event.py:657
+#: ../pykolab/xml/event.py:550
#, python-format
-msgid "Invalid status %r"
+msgid "Invalid classification %r"
msgstr ""
-#: ../pykolab/xml/event.py:678 ../pykolab/xml/event.py:728
-msgid "Event start needs datetime.date or datetime.datetime instance"
+#: ../pykolab/xml/event.py:577
+msgid "Event end needs datetime.date or datetime.datetime instance"
msgstr ""
-#: ../pykolab/xml/event.py:764
+#: ../pykolab/xml/event.py:761
#, python-format
msgid "Invalid status set: %r"
msgstr ""
-#: ../pykolab/xml/event.py:882
+#: ../pykolab/xml/event.py:923
msgid "No sender specified"
msgstr ""
-#: ../saslauthd/__init__.py:126 ../saslauthd/__init__.py:134
-msgid "Traceback occurred, please report a bug at http://bugzilla.kolabsys.com"
+#: ../pykolab/xml/event.py:932
+#, python-format
+msgid "Invitation for %s was %s"
+msgstr ""
+
+#: ../pykolab/xml/event.py:937
+msgid "This is an automated response to one of your event requests."
+msgstr ""
+
+#: ../saslauthd/__init__.py:99
+#, python-format
+msgid "Could not create %r: %r"
+msgstr ""
+
+#: ../saslauthd/__init__.py:137 ../saslauthd/__init__.py:145
+#: ../wallace/__init__.py:403 ../wallace/__init__.py:412
+msgid ""
+"Traceback occurred, please report a bug at http://bugzilla.kolabsys.com"
+msgstr ""
+
+#: ../saslauthd/__init__.py:185
+msgid "kolab-saslauthd could not accept "
+msgstr ""
+
+#: ../saslauthd/__init__.py:190
+msgid "Maximum tries exceeded, exiting"
+msgstr ""
+
+#: ../tests/functional/test_wallace/test_005_resource_invitation.py:190
+#: ../wallace/module_resources.py:879
+#, python-format
+msgid "Reservation Request for %(summary)s was %(status)s"
+msgstr ""
+
+#. check notification message sent to resource owner (jane)
+#: ../tests/functional/test_wallace/test_005_resource_invitation.py:605
+#: ../tests/functional/test_wallace/test_005_resource_invitation.py:621
+#: ../wallace/module_resources.py:954
+#, python-format
+msgid "Booking for %s has been %s"
+msgstr ""
+
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:146
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:720
+#: ../wallace/module_invitationpolicy.py:374
+#, python-format
+msgid "\"%(summary)s\" has been %(status)s"
msgstr ""
-#: ../wallace/__init__.py:62
+#. check for notification message
+#. this notification should be suppressed until mark has replied, too
+#. this triggers an additional notification
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:616
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:622
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:635
+#: ../wallace/module_invitationpolicy.py:925
+#, python-format
+msgid "\"%s\" has been updated"
+msgstr ""
+
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:627
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:639
+msgid "PENDING"
+msgstr ""
+
+#: ../wallace/__init__.py:57
+#, python-format
+msgid "Wallace modules: %r"
+msgstr ""
+
+#: ../wallace/__init__.py:69
+#, python-format
+msgid "Module %s.execute() failed on message %r with error: %s"
+msgstr ""
+
+#: ../wallace/__init__.py:78
#, python-format
msgid "Worker process %s initializing"
msgstr ""
-#: ../wallace/__init__.py:81
+#: ../wallace/__init__.py:100
msgid "Bind address for Wallace."
msgstr ""
-#: ../wallace/__init__.py:107
+#: ../wallace/__init__.py:126
msgid "Port that Wallace is supposed to use."
msgstr ""
-#: ../wallace/__init__.py:158
+#: ../wallace/__init__.py:177
#, python-format
msgid "Could not bind to socket on port %d on bind "
msgstr ""
-#: ../wallace/__init__.py:170
+#: ../wallace/__init__.py:189
msgid "Could not shut down socket"
msgstr ""
-#: ../wallace/__init__.py:227
+#: ../wallace/__init__.py:253
msgid "Accepted connection"
msgstr ""
-#: ../wallace/__init__.py:398
+#: ../wallace/__init__.py:428
#, python-format
msgid "Could not write pid file %s"
msgstr ""
-#: ../wallace/module_optout.py:61 ../wallace/module_resources.py:93
+#: ../wallace/module_footer.py:60 ../wallace/module_gpgencrypt.py:60
+#: ../wallace/module_invitationpolicy.py:168 ../wallace/module_optout.py:61
+#: ../wallace/module_resources.py:120
#, python-format
msgid "Issuing callback after processing to stage %s"
msgstr ""
-#: ../wallace/module_optout.py:62 ../wallace/module_resources.py:99
+#: ../wallace/module_footer.py:61 ../wallace/module_gpgencrypt.py:61
+#: ../wallace/module_invitationpolicy.py:170 ../wallace/module_optout.py:62
+#: ../wallace/module_resources.py:126
#, python-format
msgid "Testing cb_action_%s()"
msgstr ""
-#: ../wallace/module_optout.py:64 ../wallace/module_resources.py:102
+#: ../wallace/module_footer.py:63 ../wallace/module_gpgencrypt.py:63
+#: ../wallace/module_invitationpolicy.py:172 ../wallace/module_optout.py:64
+#: ../wallace/module_resources.py:129
#, python-format
msgid "Attempting to execute cb_action_%s()"
msgstr ""
+#: ../wallace/module_footer.py:67
+#, python-format
+msgid "Executing module footer for %r, %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:66
+#, python-format
+msgid "Executing module gpgencrypt for %r, %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:98
+msgid "Message is already encrypted (app/pgp-enc content-type)"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:102
+msgid "Message already encrypted by main content-type header"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:131
+msgid ""
+"Configured to encrypt to a key not configured, and strict policy enabled. "
+"Bailing out."
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:134
+msgid ""
+"Configured to encrypt to a key not configured, but continuing anyway (see "
+"'gpgencrypt_strict')."
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:171
+#, python-format
+msgid "Recipients: %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:183
+#, python-format
+msgid "Current keys: %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:188
+#, python-format
+msgid "Retrieving key for recipient: %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:192 ../wallace/module_gpgencrypt.py:208
+#, python-format
+msgid "Found matching address %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:200
+#, python-format
+msgid "Found matching address %r in remote keys"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:232
+#, python-format
+msgid "An error occurred: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:154
+#, python-format
+msgid "Invitation policy called for %r, %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:211
+#: ../wallace/module_resources.py:169
+#, python-format
+msgid "Failed to parse iTip events from message: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:215
+msgid ""
+"Message is not an iTip message or does not contain any (valid) iTip events."
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:219
+#, python-format
+msgid ""
+"iTip events attached to this message contain the following information: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:232
+#, python-format
+msgid "No itips, no users, pass along %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:235
+#, python-format
+msgid "iTips, but no users, pass along %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:255
+#, python-format
+msgid "No user attendee matching envelope recipient %s, skip message"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:259
+#, python-format
+msgid "Receiving user: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:284
+#, python-format
+msgid "Apply invitation policy %r for domain %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:295
+#, python-format
+msgid "Ignoring '%s' iTip method"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:299
+#, python-format
+msgid "iTip message %r consumed by the invitationpolicy module"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:315
+msgid "Pass invitation for manual processing"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:320
+#, python-format
+msgid "Receiving Attendee: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:339
+#, python-format
+msgid "Existing event: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:350
+#, python-format
+msgid "Precondition for event %r fulfilled: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:386
+#, python-format
+msgid "No RSVP for recipient %r requested"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:412
+msgid "Pass reply for manual processing"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:419
+#, python-format
+msgid "Sender Attendee: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:431
+#, python-format
+msgid ""
+"The iTip reply sequence (%r) doesn't match the referred event version (%r). "
+"Forwarding to Inbox."
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:437
+#, python-format
+msgid "Auto-updating event %r on iTip REPLY"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:459
+#: ../wallace/module_invitationpolicy.py:488
+msgid ""
+"The event referred by this reply was not found in the user's calendars. "
+"Forwarding to Inbox."
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:472
+msgid "Pass cancellation for manual processing"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:517
+#, python-format
+msgid "Checking if email address %r belongs to a local user"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:522
+#, python-format
+msgid "User DN: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:524
+#, python-format
+msgid "No user record(s) found for %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:577
+#, python-format
+msgid "User record doesn't have the mailbox attribute %r set"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:590
+#, python-format
+msgid "IMAP proxy authentication failed: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:612
+#, python-format
+msgid "List calendar folders for user %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:628
+#, python-format
+msgid "IMAP metadata for %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:658
+#, python-format
+msgid "Searching folder %r for event %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:670
+#: ../wallace/module_invitationpolicy.py:709
+#: ../wallace/module_resources.py:486
+#, python-format
+msgid "Failed to parse event from message %s/%s: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:696
+#, python-format
+msgid "Listing events from folder %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:715
+#, python-format
+msgid "Existing event %r conflicts with invitation %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:722
+#: ../wallace/module_resources.py:344
+#, python-format
+msgid "start: %r, end: %r, total: %r, messages: %d"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:748
+#, python-format
+msgid "%r is locked, waiting..."
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:811
+#, python-format
+msgid "Failed to save event: no calendar folder found for user %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:814
+#, python-format
+msgid "Save event %r to user calendar %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:827
+#, python-format
+msgid "Failed to save event to user calendar at %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:843
+#, python-format
+msgid "Delete event %r in %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:863
+#, python-format
+msgid "Compose participation status summary for event %r to user %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:901
+#, python-format
+msgid ""
+"Waiting for more automated replies (got %d of %d); skipping notification"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:998
+#, python-format
+msgid "Updated %s's copy of %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:1001
+#, python-format
+msgid "Attendee %s's copy of %r not found"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:1004
+#, python-format
+msgid "Attendee %r not found in LDAP"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:1008
+#, python-format
+msgid ""
+"\n"
+" %(name)s has %(status)s your invitation for %(summary)s.\n"
+"\n"
+" *** This is an automated response sent by the Kolab Invitation system ***\n"
+" "
+msgstr ""
+
#. modules.next_module('optout')
#: ../wallace/module_optout.py:70
#, python-format
@@ -2176,171 +3187,256 @@ msgstr ""
msgid "Could not send request to optout_url %s"
msgstr ""
-#: ../wallace/module_resources.py:80
+#: ../wallace/module_resources.py:110
#, python-format
msgid "Resource Management called for %r, %r"
msgstr ""
-#: ../wallace/module_resources.py:143
+#: ../wallace/module_resources.py:174
msgid "Message is not an iTip message or does not contain any "
msgstr ""
-#: ../wallace/module_resources.py:151
+#: ../wallace/module_resources.py:182
msgid "iTip events attached to this message contain the "
msgstr ""
-#: ../wallace/module_resources.py:171
+#: ../wallace/module_resources.py:205
msgid "Not an iTip message, but sent to resource nonetheless. Reject message"
msgstr ""
-#: ../wallace/module_resources.py:179
-msgid "No itips, no resources, pass along"
+#: ../wallace/module_resources.py:213
+#, python-format
+msgid "No itips, no resources, pass along %r"
msgstr ""
-#: ../wallace/module_resources.py:183
-msgid "iTips, but no resources, pass along"
+#: ../wallace/module_resources.py:216
+#, python-format
+msgid "iTips, but no resources, pass along %r"
msgstr ""
-#: ../wallace/module_resources.py:215
+#: ../wallace/module_resources.py:225
#, python-format
-msgid "Resources: %r"
+msgid "No resource attendees matching envelope recipient %s, Reject message"
msgstr ""
-#: ../wallace/module_resources.py:233
+#: ../wallace/module_resources.py:234
#, python-format
-msgid "Checking events in resource folder %r"
+msgid "Resources: %r; %r"
msgstr ""
-#: ../wallace/module_resources.py:240
+#: ../wallace/module_resources.py:244
#, python-format
-msgid "Mailbox for resource %r doesn't exist"
+msgid "Receiving Resource: %r; %r"
msgstr ""
-#: ../wallace/module_resources.py:253
+#: ../wallace/module_resources.py:252
#, python-format
-msgid "Fetching message UID %r from folder %r"
+msgid "Recipient %r is non-participant, ignoring message"
msgstr ""
-#: ../wallace/module_resources.py:292
+#: ../wallace/module_resources.py:279
#, python-format
-msgid "Event %r conflicts with event "
+msgid "Accept invitation for individual resource %r / %r"
msgstr ""
-#: ../wallace/module_resources.py:305
+#: ../wallace/module_resources.py:308
#, python-format
-msgid "start: %r, end: %r, total: %r, messages: %r"
+msgid "Delegate invitation for resource collection %r to %r"
msgstr ""
-#: ../wallace/module_resources.py:312
+#: ../wallace/module_resources.py:340
+#, python-format
+msgid "Failed to read resource calendar for %r: %r"
+msgstr ""
+
+#: ../wallace/module_resources.py:350
#, python-format
msgid "Polling for resource %r"
msgstr ""
-#: ../wallace/module_resources.py:316
+#: ../wallace/module_resources.py:353
#, python-format
msgid "Resource %r has been popped from the list"
msgstr ""
-#: ../wallace/module_resources.py:323
+#: ../wallace/module_resources.py:357
msgid "Resource is a collection"
msgstr ""
-#: ../wallace/module_resources.py:371 ../wallace/module_resources.py:421
+#: ../wallace/module_resources.py:368
#, python-format
-msgid "Adding event to %r"
+msgid "Removed conflicting resources from %r: (%r) => %r"
msgstr ""
-#: ../wallace/module_resources.py:470
+#: ../wallace/module_resources.py:380
#, python-format
-msgid "Method %r not really interesting for us."
+msgid "Conflicting events: %r for resource %r"
msgstr ""
-#: ../wallace/module_resources.py:478
+#: ../wallace/module_resources.py:397
#, python-format
-msgid "Raw iTip payload: %s"
+msgid "Delegate to another resource collection member: %r to %r"
msgstr ""
-#: ../wallace/module_resources.py:488
-msgid "Could not read iTip from message."
+#: ../wallace/module_resources.py:459
+#, python-format
+msgid "Checking events in resource folder %r"
msgstr ""
-#: ../wallace/module_resources.py:510
-msgid "iTip event without a start"
+#: ../wallace/module_resources.py:475
+#, python-format
+msgid "Fetching message UID %r from folder %r"
msgstr ""
-#. end if c.name == "VEVENT"
-#. end for c in cal.walk()
-#. end if part.get_content_type() == "text/calendar"
-#. end for part in message.walk()
-#. if message.is_multipart()
-#: ../wallace/module_resources.py:540
-msgid "Message is not an iTip message (non-multipart message)"
+#: ../wallace/module_resources.py:498
+#, python-format
+msgid "Event %r conflicts with event %r"
msgstr ""
-#: ../wallace/module_resources.py:561
+#: ../wallace/module_resources.py:525
#, python-format
-msgid "Checking if email address %r belongs to a resource (collection)"
+msgid "Adding event to %r: %r"
msgstr ""
-#: ../wallace/module_resources.py:572 ../wallace/module_resources.py:646
-#: ../wallace/module_resources.py:696
+#: ../wallace/module_resources.py:573
#, python-format
-msgid "No resource (collection) records found for %r"
+msgid "Failed to save event to resource calendar at %r: %r"
+msgstr ""
+
+#: ../wallace/module_resources.py:590
+#, python-format
+msgid "Delete resource calendar object %r in %r: %r"
+msgstr ""
+
+#: ../wallace/module_resources.py:633
+#, python-format
+msgid "Checking if email address %r belongs to a resource (collection)"
msgstr ""
-#: ../wallace/module_resources.py:580 ../wallace/module_resources.py:654
-#: ../wallace/module_resources.py:704
+#: ../wallace/module_resources.py:641 ../wallace/module_resources.py:709
+#: ../wallace/module_resources.py:743
#, python-format
msgid "Resource record(s): %r"
msgstr ""
-#: ../wallace/module_resources.py:586 ../wallace/module_resources.py:661
-#: ../wallace/module_resources.py:711
+#: ../wallace/module_resources.py:643 ../wallace/module_resources.py:711
+#: ../wallace/module_resources.py:746
+#, python-format
+msgid "No resource (collection) records found for %r"
+msgstr ""
+
+#: ../wallace/module_resources.py:647 ../wallace/module_resources.py:715
+#: ../wallace/module_resources.py:750
#, python-format
msgid "Resource record: %r"
msgstr ""
-#: ../wallace/module_resources.py:605
+#: ../wallace/module_resources.py:667
#, python-format
msgid "Raw itip_events: %r"
msgstr ""
-#: ../wallace/module_resources.py:613
+#: ../wallace/module_resources.py:675
#, python-format
msgid "Raw set of attendees: %r"
msgstr ""
-#: ../wallace/module_resources.py:621
+#: ../wallace/module_resources.py:683
#, python-format
msgid "Raw set of resources: %r"
msgstr ""
-#: ../wallace/module_resources.py:635
+#: ../wallace/module_resources.py:702
#, python-format
msgid "Checking if attendee %r is a resource (collection)"
msgstr ""
-#: ../wallace/module_resources.py:668 ../wallace/module_resources.py:714
+#: ../wallace/module_resources.py:718 ../wallace/module_resources.py:752
msgid "Resource reservation made but no resource records found"
msgstr ""
-#: ../wallace/module_resources.py:686
+#: ../wallace/module_resources.py:737
#, python-format
msgid "Checking if resource %r is a resource (collection)"
msgstr ""
-#: ../wallace/module_resources.py:718
+#: ../wallace/module_resources.py:755
msgid "The following resources are being referred to in the "
msgstr ""
+#: ../wallace/module_resources.py:894
+#, python-format
+msgid ""
+"\n"
+" *** This is an automated response, please do not reply! ***\n"
+"\n"
+" Your reservation was delegated to \"%s\" which is available for the requested time.\n"
+" "
+msgstr ""
+
+#: ../wallace/module_resources.py:905
+#, python-format
+msgid ""
+"\n"
+" *** This is an automated response, please do not reply! ***\n"
+" \n"
+" We hereby inform you that your reservation was %s.\n"
+" "
+msgstr ""
+
+#: ../wallace/module_resources.py:912
+#, python-format
+msgid ""
+"\n"
+" If you have questions about this reservation, please contact\n"
+" %s <%s> %s\n"
+" "
+msgstr ""
+
+#: ../wallace/module_resources.py:941
+#, python-format
+msgid "Sending booking notification for event %r to %r from %r"
+msgstr ""
+
+#: ../wallace/module_resources.py:954
+msgid "failed"
+msgstr ""
+
+#: ../wallace/module_resources.py:973
+#, python-format
+msgid ""
+"\n"
+" The resource booking for %(resource)s by %(orgname)s <%(orgemail)s> has been %(status)s for %(date)s.\n"
+"\n"
+" *** This is an automated message, sent to you as the resource owner. ***\n"
+" "
+msgstr ""
+
+#: ../wallace/module_resources.py:979
+#, python-format
+msgid ""
+"\n"
+" A reservation request for %(resource)s could not be processed automatically.\n"
+" Please contact %(orgname)s <%(orgemail)s> who requested this resource for %(date)s. Subject: %(summary)s.\n"
+"\n"
+" *** This is an automated message, sent to you as the resource owner. ***\n"
+" "
+msgstr ""
+
#. This is a nested module
#: ../wallace/modules.py:97
#, python-format
msgid "Module Group: %s"
msgstr ""
-#: ../wallace/modules.py:108 ../wallace/modules.py:113
-msgid "No such module."
+#: ../wallace/modules.py:108
+#, python-format
+msgid "No such module %r in modules %r (1)."
+msgstr ""
+
+#: ../wallace/modules.py:113
+#, python-format
+msgid "No such module %r in modules %r (2)."
msgstr ""
#: ../wallace/modules.py:119
@@ -2353,33 +3449,33 @@ msgstr ""
msgid "Deferring message in %s (by module %s)"
msgstr ""
-#: ../wallace/modules.py:133
+#: ../wallace/modules.py:134
#, python-format
msgid "The time when the message was sent: %r"
msgstr ""
-#: ../wallace/modules.py:134
+#: ../wallace/modules.py:135
#, python-format
msgid "The time now: %r"
msgstr ""
-#: ../wallace/modules.py:135
+#: ../wallace/modules.py:136
#, python-format
msgid "The time delta: %r"
msgstr ""
#. TODO: Send NDR back to user
-#: ../wallace/modules.py:139
+#: ../wallace/modules.py:140
#, python-format
msgid "Message in file %s older then 5 days, deleting"
msgstr ""
-#: ../wallace/modules.py:164
+#: ../wallace/modules.py:165
#, python-format
msgid "Rejecting message in %s (by module %s)"
msgstr ""
-#: ../wallace/modules.py:185
+#: ../wallace/modules.py:186
#, python-format
msgid ""
"This is the email system Wallace at %s.\n"
@@ -2394,19 +3490,29 @@ msgid ""
"recipients.\n"
msgstr ""
-#: ../wallace/modules.py:200
+#: ../wallace/modules.py:201
#, python-format
msgid ""
"X-Wallace-Module: %s\n"
"X-Wallace-Result: REJECT\n"
msgstr ""
-#: ../wallace/modules.py:253
+#: ../wallace/modules.py:260
#, python-format
msgid "Accepting message in %s (by module %s)"
msgstr ""
-#: ../wallace/modules.py:326
+#: ../wallace/modules.py:262
+#, python-format
+msgid "Accepting message in: %r"
+msgstr ""
+
+#: ../wallace/modules.py:269
+#, python-format
+msgid "recipients: %r"
+msgstr ""
+
+#: ../wallace/modules.py:347
#, python-format
msgid "Module '%s' already registered"
msgstr ""
diff --git a/po/pykolab.pot b/po/pykolab.pot
index c3c276a..389ca9b 100644
--- a/po/pykolab.pot
+++ b/po/pykolab.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-02-21 02:15-0500\n"
+"POT-Creation-Date: 2014-07-10 07:21-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -291,66 +291,66 @@ msgstr ""
msgid "No objections"
msgstr ""
-#: ../conf.py:37 ../kolab.py:34 ../saslauthd.py:33
+#: ../conf.py:37 ../kolab-cli.py:34 ../saslauthd.py:33
msgid "Cannot load pykolab/logger.py:"
msgstr ""
#: ../kolabd/__init__.py:49 ../saslauthd/__init__.py:51
-#: ../wallace/__init__.py:68
+#: ../wallace/__init__.py:85
msgid "Daemon Options"
msgstr ""
#: ../kolabd/__init__.py:56 ../saslauthd/__init__.py:58
-#: ../wallace/__init__.py:75
+#: ../wallace/__init__.py:92
msgid "Fork to the background."
msgstr ""
#: ../kolabd/__init__.py:65 ../saslauthd/__init__.py:67
-#: ../wallace/__init__.py:101
+#: ../wallace/__init__.py:118
msgid "Path to the PID file to use."
msgstr ""
#: ../kolabd/__init__.py:74 ../saslauthd/__init__.py:76
-#: ../wallace/__init__.py:118
+#: ../wallace/__init__.py:135
msgid "Run as user USERNAME"
msgstr ""
#: ../kolabd/__init__.py:84 ../saslauthd/__init__.py:86
-#: ../wallace/__init__.py:92
+#: ../wallace/__init__.py:109
msgid "Run as group GROUPNAME"
msgstr ""
#: ../kolabd/__init__.py:122 ../pykolab/logger.py:139 ../pykolab/utils.py:234
-#: ../saslauthd/__init__.py:292 ../wallace/__init__.py:312
+#: ../saslauthd/__init__.py:292 ../wallace/__init__.py:329
#, python-format
msgid "Group %s does not exist"
msgstr ""
#: ../kolabd/__init__.py:131 ../saslauthd/__init__.py:301
-#: ../wallace/__init__.py:321
+#: ../wallace/__init__.py:338
#, python-format
msgid "Switching real and effective group id to %d"
msgstr ""
#: ../kolabd/__init__.py:153 ../pykolab/logger.py:159 ../pykolab/utils.py:258
-#: ../saslauthd/__init__.py:323 ../wallace/__init__.py:343
+#: ../saslauthd/__init__.py:323 ../wallace/__init__.py:360
#, python-format
msgid "User %s does not exist"
msgstr ""
#: ../kolabd/__init__.py:163 ../saslauthd/__init__.py:333
-#: ../wallace/__init__.py:353
+#: ../wallace/__init__.py:370
#, python-format
msgid "Switching real and effective user id to %d"
msgstr ""
#: ../kolabd/__init__.py:172 ../saslauthd/__init__.py:342
-#: ../wallace/__init__.py:362
+#: ../wallace/__init__.py:379
msgid "Could not change real and effective uid and/or gid"
msgstr ""
#: ../kolabd/__init__.py:192 ../saslauthd/__init__.py:133
-#: ../wallace/__init__.py:382
+#: ../wallace/__init__.py:399
msgid "Interrupted by user"
msgstr ""
@@ -359,7 +359,7 @@ msgid "Traceback occurred, please report a "
msgstr ""
#: ../kolabd/__init__.py:203 ../saslauthd/__init__.py:141
-#: ../wallace/__init__.py:391
+#: ../wallace/__init__.py:408
#, python-format
msgid "Type Error: %s"
msgstr ""
@@ -368,7 +368,7 @@ msgstr ""
msgid "Could not connect to LDAP, is it running?"
msgstr ""
-#: ../kolabd/__init__.py:233 ../pykolab/auth/ldap/__init__.py:2110
+#: ../kolabd/__init__.py:233 ../pykolab/auth/ldap/__init__.py:2137
#: ../pykolab/cli/cmd_sync.py:36
msgid "Listing domains..."
msgstr ""
@@ -442,7 +442,7 @@ msgstr ""
msgid "Updating timestamp for cache entry %r"
msgstr ""
-#: ../pykolab/auth/ldap/cache.py:154
+#: ../pykolab/auth/ldap/cache.py:155
#, python-format
msgid "Updating result_attribute for cache entry %r"
msgstr ""
@@ -668,95 +668,95 @@ msgstr ""
msgid "Invalid DN, username and/or password."
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1234 ../pykolab/auth/ldap/__init__.py:1247
-#: ../pykolab/auth/ldap/__init__.py:1603 ../pykolab/auth/ldap/__init__.py:1616
+#: ../pykolab/auth/ldap/__init__.py:1236 ../pykolab/auth/ldap/__init__.py:1249
+#: ../pykolab/auth/ldap/__init__.py:1614 ../pykolab/auth/ldap/__init__.py:1627
#, python-format
msgid "Found a subject %r with access %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1354
+#: ../pykolab/auth/ldap/__init__.py:1356
#, python-format
msgid "Entry %s attribute value: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1362
+#: ../pykolab/auth/ldap/__init__.py:1364
#, python-format
msgid "imap.user_mailbox_server(%r) result: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1673 ../pykolab/auth/ldap/__init__.py:1830
+#: ../pykolab/auth/ldap/__init__.py:1684 ../pykolab/auth/ldap/__init__.py:1853
#, python-format
msgid "Result from recipient policy: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:1885
+#: ../pykolab/auth/ldap/__init__.py:1908
#, python-format
msgid "Kolab user %s does not have a result attribute %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2040
+#: ../pykolab/auth/ldap/__init__.py:2067
#, python-format
msgid "Finding domain root dn for domain %s"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2137
+#: ../pykolab/auth/ldap/__init__.py:2164
msgid "Authentication database DOWN"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2221 ../pykolab/auth/ldap/__init__.py:2269
+#: ../pykolab/auth/ldap/__init__.py:2248 ../pykolab/auth/ldap/__init__.py:2296
#, python-format
msgid "Entry type: %s"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2294
+#: ../pykolab/auth/ldap/__init__.py:2321
#, python-format
msgid "Done with _synchronize_callback() for entry %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2366
+#: ../pykolab/auth/ldap/__init__.py:2393
msgid "LDAP Search Result Data Entry:"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2382
+#: ../pykolab/auth/ldap/__init__.py:2409
msgid "Entry Change Notification attributes:"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2387
+#: ../pykolab/auth/ldap/__init__.py:2414
#, python-format
msgid "Change Type: %r (%r)"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2395
+#: ../pykolab/auth/ldap/__init__.py:2422
#, python-format
msgid "Previous DN: %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2450
+#: ../pykolab/auth/ldap/__init__.py:2477
#, python-format
msgid "Object %s searched no longer exists"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2460
+#: ../pykolab/auth/ldap/__init__.py:2487
#, python-format
msgid "%d results..."
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2563
+#: ../pykolab/auth/ldap/__init__.py:2590
#, python-format
msgid "Searching with filter %r"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2615
+#: ../pykolab/auth/ldap/__init__.py:2642
#, python-format
msgid "Checking for support for %s on %s"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2634
+#: ../pykolab/auth/ldap/__init__.py:2661
#, python-format
msgid "Found support for %s"
msgstr ""
-#: ../pykolab/auth/ldap/__init__.py:2679
+#: ../pykolab/auth/ldap/__init__.py:2706
#, python-format
msgid "An error occured using %s: %r"
msgstr ""
@@ -916,7 +916,10 @@ msgstr ""
#: ../pykolab/cli/cmd_list_mailbox_metadata.py:54
#: ../pykolab/cli/cmd_set_mailbox_acl.py:54
#: ../pykolab/cli/cmd_set_mailbox_metadata.py:66
-#: ../pykolab/cli/cmd_set_quota.py:46
+#: ../pykolab/cli/cmd_set_quota.py:46 ../tests/unit/test-015-translate.py:12
+#: ../tests/unit/test-015-translate.py:16
+#: ../tests/unit/test-015-translate.py:18
+#: ../tests/unit/test-015-translate.py:20
msgid "Folder name"
msgstr ""
@@ -1063,7 +1066,7 @@ msgstr ""
#: ../pykolab/cli/cmd_remove_user_subscription.py:92
#, python-format
-msgid "User %s not be unsubscribed from any folders."
+msgid "User %s was not unsubscribed from any folders."
msgstr ""
#: ../pykolab/cli/cmd_rename_mailbox.py:52
@@ -1432,76 +1435,76 @@ msgstr ""
msgid "No command supplied"
msgstr ""
-#: ../pykolab/conf/__init__.py:411
+#: ../pykolab/conf/__init__.py:416
msgid "Insufficient options. Need section, key and value -in that order."
msgstr ""
-#: ../pykolab/conf/__init__.py:414
+#: ../pykolab/conf/__init__.py:419
#, python-format
msgid "No section '%s' exists."
msgstr ""
-#: ../pykolab/conf/__init__.py:445
+#: ../pykolab/conf/__init__.py:461
#, python-format
msgid "Setting %s to %r (from the default values for CLI options)"
msgstr ""
-#: ../pykolab/conf/__init__.py:518
+#: ../pykolab/conf/__init__.py:534
#, python-format
msgid "Could not execute configuration function: %s"
msgstr ""
-#: ../pykolab/conf/__init__.py:526
+#: ../pykolab/conf/__init__.py:542
#, python-format
msgid "Option %s/%s does not exist in config file %s, pulling from defaults"
msgstr ""
-#: ../pykolab/conf/__init__.py:534 ../pykolab/conf/__init__.py:537
+#: ../pykolab/conf/__init__.py:550 ../pykolab/conf/__init__.py:553
msgid "Option does not exist in defaults."
msgstr ""
-#: ../pykolab/conf/__init__.py:547
+#: ../pykolab/conf/__init__.py:563
#, python-format
msgid "Configuration file %s not readable."
msgstr ""
-#: ../pykolab/conf/__init__.py:550
+#: ../pykolab/conf/__init__.py:566
#, python-format
msgid "Configuration file %s does not exist."
msgstr ""
-#: ../pykolab/conf/__init__.py:555
+#: ../pykolab/conf/__init__.py:571
msgid ""
"WARNING: A negative debug level value does not make this program be any more "
"silent."
msgstr ""
-#: ../pykolab/conf/__init__.py:561
+#: ../pykolab/conf/__init__.py:577
msgid "This program has 9 levels of verbosity. Using the maximum of 9."
msgstr ""
-#: ../pykolab/conf/__init__.py:569 ../pykolab/conf/__init__.py:575
+#: ../pykolab/conf/__init__.py:585 ../pykolab/conf/__init__.py:591
msgid "Cannot start SASL authentication daemon"
msgstr ""
-#: ../pykolab/conf/__init__.py:586
+#: ../pykolab/conf/__init__.py:602
msgid "No imaplib library found."
msgstr ""
-#: ../pykolab/conf/__init__.py:596
+#: ../pykolab/conf/__init__.py:612
msgid "No LMTP class found in the smtplib library."
msgstr ""
-#: ../pykolab/conf/__init__.py:606
+#: ../pykolab/conf/__init__.py:622
msgid "No SMTP class found in the smtplib library."
msgstr ""
-#: ../pykolab/conf/__init__.py:620
+#: ../pykolab/conf/__init__.py:636
#, python-format
msgid "Found you specified a specific set of items to test: %s"
msgstr ""
-#: ../pykolab/conf/__init__.py:628
+#: ../pykolab/conf/__init__.py:644
#, python-format
msgid "Selectively selecting: %s"
msgstr ""
@@ -1686,169 +1689,174 @@ msgstr ""
msgid "%r has no attribute %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:390 ../pykolab/imap/__init__.py:425
+#: ../pykolab/imap/__init__.py:393 ../pykolab/imap/__init__.py:428
#, python-format
msgid "Creating new shared folder %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:450 ../pykolab/imap/__init__.py:672
+#: ../pykolab/imap/__init__.py:453 ../pykolab/imap/__init__.py:675
#, python-format
msgid "Downcasing mailbox name %r"
msgstr ""
-#: ../pykolab/imap/__init__.py:454
+#: ../pykolab/imap/__init__.py:457
#, python-format
msgid "Creating new mailbox for user %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:467
+#: ../pykolab/imap/__init__.py:470
msgid "Waiting for the Cyrus IMAP Murder to settle..."
msgstr ""
-#: ../pykolab/imap/__init__.py:513
+#: ../pykolab/imap/__init__.py:516
#, python-format
msgid "Creating additional folders for user %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:532
+#: ../pykolab/imap/__init__.py:535
#, python-format
msgid "Waiting for the Cyrus murder to settle... %r"
msgstr ""
-#: ../pykolab/imap/__init__.py:544
+#: ../pykolab/imap/__init__.py:547
#, python-format
msgid "Correcting additional folder name from %r to %r"
msgstr ""
-#: ../pykolab/imap/__init__.py:550
+#: ../pykolab/imap/__init__.py:553
#, python-format
msgid "Mailbox already exists: %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:590
+#: ../pykolab/imap/__init__.py:593
msgid "Subscribing user to the additional folders"
msgstr ""
-#: ../pykolab/imap/__init__.py:604
+#: ../pykolab/imap/__init__.py:607
msgid "Using the following tests for folder subscriptions:"
msgstr ""
-#: ../pykolab/imap/__init__.py:606
+#: ../pykolab/imap/__init__.py:609
#, python-format
msgid " %r"
msgstr ""
-#: ../pykolab/imap/__init__.py:609
+#: ../pykolab/imap/__init__.py:612
#, python-format
msgid "Folder %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:621
+#: ../pykolab/imap/__init__.py:624
#, python-format
msgid "Subscribing %s to folder %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:625
+#: ../pykolab/imap/__init__.py:628
#, python-format
msgid "Subscribing %s to folder %s failed: %r"
msgstr ""
-#: ../pykolab/imap/__init__.py:655
+#: ../pykolab/imap/__init__.py:658
#, python-format
msgid "Could not rename %s to reside on partition %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:688 ../pykolab/imap/__init__.py:764
+#: ../pykolab/imap/__init__.py:691
+#, python-format
+msgid "INBOX folder to rename (%s) does not exist"
+msgstr ""
+
+#: ../pykolab/imap/__init__.py:694 ../pykolab/imap/__init__.py:770
#, python-format
msgid "Renaming INBOX from %s to %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:692
+#: ../pykolab/imap/__init__.py:698
#, python-format
msgid "Could not rename INBOX folder %s to %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:694 ../pykolab/imap/__init__.py:768
+#: ../pykolab/imap/__init__.py:700 ../pykolab/imap/__init__.py:774
#, python-format
msgid "Moving INBOX folder %s won't succeed as target folder %s already exists"
msgstr ""
-#: ../pykolab/imap/__init__.py:698
+#: ../pykolab/imap/__init__.py:704
#, python-format
msgid "Server for mailbox %r is %r"
msgstr ""
-#: ../pykolab/imap/__init__.py:706
+#: ../pykolab/imap/__init__.py:712
#, python-format
msgid "Looking for folder '%s', we found folders: %r"
msgstr ""
-#: ../pykolab/imap/__init__.py:729
+#: ../pykolab/imap/__init__.py:735
#, python-format
msgid "Setting ACL rights %s for subject %s on folder "
msgstr ""
-#: ../pykolab/imap/__init__.py:740
+#: ../pykolab/imap/__init__.py:746
#, python-format
msgid "Removing ACL rights %s for subject %s on folder "
msgstr ""
-#: ../pykolab/imap/__init__.py:761
+#: ../pykolab/imap/__init__.py:767
#, python-format
msgid "Found old INBOX folder %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:770
+#: ../pykolab/imap/__init__.py:776
#, python-format
msgid "Did not find old folder user/%s to rename"
msgstr ""
-#: ../pykolab/imap/__init__.py:772
+#: ../pykolab/imap/__init__.py:778
msgid "Value for user is not a dictionary"
msgstr ""
#. TODO: Go in fact correct the quota.
-#: ../pykolab/imap/__init__.py:840
+#: ../pykolab/imap/__init__.py:846
#, python-format
msgid "Cannot get current IMAP quota for folder %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:853
+#: ../pykolab/imap/__init__.py:859
#, python-format
msgid "Quota for %s currently is %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:859
+#: ../pykolab/imap/__init__.py:865
#, python-format
msgid "Adjusting authentication database quota for folder %s to %d"
msgstr ""
-#: ../pykolab/imap/__init__.py:864
+#: ../pykolab/imap/__init__.py:870
#, python-format
msgid "Correcting quota for %s to %s (currently %s)"
msgstr ""
-#: ../pykolab/imap/__init__.py:941
+#: ../pykolab/imap/__init__.py:947
#, python-format
msgid "Checking folder: %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:946
+#: ../pykolab/imap/__init__.py:952
#, python-format
msgid "Folder has no corresponding user (1): %s"
msgstr ""
-#: ../pykolab/imap/__init__.py:949
+#: ../pykolab/imap/__init__.py:955
#, python-format
msgid "Folder has no corresponding user (2): %s"
msgstr ""
#. We got user identifier only
-#: ../pykolab/imap/__init__.py:964
+#: ../pykolab/imap/__init__.py:970
msgid "Please don't give us just a user identifier"
msgstr ""
-#: ../pykolab/imap/__init__.py:967
+#: ../pykolab/imap/__init__.py:973
#, python-format
msgid "Deleting folder %s"
msgstr ""
@@ -1857,6 +1865,44 @@ msgstr ""
msgid "Returning thread local configuration"
msgstr ""
+#: ../pykolab/itip/__init__.py:43
+#, python-format
+msgid "Method %r not really interesting for us."
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:49
+#, python-format
+msgid "Raw iTip payload: %s"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:59
+msgid "Could not read iTip from message."
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:67
+#, python-format
+msgid "Duplicate iTip object: %s"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:90
+msgid "iTip event without a start"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:132
+msgid "Message is not an iTip message (non-multipart message)"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:225
+#, python-format
+msgid "Failed to compose iTip reply message: %r"
+msgstr ""
+
+#: ../pykolab/itip/__init__.py:236 ../wallace/module_invitationpolicy.py:936
+#: ../wallace/module_resources.py:964
+#, python-format
+msgid "SMTP sendmail error: %r"
+msgstr ""
+
#: ../pykolab/logger.py:173 ../pykolab/logger.py:179
#, python-format
msgid "Could not change permissions on %s: %r"
@@ -1991,6 +2037,16 @@ msgid ""
"Attribute substitution for 'alternative_mail' failed in Recipient Policy"
msgstr ""
+#: ../pykolab/plugins/roundcubedb/__init__.py:48
+#, python-format
+msgid "user_delete: %r"
+msgstr ""
+
+#: ../pykolab/plugins/roundcubedb/__init__.py:55
+#: ../pykolab/setup/setup_roundcube.py:160
+msgid "Roundcube installation path not found."
+msgstr ""
+
#: ../pykolab/plugins/sievemgmt/__init__.py:51
msgid "Wrong number of arguments for sieve management plugin"
msgstr ""
@@ -2007,11 +2063,11 @@ msgstr ""
msgid "No such component."
msgstr ""
-#: ../pykolab/setup/setup_freebusy.py:45
+#: ../pykolab/setup/setup_freebusy.py:46
msgid "Setup Free/Busy."
msgstr ""
-#: ../pykolab/setup/setup_freebusy.py:49
+#: ../pykolab/setup/setup_freebusy.py:50
msgid "Free/Busy is not installed on this system"
msgstr ""
@@ -2033,7 +2089,7 @@ msgstr ""
#: ../pykolab/setup/setup_imap.py:173 ../pykolab/setup/setup_kolabd.py:81
#: ../pykolab/setup/setup_ldap.py:426 ../pykolab/setup/setup_mta.py:455
-#: ../pykolab/setup/setup_mysql.py:58 ../pykolab/setup/setup_roundcube.py:234
+#: ../pykolab/setup/setup_mysql.py:58 ../pykolab/setup/setup_roundcube.py:237
#: ../pykolab/setup/setup_syncroton.py:102
msgid "Could not configure to start on boot, the "
msgstr ""
@@ -2371,10 +2427,6 @@ msgstr ""
msgid "Could not start the MySQL database service."
msgstr ""
-#. Regular old-fashioned Enterprise Linux
-#. Debian
-#. (open)SUSE
-#. "Unbreakable" Linux from Oracle
#: ../pykolab/setup/setup_mysql.py:71
msgid "What MySQL server are we setting up?"
msgstr ""
@@ -2390,7 +2442,7 @@ msgid ""
msgstr ""
#: ../pykolab/setup/setup_mysql.py:82 ../pykolab/setup/setup_mysql.py:99
-#: ../pykolab/setup/setup_roundcube.py:180
+#: ../pykolab/setup/setup_roundcube.py:183
#: ../pykolab/setup/setup_syncroton.py:63
msgid "MySQL root password"
msgstr ""
@@ -2486,21 +2538,17 @@ msgstr ""
msgid "MySQL roundcube password"
msgstr ""
-#: ../pykolab/setup/setup_roundcube.py:117
+#: ../pykolab/setup/setup_roundcube.py:120
#, python-format
msgid "Using template file %r"
msgstr ""
-#: ../pykolab/setup/setup_roundcube.py:124
+#: ../pykolab/setup/setup_roundcube.py:127
#, python-format
msgid "Successfully compiled template %r, writing out to %r"
msgstr ""
-#: ../pykolab/setup/setup_roundcube.py:157
-msgid "Roundcube installation path not found."
-msgstr ""
-
-#: ../pykolab/setup/setup_roundcube.py:225
+#: ../pykolab/setup/setup_roundcube.py:228
#: ../pykolab/setup/setup_syncroton.py:93
msgid "Could not start the webserver server service."
msgstr ""
@@ -2570,29 +2618,29 @@ msgstr ""
msgid "Could not change the permissions on %s"
msgstr ""
-#: ../pykolab/utils.py:476
+#: ../pykolab/utils.py:479
#, python-format
msgid "Transliterating string %r with locale %r"
msgstr ""
-#: ../pykolab/utils.py:484
+#: ../pykolab/utils.py:487
msgid "Attempting to set locale"
msgstr ""
-#: ../pykolab/utils.py:486
+#: ../pykolab/utils.py:489
msgid "Success setting locale"
msgstr ""
-#: ../pykolab/utils.py:488
+#: ../pykolab/utils.py:491
msgid "Failure to set locale"
msgstr ""
-#: ../pykolab/utils.py:496
+#: ../pykolab/utils.py:499
#, python-format
msgid "Executing '%s | %s'"
msgstr ""
-#: ../pykolab/utils.py:507
+#: ../pykolab/utils.py:510
#, python-format
msgid "Could not translate %s using locale %s"
msgstr ""
@@ -2608,90 +2656,124 @@ msgid "Got response: %r"
msgstr ""
#. Some data is not JSON
-#: ../pykolab/wap_client/__init__.py:333
+#: ../pykolab/wap_client/__init__.py:334
msgid "Response data is not JSON"
msgstr ""
-#: ../pykolab/xml/attendee.py:86 ../pykolab/xml/attendee.py:108
+#. support integer values, too
+#: ../pykolab/xml/attendee.py:9 ../pykolab/xml/attendee.py:17
+msgid "Needs Action"
+msgstr ""
+
+#: ../pykolab/xml/attendee.py:10 ../pykolab/xml/attendee.py:18
+msgid "Accepted"
+msgstr ""
+
+#: ../pykolab/xml/attendee.py:11 ../pykolab/xml/attendee.py:19
+msgid "Declined"
+msgstr ""
+
+#: ../pykolab/xml/attendee.py:12 ../pykolab/xml/attendee.py:20
+msgid "Tentatively Accepted"
+msgstr ""
+
+#: ../pykolab/xml/attendee.py:13 ../pykolab/xml/attendee.py:21
+msgid "Delegated"
+msgstr ""
+
+#: ../pykolab/xml/attendee.py:14
+msgid "Completed"
+msgstr ""
+
+#: ../pykolab/xml/attendee.py:15
+msgid "In Process"
+msgstr ""
+
+#: ../pykolab/xml/attendee.py:108 ../pykolab/xml/attendee.py:130
msgid "Not a valid attendee"
msgstr ""
-#: ../pykolab/xml/attendee.py:93
+#: ../pykolab/xml/attendee.py:115
msgid "No valid delegator references found"
msgstr ""
-#: ../pykolab/xml/attendee.py:113
+#: ../pykolab/xml/attendee.py:135
msgid "No valid delegatee references found"
msgstr ""
-#: ../pykolab/xml/attendee.py:149
+#: ../pykolab/xml/attendee.py:180
#, python-format
msgid "Invalid cutype %r"
msgstr ""
-#: ../pykolab/xml/attendee.py:160
+#: ../pykolab/xml/attendee.py:192
#, python-format
msgid "Invalid participant status %r"
msgstr ""
-#: ../pykolab/xml/attendee.py:168
+#: ../pykolab/xml/attendee.py:200
#, python-format
msgid "Invalid role %r"
msgstr ""
-#: ../pykolab/xml/event.py:70 ../pykolab/xml/event.py:570
-#: ../pykolab/xml/event.py:606
+#: ../pykolab/xml/event.py:100 ../pykolab/xml/event.py:708
+#: ../pykolab/xml/event.py:751
msgid "Event start needs datetime.date or datetime.datetime instance"
msgstr ""
-#: ../pykolab/xml/event.py:198
+#: ../pykolab/xml/event.py:241
#, python-format
msgid "No attendee with email or name %r"
msgstr ""
-#: ../pykolab/xml/event.py:206
+#: ../pykolab/xml/event.py:249
#, python-format
msgid "Invalid argument value attendee %r, must be basestring or Attendee"
msgstr ""
-#: ../pykolab/xml/event.py:212
+#: ../pykolab/xml/event.py:255
#, python-format
msgid "No attendee with email %r"
msgstr ""
-#: ../pykolab/xml/event.py:218
+#: ../pykolab/xml/event.py:261
#, python-format
msgid "No attendee with name %r"
msgstr ""
-#: ../pykolab/xml/event.py:323
+#: ../pykolab/xml/event.py:426
msgid "Invalid participant status"
msgstr ""
-#: ../pykolab/xml/event.py:445
-msgid "Event end needs datetime.date or datetime.datetime instance"
+#: ../pykolab/xml/event.py:542
+#, python-format
+msgid "Invalid status %r"
msgstr ""
-#: ../pykolab/xml/event.py:549
+#: ../pykolab/xml/event.py:550
#, python-format
-msgid "Invalid status %r"
+msgid "Invalid classification %r"
msgstr ""
-#: ../pykolab/xml/event.py:616
+#: ../pykolab/xml/event.py:577
+msgid "Event end needs datetime.date or datetime.datetime instance"
+msgstr ""
+
+#: ../pykolab/xml/event.py:761
#, python-format
msgid "Invalid status set: %r"
msgstr ""
-#: ../pykolab/xml/event.py:735
+#: ../pykolab/xml/event.py:923
msgid "No sender specified"
msgstr ""
-#: ../pykolab/xml/event.py:744
+#: ../pykolab/xml/event.py:932
#, python-format
-msgid "Reservation Request for %s was %s"
+msgid "Invitation for %s was %s"
msgstr ""
-#: ../pykolab/xml/event.py:749
+#: ../pykolab/xml/event.py:937
msgid "This is an automated response to one of your event requests."
msgstr ""
@@ -2701,7 +2783,7 @@ msgid "Could not create %r: %r"
msgstr ""
#: ../saslauthd/__init__.py:137 ../saslauthd/__init__.py:145
-#: ../wallace/__init__.py:386 ../wallace/__init__.py:395
+#: ../wallace/__init__.py:403 ../wallace/__init__.py:412
msgid "Traceback occurred, please report a bug at http://bugzilla.kolabsys.com"
msgstr ""
@@ -2713,51 +2795,101 @@ msgstr ""
msgid "Maximum tries exceeded, exiting"
msgstr ""
-#: ../wallace/__init__.py:61
+#: ../tests/functional/test_wallace/test_005_resource_invitation.py:190
+#: ../wallace/module_resources.py:879
+#, python-format
+msgid "Reservation Request for %(summary)s was %(status)s"
+msgstr ""
+
+#. check notification message sent to resource owner (jane)
+#: ../tests/functional/test_wallace/test_005_resource_invitation.py:605
+#: ../tests/functional/test_wallace/test_005_resource_invitation.py:621
+#: ../wallace/module_resources.py:954
+#, python-format
+msgid "Booking for %s has been %s"
+msgstr ""
+
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:146
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:720
+#: ../wallace/module_invitationpolicy.py:374
+#, python-format
+msgid "\"%(summary)s\" has been %(status)s"
+msgstr ""
+
+#. check for notification message
+#. this notification should be suppressed until mark has replied, too
+#. this triggers an additional notification
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:616
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:622
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:635
+#: ../wallace/module_invitationpolicy.py:925
+#, python-format
+msgid "\"%s\" has been updated"
+msgstr ""
+
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:627
+#: ../tests/functional/test_wallace/test_007_invitationpolicy.py:639
+msgid "PENDING"
+msgstr ""
+
+#: ../wallace/__init__.py:57
+#, python-format
+msgid "Wallace modules: %r"
+msgstr ""
+
+#: ../wallace/__init__.py:69
+#, python-format
+msgid "Module %s.execute() failed on message %r with error: %s"
+msgstr ""
+
+#: ../wallace/__init__.py:78
#, python-format
msgid "Worker process %s initializing"
msgstr ""
-#: ../wallace/__init__.py:83
+#: ../wallace/__init__.py:100
msgid "Bind address for Wallace."
msgstr ""
-#: ../wallace/__init__.py:109
+#: ../wallace/__init__.py:126
msgid "Port that Wallace is supposed to use."
msgstr ""
-#: ../wallace/__init__.py:160
+#: ../wallace/__init__.py:177
#, python-format
msgid "Could not bind to socket on port %d on bind "
msgstr ""
-#: ../wallace/__init__.py:172
+#: ../wallace/__init__.py:189
msgid "Could not shut down socket"
msgstr ""
-#: ../wallace/__init__.py:236
+#: ../wallace/__init__.py:253
msgid "Accepted connection"
msgstr ""
-#: ../wallace/__init__.py:411
+#: ../wallace/__init__.py:428
#, python-format
msgid "Could not write pid file %s"
msgstr ""
-#: ../wallace/module_footer.py:60 ../wallace/module_optout.py:61
-#: ../wallace/module_resources.py:109
+#: ../wallace/module_footer.py:60 ../wallace/module_gpgencrypt.py:60
+#: ../wallace/module_invitationpolicy.py:168 ../wallace/module_optout.py:61
+#: ../wallace/module_resources.py:120
#, python-format
msgid "Issuing callback after processing to stage %s"
msgstr ""
-#: ../wallace/module_footer.py:61 ../wallace/module_optout.py:62
-#: ../wallace/module_resources.py:115
+#: ../wallace/module_footer.py:61 ../wallace/module_gpgencrypt.py:61
+#: ../wallace/module_invitationpolicy.py:170 ../wallace/module_optout.py:62
+#: ../wallace/module_resources.py:126
#, python-format
msgid "Testing cb_action_%s()"
msgstr ""
-#: ../wallace/module_footer.py:63 ../wallace/module_optout.py:64
-#: ../wallace/module_resources.py:118
+#: ../wallace/module_footer.py:63 ../wallace/module_gpgencrypt.py:63
+#: ../wallace/module_invitationpolicy.py:172 ../wallace/module_optout.py:64
+#: ../wallace/module_resources.py:129
#, python-format
msgid "Attempting to execute cb_action_%s()"
msgstr ""
@@ -2767,6 +2899,299 @@ msgstr ""
msgid "Executing module footer for %r, %r"
msgstr ""
+#: ../wallace/module_gpgencrypt.py:66
+#, python-format
+msgid "Executing module gpgencrypt for %r, %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:98
+msgid "Message is already encrypted (app/pgp-enc content-type)"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:102
+msgid "Message already encrypted by main content-type header"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:131
+msgid ""
+"Configured to encrypt to a key not configured, and strict policy enabled. "
+"Bailing out."
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:134
+msgid ""
+"Configured to encrypt to a key not configured, but continuing anyway (see "
+"'gpgencrypt_strict')."
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:171
+#, python-format
+msgid "Recipients: %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:183
+#, python-format
+msgid "Current keys: %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:188
+#, python-format
+msgid "Retrieving key for recipient: %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:192 ../wallace/module_gpgencrypt.py:208
+#, python-format
+msgid "Found matching address %r"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:200
+#, python-format
+msgid "Found matching address %r in remote keys"
+msgstr ""
+
+#: ../wallace/module_gpgencrypt.py:232
+#, python-format
+msgid "An error occurred: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:154
+#, python-format
+msgid "Invitation policy called for %r, %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:211
+#: ../wallace/module_resources.py:169
+#, python-format
+msgid "Failed to parse iTip events from message: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:215
+msgid ""
+"Message is not an iTip message or does not contain any (valid) iTip events."
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:219
+#, python-format
+msgid ""
+"iTip events attached to this message contain the following information: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:232
+#, python-format
+msgid "No itips, no users, pass along %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:235
+#, python-format
+msgid "iTips, but no users, pass along %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:255
+#, python-format
+msgid "No user attendee matching envelope recipient %s, skip message"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:259
+#, python-format
+msgid "Receiving user: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:284
+#, python-format
+msgid "Apply invitation policy %r for domain %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:295
+#, python-format
+msgid "Ignoring '%s' iTip method"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:299
+#, python-format
+msgid "iTip message %r consumed by the invitationpolicy module"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:315
+msgid "Pass invitation for manual processing"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:320
+#, python-format
+msgid "Receiving Attendee: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:339
+#, python-format
+msgid "Existing event: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:350
+#, python-format
+msgid "Precondition for event %r fulfilled: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:386
+#, python-format
+msgid "No RSVP for recipient %r requested"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:412
+msgid "Pass reply for manual processing"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:419
+#, python-format
+msgid "Sender Attendee: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:431
+#, python-format
+msgid ""
+"The iTip reply sequence (%r) doesn't match the referred event version (%r). "
+"Forwarding to Inbox."
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:437
+#, python-format
+msgid "Auto-updating event %r on iTip REPLY"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:459
+#: ../wallace/module_invitationpolicy.py:488
+msgid ""
+"The event referred by this reply was not found in the user's calendars. "
+"Forwarding to Inbox."
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:472
+msgid "Pass cancellation for manual processing"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:517
+#, python-format
+msgid "Checking if email address %r belongs to a local user"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:522
+#, python-format
+msgid "User DN: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:524
+#, python-format
+msgid "No user record(s) found for %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:577
+#, python-format
+msgid "User record doesn't have the mailbox attribute %r set"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:590
+#, python-format
+msgid "IMAP proxy authentication failed: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:612
+#, python-format
+msgid "List calendar folders for user %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:628
+#, python-format
+msgid "IMAP metadata for %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:658
+#, python-format
+msgid "Searching folder %r for event %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:670
+#: ../wallace/module_invitationpolicy.py:709
+#: ../wallace/module_resources.py:486
+#, python-format
+msgid "Failed to parse event from message %s/%s: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:696
+#, python-format
+msgid "Listing events from folder %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:715
+#, python-format
+msgid "Existing event %r conflicts with invitation %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:722
+#: ../wallace/module_resources.py:344
+#, python-format
+msgid "start: %r, end: %r, total: %r, messages: %d"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:748
+#, python-format
+msgid "%r is locked, waiting..."
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:811
+#, python-format
+msgid "Failed to save event: no calendar folder found for user %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:814
+#, python-format
+msgid "Save event %r to user calendar %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:827
+#, python-format
+msgid "Failed to save event to user calendar at %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:843
+#, python-format
+msgid "Delete event %r in %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:863
+#, python-format
+msgid "Compose participation status summary for event %r to user %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:901
+#, python-format
+msgid ""
+"Waiting for more automated replies (got %d of %d); skipping notification"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:998
+#, python-format
+msgid "Updated %s's copy of %r: %r"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:1001
+#, python-format
+msgid "Attendee %s's copy of %r not found"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:1004
+#, python-format
+msgid "Attendee %r not found in LDAP"
+msgstr ""
+
+#: ../wallace/module_invitationpolicy.py:1008
+#, python-format
+msgid ""
+"\n"
+" %(name)s has %(status)s your invitation for %(summary)s.\n"
+"\n"
+" *** This is an automated response sent by the Kolab Invitation "
+"system ***\n"
+" "
+msgstr ""
+
#. modules.next_module('optout')
#: ../wallace/module_optout.py:70
#, python-format
@@ -2788,220 +3213,262 @@ msgstr ""
msgid "Could not send request to optout_url %s"
msgstr ""
-#: ../wallace/module_resources.py:96
+#: ../wallace/module_resources.py:110
#, python-format
msgid "Resource Management called for %r, %r"
msgstr ""
-#: ../wallace/module_resources.py:159
+#: ../wallace/module_resources.py:174
msgid "Message is not an iTip message or does not contain any "
msgstr ""
-#: ../wallace/module_resources.py:167
+#: ../wallace/module_resources.py:182
msgid "iTip events attached to this message contain the "
msgstr ""
-#: ../wallace/module_resources.py:188
+#: ../wallace/module_resources.py:205
msgid "Not an iTip message, but sent to resource nonetheless. Reject message"
msgstr ""
-#: ../wallace/module_resources.py:196
-msgid "No itips, no resources, pass along"
+#: ../wallace/module_resources.py:213
+#, python-format
+msgid "No itips, no resources, pass along %r"
msgstr ""
-#: ../wallace/module_resources.py:200
-msgid "iTips, but no resources, pass along"
+#: ../wallace/module_resources.py:216
+#, python-format
+msgid "iTips, but no resources, pass along %r"
+msgstr ""
+
+#: ../wallace/module_resources.py:225
+#, python-format
+msgid "No resource attendees matching envelope recipient %s, Reject message"
msgstr ""
-#: ../wallace/module_resources.py:233
+#: ../wallace/module_resources.py:234
#, python-format
msgid "Resources: %r; %r"
msgstr ""
-#: ../wallace/module_resources.py:242
+#: ../wallace/module_resources.py:244
#, python-format
msgid "Receiving Resource: %r; %r"
msgstr ""
-#: ../wallace/module_resources.py:250
+#: ../wallace/module_resources.py:252
#, python-format
msgid "Recipient %r is non-participant, ignoring message"
msgstr ""
-#: ../wallace/module_resources.py:281
+#: ../wallace/module_resources.py:279
#, python-format
-msgid "Failed to read resource calendar for %r: %r"
+msgid "Accept invitation for individual resource %r / %r"
msgstr ""
-#: ../wallace/module_resources.py:286
+#: ../wallace/module_resources.py:308
#, python-format
-msgid "start: %r, end: %r, total: %r, messages: %d"
+msgid "Delegate invitation for resource collection %r to %r"
+msgstr ""
+
+#: ../wallace/module_resources.py:340
+#, python-format
+msgid "Failed to read resource calendar for %r: %r"
msgstr ""
-#: ../wallace/module_resources.py:292
+#: ../wallace/module_resources.py:350
#, python-format
msgid "Polling for resource %r"
msgstr ""
-#: ../wallace/module_resources.py:295
+#: ../wallace/module_resources.py:353
#, python-format
msgid "Resource %r has been popped from the list"
msgstr ""
-#: ../wallace/module_resources.py:299
+#: ../wallace/module_resources.py:357
msgid "Resource is a collection"
msgstr ""
-#: ../wallace/module_resources.py:310
+#: ../wallace/module_resources.py:368
#, python-format
msgid "Removed conflicting resources from %r: (%r) => %r"
msgstr ""
-#: ../wallace/module_resources.py:322
+#: ../wallace/module_resources.py:380
#, python-format
msgid "Conflicting events: %r for resource %r"
msgstr ""
-#: ../wallace/module_resources.py:351
+#: ../wallace/module_resources.py:397
#, python-format
-msgid "Accept invitation for individual resource %r / %r"
+msgid "Delegate to another resource collection member: %r to %r"
msgstr ""
-#: ../wallace/module_resources.py:365
-#, python-format
-msgid "Delegate invitation for resource collection %r to %r"
-msgstr ""
-
-#: ../wallace/module_resources.py:408
+#: ../wallace/module_resources.py:459
#, python-format
msgid "Checking events in resource folder %r"
msgstr ""
-#: ../wallace/module_resources.py:424
+#: ../wallace/module_resources.py:475
#, python-format
msgid "Fetching message UID %r from folder %r"
msgstr ""
-#: ../wallace/module_resources.py:472
+#: ../wallace/module_resources.py:498
#, python-format
msgid "Event %r conflicts with event %r"
msgstr ""
-#: ../wallace/module_resources.py:499
+#: ../wallace/module_resources.py:525
#, python-format
msgid "Adding event to %r: %r"
msgstr ""
-#: ../wallace/module_resources.py:537
+#: ../wallace/module_resources.py:573
#, python-format
msgid "Failed to save event to resource calendar at %r: %r"
msgstr ""
-#: ../wallace/module_resources.py:553
+#: ../wallace/module_resources.py:590
#, python-format
msgid "Delete resource calendar object %r in %r: %r"
msgstr ""
-#: ../wallace/module_resources.py:587
-#, python-format
-msgid "Method %r not really interesting for us."
-msgstr ""
-
-#: ../wallace/module_resources.py:595
-#, python-format
-msgid "Raw iTip payload: %s"
-msgstr ""
-
-#: ../wallace/module_resources.py:605
-msgid "Could not read iTip from message."
-msgstr ""
-
-#: ../wallace/module_resources.py:613
-#, python-format
-msgid "Duplicate iTip event: %s"
-msgstr ""
-
-#: ../wallace/module_resources.py:638
-msgid "iTip event without a start"
-msgstr ""
-
-#: ../wallace/module_resources.py:676
-msgid "Message is not an iTip message (non-multipart message)"
-msgstr ""
-
-#: ../wallace/module_resources.py:705
+#: ../wallace/module_resources.py:633
#, python-format
msgid "Checking if email address %r belongs to a resource (collection)"
msgstr ""
-#: ../wallace/module_resources.py:713 ../wallace/module_resources.py:781
-#: ../wallace/module_resources.py:815
+#: ../wallace/module_resources.py:641 ../wallace/module_resources.py:709
+#: ../wallace/module_resources.py:743
#, python-format
msgid "Resource record(s): %r"
msgstr ""
-#: ../wallace/module_resources.py:715 ../wallace/module_resources.py:783
-#: ../wallace/module_resources.py:818
+#: ../wallace/module_resources.py:643 ../wallace/module_resources.py:711
+#: ../wallace/module_resources.py:746
#, python-format
msgid "No resource (collection) records found for %r"
msgstr ""
-#: ../wallace/module_resources.py:719 ../wallace/module_resources.py:787
-#: ../wallace/module_resources.py:822
+#: ../wallace/module_resources.py:647 ../wallace/module_resources.py:715
+#: ../wallace/module_resources.py:750
#, python-format
msgid "Resource record: %r"
msgstr ""
-#: ../wallace/module_resources.py:739
+#: ../wallace/module_resources.py:667
#, python-format
msgid "Raw itip_events: %r"
msgstr ""
-#: ../wallace/module_resources.py:747
+#: ../wallace/module_resources.py:675
#, python-format
msgid "Raw set of attendees: %r"
msgstr ""
-#: ../wallace/module_resources.py:755
+#: ../wallace/module_resources.py:683
#, python-format
msgid "Raw set of resources: %r"
msgstr ""
-#: ../wallace/module_resources.py:774
+#: ../wallace/module_resources.py:702
#, python-format
msgid "Checking if attendee %r is a resource (collection)"
msgstr ""
-#: ../wallace/module_resources.py:790 ../wallace/module_resources.py:824
+#: ../wallace/module_resources.py:718 ../wallace/module_resources.py:752
msgid "Resource reservation made but no resource records found"
msgstr ""
-#: ../wallace/module_resources.py:809
+#: ../wallace/module_resources.py:737
#, python-format
msgid "Checking if resource %r is a resource (collection)"
msgstr ""
-#: ../wallace/module_resources.py:827
+#: ../wallace/module_resources.py:755
msgid "The following resources are being referred to in the "
msgstr ""
-#: ../wallace/module_resources.py:867
+#: ../wallace/module_resources.py:894
#, python-format
msgid ""
"\n"
-" Your reservation was delegated to \"%s\"\n"
-" which is available for the requested time.\n"
+" *** This is an automated response, please do not reply! ***\n"
+"\n"
+" Your reservation was delegated to \"%s\" which is available "
+"for the requested time.\n"
" "
msgstr ""
+#: ../wallace/module_resources.py:905
+#, python-format
+msgid ""
+"\n"
+" *** This is an automated response, please do not reply! ***\n"
+" \n"
+" We hereby inform you that your reservation was %s.\n"
+" "
+msgstr ""
+
+#: ../wallace/module_resources.py:912
+#, python-format
+msgid ""
+"\n"
+" If you have questions about this reservation, please contact\n"
+" %s <%s> %s\n"
+" "
+msgstr ""
+
+#: ../wallace/module_resources.py:941
+#, python-format
+msgid "Sending booking notification for event %r to %r from %r"
+msgstr ""
+
+#: ../wallace/module_resources.py:954
+msgid "failed"
+msgstr ""
+
+#: ../wallace/module_resources.py:973
+#, python-format
+msgid ""
+"\n"
+" The resource booking for %(resource)s by %(orgname)s <%(orgemail)"
+"s> has been %(status)s for %(date)s.\n"
+"\n"
+" *** This is an automated message, sent to you as the resource "
+"owner. ***\n"
+" "
+msgstr ""
+
+#: ../wallace/module_resources.py:979
+#, python-format
+msgid ""
+"\n"
+" A reservation request for %(resource)s could not be processed "
+"automatically.\n"
+" Please contact %(orgname)s <%(orgemail)s> who requested this "
+"resource for %(date)s. Subject: %(summary)s.\n"
+"\n"
+" *** This is an automated message, sent to you as the resource "
+"owner. ***\n"
+" "
+msgstr ""
+
#. This is a nested module
#: ../wallace/modules.py:97
#, python-format
msgid "Module Group: %s"
msgstr ""
-#: ../wallace/modules.py:108 ../wallace/modules.py:113
-msgid "No such module."
+#: ../wallace/modules.py:108
+#, python-format
+msgid "No such module %r in modules %r (1)."
+msgstr ""
+
+#: ../wallace/modules.py:113
+#, python-format
+msgid "No such module %r in modules %r (2)."
msgstr ""
#: ../wallace/modules.py:119
diff --git a/pykolab/Makefile.am b/pykolab/Makefile.am
index cb127cf..e43cdbb 100644
--- a/pykolab/Makefile.am
+++ b/pykolab/Makefile.am
@@ -42,6 +42,10 @@ pykolab_imap_PYTHON = \
imap/__init__.py \
imap/cyrus.py
+pykolab_itipdir = $(pythondir)/$(PACKAGE)/itip
+pykolab_itip_PYTHON = \
+ itip/__init__.py
+
pykolab_pluginsdir = $(pythondir)/$(PACKAGE)/plugins
pykolab_plugins_PYTHON = \
plugins/__init__.py
diff --git a/pykolab/auth/__init__.py b/pykolab/auth/__init__.py
index 32d1c26..61e848b 100644
--- a/pykolab/auth/__init__.py
+++ b/pykolab/auth/__init__.py
@@ -204,7 +204,10 @@ class Auth(pykolab.base.Base):
return result
def find_user(self, attr, value, **kw):
- return self._auth._find_user(attr, value, **kw)
+ return self._auth.search_entry_by_attribute(attr, value, **kw)
+
+ def find_user_dn(self, login, kolabuser=False):
+ return self._auth._find_user_dn(login, kolabuser);
def list_domains(self, domain=None):
"""
diff --git a/pykolab/auth/ldap/__init__.py b/pykolab/auth/ldap/__init__.py
index d36f7f6..5fd337e 100644
--- a/pykolab/auth/ldap/__init__.py
+++ b/pykolab/auth/ldap/__init__.py
@@ -416,7 +416,7 @@ class LDAP(pykolab.base.Base):
if entry[folderacl_entry_attribute] is not None:
# Parse it before assigning it
- entry['kolabmailfolderaclentry'] = []
+ entry['kolabfolderaclentry'] = []
if not isinstance(entry[folderacl_entry_attribute], list):
entry[folderacl_entry_attribute] = [ entry[folderacl_entry_attribute] ]
@@ -427,11 +427,15 @@ class LDAP(pykolab.base.Base):
log.debug(_("Found a subject %r with access %r") % (aci_subject, acl_access), level=8)
access_lookup_dict = {
+ 'all': 'lrsedntxakcpiw',
+ 'append': 'wip',
+ 'full': 'lrswipkxtecdn',
'read': 'lrs',
+ 'read-only': 'lrs',
+ 'read-write': 'lrswitedn',
'post': 'p',
- 'append': 'wip',
+ 'semi-full': 'lrswit',
'write': 'lrswite',
- 'all': 'lrsedntxakcpiw'
}
if access_lookup_dict.has_key(acl_access):
@@ -439,9 +443,9 @@ class LDAP(pykolab.base.Base):
log.debug(_("Found a subject %r with access %r") % (aci_subject, acl_access), level=8)
- entry['kolabmailfolderaclentry'].append("(%r, %r, %r)" % (folder_path, aci_subject, acl_access))
+ entry['kolabfolderaclentry'].append("(%r, %r, %r)" % (folder_path, aci_subject, acl_access))
- self.init_entry_attribute(entry, 'kolabmailfolderaclentry')
+ self.init_entry_attribute(entry, 'kolabfolderaclentry')
def get_folder_path(self, entry):
"""
@@ -1187,7 +1191,7 @@ class LDAP(pykolab.base.Base):
"""
pass
- def _change_add_sharedfolder(self, entry, change):
+ def _change_add_sharedfolder(self, entry, change, modify=False):
"""
An entry of type sharedfolder was added.
"""
@@ -1235,11 +1239,15 @@ class LDAP(pykolab.base.Base):
folder_path,
entry['kolabfoldertype']
)
+ elif modify:
+ self.imap.set_acl(folder_path, 'anyone', '')
- if entry.get('kolabmailfolderaclentry'):
+ if entry.get('kolabfolderaclentry'):
self.imap._set_kolab_mailfolder_acls(
- entry['kolabmailfolderaclentry']
+ entry['kolabfolderaclentry']
)
+ else:
+ self.imap.set_acl(folder_path, 'anyone', '')
if entry.get(delivery_address_attribute):
self.imap.set_acl(folder_path, 'anyone', '+p')
@@ -1374,7 +1382,7 @@ class LDAP(pykolab.base.Base):
success = True
for _type in ['user','group','role','sharedfolder']:
try:
- eval("self._change_delete_%s(entry, change)" % (_type))
+ eval("success = self._change_delete_%s(entry, change)" % (_type))
except:
success = False
@@ -1467,8 +1475,8 @@ class LDAP(pykolab.base.Base):
def _change_modify_role(self, entry, change):
pass
- # A shared folder was modified.
- _change_modify_sharedfolder = _change_add_sharedfolder
+ def _change_modify_sharedfolder(self, entry, change):
+ self._change_add_sharedfolder(entry, change, modify=True)
def _change_modify_user(self, entry, change):
"""
@@ -1575,7 +1583,18 @@ class LDAP(pykolab.base.Base):
self.init_entry_attribute(entry, 'kolabfoldertype')
- #self.init_entry_attribute(entry, 'kolabmailfolderaclentry')
+ folderacl_entry_attribute = conf.get('ldap', 'folderacl_entry_attribute')
+ if folderacl_entry_attribute is None:
+ folderacl_entry_attribute = 'acl'
+
+ if not entry.has_key(folderacl_entry_attribute):
+ entry['kolabfolderaclentry'] = self.get_entry_attribute(
+ entry['id'],
+ folderacl_entry_attribute
+ )
+ else:
+ entry['kolabfolderaclentry'] = entry[folderacl_entry_attribute]
+ del entry[folderacl_entry_attribute]
folder_path = self.get_folder_path(entry)
@@ -1588,11 +1607,33 @@ class LDAP(pykolab.base.Base):
entry['kolabfoldertype']
)
- if entry.get('kolabmailfolderaclentry'):
+ if entry.get('kolabfolderaclentry'):
+
+ if isinstance(entry['kolabfolderaclentry'], basestring):
+ entry['kolabfolderaclentry'] = [ entry['kolabfolderaclentry'] ]
+
+ import copy
+ _acls = copy.deepcopy(entry['kolabfolderaclentry'])
+ entry['kolabfolderaclentry'] = []
+
+ for _entry in _acls:
+ if _entry[0] == "(":
+ entry['kolabfolderaclentry'].append(_entry)
+ continue
+
+ s,r = [x.strip() for x in _entry.split(',')]
+
+ entry['kolabfolderaclentry'].append("('%s', '%s', '%s')" % (folder_path, s, r))
+
self.imap._set_kolab_mailfolder_acls(
- entry['kolabmailfolderaclentry']
+ entry['kolabfolderaclentry']
)
+ elif entry['kolabfolderaclentry'] in [None,[]]:
+ for ace in self.imap.list_acls(folder_path):
+ aci_subject = ace.split()[0]
+ self.imap.set_acl(folder_path, aci_subject, '')
+
delivery_address_attribute = self.config_get('sharedfolder_delivery_address_attribute')
if entry.get(delivery_address_attribute):
self.imap.set_acl(folder_path, 'anyone', '+p')
@@ -1766,16 +1807,17 @@ class LDAP(pykolab.base.Base):
else:
return _type
- def _find_user_dn(self, login, realm):
+ def _find_user_dn(self, login, kolabuser=False):
"""
- Find the distinguished name (DN) for an entry in LDAP.
+ Find the distinguished name (DN) for a (Kolab) user entry in LDAP.
"""
+ conf_prefix = 'kolab_' if kolabuser else ''
domain_root_dn = self._kolab_domain_root_dn(self.domain)
- base_dn = self.config_get('user_base_dn')
- if base_dn is None:
- base_dn = self.config_get('base_dn')
+ user_base_dn = self.config_get(conf_prefix + 'user_base_dn')
+ if user_base_dn is None:
+ user_base_dn = self.config_get('base_dn')
auth_attrs = self.config_get_list('auth_attributes')
@@ -1783,18 +1825,21 @@ class LDAP(pykolab.base.Base):
for auth_attr in auth_attrs:
auth_search_filter.append('(%s=%s)' % (auth_attr,login))
- auth_search_filter.append(
- '(%s=%s@%s)' % (
- auth_attr,
- login,
- self.domain
- )
- )
+ if not '@' in login:
+ auth_search_filter.append(
+ '(%s=%s@%s)' % (
+ auth_attr,
+ login,
+ self.domain
+ )
+ )
auth_search_filter.append(')')
auth_search_filter = ''.join(auth_search_filter)
+ user_filter = self.config_get(conf_prefix + 'user_filter')
+
search_filter = "(&%s%s)" % (
auth_search_filter,
user_filter
@@ -2018,7 +2063,7 @@ class LDAP(pykolab.base.Base):
try:
entry['type'] = self._entry_type(entry)
except:
- entry['type'] = "unknown"
+ entry['type'] = None
log.debug(_("Entry type: %s") % (entry['type']), level=8)
@@ -2092,14 +2137,6 @@ class LDAP(pykolab.base.Base):
#
# server = self.imap.user_mailbox_server(folder)
- log.debug(
- _("Done with _synchronize_callback() for entry %r") % (
- entry['id']
- ),
- level=9
- )
-
-
def _unbind(self):
"""
Discard the current set of bind credentials.
@@ -2468,9 +2505,13 @@ class LDAP(pykolab.base.Base):
except Exception, errmsg:
log.error(_("An error occured using %s: %r") % (supported_control, errmsg))
+ import traceback
+
if conf.debuglevel > 8:
- import traceback
traceback.print_exc()
+
+ log.error(_("%s") % (traceback.format_exc()))
+
continue
return _results
diff --git a/pykolab/auth/ldap/cache.py b/pykolab/auth/ldap/cache.py
index 292bc52..5dcbb76 100644
--- a/pykolab/auth/ldap/cache.py
+++ b/pykolab/auth/ldap/cache.py
@@ -54,7 +54,7 @@ class Entry(object):
self.uniqueid = uniqueid
self.result_attribute = result_attr
- modifytimestamp_format = conf.get('ldap', 'modifytimestamp_format')
+ modifytimestamp_format = conf.get_raw('ldap', 'modifytimestamp_format')
if modifytimestamp_format is None:
modifytimestamp_format = "%Y%m%d%H%M%SZ"
@@ -86,7 +86,7 @@ mapper(Entry, entry_table)
##
def delete_entry(domain, entry):
- result_attribute = conf.get('cyrus-sasl', 'result_attribute')
+ result_attribute = conf.get_raw('cyrus-sasl', 'result_attribute')
db = init_db(domain)
_entry = db.query(Entry).filter_by(uniqueid=entry['id']).first()
@@ -96,7 +96,7 @@ def delete_entry(domain, entry):
db.commit()
def get_entry(domain, entry, update=True):
- result_attribute = conf.get('cyrus-sasl', 'result_attribute')
+ result_attribute = conf.get_raw('cyrus-sasl', 'result_attribute')
_entry = None
@@ -130,7 +130,7 @@ def get_entry(domain, entry, update=True):
db.commit()
_entry = db.query(Entry).filter_by(uniqueid=entry['id']).first()
else:
- modifytimestamp_format = conf.get('ldap', 'modifytimestamp_format')
+ modifytimestamp_format = conf.get_raw('ldap', 'modifytimestamp_format')
if modifytimestamp_format is None:
modifytimestamp_format = "%Y%m%d%H%M%SZ"
@@ -166,9 +166,13 @@ def init_db(domain,reinit=False):
db_uri = 'sqlite:///%s/%s.db' % (KOLAB_LIB_PATH, domain)
echo = conf.debuglevel > 8
- engine = create_engine(db_uri, echo=echo)
- metadata.create_all(engine)
+ try:
+ engine = create_engine(db_uri, echo=echo)
+ metadata.create_all(engine)
+ except:
+ engine = create_engine('sqlite://')
+ metadata.create_all(engine)
Session = sessionmaker(bind=engine)
db = Session()
@@ -176,14 +180,17 @@ def init_db(domain,reinit=False):
return db
def last_modify_timestamp(domain):
- db = init_db(domain)
- last_change = db.query(Entry).order_by(desc(Entry.last_change)).first()
-
- modifytimestamp_format = conf.get('ldap', 'modifytimestamp_format')
+ modifytimestamp_format = conf.get_raw('ldap', 'modifytimestamp_format')
if modifytimestamp_format is None:
modifytimestamp_format = "%Y%m%d%H%M%SZ"
- if last_change is not None:
- return last_change.last_change.strftime(modifytimestamp_format)
-
- return datetime.datetime(1900, 01, 01, 00, 00, 00).strftime(modifytimestamp_format)
+ try:
+ db = init_db(domain)
+ last_change = db.query(Entry).order_by(desc(Entry.last_change)).first()
+
+ if last_change is not None:
+ return last_change.last_change.strftime(modifytimestamp_format)
+ else:
+ return datetime.datetime(1900, 01, 01, 00, 00, 00).strftime(modifytimestamp_format)
+ except:
+ return datetime.datetime(1900, 01, 01, 00, 00, 00).strftime(modifytimestamp_format)
diff --git a/pykolab/cli/cmd_count_domain_mailboxes.py b/pykolab/cli/cmd_count_domain_mailboxes.py
index 8aecd2d..958bccd 100644
--- a/pykolab/cli/cmd_count_domain_mailboxes.py
+++ b/pykolab/cli/cmd_count_domain_mailboxes.py
@@ -56,10 +56,8 @@ def execute(*args, **kw):
domains = auth.list_domains()
folders = []
- for primary,secondaries in domains:
- print "%s: %d" % (primary,len(imap.lm("user/%%@%s" % (primary))))
- for secondary in secondaries:
- print "%s: %d" % (secondary,len(imap.lm("user/%%@%s" % (secondary))))
+ for domain in domains.keys():
+ print "%s: %d" % (domain,len(imap.lm("user/%%@%s" % (domain))))
null_realm = len(imap.lm("user/%%"))
diff --git a/pykolab/cli/cmd_delete_mailbox.py b/pykolab/cli/cmd_delete_mailbox.py
index 3f9c981..5a65f56 100644
--- a/pykolab/cli/cmd_delete_mailbox.py
+++ b/pykolab/cli/cmd_delete_mailbox.py
@@ -58,5 +58,8 @@ def execute(*args, **kw):
sys.exit(1)
for delete_folder in delete_folders:
- imap.delete_mailfolder(delete_folder)
+ try:
+ imap.delete_mailfolder(delete_folder)
+ except Exception, errmsg:
+ log.error(_("Could not delete mailbox '%s'") % (delete_folder))
diff --git a/pykolab/cli/cmd_list_ous.py b/pykolab/cli/cmd_list_ous.py
new file mode 100644
index 0000000..670b609
--- /dev/null
+++ b/pykolab/cli/cmd_list_ous.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Copyright 2010-2012 Kolab Systems AG (http://www.kolabsys.com)
+#
+# Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import commands
+
+import pykolab
+
+from pykolab import utils
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+ commands.register('list_ous', execute, description="List organizational units.")
+
+def execute(*args, **kw):
+ from pykolab import wap_client
+
+ wap_client.authenticate(username=conf.get("ldap", "bind_dn"), password=conf.get("ldap", "bind_pw"))
+
+ ous = wap_client.ous_list()
+ print '\n'.join(ous['list'].keys())
diff --git a/pykolab/cli/cmd_list_users.py b/pykolab/cli/cmd_list_users.py
new file mode 100644
index 0000000..ff1ddef
--- /dev/null
+++ b/pykolab/cli/cmd_list_users.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Copyright 2010-2012 Kolab Systems AG (http://www.kolabsys.com)
+#
+# Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import commands
+
+import pykolab
+
+from pykolab import utils
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+ commands.register('list_users', execute, description="List organizational units.")
+
+def execute(*args, **kw):
+ from pykolab import wap_client
+
+ wap_client.authenticate(username=conf.get("ldap", "bind_dn"), password=conf.get("ldap", "bind_pw"))
+
+ users = wap_client.users_list()
+ print '\n'.join(users['list'].keys())
diff --git a/pykolab/cli/cmd_rename_mailbox.py b/pykolab/cli/cmd_rename_mailbox.py
index f7cf1c1..dfb759d 100644
--- a/pykolab/cli/cmd_rename_mailbox.py
+++ b/pykolab/cli/cmd_rename_mailbox.py
@@ -66,7 +66,7 @@ def execute(*args, **kw):
utils.message(_("Source folder %r does not exist") % (source_folder))
sys.exit(1)
- if imap.has_folder(target_folder):
+ if imap.has_folder(target_folder) and partition is None:
utils.message(_("Target folder %r already exists") % (target_folder))
sys.exit(1)
diff --git a/pykolab/cli/commands.py b/pykolab/cli/commands.py
index 82241a4..b6dff3a 100644
--- a/pykolab/cli/commands.py
+++ b/pykolab/cli/commands.py
@@ -35,7 +35,6 @@ def __init__():
register('help', lambda *args, **kw: list_commands(commands, *args, **kw))
- register('list_users', not_yet_implemented, description="Not yet implemented")
register('delete_user', not_yet_implemented, description="Not yet implemented")
register('list_groups', not_yet_implemented, description="Not yet implemented")
diff --git a/pykolab/imap/__init__.py b/pykolab/imap/__init__.py
index 2115bdf..be71fd5 100644
--- a/pykolab/imap/__init__.py
+++ b/pykolab/imap/__init__.py
@@ -211,8 +211,7 @@ class IMAP(object):
folder_path = self.folder_utf7(folder_path)
if server is not None:
- if not self._imap.has_key(server):
- self.connect(server=server)
+ self.connect(server=server)
try:
self._imap[server].cm(folder_path, partition=partition)
@@ -321,11 +320,15 @@ class IMAP(object):
Set an ACL entry on a folder.
"""
short_rights = {
- 'all': 'lrswipkxtecda',
+ 'all': 'lrsedntxakcpiw',
+ 'append': 'wip',
+ 'full': 'lrswipkxtecdn',
+ 'read': 'lrs',
'read-only': 'lrs',
- 'read-write': 'lrswited',
+ 'read-write': 'lrswitedn',
+ 'post': 'p',
'semi-full': 'lrswit',
- 'full': 'lrswipkxtecd'
+ 'write': 'lrswite',
}
if short_rights.has_key(acl):
@@ -651,7 +654,7 @@ class IMAP(object):
if additional_folders[additional_folder].has_key("partition"):
partition = additional_folders[additional_folder]["partition"]
try:
- self.imap.rename(folder_name, folder_name, partition)
+ self.imap._rename(folder_name, folder_name, partition)
except:
log.error(_("Could not rename %s to reside on partition %s") % (folder_name, partition))
diff --git a/pykolab/imap/cyrus.py b/pykolab/imap/cyrus.py
index 978fef9..9664edd 100644
--- a/pykolab/imap/cyrus.py
+++ b/pykolab/imap/cyrus.py
@@ -236,8 +236,12 @@ class Cyrus(cyruslib.CYRUS):
server = self.find_mailfolder_server(from_mailfolder)
self.connect(self.uri.replace(self.server,server))
- log.debug(_("Moving INBOX folder %s to %s") % (from_mailfolder,to_mailfolder), level=8)
- self.m.rename(from_mailfolder, to_mailfolder, partition)
+ if not partition == None:
+ log.debug(_("Moving INBOX folder %s to %s on partition %s") % (from_mailfolder,to_mailfolder, partition), level=8)
+ else:
+ log.debug(_("Moving INBOX folder %s to %s") % (from_mailfolder,to_mailfolder), level=8)
+
+ self.m.rename(self.folder_utf7(from_mailfolder), self.folder_utf7(to_mailfolder), '"%s"' % (partition))
def _getannotation(self, *args, **kw):
return self.getannotation(*args, **kw)
@@ -384,9 +388,9 @@ class Cyrus(cyruslib.CYRUS):
verify_folder_search = "%s@%s" % (verify_folder_search, mbox['domain'])
if ' ' in verify_folder_search:
- folders = self.lm('"%s"' % verify_folder_search)
+ folders = self.lm('"%s"' % self.folder_utf7(verify_folder_search))
else:
- folders = self.lm(verify_folder_search)
+ folders = self.lm(self.folder_utf7(verify_folder_search))
# NOTE: Case also covered is valid hexadecimal folders; won't be the
# actual check as intended, but doesn't give you anyone else's data
diff --git a/pykolab/itip/__init__.py b/pykolab/itip/__init__.py
new file mode 100644
index 0000000..ddcb392
--- /dev/null
+++ b/pykolab/itip/__init__.py
@@ -0,0 +1,287 @@
+import icalendar
+import pykolab
+
+from pykolab.xml import to_dt
+from pykolab.xml import event_from_ical
+from pykolab.xml import participant_status_label
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.wallace')
+
+
+def events_from_message(message, methods=None):
+ return objects_from_message(message, "VEVENT", methods)
+
+def todos_from_message(message, methods=None):
+ return objects_from_message(message, "VTODO", methods)
+
+
+def objects_from_message(message, objname, methods=None):
+ """
+ Obtain the iTip payload from email.message <message>
+ """
+ # Placeholder for any itip_objects found in the message.
+ itip_objects = []
+ seen_uids = []
+
+ # iTip methods we are actually interested in. Other methods will be ignored.
+ if methods is None:
+ methods = [ "REQUEST", "CANCEL" ]
+
+ # Are all iTip messages multipart? No! RFC 6047, section 2.4 states "A
+ # MIME body part containing content information that conforms to this
+ # document MUST have (...)" but does not state whether an iTip message must
+ # therefore also be multipart.
+
+ # Check each part
+ for part in message.walk():
+
+ # The iTip part MUST be Content-Type: text/calendar (RFC 6047, section 2.4)
+ # But in real word, other mime-types are used as well
+ if part.get_content_type() in [ "text/calendar", "text/x-vcalendar", "application/ics" ]:
+ if not str(part.get_param('method')).upper() in methods:
+ log.info(_("Method %r not really interesting for us.") % (part.get_param('method')))
+ continue
+
+ # Get the itip_payload
+ itip_payload = part.get_payload(decode=True)
+
+ log.debug(_("Raw iTip payload: %s") % (itip_payload), level=9)
+
+ # Python iCalendar prior to 3.0 uses "from_string".
+ if hasattr(icalendar.Calendar, 'from_ical'):
+ cal = icalendar.Calendar.from_ical(itip_payload)
+ elif hasattr(icalendar.Calendar, 'from_string'):
+ cal = icalendar.Calendar.from_string(itip_payload)
+
+ # If we can't read it, we're out
+ else:
+ log.error(_("Could not read iTip from message."))
+ return []
+
+ for c in cal.walk():
+ if c.name == objname:
+ itip = {}
+
+ if c['uid'] in seen_uids:
+ log.debug(_("Duplicate iTip object: %s") % (c['uid']), level=9)
+ continue
+
+ # From the event, take the following properties:
+ #
+ # - method
+ # - uid
+ # - sequence
+ # - start
+ # - end (if any)
+ # - duration (if any)
+ # - organizer
+ # - attendees (if any)
+ # - resources (if any)
+ #
+
+ itip['uid'] = str(c['uid'])
+ itip['method'] = str(cal['method']).upper()
+ itip['sequence'] = int(c['sequence']) if c.has_key('sequence') else 0
+
+ if c.has_key('dtstart'):
+ itip['start'] = c['dtstart'].dt
+ else:
+ log.error(_("iTip event without a start"))
+ continue
+
+ if c.has_key('dtend'):
+ itip['end'] = c['dtend'].dt
+
+ if c.has_key('duration'):
+ itip['duration'] = c['duration'].dt
+ itip['end'] = itip['start'] + c['duration'].dt
+
+ itip['organizer'] = c['organizer']
+
+ itip['attendees'] = c['attendee']
+
+ if itip.has_key('attendees') and not isinstance(itip['attendees'], list):
+ itip['attendees'] = [c['attendee']]
+
+ if c.has_key('resources'):
+ itip['resources'] = c['resources']
+
+ itip['raw'] = itip_payload
+
+ try:
+ # TODO: distinguish event and todo here
+ itip['xml'] = event_from_ical(c.to_ical())
+ except Exception, e:
+ log.error("event_from_ical() exception: %r; iCal: %s" % (e, itip_payload))
+ continue
+
+ itip_objects.append(itip)
+
+ seen_uids.append(c['uid'])
+
+ # end if c.name == "VEVENT"
+
+ # end for c in cal.walk()
+
+ # end if part.get_content_type() == "text/calendar"
+
+ # end for part in message.walk()
+
+ if not len(itip_objects) and not message.is_multipart():
+ log.debug(_("Message is not an iTip message (non-multipart message)"), level=5)
+
+ return itip_objects
+
+
+def check_event_conflict(kolab_event, itip_event):
+ """
+ Determine whether the given kolab event conflicts with the given itip event
+ """
+ conflict = False
+
+ # don't consider conflict with myself
+ if kolab_event.uid == itip_event['uid']:
+ return conflict
+
+ # don't consider conflict if event has TRANSP:TRANSPARENT
+ if kolab_event.get_transparency():
+ return conflict
+
+ _es = to_dt(kolab_event.get_start())
+ _ee = to_dt(kolab_event.get_ical_dtend()) # use iCal style end date: next day for all-day events
+
+ # naive loops to check for collisions in (recurring) events
+ # TODO: compare recurrence rules directly (e.g. matching time slot or weekday or monthday)
+ while not conflict and _es is not None:
+ _is = to_dt(itip_event['start'])
+ _ie = to_dt(itip_event['end'])
+
+ while not conflict and _is is not None:
+ # log.debug("* Comparing event dates at %s/%s with %s/%s" % (_es, _ee, _is, _ie), level=9)
+ conflict = check_date_conflict(_es, _ee, _is, _ie)
+ _is = to_dt(itip_event['xml'].get_next_occurence(_is)) if kolab_event.is_recurring() else None
+ _ie = to_dt(itip_event['xml'].get_occurence_end_date(_is))
+
+ _es = to_dt(kolab_event.get_next_occurence(_es)) if kolab_event.is_recurring() else None
+ _ee = to_dt(kolab_event.get_occurence_end_date(_es))
+
+ return conflict
+
+
+def check_date_conflict(_es, _ee, _is, _ie):
+ """
+ Check the given event start/end dates for conflicts
+ """
+ conflict = False
+
+ # TODO: add margin for all-day dates (+13h; -12h)
+
+ if _es < _is:
+ if _es <= _ie:
+ if _ee <= _is:
+ conflict = False
+ else:
+ conflict = True
+ else:
+ conflict = True
+ elif _es == _is:
+ conflict = True
+ else: # _es > _is
+ if _es <= _ie:
+ conflict = True
+ else:
+ conflict = False
+
+ return conflict
+
+
+def send_reply(from_address, itip_events, response_text, subject=None):
+ """
+ Send the given iCal events as a valid iTip REPLY to the organizer.
+ """
+ import smtplib
+
+ conf = pykolab.getConf()
+ smtp = None
+
+ if isinstance(itip_events, dict):
+ itip_events = [ itip_events ]
+
+ for itip_event in itip_events:
+ attendee = itip_event['xml'].get_attendee_by_email(from_address)
+ participant_status = itip_event['xml'].get_ical_attendee_participant_status(attendee)
+
+ event_summary = itip_event['xml'].get_summary()
+ message_text = response_text % { 'summary':event_summary, 'status':participant_status_label(participant_status), 'name':attendee.get_name() }
+
+ if subject is not None:
+ subject = subject % { 'summary':event_summary, 'status':participant_status_label(participant_status), 'name':attendee.get_name() }
+
+ try:
+ message = itip_event['xml'].to_message_itip(from_address,
+ method="REPLY",
+ participant_status=participant_status,
+ message_text=message_text,
+ subject=subject
+ )
+ except Exception, e:
+ log.error(_("Failed to compose iTip reply message: %r") % (e))
+ return
+
+ smtp = smtplib.SMTP("localhost", 10026) # replies go through wallace again
+
+ if conf.debuglevel > 8:
+ smtp.set_debuglevel(True)
+
+ try:
+ smtp.sendmail(message['From'], message['To'], message.as_string())
+ except Exception, e:
+ log.error(_("SMTP sendmail error: %r") % (e))
+
+ if smtp:
+ smtp.quit()
+
+
+def send_request(to_address, itip_events, request_text, subject=None, direct=False):
+ """
+ Send an iTip REQUEST message from the given iCal events
+ """
+ import smtplib
+
+ conf = pykolab.getConf()
+ smtp = None
+
+ if isinstance(itip_events, dict):
+ itip_events = [ itip_events ]
+
+ for itip_event in itip_events:
+ event_summary = itip_event['xml'].get_summary()
+ message_text = request_text % { 'summary':event_summary }
+
+ if subject is not None:
+ subject = subject % { 'summary':event_summary }
+
+ try:
+ message = itip_event['xml'].to_message_itip(None,
+ method="REQUEST",
+ message_text=message_text,
+ subject=subject
+ )
+ except Exception, e:
+ log.error(_("Failed to compose iTip request message: %r") % (e))
+ return
+
+ port = 10027 if direct else 10026
+ smtp = smtplib.SMTP("localhost", port)
+
+ if conf.debuglevel > 8:
+ smtp.set_debuglevel(True)
+
+ try:
+ smtp.sendmail(message['From'], to_address, message.as_string())
+ except Exception, e:
+ log.error(_("SMTP sendmail error: %r") % (e))
+
+ if smtp:
+ smtp.quit()
diff --git a/pykolab/translate.py b/pykolab/translate.py
index bee8fc2..080cbc2 100644
--- a/pykolab/translate.py
+++ b/pykolab/translate.py
@@ -26,7 +26,10 @@ import gettext
import os
N_ = lambda x: x
-_ = lambda x: gettext.ldgettext(domain, x)
+_ = lambda x: current.lgettext(x)
+
+localedir = '/usr/local/share/locale'
+current = gettext.translation(domain, localedir, fallback=True)
def getDefaultLangs():
languages = []
@@ -45,3 +48,16 @@ def getDefaultLangs():
if nelang not in nelangs:
nelangs.append(nelang)
return nelangs
+
+def setUserLanguage(lang):
+ global current
+
+ langs = []
+ for l in gettext._expand_lang(lang):
+ if l not in langs:
+ langs.append(l)
+
+ try:
+ current = gettext.translation(domain, localedir, languages=langs, fallback=True)
+ except:
+ pass
diff --git a/pykolab/wap_client/__init__.py b/pykolab/wap_client/__init__.py
index 38a5381..7b2b565 100644
--- a/pykolab/wap_client/__init__.py
+++ b/pykolab/wap_client/__init__.py
@@ -67,8 +67,12 @@ def authenticate(username=None, password=None, domain=None):
response = request('POST', "system.authenticate", post=post)
+ if not response:
+ return False
+
if response.has_key('session_token'):
session_id = response['session_token']
+ return True
def connect():
global conn
@@ -125,6 +129,31 @@ def form_value_generate(params):
return request('POST', 'form_value.generate', post=post)
+def form_value_generate_password(*args, **kw):
+ return request('GET', 'form_value.generate_password')
+
+def form_value_list_options(object_type, object_type_id, attribute):
+ post = json.dumps(
+ {
+ 'object_type': object_type,
+ 'type_id': object_type_id,
+ 'attribute': attribute
+ }
+ )
+
+ return request('POST', 'form_value.list_options', post=post)
+
+def form_value_select_options(object_type, object_type_id, attribute):
+ post = json.dumps(
+ {
+ 'object_type': object_type,
+ 'type_id': object_type_id,
+ 'attributes': [ attribute ]
+ }
+ )
+
+ return request('POST', 'form_value.select_options', post=post)
+
def get_group_input():
group_types = group_types_list()
@@ -274,10 +303,18 @@ def group_form_value_generate_mail(params=None):
return request('POST', 'group_form_value.generate_mail', params)
-def group_info():
- group = utils.ask_question("Group email address")
- group = request('GET', 'group.info?group=%s' % (group))
- return group
+def group_find(params=None):
+ post = { 'search': { 'params': {} } }
+
+ for (k,v) in params.iteritems():
+ post['search']['params'][k] = { 'value': v, 'type': 'exact' }
+
+ return request('POST', 'group.find', post=json.dumps(post))
+
+def group_info(group=None):
+ if group == None:
+ group = utils.ask_question("group DN")
+ return request('GET', 'group.info', get={ 'id': group })
def group_members_list(group=None):
if group is None:
@@ -288,8 +325,32 @@ def group_members_list(group=None):
def group_types_list():
return request('GET', 'group_types.list')
-def groups_list():
- return request('GET', 'groups.list')
+def groups_list(params={}):
+ return request('POST', 'groups.list', post=json.dumps(params))
+
+def ou_add(params={}):
+ return request('POST', 'ou.add', post=json.dumps(params))
+
+def ou_delete(params={}):
+ return request('POST', 'ou.delete', post=json.dumps(params))
+
+def ou_find(params=None):
+ post = { 'search': { 'params': {} } }
+
+ for (k,v) in params.iteritems():
+ post['search']['params'][k] = { 'value': v, 'type': 'exact' }
+
+ return request('POST', 'ou.find', post=json.dumps(post))
+
+def ou_info(ou):
+ _params = { 'id': ou }
+
+ ou = request('GET', 'ou.info', get=_params)
+
+ return ou
+
+def ous_list(params={}):
+ return request('POST', 'ous.list', post=json.dumps(params))
def request(method, api_uri, get=None, post=None, headers={}):
response_data = request_raw(method, api_uri, get, post, headers)
@@ -298,7 +359,6 @@ def request(method, api_uri, get=None, post=None, headers={}):
del response_data['status']
return response_data['result']
else:
- print "ERROR: %r" % (response_data['reason'])
return False
def request_raw(method, api_uri, get=None, post=None, headers={}):
@@ -312,8 +372,6 @@ def request_raw(method, api_uri, get=None, post=None, headers={}):
if conf.debuglevel > 8:
conn.set_debuglevel(9)
- conn.set_debuglevel(9)
-
if get is not None:
_get = "?%s" % (urllib.urlencode(get))
else:
@@ -337,9 +395,111 @@ def request_raw(method, api_uri, get=None, post=None, headers={}):
return response_data
+def resource_add(params=None):
+ if params == None:
+ params = get_user_input()
+
+ return request('POST', 'resource.add', post=json.dumps(params))
+
+def resource_delete(params=None):
+ if params == None:
+ params = {
+ 'id': utils.ask_question("Resource DN to delete", "resource")
+ }
+
+ return request('POST', 'resource.delete', post=json.dumps(params))
+
+def resource_find(params=None):
+ post = { 'search': { 'params': {} } }
+
+ for (k,v) in params.iteritems():
+ post['search']['params'][k] = { 'value': v, 'type': 'exact' }
+
+ return request('POST', 'resource.find', post=json.dumps(post))
+
+def resource_info(resource=None):
+ if resource == None:
+ resource = utils.ask_question("Resource DN")
+ return request('GET', 'resource.info', get={ 'id': resource })
+
+def resource_types_list():
+ return request('GET', 'resource_types.list')
+
+def resources_list(params={}):
+ return request('POST', 'resources.list', post=json.dumps(params))
+
+def role_add(params=None):
+ if params is None:
+ role_name = utils.ask_question("Role name")
+ params = {
+ 'cn': role_name
+ }
+
+ params = json.dumps(params)
+
+ return request('POST', 'role.add', params)
+
def role_capabilities():
return request('GET', 'role.capabilities')
+def role_delete(params=None):
+ if params is None:
+ role_name = utils.ask_question("Role name")
+ role = role_find_by_attribute({'cn': role_name})
+ params = {
+ 'role': role.keys()[0]
+ }
+
+ if not params.has_key('role'):
+ role = role_find_by_attribute(params)
+ params = {
+ 'role': role.keys()[0]
+ }
+
+ post = json.dumps(params)
+
+ return request('POST', 'role.delete', post=post)
+
+def role_find_by_attribute(params=None):
+ if params is None:
+ role_name = utils.ask_question("Role name")
+ else:
+ role_name = params['cn']
+
+ get = { 'cn': role_name }
+ role = request('GET', 'role.find_by_attribute', get=get)
+
+ return role
+
+def role_info(role_name):
+ role = role_find_by_attribute({'cn': role_name})
+
+ get = { 'role': role['id'] }
+
+ role = request('GET', 'role.info', get=get)
+
+ return role
+
+def roles_list():
+ return request('GET', 'roles.list')
+
+def sharedfolder_add(params=None):
+ if params == None:
+ params = get_user_input()
+
+ return request('POST', 'sharedfolder.add', post=json.dumps(params))
+
+def sharedfolder_delete(params=None):
+ if params == None:
+ params = {
+ 'id': utils.ask_question("Shared Folder DN to delete", "sharedfolder")
+ }
+
+ return request('POST', 'sharedfolder.delete', post=json.dumps(params))
+
+def sharedfolders_list(params={}):
+ return request('POST', 'sharedfolders.list', post=json.dumps(params))
+
def system_capabilities():
return request('GET', 'system.capabilities')
@@ -425,83 +585,6 @@ def user_form_value_generate(params=None):
return request('POST', 'form_value.generate', post=post)
-def form_value_generate_password(*args, **kw):
- return request('GET', 'form_value.generate_password')
-
-def form_value_list_options(object_type, object_type_id, attribute):
- post = json.dumps(
- {
- 'object_type': object_type,
- 'type_id': object_type_id,
- 'attribute': attribute
- }
- )
-
- return request('POST', 'form_value.list_options', post=post)
-
-def form_value_select_options(object_type, object_type_id, attribute):
- post = json.dumps(
- {
- 'object_type': object_type,
- 'type_id': object_type_id,
- 'attributes': [ attribute ]
- }
- )
-
- return request('POST', 'form_value.select_options', post=post)
-
-def role_find_by_attribute(params=None):
- if params is None:
- role_name = utils.ask_question("Role name")
- else:
- role_name = params['cn']
-
- get = { 'cn': role_name }
- role = request('GET', 'role.find_by_attribute', get=get)
-
- return role
-
-def role_add(params=None):
- if params is None:
- role_name = utils.ask_question("Role name")
- params = {
- 'cn': role_name
- }
-
- params = json.dumps(params)
-
- return request('POST', 'role.add', params)
-
-def role_delete(params=None):
- if params is None:
- role_name = utils.ask_question("Role name")
- role = role_find_by_attribute({'cn': role_name})
- params = {
- 'role': role.keys()[0]
- }
-
- if not params.has_key('role'):
- role = role_find_by_attribute(params)
- params = {
- 'role': role.keys()[0]
- }
-
- post = json.dumps(params)
-
- return request('POST', 'role.delete', post=post)
-
-def role_info(role_name):
- role = role_find_by_attribute({'cn': role_name})
-
- get = { 'role': role['id'] }
-
- role = request('GET', 'role.info', get=get)
-
- return role
-
-def roles_list():
- return request('GET', 'roles.list')
-
def user_form_value_generate_uid(params=None):
if params is None:
params = get_user_input()
@@ -524,34 +607,8 @@ def user_info(user=None):
return user
+def users_list(params={}):
+ return request('POST', 'users.list', post=json.dumps(params))
+
def user_types_list():
return request('GET', 'user_types.list')
-
-def users_list():
- return request('GET', 'users.list')
-
-def resource_types_list():
- return request('GET', 'resource_types.list')
-
-def resources_list():
- return request('GET', 'resources.list')
-
-def resource_info(resource=None):
- if resource == None:
- resource = utils.ask_question("Resource DN")
- return request('GET', 'resource.info', get={ 'id': resource })
-
-def resource_add(params=None):
- if params == None:
- params = get_user_input()
-
- return request('POST', 'resource.add', post=json.dumps(params))
-
-def resource_delete(params=None):
- if params == None:
- params = {
- 'id': utils.ask_question("Resource DN to delete", "resource")
- }
-
- return request('POST', 'resource.delete', post=json.dumps(params))
-
diff --git a/pykolab/xml/__init__.py b/pykolab/xml/__init__.py
index 5ca2837..3e12716 100644
--- a/pykolab/xml/__init__.py
+++ b/pykolab/xml/__init__.py
@@ -1,14 +1,17 @@
from attendee import Attendee
from attendee import InvalidAttendeeParticipantStatusError
+from attendee import participant_status_label
from contact import Contact
from contact_reference import ContactReference
+from recurrence_rule import RecurrenceRule
from event import Event
from event import EventIntegrityError
from event import InvalidEventDateError
from event import event_from_ical
from event import event_from_string
+from event import event_from_message
from utils import to_dt
@@ -17,6 +20,7 @@ __all__ = [
"Contact",
"ContactReference",
"Event",
+ "RecurrenceRule",
"event_from_ical",
"event_from_string",
"to_dt",
diff --git a/pykolab/xml/attendee.py b/pykolab/xml/attendee.py
index 974af57..fe381ce 100644
--- a/pykolab/xml/attendee.py
+++ b/pykolab/xml/attendee.py
@@ -1,9 +1,30 @@
import kolabformat
from pykolab.translate import _
+from pykolab.translate import N_
from contact_reference import ContactReference
+participant_status_labels = {
+ "NEEDS-ACTION": N_("Needs Action"),
+ "ACCEPTED": N_("Accepted"),
+ "DECLINED": N_("Declined"),
+ "TENTATIVE": N_("Tentatively Accepted"),
+ "DELEGATED": N_("Delegated"),
+ "COMPLETED": N_("Completed"),
+ "IN-PROCESS": N_("In Process"),
+ # support integer values, too
+ kolabformat.PartNeedsAction: N_("Needs Action"),
+ kolabformat.PartAccepted: N_("Accepted"),
+ kolabformat.PartDeclined: N_("Declined"),
+ kolabformat.PartTentative: N_("Tentatively Accepted"),
+ kolabformat.PartDelegated: N_("Delegated"),
+ }
+
+def participant_status_label(status):
+ return _(participant_status_labels[status]) if participant_status_labels.has_key(status) else _(status)
+
+
class Attendee(kolabformat.Attendee):
cutype_map = {
"INDIVIDUAL": kolabformat.CutypeIndividual,
@@ -35,6 +56,13 @@ class Attendee(kolabformat.Attendee):
"FALSE": False,
}
+ properties_map = {
+ 'role': 'get_role',
+ 'rsvp': 'rsvp',
+ 'partstat': 'get_participant_status',
+ 'cutype': 'get_cutype',
+ }
+
def __init__(
self,
email,
@@ -76,6 +104,12 @@ class Attendee(kolabformat.Attendee):
if not participant_status == None:
self.set_participant_status(participant_status)
+ def copy_from(self, obj):
+ if isinstance(obj, kolabformat.Attendee):
+ kolabformat.Attendee.__init__(self, obj)
+ self.contactreference = ContactReference(obj.contact())
+ self.email = self.contactreference.get_email()
+
def delegate_from(self, delegators):
crefs = []
@@ -117,8 +151,11 @@ class Attendee(kolabformat.Attendee):
self.setDelegatedTo(list(set(crefs)))
- def get_cutype(self):
- return self.cutype()
+ def get_cutype(self, translated=False):
+ cutype = self.cutype()
+ if translated:
+ return self._translate_value(cutype, self.cutype_map)
+ return cutype
def get_delegated_from(self):
return self.delegatedFrom()
@@ -132,15 +169,30 @@ class Attendee(kolabformat.Attendee):
def get_name(self):
return self.contactreference.get_name()
- def get_participant_status(self):
- return self.partStat()
+ def get_displayname(self):
+ name = self.contactreference.get_name()
+ email = self.contactreference.get_email()
+ return "%s <%s>" % (name, email) if not name == "" else email
+
+ def get_participant_status(self, translated=False):
+ partstat = self.partStat()
+ if translated:
+ return self._translate_value(partstat, self.participant_status_map)
+ return partstat
- def get_role(self):
- return self.role()
+ def get_role(self, translated=False):
+ role = self.role()
+ if translated:
+ return self._translate_value(role, self.role_map)
+ return role
def get_rsvp(self):
return self.rsvp()
+ def _translate_value(self, val, map):
+ name_map = dict([(v, k) for (k, v) in map.iteritems()])
+ return name_map[val] if name_map.has_key(val) else 'UNKNOWN'
+
def set_cutype(self, cutype):
if cutype in self.cutype_map.keys():
self.setCutype(self.cutype_map[cutype])
@@ -151,6 +203,7 @@ class Attendee(kolabformat.Attendee):
def set_name(self, name):
self.contactreference.set_name(name)
+ self.setContact(self.contactreference)
def set_participant_status(self, participant_status):
if participant_status in self.participant_status_map.keys():
@@ -171,6 +224,22 @@ class Attendee(kolabformat.Attendee):
def set_rsvp(self, rsvp):
self.setRSVP(rsvp)
+ def to_dict(self):
+ data = self.contactreference.to_dict()
+ data.pop('type', None)
+
+ for p, getter in self.properties_map.iteritems():
+ val = None
+ args = {}
+ if hasattr(self, getter):
+ if getter.startswith('get_'):
+ args = dict(translated=True)
+ val = getattr(self, getter)(**args)
+ if val is not None:
+ data[p] = val
+
+ return data
+
def __str__(self):
return self.email
diff --git a/pykolab/xml/contact.py b/pykolab/xml/contact.py
index 2871201..c8410f5 100644
--- a/pykolab/xml/contact.py
+++ b/pykolab/xml/contact.py
@@ -39,5 +39,9 @@ class Contact(kolabformat.Contact):
def set_name(self, name):
self.setName(name)
+ def to_ditc(self):
+ # TODO: implement this
+ return dict(name=self.name())
+
def __str__(self):
return kolabformat.writeContact(self)
diff --git a/pykolab/xml/contact_reference.py b/pykolab/xml/contact_reference.py
index 87d7957..022e2e6 100644
--- a/pykolab/xml/contact_reference.py
+++ b/pykolab/xml/contact_reference.py
@@ -11,9 +11,18 @@ import kolabformat
"""
class ContactReference(kolabformat.ContactReference):
+ properties_map = {
+ 'email': 'email',
+ 'name': 'name',
+ 'type': 'type',
+ 'uid': 'uid',
+ }
+
def __init__(self, email=None):
if email is None:
kolabformat.ContactReference.__init__(self)
+ elif isinstance(email, kolabformat.ContactReference):
+ kolabformat.ContactReference.__init__(self, email.email(), email.name(), email.uid())
else:
kolabformat.ContactReference.__init__(self, email)
@@ -31,3 +40,15 @@ class ContactReference(kolabformat.ContactReference):
def set_name(self, name):
self.setName(name)
+
+ def to_dict(self):
+ data = dict()
+
+ for p, getter in self.properties_map.iteritems():
+ val = None
+ if hasattr(self, getter):
+ val = getattr(self, getter)()
+ if val is not None:
+ data[p] = val
+
+ return data
diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index 14128e2..83035d6 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -6,15 +6,20 @@ import kolabformat
import pytz
import time
import uuid
+import base64
+import re
import pykolab
from pykolab import constants
from pykolab import utils
from pykolab.xml import utils as xmlutils
+from pykolab.xml import participant_status_label
from pykolab.translate import _
+from os import path
from attendee import Attendee
from contact_reference import ContactReference
+from recurrence_rule import RecurrenceRule
log = pykolab.getLogger('pykolab.xml_event')
@@ -24,6 +29,21 @@ def event_from_ical(string):
def event_from_string(string):
return Event(from_string=string)
+def event_from_message(message):
+ event = None
+ if message.is_multipart():
+ for part in message.walk():
+ if part.get_content_type() == "application/calendar+xml":
+ payload = part.get_payload(decode=True)
+ event = event_from_string(payload)
+
+ # append attachment parts to Event object
+ elif event and part.has_key('Content-ID'):
+ event._attachment_parts.append(part)
+
+ return event
+
+
class Event(object):
status_map = {
"TENTATIVE": kolabformat.StatusTentative,
@@ -31,27 +51,81 @@ class Event(object):
"CANCELLED": kolabformat.StatusCancelled,
}
+ classification_map = {
+ "PUBLIC": kolabformat.ClassPublic,
+ "PRIVATE": kolabformat.ClassPrivate,
+ "CONFIDENTIAL": kolabformat.ClassConfidential,
+ }
+
+ alarm_type_map = {
+ 'EMAIL': kolabformat.Alarm.EMailAlarm,
+ 'DISPLAY': kolabformat.Alarm.DisplayAlarm,
+ 'AUDIO': kolabformat.Alarm.AudioAlarm
+ }
+
+ related_map = {
+ 'START': kolabformat.Start,
+ 'END': kolabformat.End
+ }
+
+ properties_map = {
+ # property: getter
+ "uid": "get_uid",
+ "created": "get_created",
+ "lastmodified-date": "get_lastmodified",
+ "sequence": "sequence",
+ "classification": "get_classification",
+ "categories": "categories",
+ "start": "get_start",
+ "end": "get_end",
+ "duration": "get_duration",
+ "transparency": "transparency",
+ "rrule": "recurrenceRule",
+ "rdate": "recurrenceDates",
+ "exdate": "exceptionDates",
+ "recurrence-id": "recurrenceID",
+ "summary": "summary",
+ "description": "description",
+ "priority": "priority",
+ "status": "get_status",
+ "location": "location",
+ "organizer": "organizer",
+ "attendee": "get_attendees",
+ "attach": "attachments",
+ "url": "url",
+ "alarm": "alarms",
+ "x-custom": "customProperties",
+ # TODO: add to_dict() support for these
+ # "exception": "exceptions",
+ }
+
def __init__(self, from_ical="", from_string=""):
self._attendees = []
self._categories = []
+ self._attachment_parts = []
if from_ical == "":
if from_string == "":
self.event = kolabformat.Event()
else:
self.event = kolabformat.readEvent(from_string, False)
+ self._load_attendees()
else:
self.from_ical(from_ical)
self.uid = self.get_uid()
+ def _load_attendees(self):
+ for a in self.event.attendees():
+ self._attendees.append(Attendee(a.contact().email(), a.contact().name(), a.rsvp(), a.role(), a.partStat(), a.cutype()))
+
def add_attendee(self, email, name=None, rsvp=False, role=None, participant_status=None, cutype="INDIVIDUAL", params=None):
attendee = Attendee(email, name, rsvp, role, participant_status, cutype, params)
self._attendees.append(attendee)
self.event.setAttendees(self._attendees)
def add_category(self, category):
- self._categories.append(category)
+ self._categories.append(str(category))
self.event.setCategories(self._categories)
def add_exception_date(self, _datetime):
@@ -97,29 +171,35 @@ class Event(object):
# NOTE: Make sure to set() or duplicates may arise
for attr in set(event.singletons):
- if hasattr(self, 'get_ical_%s' % (attr.lower())):
- retval = getattr(self, "get_ical_%s" % attr.lower())()
+ ical_getter = 'get_ical_%s' % (attr.lower())
+ default_getter = 'get_%s' % (attr.lower())
+ retval = None
+ if hasattr(self, ical_getter):
+ retval = getattr(self, ical_getter)()
if retval:
event.add(attr.lower(), retval)
-
- elif hasattr(self, 'get_%s' % (attr.lower())):
- retval = getattr(self, "get_%s" % attr.lower())()
+ elif hasattr(self, default_getter):
+ retval = getattr(self, default_getter)()
if retval:
event.add(attr.lower(), retval, encode=0)
# NOTE: Make sure to set() or duplicates may arise
for attr in set(event.multiple):
- if hasattr(self, 'get_ical_%s' % (attr.lower())):
- retval = getattr(self, "get_ical_%s" % attr.lower())()
- if isinstance(retval, list) and retval:
- for _retval in retval:
- event.add(attr.lower(), _retval, encode=0)
-
- elif hasattr(self, 'get_%s' % (attr.lower())):
- retval = getattr(self, "get_%s" % attr.lower())()
- if isinstance(retval, list) and retval:
- for _retval in retval:
- event.add(attr.lower(), _retval, encode=0)
+ ical_getter = 'get_ical_%s' % (attr.lower())
+ default_getter = 'get_%s' % (attr.lower())
+ retval = None
+ if hasattr(self, ical_getter):
+ retval = getattr(self, ical_getter)()
+ elif hasattr(self, default_getter):
+ retval = getattr(self, default_getter)()
+
+ if isinstance(retval, list) and retval:
+ for _retval in retval:
+ event.add(attr.lower(), _retval, encode=0)
+
+ # copy custom properties to iCal
+ for cs in self.event.customProperties():
+ event.add(cs.identifier, cs.value)
cal.add_component(event)
@@ -161,12 +241,18 @@ class Event(object):
self.event.setAttendees(self._attendees)
def from_ical(self, ical):
- self.event = kolabformat.Event()
if hasattr(icalendar.Event, 'from_ical'):
ical_event = icalendar.Event.from_ical(ical)
elif hasattr(icalendar.Event, 'from_string'):
ical_event = icalendar.Event.from_string(ical)
+ # use the libkolab calendaring bindings to load the full iCal data
+ if ical_event.has_key('RRULE') or ical_event.has_key('ATTACH') \
+ or [part for part in ical_event.walk() if part.name == 'VALARM']:
+ self._xml_from_ical(ical)
+ else:
+ self.event = kolabformat.Event()
+
# TODO: Clause the timestamps for zulu suffix causing datetime.datetime
# to fail substitution.
for attr in set(ical_event.required):
@@ -183,13 +269,10 @@ class Event(object):
if ical_event.has_key(attr):
self.set_from_ical(attr.lower(), ical_event[attr])
- # HACK: use calendaring::EventCal::fromICal() to parse RRULEs
- if ical_event.has_key('RRULE'):
- from kolab.calendaring import EventCal
- event_xml = EventCal()
- event_xml.fromICal("BEGIN:VCALENDAR\nVERSION:2.0\n" + ical + "\nEND:VCALENDAR")
- self.event.setRecurrenceRule(event_xml.recurrenceRule())
- self.event.setExceptionDates(event_xml.exceptionDates())
+ def _xml_from_ical(self, ical):
+ from kolab.calendaring import EventCal
+ self.event = EventCal()
+ self.event.fromICal("BEGIN:VCALENDAR\nVERSION:2.0\n" + ical + "\nEND:VCALENDAR")
def get_attendee_participant_status(self, attendee):
return attendee.get_participant_status()
@@ -229,20 +312,26 @@ class Event(object):
return self._attendees
def get_categories(self):
- return self.event.categories()
+ return [str(c) for c in self.event.categories()]
def get_classification(self):
- return self.classification()
+ return self.event.classification()
def get_created(self):
try:
- return xmlutils.from_cdatetime(self.event.created(), False)
+ return xmlutils.from_cdatetime(self.event.created(), True)
except ValueError:
return datetime.datetime.now()
def get_description(self):
return self.event.description()
+ def get_comment(self):
+ if hasattr(self.event, 'comment'):
+ return self.event.comment()
+ else:
+ return None
+
def get_duration(self):
duration = self.event.duration()
if duration and duration.isValid():
@@ -265,9 +354,50 @@ class Event(object):
dt = self.get_start() + duration
return dt
+ def get_date_text(self, date_format='%Y-%m-%d', time_format='%H:%M %Z'):
+ start = self.get_start()
+ end = self.get_end()
+ all_day = not hasattr(start, 'date')
+ start_date = start.date() if not all_day else start
+ end_date = end.date() if not all_day else end
+
+ if start_date == end_date:
+ end_format = time_format
+ else:
+ end_format = date_format + " " + time_format
+
+ if all_day:
+ time_format = ''
+ if start_date == end_date:
+ return start.strftime(date_format)
+
+ return "%s - %s" % (start.strftime(date_format + " " + time_format), end.strftime(end_format))
+
def get_exception_dates(self):
return map(lambda _: xmlutils.from_cdatetime(_, True), self.event.exceptionDates())
+ def get_attachments(self):
+ return self.event.attachments()
+
+ def get_attachment_data(self, i):
+ vattach = self.event.attachments()
+ if i < len(vattach):
+ attachment = vattach[i]
+ uri = attachment.uri()
+ if uri and uri[0:4] == 'cid:':
+ # get data from MIME part with matching content-id
+ cid = '<' + uri[4:] + '>'
+ for p in self._attachment_parts:
+ if p['Content-ID'] == cid:
+ return p.get_payload(decode=True)
+ else:
+ return attachment.data()
+
+ return None
+
+ def get_alarms(self):
+ return self.event.alarms()
+
def get_ical_attendee(self):
# TODO: Formatting, aye? See also the example snippet:
#
@@ -353,7 +483,11 @@ class Event(object):
return self.get_created()
def get_ical_dtend(self):
- return self.get_end()
+ dtend = self.get_end()
+ # shift end by one day on all-day events
+ if not hasattr(dtend, 'hour'):
+ dtend = dtend + datetime.timedelta(days=1)
+ return dtend
def get_ical_dtstamp(self):
try:
@@ -382,12 +516,20 @@ class Event(object):
if status in self.status_map.keys():
return status
- if status in self.status_map.values():
- return [k for k, v in self.status_map.iteritems() if v == status][0]
+ return self._translate_value(status, self.status_map) if status else None
def get_ical_sequence(self):
return str(self.event.sequence()) if self.event.sequence() else None
+ def get_ical_comment(self):
+ comment = self.get_comment()
+ if comment is not None:
+ return [ comment ]
+ return None
+
+ def get_location(self):
+ return self.event.location()
+
def get_lastmodified(self):
try:
_datetime = self.event.lastModified()
@@ -396,7 +538,7 @@ class Event(object):
except:
self.__str__()
- return xmlutils.from_cdatetime(self.event.lastModified(), False)
+ return xmlutils.from_cdatetime(self.event.lastModified(), True)
def get_organizer(self):
organizer = self.event.organizer()
@@ -408,11 +550,12 @@ class Event(object):
def get_start(self):
return xmlutils.from_cdatetime(self.event.start(), True)
- def get_status(self):
+ def get_status(self, translated=False):
status = self.event.status()
- for key in self.status_map.keys():
- if self.status_map[key] == status:
- return key
+ if translated:
+ return self._translate_value(status, self.status_map) if status else None
+
+ return status
def get_summary(self):
return self.event.summary()
@@ -428,7 +571,13 @@ class Event(object):
def get_sequence(self):
return self.event.sequence()
- def set_attendee_participant_status(self, attendee, status):
+ def get_url(self):
+ return self.event.url()
+
+ def get_transparency(self):
+ return self.event.transparency()
+
+ def set_attendee_participant_status(self, attendee, status, rsvp=None):
"""
Set the participant status of an attendee to status.
@@ -437,12 +586,20 @@ class Event(object):
attendees for this event.
"""
attendee = self.get_attendee(attendee)
-
attendee.set_participant_status(status)
+
+ if rsvp is not None:
+ attendee.set_rsvp(rsvp)
+
self.event.setAttendees(self._attendees)
def set_classification(self, classification):
- self.event.setClassification(classification)
+ if classification in self.classification_map.keys():
+ self.event.setClassification(self.classification_map[classification])
+ elif classification in self.classification_map.values():
+ self.event.setClassification(status)
+ else:
+ raise ValueError, _("Invalid classification %r") % (classification)
def set_created(self, _datetime=None):
if _datetime is None:
@@ -451,7 +608,11 @@ class Event(object):
self.event.setCreated(xmlutils.to_cdatetime(_datetime, False))
def set_description(self, description):
- self.event.setDescription(description)
+ self.event.setDescription(str(description))
+
+ def set_comment(self, comment):
+ if hasattr(self.event, 'setComment'):
+ self.event.setComment(str(comment))
def set_dtstamp(self, _datetime):
self.event.setLastModified(xmlutils.to_cdatetime(_datetime, False))
@@ -477,27 +638,36 @@ class Event(object):
for _datetime in _datetimes:
self.add_exception_date(_datetime)
+ def add_custom_property(self, name, value):
+ if not name.upper().startswith('X-'):
+ raise ValueError, _("Invalid custom property name %r") % (name)
+
+ props = self.event.customProperties()
+ props.append(kolabformat.CustomProperty(name.upper(), value))
+ self.event.setCustomProperties(props)
+
def set_from_ical(self, attr, value):
+ ical_setter = 'set_ical_' + attr
+ default_setter = 'set_' + attr
+
if attr == "dtend":
self.set_ical_dtend(value.dt)
elif attr == "dtstart":
self.set_ical_dtstart(value.dt)
- elif attr == "duration":
- self.set_ical_duration(value)
- elif attr == "status":
- self.set_ical_status(value)
- elif attr == "summary":
- self.set_ical_summary(value)
- elif attr == "priority":
- self.set_ical_priority(value)
- elif attr == "sequence":
- self.set_ical_sequence(value)
- elif attr == "attendee":
- self.set_ical_attendee(value)
- elif attr == "organizer":
- self.set_ical_organizer(value)
- elif attr == "uid":
- self.set_ical_uid(value)
+ elif attr == "dtstamp":
+ self.set_ical_dtstamp(value.dt)
+ elif attr == "created":
+ self.set_created(value.dt)
+ elif attr == "lastmodified":
+ self.set_lastmodified(value.dt)
+ elif attr == "categories":
+ self.add_category(value)
+ elif attr == "class":
+ self.set_classification(value)
+ elif hasattr(self, ical_setter):
+ getattr(self, ical_setter)(value)
+ elif hasattr(self, default_setter):
+ getattr(self, default_setter)(value)
def set_ical_attendee(self, _attendee):
if isinstance(_attendee, basestring):
@@ -540,6 +710,9 @@ class Event(object):
att = self.add_attendee(address, name=name, rsvp=rsvp, role=role, participant_status=partstat, cutype=cutype, params=params)
def set_ical_dtend(self, dtend):
+ # shift end by one day on all-day events
+ if not hasattr(dtend, 'hour'):
+ dtend = dtend - datetime.timedelta(days=1)
self.set_end(dtend)
def set_ical_dtstamp(self, dtstamp):
@@ -548,6 +721,9 @@ class Event(object):
def set_ical_dtstart(self, dtstart):
self.set_start(dtstart)
+ def set_ical_lastmodified(self, lastmod):
+ self.set_lastmodified(lastmod)
+
def set_ical_duration(self, value):
if value.dt:
duration = kolabformat.Duration(value.dt.days, 0, 0, value.dt.seconds, False)
@@ -574,14 +750,6 @@ class Event(object):
def set_ical_sequence(self, sequence):
self.set_sequence(sequence)
- def set_ical_status(self, status):
- if status in self.status_map.keys():
- self.event.setStatus(self.status_map[status])
- elif status in self.status_map.values():
- self.event.setStatus(status)
- else:
- raise ValueError, _("Invalid status %r") % (status)
-
def set_ical_summary(self, summary):
self.set_summary(str(summary))
@@ -606,7 +774,7 @@ class Event(object):
self.event.setLastModified(xmlutils.to_cdatetime(_datetime, False))
def set_location(self, location):
- self.event.setLocation(location)
+ self.event.setLocation(str(location))
def set_organizer(self, email, name=None):
contactreference = ContactReference(email)
@@ -621,6 +789,9 @@ class Event(object):
def set_sequence(self, sequence):
self.event.setSequence(int(sequence))
+ def set_url(self, url):
+ self.event.setUrl(str(url))
+
def set_recurrence(self, recurrence):
self.event.setRecurrenceRule(recurrence)
@@ -657,8 +828,12 @@ class Event(object):
self.event.setSummary(summary)
def set_uid(self, uid):
+ self.uid = uid
self.event.setUid(str(uid))
+ def set_transparency(self, transp):
+ return self.event.setTransparency(transp)
+
def __str__(self):
event_xml = kolabformat.writeEvent(self.event)
@@ -669,6 +844,72 @@ class Event(object):
else:
raise EventIntegrityError, kolabformat.errorMessage()
+ def to_dict(self):
+ data = dict()
+
+ for p, getter in self.properties_map.iteritems():
+ val = None
+ if hasattr(self, getter):
+ val = getattr(self, getter)()
+ elif hasattr(self.event, getter):
+ val = getattr(self.event, getter)()
+
+ if isinstance(val, kolabformat.cDateTime):
+ val = xmlutils.from_cdatetime(val, True)
+ elif isinstance(val, kolabformat.vectordatetime):
+ val = [xmlutils.from_cdatetime(x, True) for x in val]
+ elif isinstance(val, kolabformat.vectors):
+ val = [str(x) for x in val]
+ elif isinstance(val, kolabformat.vectorcs):
+ for x in val:
+ data[x.identifier] = x.value
+ val = None
+ elif isinstance(val, kolabformat.ContactReference):
+ val = ContactReference(val).to_dict()
+ elif isinstance(val, kolabformat.RecurrenceRule):
+ val = RecurrenceRule(val).to_dict()
+ elif isinstance(val, kolabformat.vectorattachment):
+ val = [dict(fmttype=x.mimetype(), label=x.label(), uri=x.uri()) for x in val]
+ elif isinstance(val, kolabformat.vectoralarm):
+ val = [self._alarm_to_dict(x) for x in val]
+ elif isinstance(val, list):
+ val = [x.to_dict() for x in val if hasattr(x, 'to_dict')]
+
+ if val is not None:
+ data[p] = val
+
+ return data
+
+ def _alarm_to_dict(self, alarm):
+ ret = dict(
+ action=self._translate_value(alarm.type(), self.alarm_type_map),
+ summary=alarm.summary(),
+ description=alarm.description(),
+ trigger=None
+ )
+
+ start = alarm.start()
+ if start and start.isValid():
+ ret['trigger'] = xmlutils.from_cdatetime(start, True)
+ else:
+ ret['trigger'] = dict(related=self._translate_value(alarm.relativeTo(), self.related_map))
+ duration = alarm.relativeStart()
+ if duration and duration.isValid():
+ prefix = '-' if duration.isNegative() else '+'
+ value = prefix + "P%dW%dDT%dH%dM%dS" % (
+ duration.weeks(), duration.days(), duration.hours(), duration.minutes(), duration.seconds()
+ )
+ ret['trigger']['value'] = re.sub(r"T$", '', re.sub(r"0[WDHMS]", '', value))
+
+ if alarm.type() == kolabformat.Alarm.EMailAlarm:
+ ret['attendee'] = [ContactReference(a).to_dict() for a in alarm.attendees()]
+
+ return ret
+
+ def _translate_value(self, val, map):
+ name_map = dict([(v, k) for (k, v) in map.iteritems()])
+ return name_map[val] if name_map.has_key(val) else 'UNKNOWN'
+
def to_message(self):
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
@@ -706,6 +947,39 @@ class Event(object):
msg["Subject"] = self.get_uid()
+ # extract attachment data into separate MIME parts
+ vattach = self.event.attachments()
+ i = 0
+ for attach in vattach:
+ if attach.uri():
+ continue
+
+ mimetype = attach.mimetype()
+ (primary, seconday) = mimetype.split('/')
+ name = attach.label()
+ if not name:
+ name = 'unknown.x'
+
+ (basename, suffix) = path.splitext(name)
+ t = datetime.datetime.now()
+ cid = "%s.%s.%s%s" % (basename, time.mktime(t.timetuple()), t.microsecond + len(self._attachment_parts), suffix)
+
+ p = MIMEBase(primary, seconday)
+ p.add_header('Content-Disposition', 'attachment', filename=name)
+ p.add_header('Content-Transfer-Encoding', 'base64')
+ p.add_header('Content-ID', '<' + cid + '>')
+ p.set_payload(base64.b64encode(attach.data()))
+
+ self._attachment_parts.append(p)
+
+ # modify attachment object
+ attach.setData('', mimetype)
+ attach.setUri('cid:' + cid, mimetype)
+ vattach[i] = attach
+ i += 1
+
+ self.event.setAttachments(vattach)
+
part.set_payload(str(self))
part.add_header('Content-Disposition', 'attachment; filename="kolab.xml"')
@@ -713,6 +987,10 @@ class Event(object):
msg.attach(part)
+ # append attachment parts
+ for p in self._attachment_parts:
+ msg.attach(p)
+
return msg
def to_message_itip(self, from_address, method="REQUEST", participant_status="ACCEPTED", subject=None, message_text=None):
@@ -725,6 +1003,7 @@ class Event(object):
msg = MIMEMultipart()
msg_from = None
+ attendees = None
if method == "REPLY":
# TODO: Make user friendly name <email>
@@ -737,6 +1016,7 @@ class Event(object):
if attendee.get_email() == from_address:
# Only the attendee is supposed to be listed in a reply
attendee.set_participant_status(participant_status)
+ attendee.set_rsvp(False)
self._attendees = [attendee]
self.event.setAttendees(self._attendees)
@@ -779,7 +1059,7 @@ class Event(object):
msg['Date'] = formatdate(localtime=True)
if subject is None:
- subject = _("Reservation Request for %s was %s") % (self.get_summary(), _(participant_status))
+ subject = _("Invitation for %s was %s") % (self.get_summary(), participant_status_label(participant_status))
msg["Subject"] = subject
@@ -798,6 +1078,12 @@ class Event(object):
msg.attach(part)
+ # restore the original list of attendees
+ # attendees being reduced to the replying attendee above
+ if attendees is not None:
+ self._attendees = attendees
+ self.event.setAttendees(self._attendees)
+
return msg
def is_recurring(self):
diff --git a/pykolab/xml/recurrence_rule.py b/pykolab/xml/recurrence_rule.py
new file mode 100644
index 0000000..4a0b6c5
--- /dev/null
+++ b/pykolab/xml/recurrence_rule.py
@@ -0,0 +1,118 @@
+import kolabformat
+from pykolab.xml import utils as xmlutils
+
+"""
+ def setFrequency(self, *args): return _kolabformat.RecurrenceRule_setFrequency(self, *args)
+ def frequency(self): return _kolabformat.RecurrenceRule_frequency(self)
+ def setWeekStart(self, *args): return _kolabformat.RecurrenceRule_setWeekStart(self, *args)
+ def weekStart(self): return _kolabformat.RecurrenceRule_weekStart(self)
+ def setEnd(self, *args): return _kolabformat.RecurrenceRule_setEnd(self, *args)
+ def end(self): return _kolabformat.RecurrenceRule_end(self)
+ def setCount(self, *args): return _kolabformat.RecurrenceRule_setCount(self, *args)
+ def count(self): return _kolabformat.RecurrenceRule_count(self)
+ def setInterval(self, *args): return _kolabformat.RecurrenceRule_setInterval(self, *args)
+ def interval(self): return _kolabformat.RecurrenceRule_interval(self)
+ def setBysecond(self, *args): return _kolabformat.RecurrenceRule_setBysecond(self, *args)
+ def bysecond(self): return _kolabformat.RecurrenceRule_bysecond(self)
+ def setByminute(self, *args): return _kolabformat.RecurrenceRule_setByminute(self, *args)
+ def byminute(self): return _kolabformat.RecurrenceRule_byminute(self)
+ def setByhour(self, *args): return _kolabformat.RecurrenceRule_setByhour(self, *args)
+ def byhour(self): return _kolabformat.RecurrenceRule_byhour(self)
+ def setByday(self, *args): return _kolabformat.RecurrenceRule_setByday(self, *args)
+ def byday(self): return _kolabformat.RecurrenceRule_byday(self)
+ def setBymonthday(self, *args): return _kolabformat.RecurrenceRule_setBymonthday(self, *args)
+ def bymonthday(self): return _kolabformat.RecurrenceRule_bymonthday(self)
+ def setByyearday(self, *args): return _kolabformat.RecurrenceRule_setByyearday(self, *args)
+ def byyearday(self): return _kolabformat.RecurrenceRule_byyearday(self)
+ def setByweekno(self, *args): return _kolabformat.RecurrenceRule_setByweekno(self, *args)
+ def byweekno(self): return _kolabformat.RecurrenceRule_byweekno(self)
+ def setBymonth(self, *args): return _kolabformat.RecurrenceRule_setBymonth(self, *args)
+ def bymonth(self): return _kolabformat.RecurrenceRule_bymonth(self)
+ def isValid(self): return _kolabformat.RecurrenceRule_isValid(self)
+"""
+
+class RecurrenceRule(kolabformat.RecurrenceRule):
+ frequency_map = {
+ None: kolabformat.RecurrenceRule.FreqNone,
+ "YEARLY": kolabformat.RecurrenceRule.Yearly,
+ "MONTHLY": kolabformat.RecurrenceRule.Monthly,
+ "WEEKLY": kolabformat.RecurrenceRule.Weekly,
+ "DAILY": kolabformat.RecurrenceRule.Daily,
+ "HOURLY": kolabformat.RecurrenceRule.Hourly,
+ "MINUTELY": kolabformat.RecurrenceRule.Minutely,
+ "SECONDLY": kolabformat.RecurrenceRule.Secondly
+ }
+
+ weekday_map = {
+ "MO": kolabformat.Monday,
+ "TU": kolabformat.Tuesday,
+ "WE": kolabformat.Wednesday,
+ "TH": kolabformat.Thursday,
+ "FR": kolabformat.Friday,
+ "SA": kolabformat.Saturday,
+ "SU": kolabformat.Sunday
+ }
+
+ properties_map = {
+ 'frequency': 'get_frequency',
+ 'interval': 'interval',
+ 'count': 'count',
+ 'until': 'end',
+ 'bymonth': 'bymonth',
+ 'byday': 'byday',
+ 'bymonthday':'bymonthday',
+ 'byyearday': 'byyearday',
+ 'byweekno': 'byweekno',
+ 'byhour': 'byhour',
+ 'byminute': 'byminute',
+ 'wkst': 'get_weekstart'
+ }
+
+ def __init__(self, rrule=None):
+ if rrule == None:
+ kolabformat.RecurrenceRule.__init__(self)
+ else:
+ kolabformat.RecurrenceRule.__init__(self, rrule)
+
+ def get_frequency(self, translated=False):
+ freq = self.frequency()
+ if translated:
+ return self._translate_value(freq, self.frequency_map)
+ return freq
+
+ def get_weekstart(self, translated=False):
+ wkst = self.weekStart()
+ if translated:
+ return self._translate_value(wkst, self.weekday_map)
+ return wkst
+
+ def _translate_value(self, val, map):
+ name_map = dict([(v, k) for (k, v) in map.iteritems()])
+ return name_map[val] if name_map.has_key(val) else 'UNKNOWN'
+
+ def to_dict(self):
+ if not self.isValid() or self.frequency() == kolabformat.RecurrenceRule.FreqNone:
+ return None
+
+ data = dict()
+
+ for p, getter in self.properties_map.iteritems():
+ val = None
+ args = {}
+ if hasattr(self, getter):
+ if getter.startswith('get_'):
+ args = dict(translated=True)
+ if hasattr(self, getter):
+ val = getattr(self, getter)(**args)
+ if isinstance(val, kolabformat.cDateTime):
+ val = xmlutils.from_cdatetime(val, True)
+ elif isinstance(val, kolabformat.vectori):
+ val = ",".join([int(v) for x in val])
+ elif isinstance(val, kolabformat.vectordaypos):
+ val = ",".join(["%s%s" % (str(x.occurence()) if x.occurence() != 0 else '', self._translate_value(x.weekday(), self.weekday_map)) for x in val])
+ if val is not None:
+ data[p] = val
+
+ return data
+
+
diff --git a/tests/functional/resource_func.py b/tests/functional/resource_func.py
index 43aca96..ac80360 100644
--- a/tests/functional/resource_func.py
+++ b/tests/functional/resource_func.py
@@ -4,7 +4,7 @@ from pykolab import wap_client
conf = pykolab.getConf()
-def resource_add(type, cn, members=None, owner=None):
+def resource_add(type, cn, members=None, owner=None, **kw):
if type == None or type == '':
raise Exception
@@ -18,6 +18,8 @@ def resource_add(type, cn, members=None, owner=None):
'owner': owner
}
+ resource_details.update(kw)
+
result = wap_client.authenticate(conf.get('ldap', 'bind_dn'), conf.get('ldap', 'bind_pw'), conf.get('kolab', 'primary_domain'))
type_id = 0
diff --git a/tests/functional/test_wallace/test_005_resource_add.py b/tests/functional/test_wallace/test_005_resource_add.py
index 2de60fb..fc7f3ed 100644
--- a/tests/functional/test_wallace/test_005_resource_add.py
+++ b/tests/functional/test_wallace/test_005_resource_add.py
@@ -29,8 +29,8 @@ class TestResourceAdd(unittest.TestCase):
funcs.purge_resources()
self.audi = funcs.resource_add("car", "Audi A4")
self.passat = funcs.resource_add("car", "VW Passat")
- self.boxter = funcs.resource_add("car", "Porsche Boxter S")
- self.cars = funcs.resource_add("collection", "Company Cars", [ self.audi['dn'], self.passat['dn'], self.boxter['dn'] ])
+ self.boxter = funcs.resource_add("car", "Porsche Boxter S", kolabinvitationpolicy='ACT_ACCEPT_AND_NOTIFY')
+ self.cars = funcs.resource_add("collection", "Company Cars", [ self.audi['dn'], self.passat['dn'], self.boxter['dn'] ], kolabinvitationpolicy='ACT_ACCEPT')
from tests.functional.synchronize import synchronize_once
synchronize_once()
@@ -56,3 +56,16 @@ class TestResourceAdd(unittest.TestCase):
attrs = auth.get_entry_attributes(None, self.cars['dn'], ['*'])
self.assertIn('groupofuniquenames', attrs['objectclass'])
self.assertEqual(len(attrs['uniquemember']), 3)
+ self.assertEqual(attrs['kolabinvitationpolicy'], 'ACT_ACCEPT')
+
+ def test_003_get_resource_records(self):
+ resource_dns = module_resources.resource_record_from_email_address(self.cars['mail'])
+ self.assertEqual(resource_dns[0], self.cars['dn'])
+
+ resources = module_resources.get_resource_records(resource_dns)
+ self.assertEqual(len(resources), 4)
+
+ # check for (inherited) kolabinvitationpolicy values (bitmasks)
+ self.assertEqual(resources[self.cars['dn']]['kolabinvitationpolicy'], [module_resources.ACT_ACCEPT])
+ self.assertEqual(resources[self.audi['dn']]['kolabinvitationpolicy'], [module_resources.ACT_ACCEPT])
+ self.assertEqual(resources[self.boxter['dn']]['kolabinvitationpolicy'], [module_resources.ACT_ACCEPT_AND_NOTIFY])
diff --git a/tests/functional/test_wallace/test_005_resource_invitation.py b/tests/functional/test_wallace/test_005_resource_invitation.py
index 946cb5f..61b9402 100644
--- a/tests/functional/test_wallace/test_005_resource_invitation.py
+++ b/tests/functional/test_wallace/test_005_resource_invitation.py
@@ -8,6 +8,10 @@ import uuid
from pykolab.imap import IMAP
from wallace import module_resources
+from pykolab.translate import _
+from pykolab.xml import event_from_message
+from pykolab.xml import participant_status_label
+from pykolab.itip import events_from_message
from email import message_from_string
from twisted.trial import unittest
@@ -23,13 +27,14 @@ CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:%s
-DTSTAMP:20140213T1254140
+DTSTAMP:20140213T125414Z
DTSTART;TZID=Europe/London:%s
DTEND;TZID=Europe/London:%s
SUMMARY:test
DESCRIPTION:test
ORGANIZER;CN="Doe, John":mailto:john.doe@example.org
ATTENDEE;ROLE=REQ-PARTICIPANT;CUTYPE=RESOURCE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:%s
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;CN=Somebody Else:mailto:somebody@else.com
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR
@@ -43,7 +48,7 @@ CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:%s
-DTSTAMP:20140215T1254140
+DTSTAMP:20140215T125414Z
DTSTART;TZID=Europe/London:%s
DTEND;TZID=Europe/London:%s
SEQUENCE:2
@@ -90,7 +95,7 @@ CALSCALE:GREGORIAN
METHOD:CANCEL
BEGIN:VEVENT
UID:%s
-DTSTAMP:20140218T1254140
+DTSTAMP:20140218T125414Z
DTSTART;TZID=Europe/London:20120713T100000
DTEND;TZID=Europe/London:20120713T110000
SUMMARY:test
@@ -112,7 +117,7 @@ CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:%s
-DTSTAMP:20140213T1254140
+DTSTAMP:20140213T125414Z
DTSTART;VALUE=DATE:%s
DTEND;VALUE=DATE:%s
SUMMARY:test
@@ -133,7 +138,7 @@ CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:%s
-DTSTAMP:20140213T1254140
+DTSTAMP:20140213T125414Z
DTSTART;TZID=Europe/Zurich:%s
DTEND;TZID=Europe/Zurich:%s
RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=10
@@ -184,6 +189,8 @@ class TestResourceInvitation(unittest.TestCase):
@classmethod
def setup_class(self, *args, **kw):
+ self.itip_reply_subject = _("Reservation Request for %(summary)s was %(status)s")
+
from tests.functional.purge_users import purge_users
purge_users()
@@ -213,9 +220,12 @@ class TestResourceInvitation(unittest.TestCase):
self.boxter = funcs.resource_add("car", "Porsche Boxter S")
self.cars = funcs.resource_add("collection", "Company Cars", [ self.audi['dn'], self.passat['dn'], self.boxter['dn'] ])
- self.room1 = funcs.resource_add("confroom", "Room 101", owner=self.jane['dn'])
+ self.room1 = funcs.resource_add("confroom", "Room 101", owner=self.jane['dn'], kolabinvitationpolicy='ACT_ACCEPT_AND_NOTIFY')
self.room2 = funcs.resource_add("confroom", "Conference Room B-222")
- self.rooms = funcs.resource_add("collection", "Rooms", [ self.room1['dn'], self.room2['dn'] ], self.jane['dn'])
+ self.rooms = funcs.resource_add("collection", "Rooms", [ self.room1['dn'], self.room2['dn'] ], self.jane['dn'], kolabinvitationpolicy='ACT_ACCEPT_AND_NOTIFY')
+
+ self.room3 = funcs.resource_add("confroom", "CEOs Office 303")
+ self.viprooms = funcs.resource_add("collection", "VIP Rooms", [ self.room3['dn'] ], self.jane['dn'], kolabinvitationpolicy='ACT_MANUAL')
time.sleep(1)
from tests.functional.synchronize import synchronize_once
@@ -227,12 +237,14 @@ class TestResourceInvitation(unittest.TestCase):
smtp = smtplib.SMTP('localhost', 10026)
smtp.sendmail(from_addr, to_addr, mime_message % (to_addr, itip_payload))
+ smtp.quit()
- def send_itip_invitation(self, resource_email, start=None, allday=False, template=None):
+ def send_itip_invitation(self, resource_email, start=None, allday=False, template=None, uid=None):
if start is None:
start = datetime.datetime.now()
- uid = str(uuid.uuid4())
+ if uid is None:
+ uid = str(uuid.uuid4())
if allday:
default_template = itip_allday
@@ -328,17 +340,14 @@ class TestResourceInvitation(unittest.TestCase):
if uid and event_message['subject'] != uid:
continue
- for part in event_message.walk():
- if part.get_content_type() == "application/calendar+xml":
- payload = part.get_payload(decode=True)
- found = pykolab.xml.event_from_string(payload)
- break
-
+ found = event_from_message(event_message)
if found:
break
time.sleep(1)
+ imap.disconnect()
+
return found
def purge_mailbox(self, mailbox):
@@ -357,12 +366,10 @@ class TestResourceInvitation(unittest.TestCase):
def find_resource_by_email(self, email):
resource = None
- if (email.find(self.audi['mail']) >= 0):
- resource = self.audi
- if (email.find(self.passat['mail']) >= 0):
- resource = self.passat
- if (email.find(self.boxter['mail']) >= 0):
- resource = self.boxter
+ for r in [self.audi, self.passat, self.boxter, self.room1, self.room2]:
+ if (email.find(r['mail']) >= 0):
+ resource = r
+ break
return resource
@@ -379,7 +386,7 @@ class TestResourceInvitation(unittest.TestCase):
def test_002_invite_resource(self):
uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,7,13, 10,0,0))
- response = self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.audi['mail'])
self.assertIsInstance(response, email.message.Message)
event = self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid)
@@ -387,10 +394,11 @@ class TestResourceInvitation(unittest.TestCase):
self.assertEqual(event.get_summary(), "test")
+ # @depends test_002_invite_resource
def test_003_invite_resource_conflict(self):
uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,7,13, 12,0,0))
- response = self.check_message_received("Reservation Request for test was DECLINED", self.audi['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.audi['mail'])
self.assertIsInstance(response, email.message.Message)
self.assertEqual(self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid), None)
@@ -402,7 +410,7 @@ class TestResourceInvitation(unittest.TestCase):
uid = self.send_itip_invitation(self.cars['mail'], datetime.datetime(2014,7,13, 12,0,0))
# one of the collection members accepted the reservation
- accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(accept, email.message.Message)
delegatee = self.find_resource_by_email(accept['from'])
@@ -412,7 +420,7 @@ class TestResourceInvitation(unittest.TestCase):
self.assertIsInstance(self.check_resource_calendar_event(delegatee['kolabtargetfolder'], uid), pykolab.xml.Event)
# resource collection responds with a DELEGATED message
- response = self.check_message_received("Reservation Request for test was DELEGATED", self.cars['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DELEGATED') }, self.cars['mail'])
self.assertIsInstance(response, email.message.Message)
self.assertIn("ROLE=NON-PARTICIPANT;RSVP=FALSE", str(response))
@@ -422,13 +430,13 @@ class TestResourceInvitation(unittest.TestCase):
uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,4,1, 10,0,0))
- response = self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.audi['mail'])
self.assertIsInstance(response, email.message.Message)
self.purge_mailbox(self.john['mailbox'])
self.send_itip_update(self.audi['mail'], uid, datetime.datetime(2014,4,1, 12,0,0)) # conflict with myself
- response = self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.audi['mail'])
self.assertIsInstance(response, email.message.Message)
event = self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid)
@@ -443,13 +451,13 @@ class TestResourceInvitation(unittest.TestCase):
uid = self.send_itip_invitation(self.cars['mail'], datetime.datetime(2014,4,24, 12,0,0))
# one of the collection members accepted the reservation
- accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(accept, email.message.Message)
delegatee = self.find_resource_by_email(accept['from'])
# book that resource for the next day
self.send_itip_invitation(delegatee['mail'], datetime.datetime(2014,4,25, 14,0,0))
- accept2 = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept2 = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
# re-schedule first booking to a conflicting date
self.purge_mailbox(self.john['mailbox'])
@@ -457,7 +465,7 @@ class TestResourceInvitation(unittest.TestCase):
self.send_itip_update(delegatee['mail'], uid, datetime.datetime(2014,4,25, 12,0,0), template=update_template)
# expect response from another member of the initially delegated collection
- new_accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ new_accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(new_accept, email.message.Message)
new_delegatee = self.find_resource_by_email(new_accept['from'])
@@ -468,7 +476,7 @@ class TestResourceInvitation(unittest.TestCase):
self.assertIsInstance(event, pykolab.xml.Event)
# old resource responds with a DELEGATED message
- response = self.check_message_received("Reservation Request for test was DELEGATED", delegatee['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DELEGATED') }, delegatee['mail'])
self.assertIsInstance(response, email.message.Message)
# old reservation was removed from old delegate's calendar
@@ -489,7 +497,7 @@ class TestResourceInvitation(unittest.TestCase):
# make new reservation to the now free'd slot
self.send_itip_invitation(self.boxter['mail'], datetime.datetime(2014,5,1, 9,0,0))
- response = self.check_message_received("Reservation Request for test was ACCEPTED", self.boxter['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.boxter['mail'])
self.assertIsInstance(response, email.message.Message)
@@ -500,7 +508,7 @@ class TestResourceInvitation(unittest.TestCase):
uid = self.send_itip_invitation(self.cars['mail'], dt)
# wait for accept notification
- accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(accept, email.message.Message)
delegatee = self.find_resource_by_email(accept['from'])
@@ -511,12 +519,12 @@ class TestResourceInvitation(unittest.TestCase):
self.send_itip_update(delegatee['mail'], uid, dt, template=update_template)
# get response from delegatee
- accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(accept, email.message.Message)
self.assertIn(delegatee['mail'], accept['from'])
# no delegation response on updates
- self.assertEqual(self.check_message_received("Reservation Request for test was DELEGATED", self.cars['mail']), None)
+ self.assertEqual(self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DELEGATED') }, self.cars['mail']), None)
def test_008_allday_reservation(self):
@@ -524,7 +532,7 @@ class TestResourceInvitation(unittest.TestCase):
uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,6,2), True)
- accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(accept, email.message.Message)
event = self.check_resource_calendar_event(self.audi['kolabtargetfolder'], uid)
@@ -532,7 +540,7 @@ class TestResourceInvitation(unittest.TestCase):
self.assertIsInstance(event.get_start(), datetime.date)
uid2 = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,6,2, 16,0,0))
- response = self.check_message_received("Reservation Request for test was DECLINED", self.audi['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.audi['mail'])
self.assertIsInstance(response, email.message.Message)
@@ -543,19 +551,19 @@ class TestResourceInvitation(unittest.TestCase):
uid = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,2,20, 12,0,0),
template=itip_recurring.replace(";COUNT=10", ""))
- accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(accept, email.message.Message)
# check non-recurring against recurring
uid2 = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,3,13, 10,0,0))
- response = self.check_message_received("Reservation Request for test was DECLINED", self.audi['mail'])
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.audi['mail'])
self.assertIsInstance(response, email.message.Message)
self.purge_mailbox(self.john['mailbox'])
# check recurring against recurring
uid3 = self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,2,22, 8,0,0), template=itip_recurring)
- accept = self.check_message_received("Reservation Request for test was ACCEPTED")
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
self.assertIsInstance(accept, email.message.Message)
@@ -570,7 +578,7 @@ class TestResourceInvitation(unittest.TestCase):
itip_invalid = itip_invitation.replace("DTSTART;", "X-DTSTART;")
self.send_itip_invitation(self.audi['mail'], datetime.datetime(2014,3,24, 19,30,0), template=itip_invalid)
- self.assertEqual(self.check_message_received("Reservation Request for test was ACCEPTED", self.audi['mail']), None)
+ self.assertEqual(self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.audi['mail']), None)
def test_011_owner_info(self):
@@ -578,7 +586,7 @@ class TestResourceInvitation(unittest.TestCase):
self.send_itip_invitation(self.room1['mail'], datetime.datetime(2014,6,19, 16,0,0))
- accept = self.check_message_received("Reservation Request for test was ACCEPTED", self.room1['mail'])
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.room1['mail'])
self.assertIsInstance(accept, email.message.Message)
respose_text = str(accept.get_payload(0))
self.assertIn(self.jane['mail'], respose_text)
@@ -590,27 +598,184 @@ class TestResourceInvitation(unittest.TestCase):
self.send_itip_invitation(self.room2['mail'], datetime.datetime(2014,6,19, 16,0,0))
- accept = self.check_message_received("Reservation Request for test was ACCEPTED", self.room2['mail'])
+ accept = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.room2['mail'])
self.assertIsInstance(accept, email.message.Message)
respose_text = str(accept.get_payload(0))
self.assertIn(self.jane['mail'], respose_text)
self.assertIn(self.jane['displayname'], respose_text)
- def TODO_test_012_owner_notification(self):
+ def test_012_owner_notification(self):
self.purge_mailbox(self.john['mailbox'])
self.purge_mailbox(self.jane['mailbox'])
- self.send_itip_invitation(self.room1['mail'], datetime.datetime(2014,5,4, 13,0,0))
+ self.send_itip_invitation(self.room1['mail'], datetime.datetime(2014,8,4, 13,0,0))
# check notification message sent to resource owner (jane)
- notify = self.check_message_received("Reservation Request for test was ACCEPTED", self.room1['mail'], self.jane['mailbox'])
+ notify = self.check_message_received(_('Booking for %s has been %s') % (self.room1['cn'], participant_status_label('ACCEPTED')), self.room1['mail'], self.jane['mailbox'])
self.assertIsInstance(notify, email.message.Message)
- self.assertEqual(notify['From'], self.room1['mail'])
- self.assertEqual(notify['Cc'], self.jane['mail'])
+
+ notification_text = str(notify.get_payload())
+ self.assertIn(self.john['mail'], notification_text)
+ self.assertIn(participant_status_label('ACCEPTED'), notification_text)
+
+ self.purge_mailbox(self.john['mailbox'])
# check notification sent to collection owner (jane)
- self.send_itip_invitation(self.rooms['mail'], datetime.datetime(2014,5,4, 12,30,0))
+ self.send_itip_invitation(self.rooms['mail'], datetime.datetime(2014,8,4, 12,30,0))
+
+ # one of the collection members accepted the reservation
+ accepted = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') })
+ delegatee = self.find_resource_by_email(accepted['from'])
+
+ notify = self.check_message_received(_('Booking for %s has been %s') % (delegatee['cn'], participant_status_label('ACCEPTED')), delegatee['mail'], self.jane['mailbox'])
+ self.assertIsInstance(notify, email.message.Message)
+ self.assertIn(self.john['mail'], notification_text)
+
+
+ def test_013_owner_confirmation_accept(self):
+ self.purge_mailbox(self.john['mailbox'])
+ self.purge_mailbox(self.jane['mailbox'])
+
+ uid = self.send_itip_invitation(self.room3['mail'], datetime.datetime(2014,9,12, 14,0,0))
+
+ # requester (john) gets a TENTATIVE confirmation
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.room3['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_summary(), "test")
+ self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'TENTATIVE')
+
+ # check confirmation message sent to resource owner (jane)
+ notify = self.check_message_received(_('Booking request for %s requires confirmation') % (self.room3['cn']), mailbox=self.jane['mailbox'])
+ self.assertIsInstance(notify, email.message.Message)
+
+ itip_event = events_from_message(notify)[0]
+
+ # resource owner confirms reservation request
+ itip_reply = itip_event['xml'].to_message_itip(self.jane['mail'],
+ method="REPLY",
+ participant_status='ACCEPTED',
+ message_text="Request accepted",
+ subject=_('Booking for %s has been %s') % (self.room3['cn'], participant_status_label('ACCEPTED'))
+ )
+
+ smtp = smtplib.SMTP('localhost', 10026)
+ smtp.sendmail(self.jane['mail'], str(itip_event['organizer']), str(itip_reply))
+ smtp.quit()
+
+ # requester (john) now gets the ACCEPTED response
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.room3['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_status(True), 'CONFIRMED')
+ self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'ACCEPTED')
+
- notify = self.check_message_received("Reservation Request for test was ACCEPTED", self.room2['mail'], self.jane['mailbox'])
+ def test_014_owner_confirmation_decline(self):
+ self.purge_mailbox(self.john['mailbox'])
+ self.purge_mailbox(self.jane['mailbox'])
+
+ uid = self.send_itip_invitation(self.room3['mail'], datetime.datetime(2014,9,14, 9,0,0))
+
+ # requester (john) gets a TENTATIVE confirmation
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.room3['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ # check confirmation message sent to resource owner (jane)
+ notify = self.check_message_received(_('Booking request for %s requires confirmation') % (self.room3['cn']), mailbox=self.jane['mailbox'])
self.assertIsInstance(notify, email.message.Message)
+
+ itip_event = events_from_message(notify)[0]
+
+ # resource owner declines reservation request
+ itip_reply = itip_event['xml'].to_message_itip(self.jane['mail'],
+ method="REPLY",
+ participant_status='DECLINED',
+ message_text="Request declined",
+ subject=_('Booking for %s has been %s') % (self.room3['cn'], participant_status_label('DECLINED'))
+ )
+
+ smtp = smtplib.SMTP('localhost', 10026)
+ smtp.sendmail(self.jane['mail'], str(itip_event['organizer']), str(itip_reply))
+ smtp.quit()
+
+ # requester (john) now gets the DECLINED response
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.room3['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ # tentative reservation was set to cancelled
+ event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid)
+ self.assertEqual(event, None)
+ #self.assertEqual(event.get_status(True), 'CANCELLED')
+ #self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'DECLINED')
+
+
+ def test_015_owner_confirmation_update(self):
+ self.purge_mailbox(self.john['mailbox'])
+
+ uid = self.send_itip_invitation(self.room3['mail'], datetime.datetime(2014,8,19, 9,0,0), uid="http://a-totally.stupid/?uid")
+
+ # requester (john) gets a TENTATIVE confirmation
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.room3['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ # check first confirmation message sent to resource owner (jane)
+ notify1 = self.check_message_received(_('Booking request for %s requires confirmation') % (self.room3['cn']), mailbox=self.jane['mailbox'])
+ self.assertIsInstance(notify1, email.message.Message)
+
+ itip_event1 = events_from_message(notify1)[0]
+ self.assertEqual(itip_event1['start'].hour, 9)
+
+ self.purge_mailbox(self.jane['mailbox'])
+ self.purge_mailbox(self.john['mailbox'])
+
+ # send update with new date (and sequence)
+ self.send_itip_update(self.room3['mail'], uid, datetime.datetime(2014,8,19, 16,0,0))
+
+ event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'TENTATIVE')
+
+ # check second confirmation message sent to resource owner (jane)
+ notify2 = self.check_message_received(_('Booking request for %s requires confirmation') % (self.room3['cn']), mailbox=self.jane['mailbox'])
+ self.assertIsInstance(notify2, email.message.Message)
+
+ itip_event2 = events_from_message(notify2)[0]
+ self.assertEqual(itip_event2['start'].hour, 16)
+
+ # resource owner declines the first reservation request
+ itip_reply = itip_event1['xml'].to_message_itip(self.jane['mail'],
+ method="REPLY",
+ participant_status='DECLINED',
+ message_text="Request declined",
+ subject=_('Booking for %s has been %s') % (self.room3['cn'], participant_status_label('DECLINED'))
+ )
+ smtp = smtplib.SMTP('localhost', 10026)
+ smtp.sendmail(self.jane['mail'], str(itip_event1['organizer']), str(itip_reply))
+ smtp.quit()
+
+ time.sleep(5)
+
+ # resource owner accpets the second reservation request
+ itip_reply = itip_event2['xml'].to_message_itip(self.jane['mail'],
+ method="REPLY",
+ participant_status='ACCEPTED',
+ message_text="Request accepred",
+ subject=_('Booking for %s has been %s') % (self.room3['cn'], participant_status_label('ACCEPTED'))
+ )
+ smtp = smtplib.SMTP('localhost', 10026)
+ smtp.sendmail(self.jane['mail'], str(itip_event2['organizer']), str(itip_reply))
+ smtp.quit()
+
+ # requester (john) now gets the ACCEPTED response
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.room3['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ event = self.check_resource_calendar_event(self.room3['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_attendee_by_email(self.room3['mail']).get_participant_status(True), 'ACCEPTED')
diff --git a/tests/functional/test_wallace/test_007_invitationpolicy.py b/tests/functional/test_wallace/test_007_invitationpolicy.py
new file mode 100644
index 0000000..9bc808f
--- /dev/null
+++ b/tests/functional/test_wallace/test_007_invitationpolicy.py
@@ -0,0 +1,737 @@
+import time
+import pykolab
+import smtplib
+import email
+import datetime
+import pytz
+import uuid
+import kolabformat
+
+from pykolab.imap import IMAP
+from wallace import module_resources
+
+from pykolab.translate import _
+from pykolab.xml import event_from_message
+from pykolab.xml import participant_status_label
+from email import message_from_string
+from twisted.trial import unittest
+
+import tests.functional.resource_func as funcs
+
+conf = pykolab.getConf()
+
+itip_invitation = """
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Roundcube Webmail 0.9-0.3.el6.kolab_3.0//NONSGML Calendar//EN
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:%(uid)s
+DTSTAMP:20140213T125414Z
+DTSTART;TZID=Europe/Berlin:%(start)s
+DTEND;TZID=Europe/Berlin:%(end)s
+SUMMARY:%(summary)s
+DESCRIPTION:test
+ORGANIZER;CN="Doe, John":mailto:john.doe@example.org
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=%(partstat)s;RSVP=TRUE:mailto:%(mailto)s
+ATTENDEE;ROLE=OPT-PARTICIPANT;PARTSTAT=TENTATIVE;RSVP=FALSE:mailto:somebody@else.com
+TRANSP:OPAQUE
+SEQUENCE:%(sequence)d
+END:VEVENT
+END:VCALENDAR
+"""
+
+itip_cancellation = """
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Roundcube Webmail 0.9-0.3.el6.kolab_3.0//NONSGML Calendar//EN
+CALSCALE:GREGORIAN
+METHOD:CANCEL
+BEGIN:VEVENT
+UID:%(uid)s
+DTSTAMP:20140218T125414Z
+DTSTART;TZID=Europe/Berlin:20120713T100000
+DTEND;TZID=Europe/Berlin:20120713T110000
+SUMMARY:%(summary)s
+DESCRIPTION:test
+ORGANIZER;CN="Doe, John":mailto:john.doe@example.org
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE:mailto:%(mailto)s
+TRANSP:OPAQUE
+SEQUENCE:%(sequence)d
+END:VEVENT
+END:VCALENDAR
+"""
+
+itip_recurring = """
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//Mac OS X 10.9.2//EN
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:%(uid)s
+DTSTAMP:20140213T125414Z
+DTSTART;TZID=Europe/Zurich:%(start)s
+DTEND;TZID=Europe/Zurich:%(end)s
+RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=10
+SUMMARY:%(summary)s
+DESCRIPTION:test
+ORGANIZER;CN="Doe, John":mailto:john.doe@example.org
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=%(partstat)s;RSVP=TRUE:mailto:%(mailto)s
+TRANSP:OPAQUE
+SEQUENCE:%(sequence)d
+END:VEVENT
+END:VCALENDAR
+"""
+
+itip_reply = """
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//pykolab-0.6.9-1//kolab.org//
+CALSCALE:GREGORIAN
+METHOD:REPLY
+BEGIN:VEVENT
+SUMMARY:%(summary)s
+UID:%(uid)s
+DTSTART;TZID=Europe/Berlin;VALUE=DATE-TIME:%(start)s
+DTEND;TZID=Europe/Berlin;VALUE=DATE-TIME:%(end)s
+DTSTAMP;VALUE=DATE-TIME:20140706T171038Z
+ORGANIZER;CN="Doe, John":MAILTO:%(organizer)s
+ATTENDEE;CUTYPE=INDIVIDUAL;PARTSTAT=%(partstat)s;ROLE=REQ-PARTICIPANT:mailto:%(mailto)s
+PRIORITY:0
+SEQUENCE:%(sequence)d
+END:VEVENT
+END:VCALENDAR
+"""
+
+mime_message = """MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="=_c8894dbdb8baeedacae836230e3436fd"
+From: "Doe, John" <john.doe@example.org>
+Date: Tue, 25 Feb 2014 13:54:14 +0100
+Message-ID: <240fe7ae7e139129e9eb95213c1016d7@example.org>
+To: %s
+Subject: "test"
+
+--=_c8894dbdb8baeedacae836230e3436fd
+Content-Type: text/plain; charset=UTF-8; format=flowed
+Content-Transfer-Encoding: quoted-printable
+
+*test*
+
+--=_c8894dbdb8baeedacae836230e3436fd
+Content-Type: text/calendar; charset=UTF-8; method=%s; name=event.ics
+Content-Disposition: attachment; filename=event.ics
+Content-Transfer-Encoding: 8bit
+
+%s
+--=_c8894dbdb8baeedacae836230e3436fd--
+"""
+
+class TestWallaceInvitationpolicy(unittest.TestCase):
+
+ john = None
+ itip_reply_subject = None
+
+ @classmethod
+ def setUp(self):
+ """ Compatibility for twisted.trial.unittest
+ """
+ if not self.john:
+ self.setup_class()
+
+ @classmethod
+ def setup_class(self, *args, **kw):
+ self.itip_reply_subject = _('"%(summary)s" has been %(status)s')
+
+ from tests.functional.purge_users import purge_users
+ purge_users()
+
+ self.john = {
+ 'displayname': 'John Doe',
+ 'mail': 'john.doe@example.org',
+ 'dn': 'uid=doe,ou=People,dc=example,dc=org',
+ 'preferredlanguage': 'en_US',
+ 'mailbox': 'user/john.doe@example.org',
+ 'kolabtargetfolder': 'user/john.doe/Calendar@example.org',
+ 'kolabinvitationpolicy': ['ACT_UPDATE_AND_NOTIFY','ACT_MANUAL']
+ }
+
+ self.jane = {
+ 'displayname': 'Jane Manager',
+ 'mail': 'jane.manager@example.org',
+ 'dn': 'uid=manager,ou=People,dc=example,dc=org',
+ 'preferredlanguage': 'en_US',
+ 'mailbox': 'user/jane.manager@example.org',
+ 'kolabtargetfolder': 'user/jane.manager/Calendar@example.org',
+ 'kolabinvitationpolicy': ['ACT_ACCEPT_IF_NO_CONFLICT','ACT_REJECT_IF_CONFLICT','ACT_UPDATE']
+ }
+
+ self.jack = {
+ 'displayname': 'Jack Tentative',
+ 'mail': 'jack.tentative@example.org',
+ 'dn': 'uid=tentative,ou=People,dc=example,dc=org',
+ 'preferredlanguage': 'en_US',
+ 'mailbox': 'user/jack.tentative@example.org',
+ 'kolabtargetfolder': 'user/jack.tentative/Calendar@example.org',
+ 'kolabinvitationpolicy': ['ACT_TENTATIVE_IF_NO_CONFLICT','ACT_SAVE_TO_CALENDAR','ACT_UPDATE']
+ }
+
+ self.mark = {
+ 'displayname': 'Mark German',
+ 'mail': 'mark.german@example.org',
+ 'dn': 'uid=german,ou=People,dc=example,dc=org',
+ 'preferredlanguage': 'de_DE',
+ 'mailbox': 'user/mark.german@example.org',
+ 'kolabtargetfolder': 'user/mark.german/Calendar@example.org',
+ 'kolabinvitationpolicy': ['ACT_ACCEPT','ACT_UPDATE_AND_NOTIFY']
+ }
+
+ self.external = {
+ 'displayname': 'Bob External',
+ 'mail': 'bob.external@gmail.com'
+ }
+
+ from tests.functional.user_add import user_add
+ user_add("John", "Doe", kolabinvitationpolicy=self.john['kolabinvitationpolicy'], preferredlanguage=self.john['preferredlanguage'])
+ user_add("Jane", "Manager", kolabinvitationpolicy=self.jane['kolabinvitationpolicy'], preferredlanguage=self.jane['preferredlanguage'])
+ user_add("Jack", "Tentative", kolabinvitationpolicy=self.jack['kolabinvitationpolicy'], preferredlanguage=self.jack['preferredlanguage'])
+ user_add("Mark", "German", kolabinvitationpolicy=self.mark['kolabinvitationpolicy'], preferredlanguage=self.mark['preferredlanguage'])
+
+ time.sleep(1)
+ from tests.functional.synchronize import synchronize_once
+ synchronize_once()
+
+ def send_message(self, itip_payload, to_addr, from_addr=None, method="REQUEST"):
+ if from_addr is None:
+ from_addr = self.john['mail']
+
+ smtp = smtplib.SMTP('localhost', 10026)
+ smtp.sendmail(from_addr, to_addr, mime_message % (to_addr, method, itip_payload))
+
+ def send_itip_invitation(self, attendee_email, start=None, allday=False, template=None, summary="test", sequence=0, partstat='NEEDS-ACTION'):
+ if start is None:
+ start = datetime.datetime.now()
+
+ uid = str(uuid.uuid4())
+
+ if allday:
+ default_template = itip_allday
+ end = start + datetime.timedelta(days=1)
+ date_format = '%Y%m%d'
+ else:
+ end = start + datetime.timedelta(hours=4)
+ default_template = itip_invitation
+ date_format = '%Y%m%dT%H%M%S'
+
+ self.send_message((template if template is not None else default_template) % {
+ 'uid': uid,
+ 'start': start.strftime(date_format),
+ 'end': end.strftime(date_format),
+ 'mailto': attendee_email,
+ 'summary': summary,
+ 'sequence': sequence,
+ 'partstat': partstat
+ },
+ attendee_email)
+
+ return uid
+
+ def send_itip_update(self, attendee_email, uid, start=None, template=None, summary="test", sequence=1, partstat='ACCEPTED'):
+ if start is None:
+ start = datetime.datetime.now()
+
+ end = start + datetime.timedelta(hours=4)
+ self.send_message((template if template is not None else itip_invitation) % {
+ 'uid': uid,
+ 'start': start.strftime('%Y%m%dT%H%M%S'),
+ 'end': end.strftime('%Y%m%dT%H%M%S'),
+ 'mailto': attendee_email,
+ 'summary': summary,
+ 'sequence': sequence,
+ 'partstat': partstat
+ },
+ attendee_email)
+
+ return uid
+
+ def send_itip_reply(self, uid, attendee_email, mailto, start=None, template=None, summary="test", sequence=0, partstat='ACCEPTED'):
+ if start is None:
+ start = datetime.datetime.now()
+
+ end = start + datetime.timedelta(hours=4)
+ self.send_message((template if template is not None else itip_reply) % {
+ 'uid': uid,
+ 'start': start.strftime('%Y%m%dT%H%M%S'),
+ 'end': end.strftime('%Y%m%dT%H%M%S'),
+ 'mailto': attendee_email,
+ 'organizer': mailto,
+ 'summary': summary,
+ 'sequence': sequence,
+ 'partstat': partstat
+ },
+ mailto,
+ attendee_email,
+ method='REPLY')
+
+ return uid
+
+ def send_itip_cancel(self, attendee_email, uid, summary="test", sequence=1):
+ self.send_message(itip_cancellation % {
+ 'uid': uid,
+ 'mailto': attendee_email,
+ 'summary': summary,
+ 'sequence': sequence,
+ },
+ attendee_email,
+ method='CANCEL')
+
+ return uid
+
+ def create_calendar_event(self, start=None, summary="test", sequence=0, user=None, attendees=None):
+ if start is None:
+ start = datetime.datetime.now(pytz.timezone("Europe/Berlin"))
+ if user is None:
+ user = self.john
+ if attendees is None:
+ attendees = [self.jane]
+
+ end = start + datetime.timedelta(hours=4)
+
+ event = pykolab.xml.Event()
+ event.set_start(start)
+ event.set_end(end)
+ event.set_organizer(user['mail'], user['displayname'])
+
+ for attendee in attendees:
+ event.add_attendee(attendee['mail'], attendee['displayname'], role="REQ-PARTICIPANT", participant_status="NEEDS-ACTION", rsvp=True)
+
+ event.set_summary(summary)
+ event.set_sequence(sequence)
+
+ # create event with attachment
+ vattach = event.get_attachments()
+ attachment = kolabformat.Attachment()
+ attachment.setLabel('attach.txt')
+ attachment.setData('This is a text attachment', 'text/plain')
+ vattach.append(attachment)
+ event.event.setAttachments(vattach)
+
+ imap = IMAP()
+ imap.connect()
+
+ mailbox = imap.folder_quote(user['kolabtargetfolder'])
+ imap.set_acl(mailbox, "cyrus-admin", "lrswipkxtecda")
+ imap.imap.m.select(mailbox)
+
+ result = imap.imap.m.append(
+ mailbox,
+ None,
+ None,
+ event.to_message().as_string()
+ )
+
+ return event.get_uid()
+
+ def update_calendar_event(self, uid, start=None, summary=None, sequence=0, user=None):
+ if user is None:
+ user = self.john
+
+ event = self.check_user_calendar_event(user['kolabtargetfolder'], uid)
+ if event:
+ if start is not None:
+ event.set_start(start)
+ if summary is not None:
+ event.set_summary(summary)
+ if sequence is not None:
+ event.set_sequence(sequence)
+
+ imap = IMAP()
+ imap.connect()
+
+ mailbox = imap.folder_quote(user['kolabtargetfolder'])
+ imap.set_acl(mailbox, "cyrus-admin", "lrswipkxtecda")
+ imap.imap.m.select(mailbox)
+
+ return imap.imap.m.append(
+ mailbox,
+ None,
+ None,
+ event.to_message().as_string()
+ )
+
+ return False
+
+ def check_message_received(self, subject, from_addr=None, mailbox=None):
+ if mailbox is None:
+ mailbox = self.john['mailbox']
+
+ imap = IMAP()
+ imap.connect()
+
+ mailbox = imap.folder_quote(mailbox)
+ imap.set_acl(mailbox, "cyrus-admin", "lrs")
+ imap.imap.m.select(mailbox)
+
+ found = None
+ retries = 15
+
+ while not found and retries > 0:
+ retries -= 1
+
+ typ, data = imap.imap.m.search(None, '(UNDELETED HEADER FROM "%s")' % (from_addr) if from_addr else 'UNDELETED')
+ for num in data[0].split():
+ typ, msg = imap.imap.m.fetch(num, '(RFC822)')
+ message = message_from_string(msg[0][1])
+ if message['Subject'] == subject:
+ found = message
+ break
+
+ time.sleep(1)
+
+ imap.disconnect()
+
+ return found
+
+ def check_user_calendar_event(self, mailbox, uid=None):
+ imap = IMAP()
+ imap.connect()
+
+ mailbox = imap.folder_quote(mailbox)
+ imap.set_acl(mailbox, "cyrus-admin", "lrs")
+ imap.imap.m.select(mailbox)
+
+ found = None
+ retries = 15
+
+ while not found and retries > 0:
+ retries -= 1
+
+ typ, data = imap.imap.m.search(None, '(UNDELETED HEADER SUBJECT "%s")' % (uid) if uid else '(UNDELETED HEADER X-Kolab-Type "application/x-vnd.kolab.event")')
+ for num in data[0].split():
+ typ, data = imap.imap.m.fetch(num, '(RFC822)')
+ event_message = message_from_string(data[0][1])
+
+ # return matching UID or first event found
+ if uid and event_message['subject'] != uid:
+ continue
+
+ found = event_from_message(event_message)
+ if found:
+ break
+
+ time.sleep(1)
+
+ return found
+
+ def purge_mailbox(self, mailbox):
+ imap = IMAP()
+ imap.connect()
+ mailbox = imap.folder_quote(mailbox)
+ imap.set_acl(mailbox, "cyrus-admin", "lrwcdest")
+ imap.imap.m.select(mailbox)
+
+ typ, data = imap.imap.m.search(None, 'ALL')
+ for num in data[0].split():
+ imap.imap.m.store(num, '+FLAGS', '\\Deleted')
+
+ imap.imap.m.expunge()
+ imap.disconnect()
+
+
+ def test_001_invite_accept_udate(self):
+ start = datetime.datetime(2014,8,13, 10,0,0)
+ uid = self.send_itip_invitation(self.jane['mail'], start)
+
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.jane['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_summary(), "test")
+
+ # send update with the same sequence: no re-scheduling
+ self.send_itip_update(self.jane['mail'], uid, start, summary="test updated", sequence=0, partstat='ACCEPTED')
+
+ time.sleep(10)
+ event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_summary(), "test updated")
+ self.assertEqual(event.get_attendee(self.jane['mail']).get_participant_status(), kolabformat.PartAccepted)
+
+
+ # @depends on test_001_invite_user
+ def test_002_invite_conflict_reject(self):
+ uid = self.send_itip_invitation(self.jane['mail'], datetime.datetime(2014,8,13, 11,0,0), summary="test2")
+
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test2', 'status':participant_status_label('DECLINED') }, self.jane['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_summary(), "test2")
+
+
+ def test_003_invite_accept_tentative(self):
+ self.purge_mailbox(self.john['mailbox'])
+
+ uid = self.send_itip_invitation(self.jack['mail'], datetime.datetime(2014,7,24, 8,0,0))
+
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.jack['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+
+ def test_004_copy_to_calendar(self):
+ self.purge_mailbox(self.john['mailbox'])
+
+ self.send_itip_invitation(self.jack['mail'], datetime.datetime(2014,7,29, 8,0,0))
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.jack['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ # send conflicting request to jack
+ uid = self.send_itip_invitation(self.jack['mail'], datetime.datetime(2014,7,29, 10,0,0), summary="test2")
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test2', 'status':participant_status_label('DECLINED') }, self.jack['mail'])
+ self.assertEqual(response, None, "No reply expected")
+
+ event = self.check_user_calendar_event(self.jack['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_summary(), "test2")
+ self.assertEqual(event.get_attendee(self.jack['mail']).get_participant_status(), kolabformat.PartNeedsAction)
+
+
+ def test_005_invite_rescheduling_accept(self):
+ self.purge_mailbox(self.john['mailbox'])
+
+ start = datetime.datetime(2014,8,14, 9,0,0, tzinfo=pytz.timezone("Europe/Berlin"))
+ uid = self.send_itip_invitation(self.jane['mail'], start)
+
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.jane['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_summary(), "test")
+
+ self.purge_mailbox(self.john['mailbox'])
+
+ # send update with new date and incremented sequence
+ new_start = datetime.datetime(2014,8,15, 15,0,0, tzinfo=pytz.timezone("Europe/Berlin"))
+ self.send_itip_update(self.jane['mail'], uid, new_start, summary="test", sequence=1)
+
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('ACCEPTED') }, self.jane['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_start(), new_start)
+ self.assertEqual(event.get_sequence(), 1)
+
+
+ def test_005_invite_rescheduling_reject(self):
+ self.purge_mailbox(self.john['mailbox'])
+ self.purge_mailbox(self.jack['kolabtargetfolder'])
+
+ start = datetime.datetime(2014,8,9, 17,0,0, tzinfo=pytz.timezone("Europe/Berlin"))
+ uid = self.send_itip_invitation(self.jack['mail'], start)
+
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('TENTATIVE') }, self.jack['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ # send update with new but conflicting date and incremented sequence
+ self.create_calendar_event(datetime.datetime(2014,8,10, 10,30,0, tzinfo=pytz.timezone("Europe/Berlin")), user=self.jack)
+ new_start = datetime.datetime(2014,8,10, 9,30,0, tzinfo=pytz.timezone("Europe/Berlin"))
+ self.send_itip_update(self.jack['mail'], uid, new_start, summary="test (updated)", sequence=1)
+
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'test', 'status':participant_status_label('DECLINED') }, self.jack['mail'])
+ self.assertEqual(response, None)
+
+ # verify re-scheduled copy in jack's calendar with NEEDS-ACTION
+ event = self.check_user_calendar_event(self.jack['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_start(), new_start)
+ self.assertEqual(event.get_sequence(), 1)
+
+ attendee = event.get_attendee(self.jack['mail'])
+ self.assertTrue(attendee.get_rsvp())
+ self.assertEqual(attendee.get_participant_status(), kolabformat.PartNeedsAction)
+
+
+ def test_006_invitation_reply(self):
+ self.purge_mailbox(self.john['mailbox'])
+
+ start = datetime.datetime(2014,8,18, 14,30,0, tzinfo=pytz.timezone("Europe/Berlin"))
+ uid = self.create_calendar_event(start, user=self.john)
+
+ event = self.check_user_calendar_event(self.john['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+
+ # send a reply from jane to john
+ self.send_itip_reply(uid, self.jane['mail'], self.john['mail'], start=start)
+
+ # check for the updated event in john's calendar
+ time.sleep(10)
+ event = self.check_user_calendar_event(self.john['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+
+ attendee = event.get_attendee(self.jane['mail'])
+ self.assertIsInstance(attendee, pykolab.xml.Attendee)
+ self.assertEqual(attendee.get_participant_status(), kolabformat.PartAccepted)
+
+ # check attachments in update event
+ attachments = event.get_attachments()
+ self.assertEqual(len(attachments), 1)
+ self.assertEqual(event.get_attachment_data(0), 'This is a text attachment')
+
+
+ def test_007_invitation_cancel(self):
+ self.purge_mailbox(self.john['mailbox'])
+
+ uid = self.send_itip_invitation(self.jane['mail'], summary="cancelled")
+
+ response = self.check_message_received(self.itip_reply_subject % { 'summary':'cancelled', 'status':participant_status_label('ACCEPTED') }, self.jane['mail'])
+ self.assertIsInstance(response, email.message.Message)
+
+ self.send_itip_cancel(self.jane['mail'], uid, summary="cancelled")
+
+ time.sleep(10)
+ event = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_summary(), "cancelled")
+ self.assertEqual(event.get_status(True), 'CANCELLED')
+ self.assertTrue(event.get_transparency())
+
+
+ def test_008_inivtation_reply_notify(self):
+ self.purge_mailbox(self.john['mailbox'])
+
+ start = datetime.datetime(2014,8,12, 16,0,0, tzinfo=pytz.timezone("Europe/Berlin"))
+ uid = self.create_calendar_event(start, user=self.john, attendees=[self.jane, self.mark, self.jack])
+
+ # send a reply from jane to john
+ self.send_itip_reply(uid, self.jane['mail'], self.john['mail'], start=start)
+
+ # check for notification message
+ # this notification should be suppressed until mark has replied, too
+ notification = self.check_message_received(_('"%s" has been updated') % ('test'), self.john['mail'])
+ self.assertEqual(notification, None)
+
+ # send a reply from mark to john
+ self.send_itip_reply(uid, self.mark['mail'], self.john['mail'], start=start, partstat='ACCEPTED')
+
+ notification = self.check_message_received(_('"%s" has been updated') % ('test'), self.john['mail'])
+ self.assertIsInstance(notification, email.message.Message)
+
+ notification_text = str(notification.get_payload());
+ self.assertIn(self.jane['mail'], notification_text)
+ self.assertIn(_("PENDING"), notification_text)
+
+ self.purge_mailbox(self.john['mailbox'])
+
+ # send a reply from mark to john
+ self.send_itip_reply(uid, self.jack['mail'], self.john['mail'], start=start, partstat='ACCEPTED')
+
+ # this triggers an additional notification
+ notification = self.check_message_received(_('"%s" has been updated') % ('test'), self.john['mail'])
+ self.assertIsInstance(notification, email.message.Message)
+
+ notification_text = str(notification.get_payload());
+ self.assertNotIn(_("PENDING"), notification_text)
+
+
+ def test_008_notify_translated(self):
+ self.purge_mailbox(self.mark['mailbox'])
+
+ start = datetime.datetime(2014,8,12, 16,0,0, tzinfo=pytz.timezone("Europe/Berlin"))
+ uid = self.create_calendar_event(start, user=self.mark, attendees=[self.jane])
+
+ # send a reply from jane to mark
+ self.send_itip_reply(uid, self.jane['mail'], self.mark['mail'], start=start)
+
+ # change translations to de_DE
+ pykolab.translate.setUserLanguage(self.mark['preferredlanguage'])
+ notification = self.check_message_received(_('"%s" has been updated') % ('test'), self.mark['mail'], self.mark['mailbox'])
+ self.assertIsInstance(notification, email.message.Message)
+
+ notification_text = str(notification.get_payload());
+ self.assertIn(self.jane['mail'], notification_text)
+ self.assertIn(participant_status_label("ACCEPTED")+":", notification_text)
+
+ # reset localization
+ pykolab.translate.setUserLanguage(conf.get('kolab','default_locale'))
+
+
+ def test_009_outdated_reply(self):
+ self.purge_mailbox(self.john['mailbox'])
+
+ start = datetime.datetime(2014,9,2, 11,0,0, tzinfo=pytz.timezone("Europe/Berlin"))
+ uid = self.create_calendar_event(start, user=self.john, sequence=2)
+
+ # send a reply from jane to john
+ self.send_itip_reply(uid, self.jane['mail'], self.john['mail'], start=start, sequence=1)
+
+ # verify jane's attendee status was not updated
+ time.sleep(10)
+ event = self.check_user_calendar_event(self.john['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+ self.assertEqual(event.get_sequence(), 2)
+ self.assertEqual(event.get_attendee(self.jane['mail']).get_participant_status(), kolabformat.PartNeedsAction)
+
+
+ def test_010_partstat_update_propagation(self):
+ # ATTENTION: this test requires wallace.invitationpolicy_autoupdate_other_attendees_on_reply to be enabled in config
+
+ start = datetime.datetime(2014,8,21, 13,0,0, tzinfo=pytz.timezone("Europe/Berlin"))
+ uid = self.create_calendar_event(start, user=self.john, attendees=[self.jane, self.jack, self.external])
+
+ event = self.check_user_calendar_event(self.john['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+
+ # send invitations to jack and jane
+ event_itip = event.as_string_itip()
+ self.send_itip_invitation(self.jane['mail'], start, template=event_itip)
+ self.send_itip_invitation(self.jack['mail'], start, template=event_itip)
+
+ # wait for replies from jack and jane to be processed and propagated
+ time.sleep(10)
+ event = self.check_user_calendar_event(self.john['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+
+ # check updated event in organizer's calendar
+ self.assertEqual(event.get_attendee(self.jane['mail']).get_participant_status(), kolabformat.PartAccepted)
+ self.assertEqual(event.get_attendee(self.jack['mail']).get_participant_status(), kolabformat.PartTentative)
+
+ # check updated partstats in jane's calendar
+ janes = self.check_user_calendar_event(self.jane['kolabtargetfolder'], uid)
+ self.assertEqual(janes.get_attendee(self.jane['mail']).get_participant_status(), kolabformat.PartAccepted)
+ self.assertEqual(janes.get_attendee(self.jack['mail']).get_participant_status(), kolabformat.PartTentative)
+
+ # check updated partstats in jack's calendar
+ jacks = self.check_user_calendar_event(self.jack['kolabtargetfolder'], uid)
+ self.assertEqual(jacks.get_attendee(self.jane['mail']).get_participant_status(), kolabformat.PartAccepted)
+ self.assertEqual(jacks.get_attendee(self.jack['mail']).get_participant_status(), kolabformat.PartTentative)
+
+ # PART 2: create conflicting event in jack's calendar
+ new_start = datetime.datetime(2014,8,21, 6,0,0, tzinfo=pytz.timezone("Europe/Berlin"))
+ self.create_calendar_event(new_start, user=self.jack, attendees=[], summary="blocker")
+
+ # re-schedule initial event to new date
+ self.update_calendar_event(uid, start=new_start, sequence=1, user=self.john)
+ self.send_itip_update(self.jane['mail'], uid, new_start, summary="test (updated)", sequence=1)
+ self.send_itip_update(self.jack['mail'], uid, new_start, summary="test (updated)", sequence=1)
+
+ # wait for replies to be processed and propagated
+ time.sleep(10)
+ event = self.check_user_calendar_event(self.john['kolabtargetfolder'], uid)
+ self.assertIsInstance(event, pykolab.xml.Event)
+
+ # check updated event in organizer's calendar (jack didn't reply yet)
+ self.assertEqual(event.get_attendee(self.jane['mail']).get_participant_status(), kolabformat.PartAccepted)
+ self.assertEqual(event.get_attendee(self.jack['mail']).get_participant_status(), kolabformat.PartTentative)
+
+ # check partstats in jack's calendar: jack's status should remain needs-action
+ jacks = self.check_user_calendar_event(self.jack['kolabtargetfolder'], uid)
+ self.assertEqual(jacks.get_attendee(self.jane['mail']).get_participant_status(), kolabformat.PartAccepted)
+ self.assertEqual(jacks.get_attendee(self.jack['mail']).get_participant_status(), kolabformat.PartNeedsAction)
+
+
diff --git a/tests/functional/user_add.py b/tests/functional/user_add.py
index 4939f93..b1b37f1 100644
--- a/tests/functional/user_add.py
+++ b/tests/functional/user_add.py
@@ -4,7 +4,7 @@ from pykolab import wap_client
conf = pykolab.getConf()
-def user_add(givenname, sn, preferredlanguage='en_US'):
+def user_add(givenname, sn, preferredlanguage='en_US', **kw):
if givenname == None:
raise Exception
@@ -25,6 +25,8 @@ def user_add(givenname, sn, preferredlanguage='en_US'):
'userpassword': 'Welcome2KolabSystems'
}
+ user_details.update(kw)
+
login = conf.get('ldap', 'bind_dn')
password = conf.get('ldap', 'bind_pw')
domain = conf.get('kolab', 'primary_domain')
diff --git a/tests/unit/test-002-attendee.py b/tests/unit/test-002-attendee.py
index 9da93c7..d7584e3 100644
--- a/tests/unit/test-002-attendee.py
+++ b/tests/unit/test-002-attendee.py
@@ -1,7 +1,9 @@
import datetime
import unittest
+import kolabformat
from pykolab.xml import Attendee
+from pykolab.xml import participant_status_label
class TestEventXML(unittest.TestCase):
attendee = Attendee("jane@doe.org")
@@ -101,5 +103,30 @@ class TestEventXML(unittest.TestCase):
self.assertEqual([k for k,v in self.attendee.cutype_map.iteritems() if v == 2][0], "INDIVIDUAL")
self.assertEqual([k for k,v in self.attendee.cutype_map.iteritems() if v == 3][0], "RESOURCE")
+ def test_018_partstat_label(self):
+ self.assertEqual(participant_status_label('NEEDS-ACTION'), "Needs Action")
+ self.assertEqual(participant_status_label(kolabformat.PartTentative), "Tentatively Accepted")
+ self.assertEqual(participant_status_label('UNKNOWN'), "UNKNOWN")
+
+ def test_020_to_dict(self):
+ name = "Doe, Jane"
+ role = 'OPT-PARTICIPANT'
+ cutype = 'RESOURCE'
+ partstat = 'ACCEPTED'
+ self.attendee.set_name(name)
+ self.attendee.set_rsvp(True)
+ self.attendee.set_role(role)
+ self.attendee.set_cutype(cutype)
+ self.attendee.set_participant_status(partstat)
+
+ data = self.attendee.to_dict()
+ self.assertIsInstance(data, dict)
+ self.assertEqual(data['role'], role)
+ self.assertEqual(data['cutype'], cutype)
+ self.assertEqual(data['partstat'], partstat)
+ self.assertEqual(data['name'], name)
+ self.assertEqual(data['email'], 'jane@doe.org')
+ self.assertTrue(data['rsvp'])
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/unit/test-003-event.py b/tests/unit/test-003-event.py
index 61ea8ec..1f54419 100644
--- a/tests/unit/test-003-event.py
+++ b/tests/unit/test-003-event.py
@@ -1,3 +1,4 @@
+import re
import datetime
import pytz
import sys
@@ -11,6 +12,233 @@ from pykolab.xml import EventIntegrityError
from pykolab.xml import InvalidAttendeeParticipantStatusError
from pykolab.xml import InvalidEventDateError
from pykolab.xml import event_from_ical
+from pykolab.xml import event_from_string
+from pykolab.xml import event_from_message
+
+ical_event = """
+BEGIN:VEVENT
+UID:7a35527d-f783-4b58-b404-b1389bd2fc57
+DTSTAMP;VALUE=DATE-TIME:20140407T122311Z
+CREATED;VALUE=DATE-TIME:20140407T122245Z
+LAST-MODIFIED;VALUE=DATE-TIME:20140407T122311Z
+DTSTART;TZID=Europe/Zurich;VALUE=DATE-TIME:20140523T110000
+DURATION:PT1H30M0S
+RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=10
+EXDATE;TZID=Europe/Zurich;VALUE=DATE-TIME:20140530T110000
+EXDATE;TZID=Europe/Zurich;VALUE=DATE-TIME:20140620T110000
+SUMMARY:Summary
+LOCATION:Location
+DESCRIPTION:Description\\n2 lines
+CATEGORIES:Personal
+TRANSP:OPAQUE
+PRIORITY:2
+SEQUENCE:2
+CLASS:PUBLIC
+ATTENDEE;CN="Manager, Jane";PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;CUTYP
+ E=INDIVIDUAL;RSVP=TRUE:mailto:jane.manager@example.org
+ATTENDEE;CUTYPE=RESOURCE;PARTSTAT=NEEDS-ACTION;ROLE=OPT-PARTICIPANT;RSVP=FA
+ LSE:MAILTO:max@imum.com
+ORGANIZER;CN=Doe\, John:mailto:john.doe@example.org
+URL:http://somelink.com/foo
+ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=image/png;X-LABEL=silhouette.pn
+ g:iVBORw0KGgoAAAANSUhEUgAAAC4AAAAuCAIAAADY27xgAAAAGXRFWHRTb2Z0d2FyZQBBZG9i
+ ZSBJbWFnZVJlYWR5ccllPAAAAsRJREFUeNrsmeluKjEMhTswrAWB4P3fECGx79CjsTDmOKRkpF
+ xxpfoHSmchX7ybFrfb7eszpPH1MfKH8ofyH6KUtd/c7/en0wmfWBdF0Wq1Op1Ou91uNGoer6iX
+ V1ar1Xa7xUJeB4qsr9frdyVlWWZH2VZyPp+xPXHIAoK70+m02+1m9JXj8bhcLi+Xi3J4xUCazS
+ bUltdtd7ud7ldUIhC3u+iTwF0sFhlR4Kds4LtRZK1w4te5UM6V6JaqhqC3CQ28OAsKggJfbZ3U
+ eozCqZ4koHIZCGmD9ivuos9YONFirmxrI0UNZG1kbZeUXdJQNJNa91RlqMn0ekYUMZDup6dXVV
+ m+1OSZhqLx6bVCELJGSsyFQtFrF15JGYMZgoxubWGDSDVhvTipDKWhoBOIpFobxtlbJ0Gh0/tg
+ lgXal4woUHi/36fQoBQncDAlupa8DeVwOPRe4lUyGAwQ+dl7W+xBXkJBhEUqR32UoJfYIKrR4d
+ ZBgcdIRqfEqn+mekl9FNRbSTA249la3ev1/kXHD47ZbEYR5L9kMplkd9vNZqMFyIYxxfN8Pk8q
+ QGlagT5QDtfrNYUMlWW9LiGNPPSmC/+OgpK2r4RO6dOatZd+4gAAemdIi6Fg9EKLD4vASWkzv3
+ ew06NSCiA40CumAIoaIrhrcAwjF7aDo58gUchgNV+0n1BAcDgcoAZrXV9mI4qkhtK6FJFhi9Fo
+ ZKPsgQI1ACJieH/Kd570t+xFoIzHYzl5Q40CFGrSqGuks3qmYIKJfIl0nPKLxAMFw7Dv1+2QYf
+ vFSOBQubbOFDSc7ZcfWvHv6DzhOzT6IeOVPuz8Roex0f6EgsE/2IL4qdg7hIXz7/pBie7q1uWr
+ tp66xrif0l1KwUE4P7Y9Gci/ZgtNRFX+Rw06Q2RigsjuDc3urwKHxuNITaaxyD9mT2WvSDAXn/
+ Pvhh8BBgBjyfPSGbSYcwAAAABJRU5ErkJggg==
+ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=text/plain;X-LABEL=text.txt:VGh
+ pcyBpcyBhIHRleHQgZmlsZQo=
+BEGIN:VALARM
+ACTION:DISPLAY
+TRIGGER:-PT30M
+END:VALARM
+END:VEVENT
+"""
+
+xml_event = """
+<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">
+ <vcalendar>
+ <properties>
+ <prodid>
+ <text>Libkolabxml-1.1</text>
+ </prodid>
+ <version>
+ <text>2.0</text>
+ </version>
+ <x-kolab-version>
+ <text>3.1.0</text>
+ </x-kolab-version>
+ </properties>
+ <components>
+ <vevent>
+ <properties>
+ <uid>
+ <text>75c740bb-b3c6-442c-8021-ecbaeb0a025e</text>
+ </uid>
+ <created>
+ <date-time>2014-07-07T01:28:23Z</date-time>
+ </created>
+ <dtstamp>
+ <date-time>2014-07-07T01:28:23Z</date-time>
+ </dtstamp>
+ <sequence>
+ <integer>1</integer>
+ </sequence>
+ <class>
+ <text>PUBLIC</text>
+ </class>
+ <dtstart>
+ <parameters>
+ <tzid>
+ <text>/kolab.org/Europe/London</text>
+ </tzid>
+ </parameters>
+ <date-time>2014-08-13T10:00:00</date-time>
+ </dtstart>
+ <dtend>
+ <parameters>
+ <tzid><text>/kolab.org/Europe/London</text></tzid>
+ </parameters>
+ <date-time>2014-08-13T14:00:00</date-time>
+ </dtend>
+ <rrule>
+ <recur>
+ <freq>DAILY</freq>
+ <until>
+ <date>2014-07-25</date>
+ </until>
+ </recur>
+ </rrule>
+ <exdate>
+ <parameters>
+ <tzid>
+ <text>/kolab.org/Europe/Berlin</text>
+ </tzid>
+ </parameters>
+ <date>2014-07-19</date>
+ <date>2014-07-26</date>
+ <date>2014-07-12</date>
+ <date>2014-07-13</date>
+ <date>2014-07-20</date>
+ <date>2014-07-27</date>
+ <date>2014-07-05</date>
+ <date>2014-07-06</date>
+ </exdate>
+ <summary>
+ <text>test</text>
+ </summary>
+ <description>
+ <text>test</text>
+ </description>
+ <priority>
+ <integer>5</integer>
+ </priority>
+ <status>
+ <text>CANCELLED</text>
+ </status>
+ <location>
+ <text>Room 101</text>
+ </location>
+ <organizer>
+ <parameters>
+ <cn><text>Doe, John</text></cn>
+ </parameters>
+ <cal-address>mailto:%3Cjohn%40example.org%3E</cal-address>
+ </organizer>
+ <attendee>
+ <parameters>
+ <partstat><text>ACCEPTED</text></partstat>
+ <role><text>REQ-PARTICIPANT</text></role>
+ <rsvp><boolean>true</boolean></rsvp>
+ </parameters>
+ <cal-address>mailto:%3Cjane%40example.org%3E</cal-address>
+ </attendee>
+ <attendee>
+ <parameters>
+ <partstat><text>TENTATIVE</text></partstat>
+ <role><text>OPT-PARTICIPANT</text></role>
+ </parameters>
+ <cal-address>mailto:%3Csomebody%40else.com%3E</cal-address>
+ </attendee>
+ <attach>
+ <parameters>
+ <fmttype>
+ <text>text/html</text>
+ </fmttype>
+ <x-label>
+ <text>noname.1395223627.5555</text>
+ </x-label>
+ </parameters>
+ <uri>cid:noname.1395223627.5555</uri>
+ </attach>
+ <x-custom>
+ <identifier>X-MOZ-RECEIVED-DTSTAMP</identifier>
+ <value>20140224T155612Z</value>
+ </x-custom>
+ <x-custom>
+ <identifier>X-GWSHOW-AS</identifier>
+ <value>BUSY</value>
+ </x-custom>
+ </properties>
+ <components>
+ <valarm>
+ <properties>
+ <action>
+ <text>DISPLAY</text>
+ </action>
+ <description>
+ <text>alarm 1</text>
+ </description>
+ <trigger>
+ <parameters>
+ <related>
+ <text>START</text>
+ </related>
+ </parameters>
+ <duration>-PT2H</duration>
+ </trigger>
+ </properties>
+ </valarm>
+ <valarm>
+ <properties>
+ <action>
+ <text>EMAIL</text>
+ </action>
+ <summary>
+ <text>test</text>
+ </summary>
+ <description>
+ <text>alarm 2</text>
+ </description>
+ <attendee>
+ <cal-address>mailto:%3Cjohn.die%40example.org%3E</cal-address>
+ </attendee>
+ <trigger>
+ <parameters>
+ <related>
+ <text>START</text>
+ </related>
+ </parameters>
+ <duration>-P1D</duration>
+ </trigger>
+ </properties>
+ </valarm>
+ </components>
+ </vevent>
+ </components>
+ </vcalendar>
+</icalendar>
+"""
class TestEventXML(unittest.TestCase):
event = Event()
@@ -117,25 +345,23 @@ class TestEventXML(unittest.TestCase):
def test_018_load_from_ical(self):
ical_str = """BEGIN:VCALENDAR
VERSION:2.0
-PRODID:-//Apple Inc.//Mac OS X 10.9.2//EN
+PRODID:-//Roundcube//Roundcube libcalendaring 1.1-git//Sabre//Sabre VObject
+ 2.1.3//EN
CALSCALE:GREGORIAN
-BEGIN:VEVENT
-DTSTART;TZID=Europe/Zurich;VALUE=DATE-TIME:20140523T110000
-DURATION:PT1H30M0S
-RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=10
-EXDATE;TZID=Europe/Zurich;VALUE=DATE-TIME:20140530T110000
-EXDATE;TZID=Europe/Zurich;VALUE=DATE-TIME:20140620T110000
-UID:7a35527d-f783-4b58-b404-b1389bd2fc57
-ATTENDEE;CN="Doe, Jane";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED
- ;ROLE=REQ-PARTICIPANT;RSVP=FALSE:MAILTO:jane@doe.org
-ATTENDEE;CUTYPE=RESOURCE;PARTSTAT=NEEDS-ACTION
- ;ROLE=OPT-PARTICIPANT;RSVP=FALSE:MAILTO:max@imum.com
-SEQUENCE:2
-END:VEVENT
-END:VCALENDAR
-"""
+METHOD:REQUEST
+ """ + ical_event + "END:VCALENDAR"
+
ical = icalendar.Calendar.from_ical(ical_str)
event = event_from_ical(ical.walk('VEVENT')[0].to_ical())
+
+ self.assertEqual(event.get_location(), "Location")
+ self.assertEqual(str(event.get_lastmodified()), "2014-04-07 12:23:11+00:00")
+ self.assertEqual(event.get_description(), "Description\n2 lines")
+ self.assertEqual(event.get_url(), "http://somelink.com/foo")
+ self.assertEqual(event.get_transparency(), False)
+ self.assertEqual(event.get_categories(), ["Personal"])
+ self.assertEqual(event.get_priority(), '2')
+ self.assertEqual(event.get_classification(), kolabformat.ClassPublic)
self.assertEqual(event.get_attendee_by_email("max@imum.com").get_cutype(), kolabformat.CutypeResource)
self.assertEqual(event.get_sequence(), 2)
self.assertTrue(event.is_recurring())
@@ -144,17 +370,62 @@ END:VCALENDAR
self.assertEqual(str(event.get_end()), "2014-05-23 12:30:00+01:00")
self.assertEqual(len(event.get_exception_dates()), 2)
self.assertIsInstance(event.get_exception_dates()[0], datetime.datetime)
+ self.assertEqual(len(event.get_alarms()), 1)
+ self.assertEqual(len(event.get_attachments()), 2)
+
+ def test_018_ical_to_message(self):
+ event = event_from_ical(ical_event)
+ message = event.to_message()
+
+ self.assertTrue(message.is_multipart())
+ self.assertEqual(message['Subject'], event.uid)
+ self.assertEqual(message['X-Kolab-Type'], 'application/x-vnd.kolab.event')
+
+ parts = [p for p in message.walk()]
+ attachments = event.get_attachments();
+
+ self.assertEqual(len(parts), 5)
+ self.assertEqual(parts[1].get_content_type(), 'text/plain')
+ self.assertEqual(parts[2].get_content_type(), 'application/calendar+xml')
+ self.assertEqual(parts[3].get_content_type(), 'image/png')
+ self.assertEqual(parts[4].get_content_type(), 'text/plain')
+ self.assertEqual(parts[2]['Content-ID'], None)
+ self.assertEqual(parts[3]['Content-ID'].strip('<>'), attachments[0].uri()[4:])
+ self.assertEqual(parts[4]['Content-ID'].strip('<>'), attachments[1].uri()[4:])
+
+ def test_018_ical_allday_events(self):
+ ical = """BEGIN:VEVENT
+UID:ffffffff-f783-4b58-b404-b1389bd2ffff
+DTSTAMP;VALUE=DATE-TIME:20140407T122311Z
+CREATED;VALUE=DATE-TIME:20140407T122245Z
+DTSTART;VALUE=DATE:20140823
+DTEND;VALUE=DATE:20140824
+SUMMARY:All day
+DESCRIPTION:One single day
+TRANSP:OPAQUE
+CLASS:PUBLIC
+END:VEVENT
+"""
+ event = event_from_ical(ical)
+ self.assertEqual(str(event.get_start()), "2014-08-23")
+ self.assertEqual(str(event.get_end()), "2014-08-23")
+ self.assertEqual(str(event.get_ical_dtend()), "2014-08-24")
+ self.assertTrue(re.match('.*<dtend>\s*<date>2014-08-23</date>', str(event), re.DOTALL))
def test_019_as_string_itip(self):
self.event.set_summary("test")
self.event.set_start(datetime.datetime(2014, 05, 23, 11, 00, 00, tzinfo=pytz.timezone("Europe/London")))
self.event.set_end(datetime.datetime(2014, 05, 23, 12, 30, 00, tzinfo=pytz.timezone("Europe/London")))
+ self.event.set_sequence(3)
+ self.event.add_custom_property('X-Custom', 'check')
ical = icalendar.Calendar.from_ical(self.event.as_string_itip())
event = ical.walk('VEVENT')[0]
self.assertEqual(event['uid'], self.event.get_uid())
self.assertEqual(event['summary'], "test")
+ self.assertEqual(event['sequence'], 3)
+ self.assertEqual(event['X-CUSTOM'], "check")
self.assertIsInstance(event['dtstamp'].dt, datetime.datetime)
def test_020_calendaring_recurrence(self):
@@ -214,6 +485,94 @@ END:VCALENDAR
self.assertEqual(self.event.get_next_occurence(_start), None)
self.assertEqual(self.event.get_last_occurrence(), None)
+ def test_022_load_from_xml(self):
+ event = event_from_string(xml_event)
+ self.assertEqual(event.uid, '75c740bb-b3c6-442c-8021-ecbaeb0a025e')
+ self.assertEqual(event.get_attendee_by_email("jane@example.org").get_participant_status(), kolabformat.PartAccepted)
+ self.assertEqual(event.get_sequence(), 1)
+ self.assertIsInstance(event.get_start(), datetime.datetime)
+ self.assertEqual(str(event.get_start()), "2014-08-13 10:00:00+00:00")
+
+ def test_023_load_from_message(self):
+ event = event_from_message(event_from_ical(ical_event).to_message())
+ event.set_sequence(3)
+
+ message = event.to_message()
+ self.assertTrue(message.is_multipart())
+
+ # check attachment MIME parts are kept
+ parts = [p for p in message.walk()]
+ attachments = event.get_attachments();
+
+ self.assertEqual(len(parts), 5)
+ self.assertEqual(parts[3].get_content_type(), 'image/png')
+ self.assertEqual(parts[3]['Content-ID'].strip('<>'), attachments[0].uri()[4:])
+ self.assertEqual(parts[4].get_content_type(), 'text/plain')
+ self.assertEqual(parts[4]['Content-ID'].strip('<>'), attachments[1].uri()[4:])
+ self.assertEqual(event.get_attachment_data(1), 'This is a text file')
+
+ def test_024_bogus_itip_data(self):
+ # DTSTAMP contains an invalid date/time value
+ vevent = """BEGIN:VEVENT
+UID:626421779C777FBE9C9B85A80D04DDFA-A4BF5BBB9FEAA271
+DTSTAMP:20120713T1254140
+DTSTART;TZID=Europe/London:20120713T100000
+DTEND;TZID=Europe/London:20120713T110000
+SUMMARY:test
+DESCRIPTION:test
+ORGANIZER;CN="Doe, John":mailto:john.doe@example.org
+ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailt
+ o:jane.doe@example.org
+ATTENDEE;ROLE=OPT-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailt
+ o:user.external@example.com
+SEQUENCE:1
+TRANSP:OPAQUE
+END:VEVENT
+"""
+ event = event_from_ical(vevent)
+ self.assertRaises(EventIntegrityError, event.to_message)
+
+ def test_025_to_dict(self):
+ data = event_from_string(xml_event).to_dict()
+
+ self.assertIsInstance(data, dict)
+ self.assertIsInstance(data['start'], datetime.datetime)
+ self.assertIsInstance(data['end'], datetime.datetime)
+ self.assertIsInstance(data['created'], datetime.datetime)
+ self.assertIsInstance(data['lastmodified-date'], datetime.datetime)
+ self.assertEqual(data['uid'], '75c740bb-b3c6-442c-8021-ecbaeb0a025e')
+ self.assertEqual(data['summary'], 'test')
+ self.assertEqual(data['location'], 'Room 101')
+ self.assertEqual(data['description'], 'test')
+ self.assertEqual(data['priority'], 5)
+ self.assertEqual(data['status'], 'CANCELLED')
+ self.assertEqual(data['sequence'], 1)
+ self.assertEqual(data['transparency'], False)
+ self.assertEqual(data['X-GWSHOW-AS'], 'BUSY')
+
+ self.assertIsInstance(data['organizer'], dict)
+ self.assertEqual(data['organizer']['email'], 'john@example.org')
+
+ self.assertEqual(len(data['attendee']), 2)
+ self.assertIsInstance(data['attendee'][0], dict)
+
+ self.assertEqual(len(data['attach']), 1)
+ self.assertIsInstance(data['attach'][0], dict)
+ self.assertEqual(data['attach'][0]['fmttype'], 'text/html')
+
+ self.assertIsInstance(data['rrule'], dict)
+ self.assertEqual(data['rrule']['frequency'], 'DAILY')
+ self.assertEqual(data['rrule']['interval'], 1)
+ self.assertEqual(data['rrule']['wkst'], 'MO')
+ self.assertIsInstance(data['rrule']['until'], datetime.date)
+
+ self.assertIsInstance(data['alarm'], list)
+ self.assertEqual(len(data['alarm']), 2)
+ self.assertEqual(data['alarm'][0]['action'], 'DISPLAY')
+ self.assertEqual(data['alarm'][1]['action'], 'EMAIL')
+ self.assertEqual(data['alarm'][1]['trigger']['value'], '-P1D')
+ self.assertEqual(len(data['alarm'][1]['attendee']), 1)
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/unit/test-011-itip.py b/tests/unit/test-011-itip.py
new file mode 100644
index 0000000..a08d05f
--- /dev/null
+++ b/tests/unit/test-011-itip.py
@@ -0,0 +1,412 @@
+import pykolab
+import datetime
+import pytz
+import kolabformat
+
+from pykolab import itip
+from pykolab.xml import Event
+from pykolab.xml import participant_status_label
+from pykolab.translate import _
+
+from icalendar import Calendar
+from email import message
+from email import message_from_string
+from wallace import module_resources
+from twisted.trial import unittest
+
+# define some iTip MIME messages
+
+itip_multipart = """MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="=_c8894dbdb8baeedacae836230e3436fd"
+From: "Doe, John" <john.doe@example.org>
+Date: Fri, 13 Jul 2012 13:54:14 +0100
+Message-ID: <240fe7ae7e139129e9eb95213c1016d7@example.org>
+User-Agent: Roundcube Webmail/0.9-0.3.el6.kolab_3.0
+To: resource-collection-car@example.org
+Subject: "test" has been updated
+
+--=_c8894dbdb8baeedacae836230e3436fd
+Content-Type: text/plain; charset=UTF-8; format=flowed
+Content-Transfer-Encoding: quoted-printable
+
+*test*
+
+--=_c8894dbdb8baeedacae836230e3436fd
+Content-Type: text/calendar; charset=UTF-8; method=REQUEST;
+ name=event.ics
+Content-Disposition: attachment;
+ filename=event.ics
+Content-Transfer-Encoding: quoted-printable
+
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Roundcube Webmail 0.9-0.3.el6.kolab_3.0//NONSGML Calendar//EN
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:626421779C777FBE9C9B85A80D04DDFA-A4BF5BBB9FEAA271
+DTSTAMP:20120713T1254140
+DTSTART;TZID=3DEurope/London:20120713T100000
+DTEND;TZID=3DEurope/London:20120713T110000
+SUMMARY:test
+DESCRIPTION:test
+ORGANIZER;CN=3D"Doe, John":mailto:john.doe@example.org
+ATTENDEE;ROLE=3DREQ-PARTICIPANT;PARTSTAT=3DNEEDS-ACTION;RSVP=3DTRUE:mailt=
+o:resource-collection-car@example.org
+ATTENDEE;ROLE=3DOPT-PARTICIPANT;PARTSTAT=3DNEEDS-ACTION;RSVP=3DTRUE:mailto:anoth=
+er-resource@example.org
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+
+--=_c8894dbdb8baeedacae836230e3436fd--
+"""
+
+itip_non_multipart = """Return-Path: <john.doe@example.org>
+Sender: john.doe@example.org
+Content-Type: text/calendar; method=REQUEST; charset=UTF-8
+Content-Transfer-Encoding: quoted-printable
+To: resource-collection-car@example.org
+From: john.doe@example.org
+Date: Mon, 24 Feb 2014 11:27:28 +0100
+Message-ID: <1a3aa8995e83dd24cf9247e538ac913a@example.org>
+Subject: test
+
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Roundcube Webmail 0.9-0.3.el6.kolab_3.0//NONSGML Calendar//EN
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:626421779C777FBE9C9B85A80D04DDFA-A4BF5BBB9FEAA271
+DTSTAMP:20120713T1254140
+DTSTART;TZID=3DEurope/London:20120713T100000
+DTEND;TZID=3DEurope/London:20120713T110000
+SUMMARY:test
+DESCRIPTION:test
+ORGANIZER;CN=3D"Doe, John":mailto:john.doe@example.org
+ATTENDEE;ROLE=3DREQ-PARTICIPANT;PARTSTAT=3DACCEPTED;RSVP=3DTRUE:mailt=
+o:resource-collection-car@example.org
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+"""
+
+itip_google_multipart = """MIME-Version: 1.0
+Message-ID: <001a11c2ad84243e0604f3246bae@google.com>
+Date: Mon, 24 Feb 2014 10:27:28 +0000
+Subject: =?ISO-8859-1?Q?Invitation=3A_iTip_from_Apple_=40_Mon_Feb_24=2C_2014_12pm_?=
+ =?ISO-8859-1?Q?=2D_1pm_=28Tom_=26_T=E4m=29?=
+From: "john.doe" <john.doe@gmail.com>
+To: <john.sample@example.org>
+Content-Type: multipart/mixed; boundary=001a11c2ad84243df004f3246bad
+
+--001a11c2ad84243df004f3246bad
+Content-Type: multipart/alternative; boundary=001a11c2ad84243dec04f3246bab
+
+--001a11c2ad84243dec04f3246bab
+Content-Type: text/plain; charset=ISO-8859-1; format=flowed; delsp=yes
+
+<some text content here>
+
+--001a11c2ad84243dec04f3246bab
+Content-Type: text/html; charset=ISO-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+<div style=3D""><!-- some HTML message content here --></div>
+--001a11c2ad84243dec04f3246bab
+Content-Type: text/calendar; charset=UTF-8; method=REQUEST
+Content-Transfer-Encoding: 7bit
+
+BEGIN:VCALENDAR
+PRODID:-//Google Inc//Google Calendar 70.9054//EN
+VERSION:2.0
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VEVENT
+DTSTART:20140224T110000Z
+DTEND:20140224T120000Z
+DTSTAMP:20140224T102728Z
+ORGANIZER:mailto:kepjllr6mcq7d0959u4cdc7000@group.calendar.google.com
+UID:0BE2F640-5814-47C9-ABAE-E7E959204E76
+ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE
+ ;X-NUM-GUESTS=0:mailto:kepjllr6mcq7d0959u4cdc7000@group.calendar.google.com
+ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=
+ TRUE;CN=John Sample;X-NUM-GUESTS=0:mailto:john.sample@example.org
+CREATED:20140224T102728Z
+DESCRIPTION:Testing Multipart structure\\nView your event at http://www.goog
+ le.com/calendar/event?action=VIEW&eid=XzYxMTRhY2k2Nm9xMzBiOWw3MG9qOGI5azZ0M
+ WppYmExODkwa2FiYTU2dDJqaWQ5cDY4bzM4aDluNm8gdGhvbWFzQGJyb3RoZXJsaS5jaA&tok=N
+ TIja2VwamxscjZtY3E3ZDA5NTl1NGNkYzcwMDBAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbTkz
+ NTcyYTU2YmUwNWMxNjY0Zjc3OTU0MzhmMDcwY2FhN2NjZjIzYWM&ctz=Europe/Zurich&hl=en
+ .
+LAST-MODIFIED:20140224T102728Z
+LOCATION:
+SEQUENCE:5
+STATUS:CONFIRMED
+SUMMARY:iTip from Apple
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+
+--001a11c2ad84243dec04f3246bab--
+--001a11c2ad84243df004f3246bad
+Content-Type: application/ics; name="invite.ics"
+Content-Disposition: attachment; filename="invite.ics"
+Content-Transfer-Encoding: base64
+
+QkVHSU46VkNBTEVOREFSDQpQUk9ESUQ6LS8vR29vZ2xlIEluYy8vR29vZ2xlIENhbGVuZGFyIDcw
+LjkwNTQvL0VODQpWRVJTSU9OOjIuMA0KQ0FMU0NBTEU6R1JFR09SSUFODQpNRVRIT0Q6UkVRVUVT
+VA0KQkVHSU46VkVWRU5UDQpEVFNUQVJUOjIwMTQwMjI0VDExMDAwMFoNCkRURU5EOjIwMTQwMjI0
+VDEyMDAwMFoNCkRUU1RBTVA6MjAxNDAyMjRUMTAyNzI4Wg0KT1JHQU5JWkVSOm1haWx0bzprZXBq
+bGxyNm1jcTdkMDk1OXU0Y2RjNzAwMEBncm91cC5jYWxlbmRhci5nb29nbGUuY29tDQpVSUQ6MEJF
+MkY2NDAtNTgxNC00N0M5LUFCQUUtRTdFOTU5MjA0RTc2DQpBVFRFTkRFRTtDVVRZUEU9SU5ESVZJ
+RFVBTDtST0xFPVJFUS1QQVJUSUNJUEFOVDtQQVJUU1RBVD1BQ0NFUFRFRDtSU1ZQPVRSVUUNCiA7
+WC1OVU0tR1VFU1RTPTA6bWFpbHRvOmtlcGpsbHI2bWNxN2QwOTU5dTRjZGM3MDAwQGdyb3VwLmNh
+bGVuZGFyLmdvb2dsZS5jb20NCkFUVEVOREVFO0NVVFlQRT1JTkRJVklEVUFMO1JPTEU9UkVRLVBB
+UlRJQ0lQQU5UO1BBUlRTVEFUPU5FRURTLUFDVElPTjtSU1ZQPQ0KIFRSVUU7WC1OVU0tR1VFU1RT
+PTA6bWFpbHRvOnRob21hc0Bicm90aGVybGkuY2gNCkFUVEVOREVFO0NVVFlQRT1JTkRJVklEVUFM
+O1JPTEU9UkVRLVBBUlRJQ0lQQU5UO1BBUlRTVEFUPU5FRURTLUFDVElPTjtSU1ZQPQ0KIFRSVUU7
+Q049VGhvbWFzIEJydWVkZXJsaTtYLU5VTS1HVUVTVFM9MDptYWlsdG86cm91bmRjdWJlQGdtYWls
+LmNvbQ0KQ1JFQVRFRDoyMDE0MDIyNFQxMDI3MjhaDQpERVNDUklQVElPTjpUZXN0aW5nIE11bHRp
+cGFydCBzdHJ1Y3R1cmVcblZpZXcgeW91ciBldmVudCBhdCBodHRwOi8vd3d3Lmdvb2cNCiBsZS5j
+b20vY2FsZW5kYXIvZXZlbnQ/YWN0aW9uPVZJRVcmZWlkPVh6WXhNVFJoWTJrMk5tOXhNekJpT1d3
+M01HOXFPR0k1YXpaME0NCiBXcHBZbUV4T0Rrd2EyRmlZVFUyZERKcWFXUTVjRFk0YnpNNGFEbHVO
+bThnZEdodmJXRnpRR0p5YjNSb1pYSnNhUzVqYUEmdG9rPU4NCiBUSWphMlZ3YW14c2NqWnRZM0Uz
+WkRBNU5UbDFOR05rWXpjd01EQkFaM0p2ZFhBdVkyRnNaVzVrWVhJdVoyOXZaMnhsTG1OdmJUa3oN
+CiBOVGN5WVRVMlltVXdOV014TmpZMFpqYzNPVFUwTXpobU1EY3dZMkZoTjJOalpqSXpZV00mY3R6
+PUV1cm9wZS9adXJpY2gmaGw9ZW4NCiAuDQpMQVNULU1PRElGSUVEOjIwMTQwMjI0VDEwMjcyOFoN
+CkxPQ0FUSU9OOg0KU0VRVUVOQ0U6NQ0KU1RBVFVTOkNPTkZJUk1FRA0KU1VNTUFSWTppVGlwIGZy
+b20gQXBwbGUNClRSQU5TUDpPUEFRVUUNCkVORDpWRVZFTlQNCkVORDpWQ0FMRU5EQVINCg==
+--001a11c2ad84243df004f3246bad--
+"""
+
+itip_application_ics = """MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="=_c8894dbdb8baeedacae836230e3436fd"
+From: "Doe, John" <john.doe@example.org>
+Date: Fri, 13 Jul 2012 13:54:14 +0100
+Message-ID: <240fe7ae7e139129e9eb95213c101622@example.org>
+User-Agent: Roundcube Webmail/0.9-0.3.el6.kolab_3.0
+To: resource-collection-car@example.org
+Subject: "test" has been updated
+
+--=_c8894dbdb8baeedacae836230e3436fd
+Content-Transfer-Encoding: quoted-printable
+Content-Type: text/plain; charset=UTF-8; format=flowed
+
+<some text here>
+
+--=_c8894dbdb8baeedacae836230e3436fd
+Content-Type: application/ics; charset=UTF-8; method=REQUEST;
+ name=event.ics
+Content-Transfer-Encoding: quoted-printable
+
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Roundcube Webmail 0.9-0.3.el6.kolab_3.0//NONSGML Calendar//EN
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:626421779C777FBE9C9B85A80D04DDFA-A4BF5BBB9FEAA271
+DTSTAMP:20120713T1254140
+DTSTART;TZID=3DEurope/London:20120713T100000
+DTEND;TZID=3DEurope/London:20120713T110000
+SUMMARY:test
+DESCRIPTION:test
+ORGANIZER;CN=3D"Doe, John":mailto:john.doe@example.org
+ATTENDEE;ROLE=3DREQ-PARTICIPANT;PARTSTAT=3DNEEDS-ACTION;RSVP=3DTRUE:mailt=
+o:resource-collection-car@example.org
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+
+--=_c8894dbdb8baeedacae836230e3436fd--
+"""
+
+itip_recurring = """Return-Path: <john.doe@example.org>
+Sender: john.doe@example.org
+Content-Type: text/calendar; method=REQUEST; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+From: john.doe@example.org
+Date: Mon, 24 Feb 2014 11:27:28 +0100
+Message-ID: <1a3aa8995e83dd24cf9247e538ac913a@example.org>
+Subject: Recurring
+
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//Mac OS X 10.9.2//EN
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:dbdb8baeedacae836230e3436fd-5e83dd24cf92
+DTSTAMP:20140213T1254140
+DTSTART;TZID=Europe/London:20120709T100000
+DTEND;TZID=Europe/London:20120709T120000
+RRULE:FREQ=DAILY;INTERVAL=1;COUNT=5
+SUMMARY:Recurring
+ORGANIZER;CN="Doe, John":mailto:john.doe@example.org
+ATTENDEE;ROLE=REQ-PARTICIPANT;CUTYPE=RESOURCE;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:jane@example.com
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+"""
+
+itip_empty = """MIME-Version: 1.0
+Date: Fri, 17 Jan 2014 13:51:50 +0100
+From: <john.doe@example.org>
+User-Agent: Roundcube Webmail/0.9.5
+To: john.sample@example.org
+Subject: "test" has been sent
+Message-ID: <52D92766.5040508@somedomain.com>
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 7bit
+
+Message plain text goes here...
+"""
+
+conf = pykolab.getConf()
+
+if not hasattr(conf, 'defaults'):
+ conf.finalize_conf()
+
+class TestITip(unittest.TestCase):
+
+ def setUp(self):
+ # intercept calls to smtplib.SMTP.sendmail()
+ import smtplib
+ self.patch(smtplib.SMTP, "__init__", self._mock_smtp_init)
+ self.patch(smtplib.SMTP, "quit", self._mock_nop)
+ self.patch(smtplib.SMTP, "sendmail", self._mock_smtp_sendmail)
+
+ self.smtplog = [];
+
+ def _mock_nop(self, domain=None):
+ pass
+
+ def _mock_smtp_init(self, host=None, port=None, local_hostname=None, timeout=0):
+ pass
+
+ def _mock_smtp_sendmail(self, from_addr, to_addr, message, mail_options=None, rcpt_options=None):
+ self.smtplog.append((from_addr, to_addr, message))
+
+
+ def test_001_itip_events_from_message(self):
+ itips1 = itip.events_from_message(message_from_string(itip_multipart))
+ self.assertEqual(len(itips1), 1, "Multipart iTip message with text/calendar")
+ self.assertEqual(itips1[0]['method'], "REQUEST", "iTip request method property")
+
+ itips2 = itip.events_from_message(message_from_string(itip_non_multipart))
+ self.assertEqual(len(itips2), 1, "Detect non-multipart iTip messages")
+
+ itips3 = itip.events_from_message(message_from_string(itip_application_ics))
+ self.assertEqual(len(itips3), 1, "Multipart iTip message with application/ics attachment")
+
+ itips4 = itip.events_from_message(message_from_string(itip_google_multipart))
+ self.assertEqual(len(itips4), 1, "Multipart iTip message from Google")
+
+ itips5 = itip.events_from_message(message_from_string(itip_empty))
+ self.assertEqual(len(itips5), 0, "Simple plain text message")
+
+ # invalid itip blocks
+ self.assertRaises(Exception, itip.events_from_message, message_from_string(itip_multipart.replace("BEGIN:VEVENT", "")))
+
+ itips6 = itip.events_from_message(message_from_string(itip_multipart.replace("DTSTART;", "X-DTSTART;")))
+ self.assertEqual(len(itips6), 0, "Event with not DTSTART")
+
+ itips7 = itip.events_from_message(message_from_string(itip_non_multipart.replace("METHOD:REQUEST", "METHOD:PUBLISH").replace("method=REQUEST", "method=PUBLISH")))
+ self.assertEqual(len(itips7), 0, "Invalid METHOD")
+
+
+ def test_002_check_date_conflict(self):
+ astart = datetime.datetime(2014,7,13, 10,0,0)
+ aend = astart + datetime.timedelta(hours=2)
+
+ bstart = datetime.datetime(2014,7,13, 10,0,0)
+ bend = astart + datetime.timedelta(hours=1)
+ self.assertTrue(itip.check_date_conflict(astart, aend, bstart, bend))
+
+ bstart = datetime.datetime(2014,7,13, 11,0,0)
+ bend = astart + datetime.timedelta(minutes=30)
+ self.assertTrue(itip.check_date_conflict(astart, aend, bstart, bend))
+
+ bend = astart + datetime.timedelta(hours=2)
+ self.assertTrue(itip.check_date_conflict(astart, aend, bstart, bend))
+
+ bstart = datetime.datetime(2014,7,13, 12,0,0)
+ bend = astart + datetime.timedelta(hours=1)
+ self.assertFalse(itip.check_date_conflict(astart, aend, bstart, bend))
+
+ bstart = datetime.datetime(2014,6,13, 10,0,0)
+ bend = datetime.datetime(2014,6,14, 12,0,0)
+ self.assertFalse(itip.check_date_conflict(astart, aend, bstart, bend))
+
+ bstart = datetime.datetime(2014,7,10, 12,0,0)
+ bend = datetime.datetime(2014,7,14, 14,0,0)
+ self.assertTrue(itip.check_date_conflict(astart, aend, bstart, bend))
+
+
+ def test_002_check_event_conflict(self):
+ itip_event = itip.events_from_message(message_from_string(itip_non_multipart))[0]
+
+ event = Event()
+ event.set_start(datetime.datetime(2012,7,13, 9,30,0, tzinfo=itip_event['start'].tzinfo))
+ event.set_end(datetime.datetime(2012,7,13, 10,30,0, tzinfo=itip_event['start'].tzinfo))
+
+ self.assertTrue(itip.check_event_conflict(event, itip_event), "Conflicting dates")
+
+ event.set_uid(itip_event['uid'])
+ self.assertFalse(itip.check_event_conflict(event, itip_event), "No conflict for same UID")
+
+ allday = Event()
+ allday.set_start(datetime.date(2012,7,13))
+ allday.set_end(datetime.date(2012,7,13))
+
+ self.assertTrue(itip.check_event_conflict(allday, itip_event), "Conflicting allday event")
+
+ allday.set_transparency(True)
+ self.assertFalse(itip.check_event_conflict(allday, itip_event), "No conflict if event is set to transparent")
+
+ event2 = Event()
+ event2.set_start(datetime.datetime(2012,7,13, 10,0,0, tzinfo=pytz.timezone("US/Central")))
+ event2.set_end(datetime.datetime(2012,7,13, 11,0,0, tzinfo=pytz.timezone("US/Central")))
+
+ self.assertFalse(itip.check_event_conflict(event, itip_event), "No conflict with timezone shift")
+
+ rrule = kolabformat.RecurrenceRule()
+ rrule.setFrequency(kolabformat.RecurrenceRule.Weekly)
+ rrule.setCount(10)
+
+ event3 = Event()
+ event3.set_recurrence(rrule);
+ event3.set_start(datetime.datetime(2012,6,29, 9,30,0, tzinfo=pytz.utc))
+ event3.set_end(datetime.datetime(2012,6,29, 10,30,0, tzinfo=pytz.utc))
+
+ self.assertTrue(itip.check_event_conflict(event3, itip_event), "Conflict in (3rd) recurring event instance")
+
+ itip_event = itip.events_from_message(message_from_string(itip_recurring))[0]
+ self.assertTrue(itip.check_event_conflict(event3, itip_event), "Conflict in two recurring events")
+
+ event4 = Event()
+ event4.set_recurrence(rrule);
+ event4.set_start(datetime.datetime(2012,7,1, 9,30,0, tzinfo=pytz.utc))
+ event4.set_end(datetime.datetime(2012,7,1, 10,30,0, tzinfo=pytz.utc))
+ self.assertFalse(itip.check_event_conflict(event4, itip_event), "No conflict in two recurring events")
+
+
+ def test_003_send_reply(self):
+ itip_events = itip.events_from_message(message_from_string(itip_non_multipart))
+ itip.send_reply("resource-collection-car@example.org", itip_events, "SUMMARY=%(summary)s; STATUS=%(status)s; NAME=%(name)s;")
+
+ self.assertEqual(len(self.smtplog), 1)
+ self.assertEqual(self.smtplog[0][0], 'resource-collection-car@example.org', "From attendee")
+ self.assertEqual(self.smtplog[0][1], 'john.doe@example.org', "To organizer")
+
+ _accepted = participant_status_label('ACCEPTED')
+ message = message_from_string(self.smtplog[0][2])
+ self.assertEqual(message.get('Subject'), _("Invitation for %(summary)s was %(status)s") % { 'summary':'test', 'status':_accepted })
+
+ text = str(message.get_payload(0));
+ self.assertIn('SUMMARY=test', text)
+ self.assertIn('STATUS=' + _accepted, text)
diff --git a/tests/unit/test-011-wallace_resources.py b/tests/unit/test-011-wallace_resources.py
index 62bfd27..9c42317 100644
--- a/tests/unit/test-011-wallace_resources.py
+++ b/tests/unit/test-011-wallace_resources.py
@@ -2,6 +2,7 @@ import pykolab
import logging
import datetime
+from pykolab import itip
from icalendar import Calendar
from email import message
from email import message_from_string
@@ -87,152 +88,6 @@ END:VEVENT
END:VCALENDAR
"""
-itip_google_multipart = """MIME-Version: 1.0
-Message-ID: <001a11c2ad84243e0604f3246bae@google.com>
-Date: Mon, 24 Feb 2014 10:27:28 +0000
-Subject: =?ISO-8859-1?Q?Invitation=3A_iTip_from_Apple_=40_Mon_Feb_24=2C_2014_12pm_?=
- =?ISO-8859-1?Q?=2D_1pm_=28Tom_=26_T=E4m=29?=
-From: "john.doe" <john.doe@gmail.com>
-To: <john.sample@example.org>
-Content-Type: multipart/mixed; boundary=001a11c2ad84243df004f3246bad
-
---001a11c2ad84243df004f3246bad
-Content-Type: multipart/alternative; boundary=001a11c2ad84243dec04f3246bab
-
---001a11c2ad84243dec04f3246bab
-Content-Type: text/plain; charset=ISO-8859-1; format=flowed; delsp=yes
-
-<some text content here>
-
---001a11c2ad84243dec04f3246bab
-Content-Type: text/html; charset=ISO-8859-1
-Content-Transfer-Encoding: quoted-printable
-
-<div style=3D""><!-- some HTML message content here --></div>
---001a11c2ad84243dec04f3246bab
-Content-Type: text/calendar; charset=UTF-8; method=REQUEST
-Content-Transfer-Encoding: 7bit
-
-BEGIN:VCALENDAR
-PRODID:-//Google Inc//Google Calendar 70.9054//EN
-VERSION:2.0
-CALSCALE:GREGORIAN
-METHOD:REQUEST
-BEGIN:VEVENT
-DTSTART:20140224T110000Z
-DTEND:20140224T120000Z
-DTSTAMP:20140224T102728Z
-ORGANIZER:mailto:kepjllr6mcq7d0959u4cdc7000@group.calendar.google.com
-UID:0BE2F640-5814-47C9-ABAE-E7E959204E76
-ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE
- ;X-NUM-GUESTS=0:mailto:kepjllr6mcq7d0959u4cdc7000@group.calendar.google.com
-ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=
- TRUE;CN=John Sample;X-NUM-GUESTS=0:mailto:john.sample@example.org
-CREATED:20140224T102728Z
-DESCRIPTION:Testing Multipart structure\\nView your event at http://www.goog
- le.com/calendar/event?action=VIEW&eid=XzYxMTRhY2k2Nm9xMzBiOWw3MG9qOGI5azZ0M
- WppYmExODkwa2FiYTU2dDJqaWQ5cDY4bzM4aDluNm8gdGhvbWFzQGJyb3RoZXJsaS5jaA&tok=N
- TIja2VwamxscjZtY3E3ZDA5NTl1NGNkYzcwMDBAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbTkz
- NTcyYTU2YmUwNWMxNjY0Zjc3OTU0MzhmMDcwY2FhN2NjZjIzYWM&ctz=Europe/Zurich&hl=en
- .
-LAST-MODIFIED:20140224T102728Z
-LOCATION:
-SEQUENCE:5
-STATUS:CONFIRMED
-SUMMARY:iTip from Apple
-TRANSP:OPAQUE
-END:VEVENT
-END:VCALENDAR
-
---001a11c2ad84243dec04f3246bab--
---001a11c2ad84243df004f3246bad
-Content-Type: application/ics; name="invite.ics"
-Content-Disposition: attachment; filename="invite.ics"
-Content-Transfer-Encoding: base64
-
-QkVHSU46VkNBTEVOREFSDQpQUk9ESUQ6LS8vR29vZ2xlIEluYy8vR29vZ2xlIENhbGVuZGFyIDcw
-LjkwNTQvL0VODQpWRVJTSU9OOjIuMA0KQ0FMU0NBTEU6R1JFR09SSUFODQpNRVRIT0Q6UkVRVUVT
-VA0KQkVHSU46VkVWRU5UDQpEVFNUQVJUOjIwMTQwMjI0VDExMDAwMFoNCkRURU5EOjIwMTQwMjI0
-VDEyMDAwMFoNCkRUU1RBTVA6MjAxNDAyMjRUMTAyNzI4Wg0KT1JHQU5JWkVSOm1haWx0bzprZXBq
-bGxyNm1jcTdkMDk1OXU0Y2RjNzAwMEBncm91cC5jYWxlbmRhci5nb29nbGUuY29tDQpVSUQ6MEJF
-MkY2NDAtNTgxNC00N0M5LUFCQUUtRTdFOTU5MjA0RTc2DQpBVFRFTkRFRTtDVVRZUEU9SU5ESVZJ
-RFVBTDtST0xFPVJFUS1QQVJUSUNJUEFOVDtQQVJUU1RBVD1BQ0NFUFRFRDtSU1ZQPVRSVUUNCiA7
-WC1OVU0tR1VFU1RTPTA6bWFpbHRvOmtlcGpsbHI2bWNxN2QwOTU5dTRjZGM3MDAwQGdyb3VwLmNh
-bGVuZGFyLmdvb2dsZS5jb20NCkFUVEVOREVFO0NVVFlQRT1JTkRJVklEVUFMO1JPTEU9UkVRLVBB
-UlRJQ0lQQU5UO1BBUlRTVEFUPU5FRURTLUFDVElPTjtSU1ZQPQ0KIFRSVUU7WC1OVU0tR1VFU1RT
-PTA6bWFpbHRvOnRob21hc0Bicm90aGVybGkuY2gNCkFUVEVOREVFO0NVVFlQRT1JTkRJVklEVUFM
-O1JPTEU9UkVRLVBBUlRJQ0lQQU5UO1BBUlRTVEFUPU5FRURTLUFDVElPTjtSU1ZQPQ0KIFRSVUU7
-Q049VGhvbWFzIEJydWVkZXJsaTtYLU5VTS1HVUVTVFM9MDptYWlsdG86cm91bmRjdWJlQGdtYWls
-LmNvbQ0KQ1JFQVRFRDoyMDE0MDIyNFQxMDI3MjhaDQpERVNDUklQVElPTjpUZXN0aW5nIE11bHRp
-cGFydCBzdHJ1Y3R1cmVcblZpZXcgeW91ciBldmVudCBhdCBodHRwOi8vd3d3Lmdvb2cNCiBsZS5j
-b20vY2FsZW5kYXIvZXZlbnQ/YWN0aW9uPVZJRVcmZWlkPVh6WXhNVFJoWTJrMk5tOXhNekJpT1d3
-M01HOXFPR0k1YXpaME0NCiBXcHBZbUV4T0Rrd2EyRmlZVFUyZERKcWFXUTVjRFk0YnpNNGFEbHVO
-bThnZEdodmJXRnpRR0p5YjNSb1pYSnNhUzVqYUEmdG9rPU4NCiBUSWphMlZ3YW14c2NqWnRZM0Uz
-WkRBNU5UbDFOR05rWXpjd01EQkFaM0p2ZFhBdVkyRnNaVzVrWVhJdVoyOXZaMnhsTG1OdmJUa3oN
-CiBOVGN5WVRVMlltVXdOV014TmpZMFpqYzNPVFUwTXpobU1EY3dZMkZoTjJOalpqSXpZV00mY3R6
-PUV1cm9wZS9adXJpY2gmaGw9ZW4NCiAuDQpMQVNULU1PRElGSUVEOjIwMTQwMjI0VDEwMjcyOFoN
-CkxPQ0FUSU9OOg0KU0VRVUVOQ0U6NQ0KU1RBVFVTOkNPTkZJUk1FRA0KU1VNTUFSWTppVGlwIGZy
-b20gQXBwbGUNClRSQU5TUDpPUEFRVUUNCkVORDpWRVZFTlQNCkVORDpWQ0FMRU5EQVINCg==
---001a11c2ad84243df004f3246bad--
-"""
-
-itip_application_ics = """MIME-Version: 1.0
-Content-Type: multipart/mixed;
- boundary="=_c8894dbdb8baeedacae836230e3436fd"
-From: "Doe, John" <john.doe@example.org>
-Date: Fri, 13 Jul 2012 13:54:14 +0100
-Message-ID: <240fe7ae7e139129e9eb95213c101622@example.org>
-User-Agent: Roundcube Webmail/0.9-0.3.el6.kolab_3.0
-To: resource-collection-car@example.org
-Subject: "test" has been updated
-
---=_c8894dbdb8baeedacae836230e3436fd
-Content-Transfer-Encoding: quoted-printable
-Content-Type: text/plain; charset=UTF-8; format=flowed
-
-<some text here>
-
---=_c8894dbdb8baeedacae836230e3436fd
-Content-Type: application/ics; charset=UTF-8; method=REQUEST;
- name=event.ics
-Content-Transfer-Encoding: quoted-printable
-
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Roundcube Webmail 0.9-0.3.el6.kolab_3.0//NONSGML Calendar//EN
-CALSCALE:GREGORIAN
-METHOD:REQUEST
-BEGIN:VEVENT
-UID:626421779C777FBE9C9B85A80D04DDFA-A4BF5BBB9FEAA271
-DTSTAMP:20120713T1254140
-DTSTART;TZID=3DEurope/London:20120713T100000
-DTEND;TZID=3DEurope/London:20120713T110000
-SUMMARY:test
-DESCRIPTION:test
-ORGANIZER;CN=3D"Doe, John":mailto:john.doe@example.org
-ATTENDEE;ROLE=3DREQ-PARTICIPANT;PARTSTAT=3DNEEDS-ACTION;RSVP=3DTRUE:mailt=
-o:resource-collection-car@example.org
-TRANSP:OPAQUE
-END:VEVENT
-END:VCALENDAR
-
---=_c8894dbdb8baeedacae836230e3436fd--
-"""
-
-itip_empty = """MIME-Version: 1.0
-Date: Fri, 17 Jan 2014 13:51:50 +0100
-From: <john.doe@example.org>
-User-Agent: Roundcube Webmail/0.9.5
-To: john.sample@example.org
-Subject: "test" has been sent
-Message-ID: <52D92766.5040508@somedomain.com>
-Content-Type: text/plain; charset=UTF-8
-Content-Transfer-Encoding: 7bit
-
-Message plain text goes here...
-"""
-
-
conf = pykolab.getConf()
if not hasattr(conf, 'defaults'):
@@ -272,7 +127,7 @@ class TestWallaceResources(unittest.TestCase):
def _mock_search_entry_by_attribute(self, attr, value, **kw):
results = []
if value == "cn=Room 101,ou=Resources,dc=example,dc=org":
- results.append({ 'dn': 'cn=Rooms,ou=Resources,dc=example,dc=org', attr: value, 'owner': 'uid=doe,ou=People,dc=example,dc=org' })
+ results.append(('cn=Rooms,ou=Resources,dc=example,dc=org', { attr: value, 'owner': 'uid=doe,ou=People,dc=example,dc=org' }))
return results
def _mock_smtp_init(self, host=None, port=None, local_hostname=None, timeout=0):
@@ -301,32 +156,6 @@ class TestWallaceResources(unittest.TestCase):
return None
- def test_001_itip_events_from_message(self):
- itips1 = module_resources.itip_events_from_message(message_from_string(itip_multipart))
- self.assertEqual(len(itips1), 1, "Multipart iTip message with text/calendar")
- self.assertEqual(itips1[0]['method'], "REQUEST", "iTip request method property")
-
- itips2 = module_resources.itip_events_from_message(message_from_string(itip_non_multipart))
- self.assertEqual(len(itips2), 1, "Detect non-multipart iTip messages")
-
- itips3 = module_resources.itip_events_from_message(message_from_string(itip_application_ics))
- self.assertEqual(len(itips3), 1, "Multipart iTip message with application/ics attachment")
-
- itips4 = module_resources.itip_events_from_message(message_from_string(itip_google_multipart))
- self.assertEqual(len(itips4), 1, "Multipart iTip message from Google")
-
- itips5 = module_resources.itip_events_from_message(message_from_string(itip_empty))
- self.assertEqual(len(itips5), 0, "Simple plain text message")
-
- # invalid itip blocks
- self.assertRaises(Exception, module_resources.itip_events_from_message, message_from_string(itip_multipart.replace("BEGIN:VEVENT", "")))
-
- itips6 = module_resources.itip_events_from_message(message_from_string(itip_multipart.replace("DTSTART;", "X-DTSTART;")))
- self.assertEqual(len(itips6), 0, "Event with not DTSTART")
-
- itips7 = module_resources.itip_events_from_message(message_from_string(itip_non_multipart.replace("METHOD:REQUEST", "METHOD:PUBLISH").replace("method=REQUEST", "method=PUBLISH")))
- self.assertEqual(len(itips7), 0, "Invalid METHOD")
-
def test_002_resource_record_from_email_address(self):
res = module_resources.resource_record_from_email_address("doe@example.org")
@@ -337,7 +166,7 @@ class TestWallaceResources(unittest.TestCase):
def test_003_resource_records_from_itip_events(self):
message = message_from_string(itip_multipart)
- itips = module_resources.itip_events_from_message(message)
+ itips = itip.events_from_message(message)
res = module_resources.resource_records_from_itip_events(itips)
self.assertEqual(len(res), 2, "Return all attendee resources");
@@ -365,7 +194,7 @@ class TestWallaceResources(unittest.TestCase):
def test_005_send_response_accept(self):
- itip_event = module_resources.itip_events_from_message(message_from_string(itip_non_multipart))
+ itip_event = itip.events_from_message(message_from_string(itip_non_multipart))
module_resources.send_response("resource-collection-car@example.org", itip_event)
self.assertEqual(len(self.smtplog), 1);
@@ -384,7 +213,7 @@ class TestWallaceResources(unittest.TestCase):
def test_006_send_response_delegate(self):
# delegate resource-collection-car@example.org => resource-car-audi-a4@example.org
- itip_event = module_resources.itip_events_from_message(message_from_string(itip_non_multipart))[0]
+ itip_event = itip.events_from_message(message_from_string(itip_non_multipart))[0]
itip_event['xml'].delegate('resource-collection-car@example.org', 'resource-car-audi-a4@example.org')
itip_event['xml'].set_attendee_participant_status(itip_event['xml'].get_attendee('resource-car-audi-a4@example.org'), "ACCEPTED")
@@ -408,30 +237,3 @@ class TestWallaceResources(unittest.TestCase):
self.assertEqual(ical2['attendee'].params['PARTSTAT'], "DELEGATED")
- def test_007_check_date_conflict(self):
- astart = datetime.datetime(2014,7,13, 10,0,0)
- aend = astart + datetime.timedelta(hours=2)
-
- bstart = datetime.datetime(2014,7,13, 10,0,0)
- bend = astart + datetime.timedelta(hours=1)
- self.assertTrue(module_resources.check_date_conflict(astart, aend, bstart, bend))
-
- bstart = datetime.datetime(2014,7,13, 11,0,0)
- bend = astart + datetime.timedelta(minutes=30)
- self.assertTrue(module_resources.check_date_conflict(astart, aend, bstart, bend))
-
- bend = astart + datetime.timedelta(hours=2)
- self.assertTrue(module_resources.check_date_conflict(astart, aend, bstart, bend))
-
- bstart = datetime.datetime(2014,7,13, 12,0,0)
- bend = astart + datetime.timedelta(hours=1)
- self.assertFalse(module_resources.check_date_conflict(astart, aend, bstart, bend))
-
- bstart = datetime.datetime(2014,6,13, 10,0,0)
- bend = datetime.datetime(2014,6,14, 12,0,0)
- self.assertFalse(module_resources.check_date_conflict(astart, aend, bstart, bend))
-
- bstart = datetime.datetime(2014,7,10, 12,0,0)
- bend = datetime.datetime(2014,7,14, 14,0,0)
- self.assertTrue(module_resources.check_date_conflict(astart, aend, bstart, bend))
-
diff --git a/tests/unit/test-012-wallace_invitationpolicy.py b/tests/unit/test-012-wallace_invitationpolicy.py
new file mode 100644
index 0000000..dbe0713
--- /dev/null
+++ b/tests/unit/test-012-wallace_invitationpolicy.py
@@ -0,0 +1,161 @@
+import os
+import pykolab
+import logging
+import time
+
+from icalendar import Calendar
+from email import message
+from email import message_from_string
+from wallace import module_invitationpolicy as MIP
+from twisted.trial import unittest
+
+from pykolab.auth.ldap import LDAP
+from pykolab.constants import *
+
+
+# define some iTip MIME messages
+
+itip_multipart = """MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="=_c8894dbdb8baeedacae836230e3436fd"
+From: "Doe, John" <john.doe@example.org>
+Date: Fri, 13 Jul 2012 13:54:14 +0100
+Message-ID: <240fe7ae7e139129e9eb95213c1016d7@example.org>
+User-Agent: Roundcube Webmail/0.9-0.3.el6.kolab_3.0
+To: jane.doe@example.org
+Subject: "test" has been updated
+
+--=_c8894dbdb8baeedacae836230e3436fd
+Content-Type: text/plain; charset=UTF-8; format=flowed
+Content-Transfer-Encoding: quoted-printable
+
+*test*
+
+--=_c8894dbdb8baeedacae836230e3436fd
+Content-Type: text/calendar; charset=UTF-8; method=REQUEST;
+ name=event.ics
+Content-Disposition: attachment;
+ filename=event.ics
+Content-Transfer-Encoding: quoted-printable
+
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Roundcube Webmail 1.0.1//NONSGML Calendar//EN
+CALSCALE:GREGORIAN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:626421779C777FBE9C9B85A80D04DDFA-A4BF5BBB9FEAA271
+DTSTAMP:20120713T125414Z
+DTSTART;TZID=3DEurope/London:20120713T100000
+DTEND;TZID=3DEurope/London:20120713T110000
+SUMMARY:test
+DESCRIPTION:test
+ORGANIZER;CN=3D"Doe, John":mailto:john.doe@example.org
+ATTENDEE;ROLE=3DREQ-PARTICIPANT;PARTSTAT=3DNEEDS-ACTION;RSVP=3DTRUE:mailt=
+o:jane.doe@example.org
+ATTENDEE;ROLE=3DOPT-PARTICIPANT;PARTSTAT=3DNEEDS-ACTION;RSVP=3DTRUE:mailt=
+user.external@example.com
+SEQUENCE:1
+TRANSP:OPAQUE
+END:VEVENT
+END:VCALENDAR
+
+--=_c8894dbdb8baeedacae836230e3436fd--
+"""
+
+conf = pykolab.getConf()
+
+if not hasattr(conf, 'defaults'):
+ conf.finalize_conf()
+
+class TestWallaceInvitationpolicy(unittest.TestCase):
+
+ def setUp(self):
+ # monkey-patch the pykolab.auth module to check API calls
+ # without actually connecting to LDAP
+ self.patch(pykolab.auth.Auth, "connect", self._mock_nop)
+ self.patch(pykolab.auth.Auth, "disconnect", self._mock_nop)
+ self.patch(pykolab.auth.Auth, "find_user_dn", self._mock_find_user_dn)
+ self.patch(pykolab.auth.Auth, "get_entry_attributes", self._mock_get_entry_attributes)
+
+ # intercept calls to smtplib.SMTP.sendmail()
+ import smtplib
+ self.patch(smtplib.SMTP, "__init__", self._mock_smtp_init)
+ self.patch(smtplib.SMTP, "quit", self._mock_nop)
+ self.patch(smtplib.SMTP, "sendmail", self._mock_smtp_sendmail)
+
+ self.smtplog = [];
+
+ def _mock_find_user_dn(self, value, kolabuser=False):
+ (prefix, domain) = value.split('@')
+ return "uid=" + prefix + ",ou=People,dc=" + ",dc=".join(domain.split('.'))
+
+ def _mock_get_entry_attributes(self, domain, entry, attributes):
+ (_, uid) = entry.split(',')[0].split('=')
+ return { 'cn': uid, 'mail': uid + "@example.org", '_attrib': attributes }
+
+ def _mock_nop(self, domain=None):
+ pass
+
+ def _mock_smtp_init(self, host=None, port=None, local_hostname=None, timeout=0):
+ pass
+
+ def _mock_smtp_sendmail(self, from_addr, to_addr, message, mail_options=None, rcpt_options=None):
+ self.smtplog.append((from_addr, to_addr, message))
+
+ def test_001_itip_events_from_message(self):
+ itips = pykolab.itip.events_from_message(message_from_string(itip_multipart))
+ self.assertEqual(len(itips), 1, "Multipart iTip message with text/calendar")
+ self.assertEqual(itips[0]['method'], "REQUEST", "iTip request method property")
+ self.assertEqual(len(itips[0]['attendees']), 2, "List attendees from iTip")
+ self.assertEqual(itips[0]['attendees'][0], "mailto:jane.doe@example.org", "First attendee from iTip")
+
+ def test_002_user_dn_from_email_address(self):
+ res = MIP.user_dn_from_email_address("doe@example.org")
+ # assert call to (patched) pykolab.auth.Auth.find_resource()
+ self.assertEqual("uid=doe,ou=People,dc=example,dc=org", res);
+
+ def test_003_get_matching_invitation_policy(self):
+ user = { 'kolabinvitationpolicy': [
+ 'ACT_ACCEPT:example.org',
+ 'ACT_REJECT:gmail.com',
+ 'ACT_MANUAL:*'
+ ] }
+ self.assertEqual(MIP.get_matching_invitation_policies(user, 'fastmail.net'), [MIP.ACT_MANUAL])
+ self.assertEqual(MIP.get_matching_invitation_policies(user, 'example.org'), [MIP.ACT_ACCEPT,MIP.ACT_MANUAL])
+ self.assertEqual(MIP.get_matching_invitation_policies(user, 'gmail.com'), [MIP.ACT_REJECT,MIP.ACT_MANUAL])
+
+ user = { 'kolabinvitationpolicy': ['ACT_ACCEPT:example.org', 'ACT_MANUAL:others'] }
+ self.assertEqual(MIP.get_matching_invitation_policies(user, 'somedomain.net'), [MIP.ACT_MANUAL])
+
+ def test_004_write_locks(self):
+ user = { 'cn': 'John Doe', 'mail': "doe@example.org" }
+
+ lock_key = MIP.get_lock_key(user, '1234567890-abcdef')
+ lock_file = os.path.join(MIP.mybasepath, 'locks', lock_key + '.lock')
+ MIP.set_write_lock(lock_key)
+
+ time.sleep(1)
+ self.assertTrue(os.path.isfile(lock_file))
+ self.assertFalse(MIP.set_write_lock(lock_key, False))
+
+ MIP.remove_write_lock(lock_key)
+ self.assertFalse(os.path.isfile(lock_file))
+
+ def test_005_is_auto_reply(self):
+ all_manual = [ 'ACT_MANUAL' ]
+ accept_none = [ 'ACT_REJECT' ]
+ accept_all = [ 'ACT_ACCEPT', 'ACT_UPDATE' ]
+ accept_cond = [ 'ACT_ACCEPT_IF_NO_CONFLICT', 'ACT_REJECT_IF_CONFLICT' ]
+ accept_some = [ 'ACT_ACCEPT_IF_NO_CONFLICT', 'ACT_SAVE_TO_CALENDAR:example.org', 'ACT_REJECT_IF_CONFLICT' ]
+ accept_avail = [ 'ACT_ACCEPT_IF_NO_CONFLICT', 'ACT_REJECT_IF_CONFLICT:example.org' ]
+
+ self.assertFalse( MIP.is_auto_reply({ 'kolabinvitationpolicy':all_manual }, 'domain.org'))
+ self.assertTrue( MIP.is_auto_reply({ 'kolabinvitationpolicy':accept_none }, 'domain.org'))
+ self.assertTrue( MIP.is_auto_reply({ 'kolabinvitationpolicy':accept_all }, 'domain.com'))
+ self.assertTrue( MIP.is_auto_reply({ 'kolabinvitationpolicy':accept_cond }, 'domain.com'))
+ self.assertTrue( MIP.is_auto_reply({ 'kolabinvitationpolicy':accept_some }, 'domain.com'))
+ self.assertFalse( MIP.is_auto_reply({ 'kolabinvitationpolicy':accept_some }, 'example.org'))
+ self.assertFalse( MIP.is_auto_reply({ 'kolabinvitationpolicy':accept_avail }, 'domain.com'))
+ self.assertTrue( MIP.is_auto_reply({ 'kolabinvitationpolicy':accept_avail }, 'example.org'))
+ \ No newline at end of file
diff --git a/tests/unit/test-015-translate.py b/tests/unit/test-015-translate.py
new file mode 100644
index 0000000..6819b80
--- /dev/null
+++ b/tests/unit/test-015-translate.py
@@ -0,0 +1,23 @@
+import unittest
+import gettext
+from pykolab import translate
+
+class TestTranslate(unittest.TestCase):
+
+ def test_001_default_langs(self):
+ self.assertTrue(len(translate.getDefaultLangs()) > 0)
+
+ def test_002_translate(self):
+ from pykolab.translate import _
+ self.assertEqual(_("Folder name"), "Folder name")
+
+ def test_003_set_lang(self):
+ from pykolab.translate import _
+ self.assertEqual(_("Folder name"), "Folder name")
+ translate.setUserLanguage('de_DE')
+ self.assertEqual(_("Folder name"), "Ordnername", "German Translation found")
+ translate.setUserLanguage('foo_bar')
+ self.assertEqual(_("Folder name"), "Folder name", "Unkonwn language falls back to NullTranslations")
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/wallace/module_invitationpolicy.py b/wallace/module_invitationpolicy.py
new file mode 100644
index 0000000..9e0fa37
--- /dev/null
+++ b/wallace/module_invitationpolicy.py
@@ -0,0 +1,1015 @@
+# -*- coding: utf-8 -*-
+# Copyright 2014 Kolab Systems AG (http://www.kolabsys.com)
+#
+# Thomas Bruederli (Kolab Systems) <bruederli@kolabsys.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+import datetime
+import os
+import tempfile
+import time
+from urlparse import urlparse
+import urllib
+import hashlib
+
+from email import message_from_string
+from email.parser import Parser
+from email.utils import formataddr
+from email.utils import getaddresses
+
+import modules
+
+import pykolab
+import kolabformat
+
+from pykolab import utils
+from pykolab.auth import Auth
+from pykolab.conf import Conf
+from pykolab.imap import IMAP
+from pykolab.xml import to_dt
+from pykolab.xml import event_from_message
+from pykolab.xml import participant_status_label
+from pykolab.itip import events_from_message
+from pykolab.itip import check_event_conflict
+from pykolab.itip import send_reply
+from pykolab.translate import _
+
+# define some contstants used in the code below
+COND_IF_AVAILABLE = 32
+COND_IF_CONFLICT = 64
+COND_TENTATIVE = 128
+COND_NOTIFY = 256
+ACT_MANUAL = 1
+ACT_ACCEPT = 2
+ACT_DELEGATE = 4
+ACT_REJECT = 8
+ACT_UPDATE = 16
+ACT_TENTATIVE = ACT_ACCEPT + COND_TENTATIVE
+ACT_ACCEPT_IF_NO_CONFLICT = ACT_ACCEPT + COND_IF_AVAILABLE
+ACT_TENTATIVE_IF_NO_CONFLICT = ACT_ACCEPT + COND_TENTATIVE + COND_IF_AVAILABLE
+ACT_DELEGATE_IF_CONFLICT = ACT_DELEGATE + COND_IF_CONFLICT
+ACT_REJECT_IF_CONFLICT = ACT_REJECT + COND_IF_CONFLICT
+ACT_UPDATE_AND_NOTIFY = ACT_UPDATE + COND_NOTIFY
+ACT_SAVE_TO_CALENDAR = 512
+
+FOLDER_TYPE_ANNOTATION = '/vendor/kolab/folder-type'
+
+MESSAGE_PROCESSED = 1
+MESSAGE_FORWARD = 2
+
+policy_name_map = {
+ 'ACT_MANUAL': ACT_MANUAL,
+ 'ACT_ACCEPT': ACT_ACCEPT,
+ 'ACT_ACCEPT_IF_NO_CONFLICT': ACT_ACCEPT_IF_NO_CONFLICT,
+ 'ACT_TENTATIVE': ACT_TENTATIVE,
+ 'ACT_TENTATIVE_IF_NO_CONFLICT': ACT_TENTATIVE_IF_NO_CONFLICT,
+ 'ACT_DELEGATE': ACT_DELEGATE,
+ 'ACT_DELEGATE_IF_CONFLICT': ACT_DELEGATE_IF_CONFLICT,
+ 'ACT_REJECT': ACT_REJECT,
+ 'ACT_REJECT_IF_CONFLICT': ACT_REJECT_IF_CONFLICT,
+ 'ACT_UPDATE': ACT_UPDATE,
+ 'ACT_UPDATE_AND_NOTIFY': ACT_UPDATE_AND_NOTIFY,
+ 'ACT_SAVE_TO_CALENDAR': ACT_SAVE_TO_CALENDAR
+}
+
+policy_value_map = dict([(v, k) for (k, v) in policy_name_map.iteritems()])
+
+log = pykolab.getLogger('pykolab.wallace')
+conf = pykolab.getConf()
+
+mybasepath = '/var/spool/pykolab/wallace/invitationpolicy/'
+
+auth = None
+imap = None
+write_locks = []
+
+def __init__():
+ modules.register('invitationpolicy', execute, description=description())
+
+def accept(filepath):
+ new_filepath = os.path.join(
+ mybasepath,
+ 'ACCEPT',
+ os.path.basename(filepath)
+ )
+
+ cleanup()
+ os.rename(filepath, new_filepath)
+ filepath = new_filepath
+ exec('modules.cb_action_ACCEPT(%r, %r)' % ('invitationpolicy',filepath))
+
+def reject(filepath):
+ new_filepath = os.path.join(
+ mybasepath,
+ 'REJECT',
+ os.path.basename(filepath)
+ )
+
+ os.rename(filepath, new_filepath)
+ filepath = new_filepath
+ exec('modules.cb_action_REJECT(%r, %r)' % ('invitationpolicy',filepath))
+
+def description():
+ return """Invitation policy execution module."""
+
+def cleanup():
+ global auth, imap, write_locks
+
+ log.debug("cleanup(): %r, %r" % (auth, imap), level=9)
+
+ auth.disconnect()
+ del auth
+
+ # Disconnect IMAP or we lock the mailbox almost constantly
+ imap.disconnect()
+ del imap
+
+ # remove remaining write locks
+ for key in write_locks:
+ remove_write_lock(key, False)
+
+def execute(*args, **kw):
+ global auth, imap
+
+ # (re)set language to default
+ pykolab.translate.setUserLanguage(conf.get('kolab','default_locale'))
+
+ if not os.path.isdir(mybasepath):
+ os.makedirs(mybasepath)
+
+ for stage in ['incoming', 'ACCEPT', 'REJECT', 'HOLD', 'DEFER', 'locks']:
+ if not os.path.isdir(os.path.join(mybasepath, stage)):
+ os.makedirs(os.path.join(mybasepath, stage))
+
+ log.debug(_("Invitation policy called for %r, %r") % (args, kw), level=9)
+
+ auth = Auth()
+ imap = IMAP()
+
+ filepath = args[0]
+
+ # ignore calls on lock files
+ if '/locks/' in filepath or kw.has_key('stage') and kw['stage'] == 'locks':
+ return False
+
+ log.debug("Invitation policy executing for %r, %r" % (filepath, '/locks/' in filepath), level=8)
+
+ if kw.has_key('stage'):
+ log.debug(_("Issuing callback after processing to stage %s") % (kw['stage']), level=8)
+
+ log.debug(_("Testing cb_action_%s()") % (kw['stage']), level=8)
+ if hasattr(modules, 'cb_action_%s' % (kw['stage'])):
+ log.debug(_("Attempting to execute cb_action_%s()") % (kw['stage']), level=8)
+
+ exec(
+ 'modules.cb_action_%s(%r, %r)' % (
+ kw['stage'],
+ 'invitationpolicy',
+ filepath
+ )
+ )
+
+ return filepath
+ else:
+ # Move to incoming
+ new_filepath = os.path.join(
+ mybasepath,
+ 'incoming',
+ os.path.basename(filepath)
+ )
+
+ if not filepath == new_filepath:
+ log.debug("Renaming %r to %r" % (filepath, new_filepath))
+ os.rename(filepath, new_filepath)
+ filepath = new_filepath
+
+ # parse full message
+ message = Parser().parse(open(filepath, 'r'))
+
+ recipients = [address for displayname,address in getaddresses(message.get_all('X-Kolab-To'))]
+ sender_email = [address for displayname,address in getaddresses(message.get_all('X-Kolab-From'))][0]
+
+ any_itips = False
+ recipient_email = None
+ recipient_user_dn = None
+
+ # An iTip message may contain multiple events. Later on, test if the message
+ # is an iTip message by checking the length of this list.
+ try:
+ itip_events = events_from_message(message, ['REQUEST', 'REPLY', 'CANCEL'])
+ except Exception, e:
+ log.error(_("Failed to parse iTip events from message: %r" % (e)))
+ itip_events = []
+
+ if not len(itip_events) > 0:
+ log.info(_("Message is not an iTip message or does not contain any (valid) iTip events."))
+
+ else:
+ any_itips = True
+ log.debug(_("iTip events attached to this message contain the following information: %r") % (itip_events), level=9)
+
+ # See if any iTip actually allocates a user.
+ if any_itips and len([x['uid'] for x in itip_events if x.has_key('attendees') or x.has_key('organizer')]) > 0:
+ auth.connect()
+
+ for recipient in recipients:
+ recipient_user_dn = user_dn_from_email_address(recipient)
+ if recipient_user_dn:
+ recipient_email = recipient
+ break
+
+ if not any_itips:
+ log.debug(_("No itips, no users, pass along %r") % (filepath), level=5)
+ return filepath
+ elif recipient_email is None:
+ log.debug(_("iTips, but no users, pass along %r") % (filepath), level=5)
+ return filepath
+
+ # we're looking at the first itip event object
+ itip_event = itip_events[0]
+
+ # for replies, the organizer is the recipient
+ if itip_event['method'] == 'REPLY':
+ user_attendees = [itip_event['organizer']] if str(itip_event['organizer']).split(':')[-1] == recipient_email else []
+
+ else:
+ # Limit the attendees to the one that is actually invited with the current message.
+ attendees = [str(a).split(':')[-1] for a in (itip_event['attendees'] if itip_event.has_key('attendees') else [])]
+ user_attendees = [a for a in attendees if a == recipient_email]
+
+ if itip_event.has_key('organizer'):
+ sender_email = itip_event['xml'].get_organizer().email()
+
+ # abort if no attendee matches the envelope recipient
+ if len(user_attendees) == 0:
+ log.info(_("No user attendee matching envelope recipient %s, skip message") % (recipient_email))
+ return filepath
+
+ receiving_user = auth.get_entry_attributes(None, recipient_user_dn, ['*'])
+ log.debug(_("Receiving user: %r") % (receiving_user), level=8)
+
+ # change gettext language to the preferredlanguage setting of the receiving user
+ if receiving_user.has_key('preferredlanguage'):
+ pykolab.translate.setUserLanguage(receiving_user['preferredlanguage'])
+
+ # find user's kolabInvitationPolicy settings and the matching policy values
+ sender_domain = str(sender_email).split('@')[-1]
+ policies = get_matching_invitation_policies(receiving_user, sender_domain)
+
+ # select a processing function according to the iTip request method
+ method_processing_map = {
+ 'REQUEST': process_itip_request,
+ 'REPLY': process_itip_reply,
+ 'CANCEL': process_itip_cancel
+ }
+
+ done = None
+ if method_processing_map.has_key(itip_event['method']):
+ processor_func = method_processing_map[itip_event['method']]
+
+ # connect as cyrus-admin
+ imap.connect()
+
+ for policy in policies:
+ log.debug(_("Apply invitation policy %r for domain %r") % (policy_value_map[policy], sender_domain), level=8)
+ done = processor_func(itip_event, policy, recipient_email, sender_email, receiving_user)
+
+ # matching policy found
+ if done is not None:
+ break
+
+ # remove possible write lock from this iteration
+ remove_write_lock(get_lock_key(receiving_user, itip_event['uid']))
+
+ else:
+ log.debug(_("Ignoring '%s' iTip method") % (itip_event['method']), level=8)
+
+ # message has been processed by the module, remove it
+ if done == MESSAGE_PROCESSED:
+ log.debug(_("iTip message %r consumed by the invitationpolicy module") % (message.get('Message-ID')), level=5)
+ os.unlink(filepath)
+ cleanup()
+ return None
+
+ # accept message into the destination inbox
+ accept(filepath)
+
+
+def process_itip_request(itip_event, policy, recipient_email, sender_email, receiving_user):
+ """
+ Process an iTip REQUEST message according to the given policy
+ """
+
+ # if invitation policy is set to MANUAL, pass message along
+ if policy & ACT_MANUAL:
+ log.info(_("Pass invitation for manual processing"))
+ return MESSAGE_FORWARD
+
+ try:
+ receiving_attendee = itip_event['xml'].get_attendee_by_email(recipient_email)
+ log.debug(_("Receiving Attendee: %r") % (receiving_attendee), level=9)
+ except Exception, e:
+ log.error("Could not find envelope attendee: %r" % (e))
+ return MESSAGE_FORWARD
+
+ # process request to participating attendees with RSVP=TRUE or PARTSTAT=NEEDS-ACTION
+ nonpart = receiving_attendee.get_role() == kolabformat.NonParticipant
+ partstat = receiving_attendee.get_participant_status()
+ save_event = not nonpart or not partstat == kolabformat.PartNeedsAction
+ rsvp = receiving_attendee.get_rsvp()
+ scheduling_required = rsvp or partstat == kolabformat.PartNeedsAction
+ respond_with = receiving_attendee.get_participant_status(True)
+ condition_fulfilled = True
+
+ # find existing event in user's calendar
+ existing = find_existing_event(itip_event['uid'], receiving_user, True)
+
+ # compare sequence number to determine a (re-)scheduling request
+ if existing is not None:
+ log.debug(_("Existing event: %r") % (existing), level=9)
+ scheduling_required = itip_event['sequence'] > 0 and itip_event['sequence'] > existing.get_sequence()
+ save_event = True
+
+ # if scheduling: check availability
+ if scheduling_required:
+ if policy & (COND_IF_AVAILABLE | COND_IF_CONFLICT):
+ condition_fulfilled = check_availability(itip_event, receiving_user)
+ if policy & COND_IF_CONFLICT:
+ condition_fulfilled = not condition_fulfilled
+
+ log.debug(_("Precondition for event %r fulfilled: %r") % (itip_event['uid'], condition_fulfilled), level=5)
+
+ respond_with = None
+ if policy & ACT_ACCEPT and condition_fulfilled:
+ respond_with = 'TENTATIVE' if policy & COND_TENTATIVE else 'ACCEPTED'
+
+ elif policy & ACT_REJECT and condition_fulfilled:
+ respond_with = 'DECLINED'
+ # TODO: only save declined invitation when a certain config option is set?
+
+ elif policy & ACT_DELEGATE and condition_fulfilled:
+ # TODO: delegate (but to whom?)
+ return None
+
+ # if RSVP, send an iTip REPLY
+ if rsvp or scheduling_required:
+ # set attendee's CN from LDAP record if yet missing
+ if not receiving_attendee.get_name() and receiving_user.has_key('cn'):
+ receiving_attendee.set_name(receiving_user['cn'])
+
+ # send iTip reply
+ if respond_with is not None:
+ receiving_attendee.set_participant_status(respond_with)
+ send_reply(recipient_email, itip_event, invitation_response_text(),
+ subject=_('"%(summary)s" has been %(status)s'))
+
+ elif policy & ACT_SAVE_TO_CALENDAR:
+ # copy the invitation into the user's calendar with PARTSTAT=NEEDS-ACTION
+ itip_event['xml'].set_attendee_participant_status(receiving_attendee, 'NEEDS-ACTION')
+ save_event = True
+
+ else:
+ # policy doesn't match, pass on to next one
+ return None
+
+ else:
+ log.debug(_("No RSVP for recipient %r requested") % (receiving_user['mail']), level=8)
+ # TODO: only update if policy & ACT_UPDATE ?
+
+ if save_event:
+ targetfolder = None
+
+ if existing:
+ # delete old version from IMAP
+ targetfolder = existing._imap_folder
+ delete_event(existing)
+
+ if not nonpart or existing:
+ # save new copy from iTip
+ if store_event(itip_event['xml'], receiving_user, targetfolder):
+ return MESSAGE_PROCESSED
+
+ return None
+
+
+def process_itip_reply(itip_event, policy, recipient_email, sender_email, receiving_user):
+ """
+ Process an iTip REPLY message according to the given policy
+ """
+
+ # if invitation policy is set to MANUAL, pass message along
+ if policy & ACT_MANUAL:
+ log.info(_("Pass reply for manual processing"))
+ return MESSAGE_FORWARD
+
+ # auto-update is enabled for this user
+ if policy & ACT_UPDATE:
+ try:
+ sender_attendee = itip_event['xml'].get_attendee_by_email(sender_email)
+ log.debug(_("Sender Attendee: %r") % (sender_attendee), level=9)
+ except Exception, e:
+ log.error("Could not find envelope sender attendee: %r" % (e))
+ return MESSAGE_FORWARD
+
+ # find existing event in user's calendar
+ # TODO: set/check lock to avoid concurrent wallace processes trying to update the same event simultaneously
+ existing = find_existing_event(itip_event['uid'], receiving_user, True)
+
+ if existing:
+ # compare sequence number to avoid outdated replies?
+ if not itip_event['sequence'] == existing.get_sequence():
+ log.info(_("The iTip reply sequence (%r) doesn't match the referred event version (%r). Forwarding to Inbox.") % (
+ itip_event['sequence'], existing.get_sequence()
+ ))
+ remove_write_lock(existing._lock_key)
+ return MESSAGE_FORWARD
+
+ log.debug(_("Auto-updating event %r on iTip REPLY") % (existing.uid), level=8)
+ try:
+ existing.set_attendee_participant_status(sender_email, sender_attendee.get_participant_status(), rsvp=False)
+ except Exception, e:
+ log.error("Could not find corresponding attende in organizer's event: %r" % (e))
+
+ # TODO: accept new participant if ACT_ACCEPT ?
+ remove_write_lock(existing._lock_key)
+ return MESSAGE_FORWARD
+
+ # update the organizer's copy of the event
+ if update_event(existing, receiving_user):
+ if policy & COND_NOTIFY:
+ send_reply_notification(existing, receiving_user)
+
+ # update all other attendee's copies
+ if conf.get('wallace','invitationpolicy_autoupdate_other_attendees_on_reply'):
+ propagate_changes_to_attendees_calendars(existing)
+
+ return MESSAGE_PROCESSED
+
+ else:
+ log.error(_("The event referred by this reply was not found in the user's calendars. Forwarding to Inbox."))
+ return MESSAGE_FORWARD
+
+ return None
+
+
+def process_itip_cancel(itip_event, policy, recipient_email, sender_email, receiving_user):
+ """
+ Process an iTip CANCEL message according to the given policy
+ """
+
+ # if invitation policy is set to MANUAL, pass message along
+ if policy & ACT_MANUAL:
+ log.info(_("Pass cancellation for manual processing"))
+ return MESSAGE_FORWARD
+
+ # auto-update the local copy with STATUS=CANCELLED
+ if policy & ACT_UPDATE:
+ # find existing event in user's calendar
+ existing = find_existing_event(itip_event['uid'], receiving_user, True)
+
+ if existing:
+ existing.set_status('CANCELLED')
+ existing.set_transparency(True)
+ if update_event(existing, receiving_user):
+ # TODO: send cancellation notification if policy & ACT_UPDATE_AND_NOTIFY: ?
+ return MESSAGE_PROCESSED
+
+ else:
+ log.error(_("The event referred by this reply was not found in the user's calendars. Forwarding to Inbox."))
+ return MESSAGE_FORWARD
+
+ return None
+
+
+def user_dn_from_email_address(email_address):
+ """
+ Resolves the given email address to a Kolab user entity
+ """
+ global auth
+
+ if not auth:
+ auth = Auth()
+ auth.connect()
+
+ # return cached value
+ if user_dn_from_email_address.cache.has_key(email_address):
+ return user_dn_from_email_address.cache[email_address]
+
+ local_domains = auth.list_domains()
+
+ if not local_domains == None:
+ local_domains = list(set(local_domains.keys()))
+
+ if not email_address.split('@')[1] in local_domains:
+ user_dn_from_email_address.cache[email_address] = None
+ return None
+
+ log.debug(_("Checking if email address %r belongs to a local user") % (email_address), level=8)
+
+ user_dn = auth.find_user_dn(email_address, True)
+
+ if isinstance(user_dn, basestring):
+ log.debug(_("User DN: %r") % (user_dn), level=8)
+ else:
+ log.debug(_("No user record(s) found for %r") % (email_address), level=9)
+
+ # remember this lookup
+ user_dn_from_email_address.cache[email_address] = user_dn
+
+ return user_dn
+
+user_dn_from_email_address.cache = {}
+
+
+def get_matching_invitation_policies(receiving_user, sender_domain):
+ # get user's kolabInvitationPolicy settings
+ policies = receiving_user['kolabinvitationpolicy'] if receiving_user.has_key('kolabinvitationpolicy') else []
+ if policies and not isinstance(policies, list):
+ policies = [policies]
+
+ if len(policies) == 0:
+ policies = conf.get_list('wallace', 'kolab_invitation_policy')
+
+ # match policies agains the given sender_domain
+ matches = []
+ for p in policies:
+ if ':' in p:
+ (value, domain) = p.split(':')
+ else:
+ value = p
+ domain = ''
+
+ if domain == '' or domain == '*' or sender_domain.endswith(domain):
+ value = value.upper()
+ if policy_name_map.has_key(value):
+ matches.append(policy_name_map[value])
+
+ # add manual as default action
+ if len(matches) == 0:
+ matches.append(ACT_MANUAL)
+
+ return matches
+
+
+def imap_proxy_auth(user_rec):
+ """
+
+ """
+ global imap
+
+ mail_attribute = conf.get('cyrus-sasl', 'result_attribute')
+ if mail_attribute == None:
+ mail_attribute = 'mail'
+
+ mail_attribute = mail_attribute.lower()
+
+ if not user_rec.has_key(mail_attribute):
+ log.error(_("User record doesn't have the mailbox attribute %r set" % (mail_attribute)))
+ return False
+
+ # do IMAP prox auth with the given user
+ backend = conf.get('kolab', 'imap_backend')
+ admin_login = conf.get(backend, 'admin_login')
+ admin_password = conf.get(backend, 'admin_password')
+
+ try:
+ imap.disconnect()
+ imap.connect(login=False)
+ imap.login_plain(admin_login, admin_password, user_rec[mail_attribute])
+ except Exception, errmsg:
+ log.error(_("IMAP proxy authentication failed: %r") % (errmsg))
+ return False
+
+ return True
+
+
+def list_user_calendars(user_rec):
+ """
+ Get a list of the given user's private calendar folders
+ """
+ global imap
+
+ # return cached list
+ if user_rec.has_key('_calendar_folders'):
+ return user_rec['_calendar_folders'];
+
+ calendars = []
+
+ if not imap_proxy_auth(user_rec):
+ return calendars
+
+ folders = imap.list_folders('*')
+ log.debug(_("List calendar folders for user %r: %r") % (user_rec['mail'], folders), level=8)
+
+ (ns_personal, ns_other, ns_shared) = imap.namespaces()
+
+ if isinstance(ns_shared, list):
+ ns_shared = ns_shared[0]
+ if isinstance(ns_other, list):
+ ns_other = ns_other[0]
+
+ for folder in folders:
+ # exclude shared and other user's namespace
+ # TODO: list shared folders the user has write privileges ?
+ if folder.startswith(ns_other) or folder.startswith(ns_shared):
+ continue;
+
+ metadata = imap.get_metadata(folder)
+ log.debug(_("IMAP metadata for %r: %r") % (folder, metadata), level=9)
+ if metadata.has_key(folder) and ( \
+ metadata[folder].has_key('/shared' + FOLDER_TYPE_ANNOTATION) and metadata[folder]['/shared' + FOLDER_TYPE_ANNOTATION].startswith('event') \
+ or metadata[folder].has_key('/private' + FOLDER_TYPE_ANNOTATION) and metadata[folder]['/private' + FOLDER_TYPE_ANNOTATION].startswith('event')):
+ calendars.append(folder)
+
+ # store default calendar folder in user record
+ if metadata[folder].has_key('/private' + FOLDER_TYPE_ANNOTATION) and metadata[folder]['/private' + FOLDER_TYPE_ANNOTATION].endswith('.default'):
+ user_rec['_default_calendar'] = folder
+
+ # cache with user record
+ user_rec['_calendar_folders'] = calendars
+
+ return calendars
+
+
+def find_existing_event(uid, user_rec, lock=False):
+ """
+ Search user's calendar folders for the given event (by UID)
+ """
+ global imap
+
+ lock_key = None
+
+ if lock:
+ lock_key = get_lock_key(user_rec, uid)
+ set_write_lock(lock_key)
+
+ event = None
+ for folder in list_user_calendars(user_rec):
+ log.debug(_("Searching folder %r for event %r") % (folder, uid), level=8)
+ imap.imap.m.select(imap.folder_utf7(folder))
+
+ typ, data = imap.imap.m.search(None, '(UNDELETED HEADER SUBJECT "%s")' % (uid))
+ for num in reversed(data[0].split()):
+ typ, data = imap.imap.m.fetch(num, '(RFC822)')
+
+ try:
+ event = event_from_message(message_from_string(data[0][1]))
+ setattr(event, '_imap_folder', folder)
+ setattr(event, '_lock_key', lock_key)
+ except Exception, e:
+ log.error(_("Failed to parse event from message %s/%s: %r") % (folder, num, e))
+ continue
+
+ if event and event.uid == uid:
+ return event
+
+ if lock_key is not None:
+ remove_write_lock(lock_key)
+
+ return event
+
+
+def check_availability(itip_event, receiving_user):
+ """
+ For the receiving user, determine if the event in question is in conflict.
+ """
+
+ start = time.time()
+ num_messages = 0
+ conflict = False
+
+ # return previously detected conflict
+ if itip_event.has_key('_conflicts'):
+ return not itip_event['_conflicts']
+
+ for folder in list_user_calendars(receiving_user):
+ log.debug(_("Listing events from folder %r") % (folder), level=8)
+ imap.imap.m.select(imap.folder_utf7(folder))
+
+ typ, data = imap.imap.m.search(None, '(UNDELETED HEADER X-Kolab-Type "application/x-vnd.kolab.event")')
+ num_messages += len(data[0].split())
+
+ for num in reversed(data[0].split()):
+ event = None
+ typ, data = imap.imap.m.fetch(num, '(RFC822)')
+
+ try:
+ event = event_from_message(message_from_string(data[0][1]))
+ except Exception, e:
+ log.error(_("Failed to parse event from message %s/%s: %r") % (folder, num, e))
+ continue
+
+ if event and event.uid:
+ conflict = check_event_conflict(event, itip_event)
+ if conflict:
+ log.info(_("Existing event %r conflicts with invitation %r") % (event.uid, itip_event['uid']))
+ break
+
+ if conflict:
+ break
+
+ end = time.time()
+ log.debug(_("start: %r, end: %r, total: %r, messages: %d") % (start, end, (end-start), num_messages), level=9)
+
+ # remember the result of this check for further iterations
+ itip_event['_conflicts'] = conflict
+
+ return not conflict
+
+
+def set_write_lock(key, wait=True):
+ """
+ Set a write-lock for the given key and wait if such a lock already exists
+ """
+ if not os.path.isdir(mybasepath):
+ os.makedirs(mybasepath)
+ if not os.path.isdir(os.path.join(mybasepath, 'locks')):
+ os.makedirs(os.path.join(mybasepath, 'locks'))
+
+ file = os.path.join(mybasepath, 'locks', key + '.lock')
+ locked = os.path.getmtime(file) if os.path.isfile(file) else 0
+ expired = time.time() - 300
+
+ # wait if file lock is in place
+ while locked and locked > expired:
+ if not wait:
+ return False
+
+ log.debug(_("%r is locked, waiting...") % (key), level=9)
+ time.sleep(0.5)
+ locked = os.path.getmtime(file) if os.path.isfile(file) else 0
+
+ # touch the file
+ if os.path.isfile(file):
+ os.utime(file, None)
+ else:
+ open(file, 'w').close()
+
+ # register active lock
+ write_locks.append(key)
+
+ return True
+
+
+def remove_write_lock(key, update=True):
+ """
+ Remove the lock file for the given key
+ """
+ global write_locks
+
+ if key is not None:
+ file = os.path.join(mybasepath, 'locks', key + '.lock')
+ if os.path.isfile(file):
+ os.remove(file)
+ if update:
+ write_locks = [k for k in write_locks if not k == key]
+
+
+def get_lock_key(user, uid):
+ return hashlib.md5("%s/%s" % (user['mail'], uid)).hexdigest()
+
+
+def update_event(event, user_rec):
+ """
+ Update the given event in IMAP (i.e. delete + append)
+ """
+ success = False
+
+ if hasattr(event, '_imap_folder'):
+ delete_event(event)
+ success = store_event(event, user_rec, event._imap_folder)
+
+ # remove write lock for this event
+ if hasattr(event, '_lock_key') and event._lock_key is not None:
+ remove_write_lock(event._lock_key)
+
+ return success
+
+
+def store_event(event, user_rec, targetfolder=None):
+ """
+ Append the given event object to the user's default calendar
+ """
+
+ # find default calendar folder to save event to
+ if targetfolder is None:
+ targetfolder = list_user_calendars(user_rec)[0]
+ if user_rec.has_key('_default_calendar'):
+ targetfolder = user_rec['_default_calendar']
+
+ if not targetfolder:
+ log.error(_("Failed to save event: no calendar folder found for user %r") % (user_rec['mail']))
+ return Fasle
+
+ log.debug(_("Save event %r to user calendar %r") % (event.uid, targetfolder), level=8)
+
+ try:
+ imap.imap.m.select(imap.folder_utf7(targetfolder))
+ result = imap.imap.m.append(
+ imap.folder_utf7(targetfolder),
+ None,
+ None,
+ event.to_message().as_string()
+ )
+ return result
+
+ except Exception, e:
+ log.error(_("Failed to save event to user calendar at %r: %r") % (
+ targetfolder, e
+ ))
+
+ return False
+
+
+def delete_event(existing):
+ """
+ Removes the IMAP object with the given UID from a user's calendar folder
+ """
+ targetfolder = existing._imap_folder
+ imap.imap.m.select(imap.folder_utf7(targetfolder))
+
+ typ, data = imap.imap.m.search(None, '(HEADER SUBJECT "%s")' % existing.uid)
+
+ log.debug(_("Delete event %r in %r: %r") % (
+ existing.uid, targetfolder, data
+ ), level=8)
+
+ for num in data[0].split():
+ imap.imap.m.store(num, '+FLAGS', '\\Deleted')
+
+ imap.imap.m.expunge()
+
+
+def send_reply_notification(event, receiving_user):
+ """
+ Send a (consolidated) notification about the current participant status to organizer
+ """
+ global auth
+
+ import smtplib
+ from email.MIMEText import MIMEText
+ from email.Utils import formatdate
+
+ log.debug(_("Compose participation status summary for event %r to user %r") % (
+ event.uid, receiving_user['mail']
+ ), level=8)
+
+ organizer = event.get_organizer()
+ orgemail = organizer.email()
+ orgname = organizer.name()
+ sender_domain = orgemail.split('@')[-1]
+
+ auto_replies_expected = 0
+ auto_replies_received = 0
+ partstats = { 'ACCEPTED':[], 'TENTATIVE':[], 'DECLINED':[], 'DELEGATED':[], 'PENDING':[] }
+ for attendee in event.get_attendees():
+ parstat = attendee.get_participant_status(True)
+ if partstats.has_key(parstat):
+ partstats[parstat].append(attendee.get_displayname())
+ else:
+ partstats['PENDING'].append(attendee.get_displayname())
+
+ # look-up kolabinvitationpolicy for this attendee
+ if attendee.get_cutype() == kolabformat.CutypeResource:
+ resource_dns = auth.find_resource(attendee.get_email())
+ if isinstance(resource_dns, list):
+ attendee_dn = resource_dns[0] if len(resource_dns) > 0 else None
+ else:
+ attendee_dn = resource_dns
+ else:
+ attendee_dn = user_dn_from_email_address(attendee.get_email())
+
+ if attendee_dn:
+ attendee_rec = auth.get_entry_attributes(None, attendee_dn, ['kolabinvitationpolicy'])
+ if is_auto_reply(attendee_rec, sender_domain):
+ auto_replies_expected += 1
+ if not parstat == 'NEEDS-ACTION':
+ auto_replies_received += 1
+
+ # skip notification until we got replies from all automatically responding attendees
+ if auto_replies_received < auto_replies_expected:
+ log.debug(_("Waiting for more automated replies (got %d of %d); skipping notification") % (
+ auto_replies_received, auto_replies_expected
+ ), level=8)
+ return
+
+ roundup = ''
+ for status,attendees in partstats.iteritems():
+ if len(attendees) > 0:
+ roundup += "\n" + participant_status_label(status) + ":\n" + "\n".join(attendees) + "\n"
+
+ message_text = """
+ The event '%(summary)s' at %(start)s has been updated in your calendar.
+ %(roundup)s
+ """ % {
+ 'summary': event.get_summary(),
+ 'start': event.get_start().strftime('%Y-%m-%d %H:%M %Z'),
+ 'roundup': roundup
+ }
+
+ # compose mime message
+ msg = MIMEText(utils.stripped_message(message_text))
+
+ msg['To'] = receiving_user['mail']
+ msg['Date'] = formatdate(localtime=True)
+ msg['Subject'] = _('"%s" has been updated') % (event.get_summary())
+ msg['From'] = '"%s" <%s>' % (orgname, orgemail) if orgname else orgemail
+
+ smtp = smtplib.SMTP("localhost", 10027)
+
+ if conf.debuglevel > 8:
+ smtp.set_debuglevel(True)
+
+ try:
+ smtp.sendmail(orgemail, receiving_user['mail'], msg.as_string())
+ except Exception, e:
+ log.error(_("SMTP sendmail error: %r") % (e))
+
+ smtp.quit()
+
+
+def is_auto_reply(user, sender_domain):
+ accept_available = False
+ accept_conflicts = False
+ for policy in get_matching_invitation_policies(user, sender_domain):
+ if policy & (ACT_ACCEPT | ACT_REJECT | ACT_DELEGATE):
+ if check_policy_condition(policy, True):
+ accept_available = True
+ if check_policy_condition(policy, False):
+ accept_conflicts = True
+
+ # we have both cases covered by a policy
+ if accept_available and accept_conflicts:
+ return True
+
+ # manual action reached
+ if policy & (ACT_MANUAL | ACT_SAVE_TO_CALENDAR):
+ return False
+
+ return False
+
+
+def check_policy_condition(policy, available):
+ condition_fulfilled = True
+ if policy & (COND_IF_AVAILABLE | COND_IF_CONFLICT):
+ condition_fulfilled = available
+ if policy & COND_IF_CONFLICT:
+ condition_fulfilled = not condition_fulfilled
+ return condition_fulfilled
+
+
+def propagate_changes_to_attendees_calendars(event):
+ """
+ Find and update copies of this event in all attendee's calendars
+ """
+ for attendee in event.get_attendees():
+ attendee_user_dn = user_dn_from_email_address(attendee.get_email())
+ if attendee_user_dn:
+ attendee_user = auth.get_entry_attributes(None, attendee_user_dn, ['*'])
+ attendee_event = find_existing_event(event.uid, attendee_user, True) # does IMAP authenticate
+ if attendee_event:
+ try:
+ attendee_entry = attendee_event.get_attendee_by_email(attendee_user['mail'])
+ except:
+ attendee_entry = None
+
+ # copy all attendees from master event (covers additions and removals)
+ new_attendees = kolabformat.vectorattendee();
+ for a in event.get_attendees():
+ # keep my own entry intact
+ if attendee_entry is not None and attendee_entry.get_email() == a.get_email():
+ new_attendees.append(attendee_entry)
+ else:
+ new_attendees.append(a)
+
+ attendee_event.event.setAttendees(new_attendees)
+
+ success = update_event(attendee_event, attendee_user)
+ log.debug(_("Updated %s's copy of %r: %r") % (attendee_user['mail'], event.uid, success), level=8)
+
+ else:
+ log.debug(_("Attendee %s's copy of %r not found") % (attendee_user['mail'], event.uid), level=8)
+
+ else:
+ log.debug(_("Attendee %r not found in LDAP") % (attendee.get_email()), level=8)
+
+
+def invitation_response_text():
+ return _("""
+ %(name)s has %(status)s your invitation for %(summary)s.
+
+ *** This is an automated response sent by the Kolab Invitation system ***
+ """)
diff --git a/wallace/module_resources.py b/wallace/module_resources.py
index 303252b..47259da 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -25,7 +25,9 @@ import random
import tempfile
import time
from urlparse import urlparse
-import urllib
+import base64
+import uuid
+import re
from email import message_from_string
from email.parser import Parser
@@ -40,11 +42,25 @@ import kolabformat
from pykolab.auth import Auth
from pykolab.conf import Conf
from pykolab.imap import IMAP
-from pykolab.xml import event_from_ical
-from pykolab.xml import event_from_string
from pykolab.xml import to_dt
+from pykolab.xml import event_from_message
+from pykolab.xml import participant_status_label
+from pykolab.itip import events_from_message
+from pykolab.itip import check_event_conflict
from pykolab.translate import _
+# define some contstants used in the code below
+COND_NOTIFY = 256
+ACT_MANUAL = 1
+ACT_ACCEPT = 2
+ACT_ACCEPT_AND_NOTIFY = ACT_ACCEPT + COND_NOTIFY
+
+policy_name_map = {
+ 'ACT_MANUAL': ACT_MANUAL,
+ 'ACT_ACCEPT': ACT_ACCEPT,
+ 'ACT_ACCEPT_AND_NOTIFY': ACT_ACCEPT_AND_NOTIFY
+}
+
log = pykolab.getLogger('pykolab.wallace')
conf = pykolab.getConf()
@@ -86,6 +102,9 @@ def cleanup():
def execute(*args, **kw):
global auth, imap
+ # (re)set language to default
+ pykolab.translate.setUserLanguage(conf.get('kolab','default_locale'))
+
if not os.path.isdir(mybasepath):
os.makedirs(mybasepath)
@@ -142,15 +161,17 @@ def execute(*args, **kw):
message = Parser().parse(open(filepath, 'r'))
recipients = [address for displayname,address in getaddresses(message.get_all('X-Kolab-To'))]
+ sender_email = [address for displayname,address in getaddresses(message.get_all('X-Kolab-From'))][0]
any_itips = False
any_resources = False
possibly_any_resources = True
+ reference_uid = None
# An iTip message may contain multiple events. Later on, test if the message
# is an iTip message by checking the length of this list.
try:
- itip_events = itip_events_from_message(message)
+ itip_events = events_from_message(message, ['REQUEST', 'REPLY', 'CANCEL'])
except Exception, e:
log.error(_("Failed to parse iTip events from message: %r" % (e)))
itip_events = []
@@ -182,6 +203,13 @@ def execute(*args, **kw):
auth.connect()
for recipient in recipients:
+ # extract reference UID from recipients like resource+UID@domain.org
+ if re.match('.+\+[A-Za-z0-9=/-]+@', recipient):
+ (prefix, host) = recipient.split('@')
+ (local, uid) = prefix.split('+')
+ reference_uid = base64.b64decode(uid, '-/')
+ recipient = local + '@' + host
+
if not len(resource_record_from_email_address(recipient)) == 0:
resource_recipient = recipient
any_resources = True
@@ -209,6 +237,7 @@ def execute(*args, **kw):
# check if resource attendees match the envelope recipient
if len(resource_dns) == 0:
log.info(_("No resource attendees matching envelope recipient %s, Reject message") % (resource_recipient))
+ log.debug("%r" % (itip_events), level=8)
reject(filepath)
return False
@@ -225,6 +254,58 @@ def execute(*args, **kw):
receiving_resource = resources[resource_dns[0]]
for itip_event in itip_events:
+ if itip_event['method'] == 'REPLY':
+ done = True
+
+ # find initial reservation referenced by the reply
+ if reference_uid:
+ event = find_existing_event(reference_uid, receiving_resource)
+ if event:
+ try:
+ sender_attendee = itip_event['xml'].get_attendee_by_email(sender_email)
+ owner_reply = sender_attendee.get_participant_status()
+ log.debug(_("Sender Attendee: %r => %r") % (sender_attendee, owner_reply), level=9)
+ except Exception, e:
+ log.error("Could not find envelope sender attendee: %r" % (e))
+ continue
+
+ # compare sequence number to avoid outdated replies
+ if not itip_event['sequence'] == event.get_sequence():
+ log.info(_("The iTip reply sequence (%r) doesn't match the referred event version (%r). Ignoring.") % (
+ itip_event['sequence'], event.get_sequence()
+ ))
+ continue
+
+ # forward owner response comment
+ comment = itip_event['xml'].get_comment()
+ if comment:
+ event.set_comment(str(comment))
+
+ itip_event_ = dict(xml=event, uid=event.get_uid())
+
+ if owner_reply == kolabformat.PartAccepted:
+ event.set_status(kolabformat.StatusConfirmed)
+ accept_reservation_request(itip_event_, receiving_resource, confirmed=True)
+ elif owner_reply == kolabformat.PartDeclined:
+ decline_reservation_request(itip_event_, receiving_resource)
+ # TODO: set status=CANCELLED instead of deleting?
+ # event.set_status(kolabformat.StatusCancelled)
+ delete_resource_event(reference_uid, receiving_resource)
+ else:
+ log.info("Invalid response (%r) recieved from resource owner for event %r" % (
+ sender_attendee.get_participant_status(True), reference_uid
+ ))
+ else:
+ log.info(_("Event referenced by this REPLY (%r) not found in resource calendar") % (reference_uid))
+
+ else:
+ log.info(_("No event reference found in this REPLY. Ignoring."))
+
+ # exit for-loop
+ break
+
+ # else:
+
try:
receiving_attendee = itip_event['xml'].get_attendee_by_email(receiving_resource['mail'])
log.debug(_("Receiving Resource: %r; %r") % (receiving_resource, receiving_attendee), level=9)
@@ -466,97 +547,107 @@ def read_resource_calendar(resource_rec, itip_events):
event_message = message_from_string(data[0][1])
- if event_message.is_multipart():
- for part in event_message.walk():
- if part.get_content_type() == "application/calendar+xml":
- payload = part.get_payload(decode=True)
- event = pykolab.xml.event_from_string(payload)
+ try:
+ event = event_from_message(message_from_string(data[0][1]))
+ except Exception, e:
+ log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, e))
+ continue
+
+ if event:
+ for itip in itip_events:
+ conflict = check_event_conflict(event, itip)
- for itip in itip_events:
- _es = to_dt(event.get_start())
- _ee = to_dt(event.get_end())
+ if event.get_uid() == itip['uid']:
+ resource_rec['existing_events'].append(itip['uid'])
- conflict = False
+ if conflict:
+ log.info(
+ _("Event %r conflicts with event %r") % (
+ itip['xml'].get_uid(),
+ event.get_uid()
+ )
+ )
- # naive loops to check for collisions in (recurring) events
- # TODO: compare recurrence rules directly (e.g. matching time slot or weekday or monthday)
- while not conflict and _es is not None:
- _is = to_dt(itip['start'])
- _ie = to_dt(itip['end'])
+ resource_rec['conflicting_events'].append(event.get_uid())
+ resource_rec['conflict'] = True
- while not conflict and _is is not None:
- log.debug("* Comparing event dates at %s/%s with %s/%s" % (_es, _ee, _is, _ie), level=9)
- conflict = check_date_conflict(_es, _ee, _is, _ie)
- _is = to_dt(itip['xml'].get_next_occurence(_is)) if event.is_recurring() else None
- _ie = to_dt(itip['xml'].get_occurence_end_date(_is))
+ return num_messages
- _es = to_dt(event.get_next_occurence(_es)) if event.is_recurring() else None
- _ee = to_dt(event.get_occurence_end_date(_es))
- if event.get_uid() == itip['uid']:
- resource_rec['existing_events'].append(itip['uid'])
+def find_existing_event(uid, resource_rec):
+ """
+ Search the resources's calendar folder for the given event (by UID)
+ """
+ global imap
- # don't register conflict for updates
- if itip['sequence'] > 0 and itip['sequence'] >= event.get_sequence():
- conflict = False
+ event = None
+ mailbox = resource_rec['kolabtargetfolder']
- if conflict:
- log.info(
- _("Event %r conflicts with event %r") % (
- itip['xml'].get_uid(),
- event.get_uid()
- )
- )
+ log.debug(_("Searching %r for event %r") % (mailbox, uid), level=9)
- resource_rec['conflicting_events'].append(event.get_uid())
- resource_rec['conflict'] = True
+ try:
+ imap.imap.m.select(imap.folder_quote(mailbox))
+ typ, data = imap.imap.m.search(None, '(UNDELETED HEADER SUBJECT "%s")' % (uid))
+ except Exception, e:
+ log.error(_("Failed to access resource calendar:: %r") % (e))
+ return event
- return num_messages
+ for num in reversed(data[0].split()):
+ typ, data = imap.imap.m.fetch(num, '(RFC822)')
-def check_date_conflict(_es, _ee, _is, _ie):
- conflict = False
+ try:
+ event = event_from_message(message_from_string(data[0][1]))
+ except Exception, e:
+ log.error(_("Failed to parse event from message %s/%s: %r") % (mailbox, num, e))
+ continue
- # TODO: add margin for all-day dates (+13h; -12h)
+ if event and event.uid == uid:
+ return event
- if _es < _is:
- if _es <= _ie:
- if _ee <= _is:
- conflict = False
- else:
- conflict = True
- else:
- conflict = True
- elif _es == _is:
- conflict = True
- else: # _es > _is
- if _es <= _ie:
- conflict = True
- else:
- conflict = False
-
- return conflict
+ return event
-def accept_reservation_request(itip_event, resource, delegator=None):
+def accept_reservation_request(itip_event, resource, delegator=None, confirmed=False):
"""
Accepts the given iTip event by booking it into the resource's
calendar. Then set the attendee status of the given resource to
ACCEPTED and sends an iTip reply message to the organizer.
"""
+ owner = get_resource_owner(resource)
+ confirmation_required = False
+
+ if not confirmed:
+ invitationpolicy = get_resource_invitationpolicy(resource)
+ log.debug(_("Apply invitation policies %r") % (invitationpolicy), level=9)
+ if invitationpolicy is not None:
+ for policy in invitationpolicy:
+ if policy & ACT_MANUAL and owner['mail']:
+ confirmation_required = True
+ break
+
+ partstat = 'TENTATIVE' if confirmation_required else 'ACCEPTED'
+
+ itip_event['xml'].set_transparency(False);
itip_event['xml'].set_attendee_participant_status(
itip_event['xml'].get_attendee_by_email(resource['mail']),
- "ACCEPTED"
+ partstat
)
- saved = save_resource_event(itip_event, resource)
+ saved = save_resource_event(itip_event, resource, replace=confirmed)
log.debug(
_("Adding event to %r: %r") % (resource['kolabtargetfolder'], saved),
level=8
)
- send_response(delegator['mail'] if delegator else resource['mail'], itip_event, get_resource_owner(resource))
+ if saved:
+ send_response(delegator['mail'] if delegator else resource['mail'], itip_event, owner)
+
+ if owner and confirmation_required:
+ send_owner_confirmation(resource, owner, itip_event)
+ elif owner:
+ send_owner_notification(resource, owner, itip_event, saved)
def decline_reservation_request(itip_event, resource):
@@ -570,17 +661,27 @@ def decline_reservation_request(itip_event, resource):
"DECLINED"
)
+ owner = get_resource_owner(resource)
send_response(resource['mail'], itip_event, get_resource_owner(resource))
+ if owner:
+ send_owner_notification(resource, owner, itip_event, True)
+
-def save_resource_event(itip_event, resource):
+def save_resource_event(itip_event, resource, replace=False):
"""
Append the given event object to the resource's calendar
"""
try:
# Administrator login name comes from configuration.
targetfolder = imap.folder_quote(resource['kolabtargetfolder'])
- imap.imap.m.setacl(targetfolder, conf.get(conf.get('kolab', 'imap_backend'), 'admin_login'), "lrswipkxtecda")
+
+ # remove old copy of the reservation (also sets ACLs)
+ if replace:
+ delete_resource_event(itip_event['uid'], resource)
+ else:
+ imap.imap.m.setacl(targetfolder, conf.get(conf.get('kolab', 'imap_backend'), 'admin_login'), "lrswipkxtecda")
+
result = imap.imap.m.append(
targetfolder,
None,
@@ -617,118 +718,6 @@ def delete_resource_event(uid, resource):
imap.imap.m.expunge()
-def itip_events_from_message(message):
- """
- Obtain the iTip payload from email.message <message>
- """
- # Placeholder for any itip_events found in the message.
- itip_events = []
- seen_uids = []
-
- # iTip methods we are actually interested in. Other methods will be ignored.
- itip_methods = [ "REQUEST", "CANCEL" ]
-
- # Are all iTip messages multipart? No! RFC 6047, section 2.4 states "A
- # MIME body part containing content information that conforms to this
- # document MUST have (...)" but does not state whether an iTip message must
- # therefore also be multipart.
-
- # Check each part
- for part in message.walk():
-
- # The iTip part MUST be Content-Type: text/calendar (RFC 6047, section 2.4)
- # But in real word, other mime-types are used as well
- if part.get_content_type() in [ "text/calendar", "text/x-vcalendar", "application/ics" ]:
- if not str(part.get_param('method')).upper() in itip_methods:
- log.error(_("Method %r not really interesting for us.") % (part.get_param('method')))
- continue
-
- # Get the itip_payload
- itip_payload = part.get_payload(decode=True)
-
- log.debug(_("Raw iTip payload: %s") % (itip_payload), level=9)
-
- # Python iCalendar prior to 3.0 uses "from_string".
- if hasattr(icalendar.Calendar, 'from_ical'):
- cal = icalendar.Calendar.from_ical(itip_payload)
- elif hasattr(icalendar.Calendar, 'from_string'):
- cal = icalendar.Calendar.from_string(itip_payload)
-
- # If we can't read it, we're out
- else:
- log.error(_("Could not read iTip from message."))
- return []
-
- for c in cal.walk():
- if c.name == "VEVENT":
- itip = {}
-
- if c['uid'] in seen_uids:
- log.debug(_("Duplicate iTip event: %s") % (c['uid']), level=9)
- continue
-
- # From the event, take the following properties:
- #
- # - method
- # - uid
- # - sequence
- # - start
- # - end (if any)
- # - duration (if any)
- # - organizer
- # - attendees (if any)
- # - resources (if any)
- #
-
- itip['uid'] = str(c['uid'])
- itip['method'] = str(cal['method']).upper()
- itip['sequence'] = int(c['sequence']) if c.has_key('sequence') else 0
-
- if c.has_key('dtstart'):
- itip['start'] = c['dtstart'].dt
- else:
- log.error(_("iTip event without a start"))
- continue
-
- if c.has_key('dtend'):
- itip['end'] = c['dtend'].dt
-
- if c.has_key('duration'):
- itip['duration'] = c['duration'].dt
- itip['end'] = itip['start'] + c['duration'].dt
-
- itip['organizer'] = c['organizer']
-
- itip['attendees'] = c['attendee']
-
- if c.has_key('resources'):
- itip['resources'] = c['resources']
-
- itip['raw'] = itip_payload
-
- try:
- itip['xml'] = event_from_ical(c.to_ical())
- except Exception, e:
- log.error("event_from_ical() exception: %r" % (e))
- continue
-
- itip_events.append(itip)
-
- seen_uids.append(c['uid'])
-
- # end if c.name == "VEVENT"
-
- # end for c in cal.walk()
-
- # end if part.get_content_type() == "text/calendar"
-
- # end for part in message.walk()
-
- if not len(itip_events) and not message.is_multipart():
- log.debug(_("Message is not an iTip message (non-multipart message)"), level=5)
-
- return itip_events
-
def reject(filepath):
new_filepath = os.path.join(
mybasepath,
@@ -814,6 +803,12 @@ def resource_records_from_itip_events(itip_events, recipient_email=None):
log.debug(_("Raw set of resources: %r") % (resources_raw), level=9)
+ # consider organizer (in REPLY messages), too
+ organizers_raw = [re.sub('\+[A-Za-z0-9=/-]+@', '@', str(y['organizer'])) for y in itip_events if y.has_key('organizer')]
+
+ log.debug(_("Raw set of organizers: %r") % (organizers_raw), level=8)
+
+
# TODO: We expect the format of an attendee line to literally be:
#
# ATTENDEE:RSVP=TRUE;ROLE=REQ-PARTICIPANT;MAILTO:lydia.bossers@kolabsys.com
@@ -822,7 +817,7 @@ def resource_records_from_itip_events(itip_events, recipient_email=None):
#
# RSVP=TRUE;ROLE=REQ-PARTICIPANT;MAILTO:lydia.bossers@kolabsys.com
#
- attendees = [x.split(':')[-1] for x in attendees_raw]
+ attendees = [x.split(':')[-1] for x in attendees_raw + organizers_raw]
# Limit the attendee resources to the one that is actually invited
# with the current message. Considering all invited resources would result in
@@ -904,21 +899,25 @@ def get_resource_records(resource_dns):
# If it is not, ...
resource_attrs = auth.get_entry_attributes(None, resource_dn, ['*'])
resource_attrs['dn'] = resource_dn
+ parse_kolabinvitationpolicy(resource_attrs)
+
if not 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]:
if resource_attrs.has_key('uniquemember'):
resources[resource_dn] = resource_attrs
for uniquemember in resource_attrs['uniquemember']:
- resource_attrs = auth.get_entry_attributes(
+ member_attrs = auth.get_entry_attributes(
None,
uniquemember,
['*']
)
- if 'kolabsharedfolder' in [x.lower() for x in resource_attrs['objectclass']]:
- resource_attrs['dn'] = uniquemember
- resources[uniquemember] = resource_attrs
+ if 'kolabsharedfolder' in [x.lower() for x in member_attrs['objectclass']]:
+ member_attrs['dn'] = uniquemember
+ parse_kolabinvitationpolicy(member_attrs, resource_attrs)
+
+ resources[uniquemember] = member_attrs
resources[uniquemember]['memberof'] = resource_dn
- if not resource_attrs.has_key('owner') and resources[resource_dn].has_key('owner'):
+ if not member_attrs.has_key('owner') and resources[resource_dn].has_key('owner'):
resources[uniquemember]['owner'] = resources[resource_dn]['owner']
resource_dns.append(uniquemember)
else:
@@ -927,6 +926,16 @@ def get_resource_records(resource_dns):
return resources
+def parse_kolabinvitationpolicy(attrs, parent=None):
+ if attrs.has_key('kolabinvitationpolicy'):
+ if not isinstance(attrs['kolabinvitationpolicy'], list):
+ attrs['kolabinvitationpolicy'] = [attrs['kolabinvitationpolicy']]
+ attrs['kolabinvitationpolicy'] = [policy_name_map[p] for p in attrs['kolabinvitationpolicy'] if policy_name_map.has_key(p)]
+
+ elif isinstance(parent, dict) and parent.has_key('kolabinvitationpolicy'):
+ attrs['kolabinvitationpolicy'] = parent['kolabinvitationpolicy']
+
+
def get_resource_collection(email_address):
"""
@@ -979,6 +988,37 @@ def get_resource_owner(resource):
return None
+def get_resource_invitationpolicy(resource):
+ """
+ Get this resource's kolabinvitationpolicy configuration
+ """
+ global auth
+
+ if not resource.has_key('kolabinvitationpolicy') or resource['kolabinvitationpolicy'] is None:
+ if not auth:
+ auth = Auth()
+ auth.connect()
+
+ # get kolabinvitationpolicy attribute from collection
+ collections = auth.search_entry_by_attribute('uniquemember', resource['dn'])
+ if not isinstance(collections, list):
+ collections = [ (collections['dn'],collections) ]
+
+ log.debug("Check collections %r for kolabinvitationpolicy attributes" % (collections), level=9)
+
+ for dn,collection in collections:
+ # ldap.search_entry_by_attribute() doesn't return the attributes lower-cased
+ if collection.has_key('kolabInvitationPolicy'):
+ collection['kolabinvitationpolicy'] = collection['kolabInvitationPolicy']
+
+ if collection.has_key('kolabinvitationpolicy'):
+ parse_kolabinvitationpolicy(collection)
+ resource['kolabinvitationpolicy'] = collection['kolabinvitationpolicy']
+ break
+
+ return resource['kolabinvitationpolicy'] if resource.has_key('kolabinvitationpolicy') else None
+
+
def send_response(from_address, itip_events, owner=None):
"""
Send the given iCal events as a valid iTip response to the organizer.
@@ -986,12 +1026,6 @@ def send_response(from_address, itip_events, owner=None):
resource, this will send an additional DELEGATED response message.
"""
- import smtplib
- smtp = smtplib.SMTP("localhost", 10027)
-
- if conf.debuglevel > 8:
- smtp.set_debuglevel(True)
-
if isinstance(itip_events, dict):
itip_events = [ itip_events ]
@@ -999,7 +1033,10 @@ def send_response(from_address, itip_events, owner=None):
attendee = itip_event['xml'].get_attendee_by_email(from_address)
participant_status = itip_event['xml'].get_ical_attendee_participant_status(attendee)
+ # TODO: look-up event organizer in LDAP and change localization to its preferredlanguage
+
message_text = reservation_response_text(participant_status, owner)
+ subject_template = _("Reservation Request for %(summary)s was %(status)s")
if participant_status == "DELEGATED":
# Extra actions to take
@@ -1007,32 +1044,21 @@ def send_response(from_address, itip_events, owner=None):
delegatee = [a for a in itip_event['xml'].get_attendees() if from_address in [b.email() for b in a.get_delegated_from()]][0]
delegatee_status = itip_event['xml'].get_ical_attendee_participant_status(delegatee)
- message = itip_event['xml'].to_message_itip(delegatee.get_email(),
- method="REPLY",
- participant_status=delegatee_status,
- message_text=reservation_response_text(delegatee_status, owner)
- )
- smtp.sendmail(message['From'], message['To'], message.as_string())
+ pykolab.itip.send_reply(delegatee.get_email(), itip_event, reservation_response_text(delegatee_status, owner),
+ subject=subject_template)
# restore list of attendees after to_message_itip()
itip_event['xml']._attendees = [ delegator, delegatee ]
itip_event['xml'].event.setAttendees(itip_event['xml']._attendees)
- participant_status = "DELEGATED"
message_text = _("""
*** This is an automated response, please do not reply! ***
Your reservation was delegated to "%s" which is available for the requested time.
""") % (delegatee.get_name())
- message = itip_event['xml'].to_message_itip(from_address,
- method="REPLY",
- participant_status=participant_status,
- message_text=message_text
- )
- smtp.sendmail(message['From'], message['To'], message.as_string())
-
- smtp.quit()
+ pykolab.itip.send_reply(from_address, itip_event, message_text,
+ subject=subject_template)
def reservation_response_text(status, owner):
@@ -1040,7 +1066,7 @@ def reservation_response_text(status, owner):
*** This is an automated response, please do not reply! ***
We hereby inform you that your reservation was %s.
- """) % (_(status))
+ """) % (participant_status_label(status))
if owner:
message_text += _("""
@@ -1049,3 +1075,145 @@ def reservation_response_text(status, owner):
""") % (owner['cn'], owner['mail'], owner['telephoneNumber'] if owner.has_key('telephoneNumber') else '')
return message_text
+
+
+def send_owner_notification(resource, owner, itip_event, success=True):
+ """
+ Send a reservation notification to the resource owner
+ """
+ import smtplib
+ from pykolab import utils
+ from email.MIMEText import MIMEText
+ from email.Utils import formatdate
+
+ notify = False
+ status = itip_event['xml'].get_attendee_by_email(resource['mail']).get_participant_status(True)
+
+ invitationpolicy = get_resource_invitationpolicy(resource)
+
+ if invitationpolicy is not None:
+ for policy in invitationpolicy:
+ # TODO: distingish ACCEPTED / DECLINED status notifications?
+ if policy & COND_NOTIFY and owner['mail']:
+ notify = True
+ break
+
+ if notify or not success:
+ log.debug(
+ _("Sending booking notification for event %r to %r from %r") % (
+ itip_event['uid'], owner['mail'], resource['cn']
+ ),
+ level=8
+ )
+
+ # change gettext language to the preferredlanguage setting of the resource owner
+ if owner.has_key('preferredlanguage'):
+ pykolab.translate.setUserLanguage(owner['preferredlanguage'])
+
+ message_text = owner_notification_text(resource, owner, itip_event['xml'], success)
+
+ msg = MIMEText(utils.stripped_message(message_text))
+
+ msg['To'] = owner['mail']
+ msg['From'] = resource['mail']
+ msg['Date'] = formatdate(localtime=True)
+ msg['Subject'] = _('Booking for %s has been %s') % (resource['cn'], participant_status_label(status) if success else _('failed'))
+
+ smtp = smtplib.SMTP("localhost", 10027)
+
+ if conf.debuglevel > 8:
+ smtp.set_debuglevel(True)
+
+ try:
+ smtp.sendmail(resource['mail'], owner['mail'], msg.as_string())
+ except Exception, e:
+ log.error(_("SMTP sendmail error: %r") % (e))
+
+ smtp.quit()
+
+def owner_notification_text(resource, owner, event, success):
+ organizer = event.get_organizer()
+ status = event.get_attendee_by_email(resource['mail']).get_participant_status(True)
+
+ if success:
+ message_text = _("""
+ The resource booking for %(resource)s by %(orgname)s <%(orgemail)s> has been %(status)s for %(date)s.
+
+ *** This is an automated message, sent to you as the resource owner. ***
+ """)
+ else:
+ message_text = _("""
+ A reservation request for %(resource)s could not be processed automatically.
+ Please contact %(orgname)s <%(orgemail)s> who requested this resource for %(date)s. Subject: %(summary)s.
+
+ *** This is an automated message, sent to you as the resource owner. ***
+ """)
+
+ return message_text % {
+ 'resource': resource['cn'],
+ 'summary': event.get_summary(),
+ 'date': event.get_date_text(),
+ 'status': participant_status_label(status),
+ 'orgname': organizer.name(),
+ 'orgemail': organizer.email()
+ }
+
+
+def send_owner_confirmation(resource, owner, itip_event):
+ """
+ Send a reservation request to the resource owner for manual confirmation (ACCEPT or DECLINE)
+
+ This clones the given invtation with a new UID and setting the resource as organizer in order to
+ receive the reply from the owner.
+ """
+
+ uid = itip_event['uid']
+ event = itip_event['xml']
+ organizer = event.get_organizer()
+ event_attendees = [a.get_displayname() for a in event.get_attendees() if not a.get_cutype() == kolabformat.CutypeResource]
+
+ # generate new UID and set the resource as organizer
+ (mail, domain) = resource['mail'].split('@')
+ event.set_uid(str(uuid.uuid4()))
+ event.set_organizer(mail + '+' + base64.b64encode(uid, '-/') + '@' + domain, resource['cn'])
+ itip_event['uid'] = event.get_uid()
+
+ # add resource owner as (the sole) attendee
+ event._attendees = []
+ event.add_attendee(owner['mail'], owner['cn'], rsvp=True, role=kolabformat.Required, participant_status=kolabformat.PartNeedsAction)
+
+ # flag this iTip message as confirmation type
+ event.add_custom_property('X-Kolab-InvitationType', 'CONFIRMATION')
+
+ log.debug(
+ _("Clone invitation for owner confirmation: %r from %r") % (
+ itip_event['uid'], event.get_organizer().email()
+ ),
+ level=8
+ )
+
+ message_text = _("""
+ A reservation request for %(resource)s requires your approval!
+ Please either accept or decline this inivitation without saving it to your calendar.
+
+ The reservation request was sent from %(orgname)s <%(orgemail)s>.
+
+ Subject: %(summary)s.
+ Date: %(date)s
+ Participants: %(attendees)s
+
+ *** This is an automated message, please don't reply by email. ***
+ """)% {
+ 'resource': resource['cn'],
+ 'orgname': organizer.name(),
+ 'orgemail': organizer.email(),
+ 'summary': event.get_summary(),
+ 'date': event.get_date_text(),
+ 'attendees': ",\n+ ".join(event_attendees)
+ }
+
+ pykolab.itip.send_request(owner['mail'], itip_event, message_text,
+ subject=_('Booking request for %s requires confirmation') % (resource['cn']),
+ direct=True)
+
+