summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>2014-07-27 16:28:03 +0200
committerJeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>2014-07-27 16:28:03 +0200
commit082464b7e59451a2c993a9851de117c979cc24af (patch)
tree192a79505b2c25c411bca50ba239c832557b01ff
parent063eb95d66064f8092a5ad918842e33cba9b8f36 (diff)
parent524849338fcb0cb40bcdb18f4dbe7e9660074f20 (diff)
downloadpykolab-082464b7e59451a2c993a9851de117c979cc24af.tar.gz
Merge branch 'master' of ssh://git.kolabsys.com/git/pykolab
-rw-r--r--conf/kolab.conf5
-rw-r--r--po/POTFILES.in13
-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/auth/__init__.py5
-rw-r--r--pykolab/auth/ldap/__init__.py28
-rw-r--r--pykolab/itip/__init__.py240
-rw-r--r--pykolab/translate.py18
-rw-r--r--pykolab/wap_client/__init__.py2
-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.py344
-rw-r--r--pykolab/xml/recurrence_rule.py117
-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.py103
-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.py338
-rw-r--r--tests/unit/test-011-itip.py406
-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.py363
30 files changed, 7880 insertions, 2089 deletions
diff --git a/conf/kolab.conf b/conf/kolab.conf
index d9a6a5a..128f0b8 100644
--- a/conf/kolab.conf
+++ b/conf/kolab.conf
@@ -365,10 +365,13 @@ admin_password = Welcome123
result_attribute = mail
[wallace]
-modules = resources, footer
+modules = resources, invitationpolicy, footer
footer_text = /etc/kolab/footer.text
footer_html = /etc/kolab/footer.html
+; 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 8109c28..5a5bc37 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -12,11 +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/__init__.py
-kolab.py
pykolab/auth/__init__.py
pykolab/auth/ldap/auth_cache.py
pykolab/auth/ldap/cache.py
@@ -84,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
@@ -137,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
@@ -160,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/auth/__init__.py b/pykolab/auth/__init__.py
index fa081b8..7309b21 100644
--- a/pykolab/auth/__init__.py
+++ b/pykolab/auth/__init__.py
@@ -205,7 +205,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 148ecf8..d1a0b2d 100644
--- a/pykolab/auth/ldap/__init__.py
+++ b/pykolab/auth/ldap/__init__.py
@@ -1987,16 +1987,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 == None:
- base_dn = self.config_get('base_dn')
+ user_base_dn = self.config_get(conf_prefix + 'user_base_dn')
+ if user_base_dn == None:
+ user_base_dn = self.config_get('base_dn')
auth_attrs = self.config_get_list('auth_attributes')
@@ -2004,18 +2005,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
diff --git a/pykolab/itip/__init__.py b/pykolab/itip/__init__.py
new file mode 100644
index 0000000..816ee1d
--- /dev/null
+++ b/pykolab/itip/__init__.py
@@ -0,0 +1,240 @@
+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" % (e))
+ 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
+
+ # TODO: don't consider conflict if event has TRANSP:TRANSPARENT
+
+ _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()
+
+ 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))
+
+ 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 b7ac1e9..eabc893 100644
--- a/pykolab/wap_client/__init__.py
+++ b/pykolab/wap_client/__init__.py
@@ -312,8 +312,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 not get == None:
_get = "?%s" % (urllib.urlencode(get))
else:
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 b496899..7921280 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 name is not None 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 1577b58..9a2c103 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 0d6dec5..5a832da 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 == 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 280d2fb..8e41a92 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -6,15 +6,19 @@ import kolabformat
import pytz
import time
import uuid
+import base64
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 +28,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 +50,70 @@ class Event(object):
"CANCELLED": kolabformat.StatusCancelled,
}
+ classification_map = {
+ "PUBLIC": kolabformat.ClassPublic,
+ "PRIVATE": kolabformat.ClassPrivate,
+ "CONFIDENTIAL": kolabformat.ClassConfidential,
+ }
+
+ 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 +159,31 @@ class Event(object):
# NOTE: Make sure to list(set()) or duplicates may arise
for attr in list(set(event.singletons)):
- if hasattr(self, 'get_ical_%s' % (attr.lower())):
- exec("retval = 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 not retval == None and not retval == "":
event.add(attr.lower(), retval)
-
- elif hasattr(self, 'get_%s' % (attr.lower())):
- exec("retval = self.get_%s()" % (attr.lower()))
+ elif hasattr(self, default_getter):
+ retval = getattr(self, default_getter)()
if not retval == None and not retval == "":
event.add(attr.lower(), retval, encode=0)
# NOTE: Make sure to list(set()) or duplicates may arise
for attr in list(set(event.multiple)):
- if hasattr(self, 'get_ical_%s' % (attr.lower())):
- exec("retval = self.get_ical_%s()" % (attr.lower()))
- if isinstance(retval, list) and not len(retval) == 0:
- for _retval in retval:
- event.add(attr.lower(), _retval, encode=0)
-
- elif hasattr(self, 'get_%s' % (attr.lower())):
- exec("retval = self.get_%s()" % (attr.lower()))
- if isinstance(retval, list) and not len(retval) == 0:
- 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 not len(retval) == 0:
+ for _retval in retval:
+ event.add(attr.lower(), _retval, encode=0)
cal.add_component(event)
@@ -161,12 +225,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 list(set(ical_event.required)):
@@ -183,13 +253,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,14 +296,14 @@ 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()
@@ -265,9 +332,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 +461,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:
@@ -388,6 +500,9 @@ class Event(object):
def get_ical_sequence(self):
return str(self.event.sequence()) if self.event.sequence() else None
+ def get_location(self):
+ return self.event.location()
+
def get_lastmodified(self):
try:
_datetime = self.event.lastModified()
@@ -396,7 +511,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()
@@ -428,7 +543,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 +558,28 @@ 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_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_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 == None:
@@ -451,7 +588,7 @@ 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_dtstamp(self, _datetime):
self.event.setLastModified(xmlutils.to_cdatetime(_datetime, False))
@@ -478,26 +615,27 @@ class Event(object):
self.add_exception_date(_datetime)
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 +678,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 +689,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 +718,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 +742,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 +757,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 +796,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 +812,42 @@ 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 = [dict(type=x.type()) 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 to_message(self):
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
@@ -706,6 +885,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 +925,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 +941,7 @@ class Event(object):
msg = MIMEMultipart()
msg_from = None
+ attendees = None
if method == "REPLY":
# TODO: Make user friendly name <email>
@@ -737,6 +954,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 +997,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 +1016,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..eb17fd5
--- /dev/null
+++ b/pykolab/xml/recurrence_rule.py
@@ -0,0 +1,117 @@
+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',
+ '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 = [int(v) for x in val]
+ elif isinstance(val, kolabformat.vectordaypos):
+ val = ["%d%s" % (x.occurence, self._translate_value(x.weekday)) 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..60b6587 100644
--- a/tests/functional/test_wallace/test_005_resource_invitation.py
+++ b/tests/functional/test_wallace/test_005_resource_invitation.py
@@ -8,6 +8,9 @@ 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 email import message_from_string
from twisted.trial import unittest
@@ -23,7 +26,7 @@ CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VEVENT
UID:%s
-DTSTAMP:20140213T1254140
+DTSTAMP:20140213T125414Z
DTSTART;TZID=Europe/London:%s
DTEND;TZID=Europe/London:%s
SUMMARY:test
@@ -43,7 +46,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 +93,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 +115,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 +136,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 +187,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 +218,9 @@ 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')
time.sleep(1)
from tests.functional.synchronize import synchronize_once
@@ -328,12 +333,7 @@ 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
@@ -357,12 +357,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 +377,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)
@@ -390,7 +388,7 @@ class TestResourceInvitation(unittest.TestCase):
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 +400,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 +410,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 +420,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 +441,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 +455,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 +466,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 +487,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 +498,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 +509,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 +522,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 +530,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 +541,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 +568,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 +576,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 +588,36 @@ 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("Reservation Request for test was ACCEPTED", self.room2['mail'], self.jane['mailbox'])
+ 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)
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..1af22ff
--- /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(), '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..5017091 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,189 @@ 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>
+ </vevent>
+ </components>
+ </vcalendar>
+</icalendar>
+"""
class TestEventXML(unittest.TestCase):
event = Event()
@@ -117,25 +301,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 +326,60 @@ 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)
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.assertIsInstance(event['dtstamp'].dt, datetime.datetime)
def test_020_calendaring_recurrence(self):
@@ -214,6 +439,87 @@ 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)
+
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..a120fd2
--- /dev/null
+++ b/tests/unit/test-011-itip.py
@@ -0,0 +1,406 @@
+import pykolab
+import datetime
+import pytz
+import kolabformat
+
+from pykolab import itip
+from pykolab.xml import Event
+
+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")
+
+ 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")
+
+ message = message_from_string(self.smtplog[0][2])
+ self.assertEqual(message.get('Subject'), 'Invitation for test was 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..de29450
--- /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 is not None:
+ 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..b31a8d0 100644
--- a/wallace/module_resources.py
+++ b/wallace/module_resources.py
@@ -40,11 +40,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 +100,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)
@@ -150,7 +167,7 @@ def execute(*args, **kw):
# 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', 'CANCEL'])
except Exception, e:
log.error(_("Failed to parse iTip events from message: %r" % (e)))
itip_events = []
@@ -466,76 +483,32 @@ 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)
-
- for itip in itip_events:
- _es = to_dt(event.get_start())
- _ee = to_dt(event.get_end())
-
- conflict = False
-
- # 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'])
-
- 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))
-
- _es = to_dt(event.get_next_occurence(_es)) if event.is_recurring() else None
- _ee = to_dt(event.get_occurence_end_date(_es))
+ 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.get_uid() == itip['uid']:
- resource_rec['existing_events'].append(itip['uid'])
+ if event:
+ for itip in itip_events:
+ conflict = check_event_conflict(event, itip)
- # don't register conflict for updates
- if itip['sequence'] > 0 and itip['sequence'] >= event.get_sequence():
- conflict = False
+ if event.get_uid() == itip['uid']:
+ resource_rec['existing_events'].append(itip['uid'])
- if conflict:
- log.info(
- _("Event %r conflicts with event %r") % (
- itip['xml'].get_uid(),
- event.get_uid()
- )
- )
+ if conflict:
+ log.info(
+ _("Event %r conflicts with event %r") % (
+ itip['xml'].get_uid(),
+ event.get_uid()
+ )
+ )
- resource_rec['conflicting_events'].append(event.get_uid())
- resource_rec['conflict'] = True
+ resource_rec['conflicting_events'].append(event.get_uid())
+ resource_rec['conflict'] = True
return num_messages
-def check_date_conflict(_es, _ee, _is, _ie):
- 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 accept_reservation_request(itip_event, resource, delegator=None):
"""
@@ -556,7 +529,13 @@ def accept_reservation_request(itip_event, resource, delegator=None):
level=8
)
- send_response(delegator['mail'] if delegator else resource['mail'], itip_event, get_resource_owner(resource))
+ owner = get_resource_owner(resource)
+
+ if saved:
+ send_response(delegator['mail'] if delegator else resource['mail'], itip_event, owner)
+
+ if owner:
+ send_owner_notification(resource, owner, itip_event, saved)
def decline_reservation_request(itip_event, resource):
@@ -570,8 +549,12 @@ 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):
"""
@@ -617,118 +600,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,
@@ -904,21 +775,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 +802,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):
"""
@@ -986,12 +871,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 +878,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 +889,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 +911,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 +920,83 @@ 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)
+
+ if resource.has_key('kolabinvitationpolicy'):
+ for policy in resource['kolabinvitationpolicy']:
+ # 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()
+ }