Branch 'pykolab-0.5' - 7 commits - cyruslib.py pykolab/cli pykolab/imap pykolab.spec.in

Jeroen van Meeuwen vanmeeuwen at kolabsys.com
Thu Aug 9 14:27:07 CEST 2012


 cyruslib.py                              |   69 ++++++++++++++++++++++++-----
 pykolab.spec.in                          |    7 +--
 pykolab/cli/cmd_list_mailbox_acls.py     |    8 ++-
 pykolab/cli/cmd_list_mailbox_metadata.py |   66 ++++++++++++++++++++++++++++
 pykolab/cli/cmd_rename_mailbox.py        |   70 ++++++++++++++++++++++++++++++
 pykolab/cli/cmd_set_mailbox_metadata.py  |   72 +++++++++++++++++++++++++++++++
 pykolab/imap/__init__.py                 |   52 +++++++++++++++++++++-
 pykolab/imap/cyrus.py                    |    5 +-
 8 files changed, 327 insertions(+), 22 deletions(-)

New commits:
commit 0df2808a48ddaaa3f7c3150e036d102303c5ec12
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed Aug 8 16:56:13 2012 +0200

    Add a command to rename a (user's) mailbox

diff --git a/pykolab/cli/cmd_rename_mailbox.py b/pykolab/cli/cmd_rename_mailbox.py
new file mode 100644
index 0000000..9917f6b
--- /dev/null
+++ b/pykolab/cli/cmd_rename_mailbox.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+# Copyright 2010-2012 Kolab Systems AG (http://www.kolabsys.com)
+#
+# Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 3 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 Library General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+
+import sys
+
+import commands
+
+import pykolab
+
+from pykolab.imap import IMAP
+from pykolab.translate import _
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('rename_mailbox', execute, description=description(), aliases=['rm'])
+
+def description():
+    return """Rename a mailbox or sub-folder."""
+
+def execute(*args, **kw):
+    """
+        Rename mailbox
+    """
+
+    try:
+        source_folder = conf.cli_args.pop(0)
+        try:
+            target_folder = conf.cli_args.pop(0)
+        except IndexError, errmsg:
+            print >> sys.stderr, _("No target mailbox name specified")
+    except IndexError, errmsg:
+        print >> sys.stderr, _("No source mailbox name specified")
+        sys.exit(1)
+
+    if len(source_folder.split('@')) > 1:
+        domain = source_folder.split('@')[1]
+    else:
+        domain = conf.get('kolab', 'primary_domain')
+
+    imap = IMAP()
+    imap.connect(domain=domain)
+
+    if not imap.has_folder(source_folder):
+        print >> sys.stderr, _("Source folder %r does not exist") % (source_folder)
+        sys.exit(1)
+
+    if imap.has_folder(target_folder):
+        print >> sys.stderr, _("Target folder %r already exists") % (target_folder)
+        sys.exit(1)
+
+    imap.user_mailbox_rename(source_folder.replace('user/',''), target_folder.replace('user/',''))
+


commit b11254c47b8e34841096aa29bdaddee77032802a
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed Aug 8 15:55:55 2012 +0100

    Fix typo

diff --git a/pykolab/imap/__init__.py b/pykolab/imap/__init__.py
index aee9148..f0cb9be 100644
--- a/pykolab/imap/__init__.py
+++ b/pykolab/imap/__init__.py
@@ -533,7 +533,7 @@ class IMAP(object):
             try:
                 self.imap.rename(old_name,new_name)
             except:
-                log.error(_("Could not rename INBOX folder %s to %s") % (oldname,new_name))
+                log.error(_("Could not rename INBOX folder %s to %s") % (old_name,new_name))
         else:
             log.warning(_("Moving INBOX folder %s won't succeed as target folder %s already exists") % (old_name,new_name))
 


commit 417ff49dc51d627a09a8c84c39d270deee3d168b
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed Aug 8 15:05:01 2012 +0100

    Ship commands to set and list mailbox metadata

diff --git a/pykolab/cli/cmd_list_mailbox_metadata.py b/pykolab/cli/cmd_list_mailbox_metadata.py
new file mode 100644
index 0000000..87fe1b0
--- /dev/null
+++ b/pykolab/cli/cmd_list_mailbox_metadata.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+# Copyright 2010-2012 Kolab Systems AG (http://www.kolabsys.com)
+#
+# Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 3 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 Library General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+
+import commands
+
+import pykolab
+
+from pykolab.imap import IMAP
+from pykolab.translate import _
+from pykolab import utils
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('list_mailbox_metadata', execute, description=description())
+
+def description():
+    return """Obtain a list of metadata entries on a folder."""
+
+def execute(*args, **kw):
+    try:
+        folder = conf.cli_args.pop(0)
+    except IndexError, errmsg:
+        folder = utils.ask_question(_("Folder name"))
+
+    if len(folder.split('@')) > 1:
+        domain = folder.split('@')[1]
+    else:
+        domain = conf.get('kolab', 'primary_domain')
+
+    imap = IMAP()
+    imap.connect(domain=domain)
+
+    if not imap.has_folder(folder):
+        print >> sys.stderr, _("No such folder %r") % (folder)
+
+    else:
+        metadata = []
+        folders = imap.lm(folder)
+        for folder in folders:
+            print "Folder", folder
+
+            metadata = imap.get_metadata(folder)
+
+            for annotation in metadata[folder].keys():
+                print "  %-49s %s" % (
+                        annotation,
+                        metadata[folder][annotation]
+                    )
diff --git a/pykolab/cli/cmd_set_mailbox_metadata.py b/pykolab/cli/cmd_set_mailbox_metadata.py
new file mode 100644
index 0000000..9aa9b4e
--- /dev/null
+++ b/pykolab/cli/cmd_set_mailbox_metadata.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+# Copyright 2010-2012 Kolab Systems AG (http://www.kolabsys.com)
+#
+# Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 3 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 Library General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+
+import sys
+
+import commands
+
+import pykolab
+
+from pykolab.imap import IMAP
+from pykolab.translate import _
+from pykolab import utils
+
+log = pykolab.getLogger('pykolab.cli')
+conf = pykolab.getConf()
+
+def __init__():
+    commands.register('set_mailbox_metadata', execute, description=description())
+
+def description():
+    return """Set an metadata entry on a folder."""
+
+def execute(*args, **kw):
+    try:
+        folder = conf.cli_args.pop(0)
+        try:
+            metadata_path = conf.cli_args.pop(0)
+            try:
+                metadata_value = conf.cli_args.pop(0)
+            except IndexError, errmsg:
+                metadata_value = utils.ask_question(_("Metadata value"))
+
+        except IndexError, errmsg:
+            metadata_path = utils.ask_question(_("Metadata path"))
+            metadata_value = utils.ask_question(_("Metadata value"))
+
+    except IndexError, errmsg:
+        folder = utils.ask_question(_("Folder name"))
+        metadata_path = utils.ask_question(_("Metadata path"))
+        metadata_value = utils.ask_question(_("Metadata value"))
+
+    if len(folder.split('@')) > 1:
+        domain = folder.split('@')[1]
+    else:
+        domain = conf.get('kolab', 'primary_domain')
+
+    imap = IMAP()
+    imap.connect(domain=domain)
+
+    if not imap.has_folder(folder):
+        print >> sys.stderr, _("No such folder %r") % (folder)
+
+    else:
+        folders = imap.lm(folder)
+        for folder in folders:
+            imap.set_metadata(folder, metadata_path, metadata_value)


commit a445352de6186de3e2f2b73adc1dfdc0a73d48ed
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed Aug 8 15:04:38 2012 +0100

    Add functions that support some of the new command line client commands

diff --git a/pykolab/imap/__init__.py b/pykolab/imap/__init__.py
index 65c232c..aee9148 100644
--- a/pykolab/imap/__init__.py
+++ b/pykolab/imap/__init__.py
@@ -212,6 +212,13 @@ class IMAP(object):
         else:
             raise AttributeError, _("%r has no attribute %s") % (self,name)
 
+    def get_metadata(self, folder):
+        """
+            Obtain all metadata entries on a folder
+        """
+
+        return self.imap.getannotation(folder, '*')
+
     def namespaces(self):
         """
             Obtain the namespaces.
@@ -255,6 +262,49 @@ class IMAP(object):
 
         self.imap.sam(folder, identifier, acl)
 
+    def set_metadata(self, folder, metadata_path, metadata_value, shared=True):
+        """
+            Set a metadata entry on a folder
+        """
+
+        if metadata_path.startswith('/shared/'):
+            shared = True
+
+        if metadata_path.startswith('/shared/'):
+            metadata_path = metadata_path.replace('/shared/', '/')
+        elif metadata_path.startswith('/private/'):
+            shared = False
+            metadata_path = metadata_path.replace('/private/', '/')
+
+        backend = conf.get('kolab', 'imap_backend')
+
+        if not self.domain == None:
+            if conf.has_section(self.domain) and conf.has_option(self.domain, 'imap_backend'):
+                backend = conf.get(self.domain, 'imap_backend')
+
+        if not shared:
+            log.warning(_("Private annotations need to be set using the appropriate user account."))
+
+        admin_login = conf.get(backend, 'admin_login')
+
+        admin_acl = None
+
+        acls = self.list_acls(folder)
+        if acls.has_key(admin_login):
+            admin_acl = acls[admin_login]
+
+        if admin_acl == None:
+            self.set_acl(folder, admin_login, 'lrsipwa')
+        elif not 'w' in admin_acl:
+            self.set_acl(folder, admin_login, '%sw' % (admin_acl))
+
+        self.imap._setannotation(folder, metadata_path, metadata_value, shared)
+
+        if admin_acl == None:
+            self.set_acl(folder, admin_login, '')
+        elif not 'w' in admin_acl:
+            self.set_acl(folder, admin_login, admin_acl)
+
     def shared_folder_create(self, folder_path, server=None):
         """
             Create a shared folder.
diff --git a/pykolab/imap/cyrus.py b/pykolab/imap/cyrus.py
index af3e5b8..9b291bf 100644
--- a/pykolab/imap/cyrus.py
+++ b/pykolab/imap/cyrus.py
@@ -81,6 +81,7 @@ class Cyrus(cyruslib.CYRUS):
 
         if conf.debuglevel > 8:
             self.VERBOSE = True
+            self.m.debug = 5
 
         # Initialize our variables
         self.separator = self.SEP
@@ -217,7 +218,7 @@ class Cyrus(cyruslib.CYRUS):
     def _getannotation(self, *args, **kw):
         return self.getannotation(*args, **kw)
 
-    def _setannotation(self, mailfolder, annotation, value):
+    def _setannotation(self, mailfolder, annotation, value, shared=False):
         """
             Login to the actual backend server, then set annotation.
         """
@@ -228,7 +229,7 @@ class Cyrus(cyruslib.CYRUS):
         #if annotation.startswith('/private'):
 
         try:
-            self.setannotation(mailfolder, annotation, value)
+            self.setannotation(mailfolder, annotation, value, shared)
         except cyruslib.CYRUSError, e:
             log.error(_("Could not set annotation %r on mail folder %r: %r") % (annotation,mailfolder,e))
 


commit c3b6340193458d39fd83b67d8c3cf9ad0869cb44
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed Aug 8 15:04:02 2012 +0100

    Print mailbox ACLs listed in a somewhat parseable format

diff --git a/pykolab/cli/cmd_list_mailbox_acls.py b/pykolab/cli/cmd_list_mailbox_acls.py
index 10a3321..7f4a9fc 100644
--- a/pykolab/cli/cmd_list_mailbox_acls.py
+++ b/pykolab/cli/cmd_list_mailbox_acls.py
@@ -55,7 +55,9 @@ def execute(*args, **kw):
         acls = []
         folders = imap.lm(folder)
         for folder in folders:
-            acls.append((folder, imap.list_acls(folder)))
+            print "Folder", folder
+            acls = imap.list_acls(folder)
+
+            for acl in acls.keys():
+                print "  %-13s %s" %(acls[acl], acl)
 
-        for folder, acl in acls:
-            print folder, acl


commit 6edd913b83e57e2df5b8403c3fb9757d237003fe
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Wed Aug 8 15:03:26 2012 +0100

    Make cyruslib support recognizing the difference between shared and private annotations

diff --git a/cyruslib.py b/cyruslib.py
index f141059..fe06b24 100644
--- a/cyruslib.py
+++ b/cyruslib.py
@@ -137,16 +137,26 @@ class IMAP4(imaplib.IMAP4):
             return False, dat[0]
         return ok(res), dat[0]
 
-    def getannotation(self, mailbox, pattern='*'):
-        typ, dat = self._simple_command('GETANNOTATION', mailbox, quote(pattern), quote('value.shared'))
+    def getannotation(self, mailbox, pattern='*', shared=None):
+        if shared == None:
+            typ, dat = self._simple_command('GETANNOTATION', mailbox, quote(pattern), quote('*'))
+        elif shared:
+            typ, dat = self._simple_command('GETANNOTATION', mailbox, quote(pattern), quote('value.shared'))
+        else:
+            typ, dat = self._simple_command('GETANNOTATION', mailbox, quote(pattern), quote('value.priv'))
+
         return self._untagged_response(typ, dat, 'ANNOTATION')
 
-    def setannotation(self, mailbox, desc, value):
+    def setannotation(self, mailbox, desc, value, shared=False):
         if value:
             value = quote(value)
         else:
             value = "NIL"
-        typ, dat = self._simple_command('SETANNOTATION', mailbox, quote(desc), "(%s %s)" % (quote('value.shared'), value) )
+        if shared:
+            typ, dat = self._simple_command('SETANNOTATION', mailbox, quote(desc), "(%s %s)" % (quote('value.shared'), value) )
+        else:
+            typ, dat = self._simple_command('SETANNOTATION', mailbox, quote(desc), "(%s %s)" % (quote('value.priv'), value) )
+
         return self._untagged_response(typ, dat, 'ANNOTATION')
 
     def setquota(self, mailbox, limit):
@@ -208,16 +218,26 @@ class IMAP4_SSL(imaplib.IMAP4_SSL):
             return False, dat[0]
         return ok(res), dat[0]
 
-    def getannotation(self, mailbox, pattern='*'):
-        typ, dat = self._simple_command('GETANNOTATION', mailbox, quote(pattern), quote('value.shared'))
+    def getannotation(self, mailbox, pattern='*', shared=None):
+        if shared == None:
+            typ, dat = self._simple_command('GETANNOTATION', mailbox, quote(pattern), quote('*'))
+        elif shared:
+            typ, dat = self._simple_command('GETANNOTATION', mailbox, quote(pattern), quote('value.shared'))
+        else:
+            typ, dat = self._simple_command('GETANNOTATION', mailbox, quote(pattern), quote('value.priv'))
+
         return self._untagged_response(typ, dat, 'ANNOTATION')
 
-    def setannotation(self, mailbox, desc, value):
+    def setannotation(self, mailbox, desc, value, shared=False):
         if value:
             value = quote(value)
         else:
             value = "NIL"
-        typ, dat = self._simple_command('SETANNOTATION', mailbox, quote(desc), "(%s %s)" % (quote('value.shared'), value) )
+        if shared:
+            typ, dat = self._simple_command('SETANNOTATION', mailbox, quote(desc), "(%s %s)" % (quote('value.shared'), value) )
+        else:
+            typ, dat = self._simple_command('SETANNOTATION', mailbox, quote(desc), "(%s %s)" % (quote('value.priv'), value) )
+
         return self._untagged_response(typ, dat, 'ANNOTATION')
 
     def setquota(self, mailbox, limit):
@@ -627,24 +647,49 @@ class CYRUS:
             return {}
         ann = {}
         for annotation in data:
+            self.__verbose( '[GETANNOTATION] RAW %r (length %d)' % (annotation,len(annotation)))
             annotation = annotation.split('"')
-            if len(annotation) != 9:
+            self.__verbose( '[GETANNOTATION] SPLIT %r (length %d)' % (annotation,len(annotation)))
+            if not len(annotation) in [ 9, 17, 21, 37 ]:
                 self.__verbose( '[GETANNOTATION] Invalid annotation entry' )
                 continue
             mbx = self.encode(annotation[1])
-            key = annotation[3]
+            _key = annotation[3]
+
+            if annotation[5] == "value.shared":
+                key = "/shared%s" % (_key)
+            elif annotation[5] == "value.priv":
+                key = "/private%s" % (_key)
+
             value = annotation[7]
+
             self.__verbose( '[GETANNOTATION %s] %s: %s' % (mbx, key, value) )
             if not ann.has_key(mbx):
                 ann[mbx] = {}
             if not ann[mbx].has_key(key):
                 ann[mbx][key] = value
+
+            if len(annotation) > 21:
+                # There's another one hidden in here.
+                if annotation[21] == "value.shared":
+                    key = "/shared%s" % (_key)
+                elif annotation[21] == "value.priv":
+                    key = "/private%s" % (_key)
+
+                value = annotation[23]
+
+                self.__verbose( '[GETANNOTATION %s] %s: %s' % (mbx, key, value) )
+                if not ann.has_key(mbx):
+                    ann[mbx] = {}
+                if not ann[mbx].has_key(key):
+                    ann[mbx][key] = value
+
         return ann
 
-    def setannotation(self, mailbox, annotation, value):
+    def setannotation(self, mailbox, annotation, value, shared=False):
         """Set Annotation"""
         self.__prepare('SETANNOTATION')
-        res, msg = self.__docommand("setannotation", self.decode(mailbox), annotation, value)
+        res, msg = self.__docommand("setannotation", self.decode(mailbox), annotation, value, shared)
         self.__verbose( '[SETANNOTATION %s] %s: %s' % (mailbox, res, msg[0]) )
 
     def __reconstruct(self, mailbox):


commit c290f4d355ef9ab4a3011eb846affd60f1457049
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Tue Aug 7 12:39:45 2012 +0100

    Remove duplicate build requirements

diff --git a/pykolab.spec.in b/pykolab.spec.in
index 00261f8..90a8176 100644
--- a/pykolab.spec.in
+++ b/pykolab.spec.in
@@ -26,7 +26,10 @@ Source0:            http://files.kolab.org/releases/%{name}-%{version}.tar.gz
 BuildRoot:          %{_tmppath}/%{name}-%{version}-%{release}-root
 BuildArch:          noarch
 BuildRequires:      gcc
+BuildRequires:      gettext
 BuildRequires:      glib2-devel
+BuildRequires:      intltool
+BuildRequires:      python
 BuildRequires:      python-icalendar
 BuildRequires:      python-kolabformat
 BuildRequires:      python-ldap
@@ -72,7 +75,6 @@ Kolab Format XML bindings wrapper for %{name}
 %package -n kolab-cli
 Summary:            Kolab CLI components
 Group:              Applications/System
-BuildRequires:      intltool, gettext, python
 Requires:           %{name} = %{version}-%{release}
 Requires:           python-augeas
 Requires:           python-cheetah
@@ -86,7 +88,6 @@ Kolab CLI utilities
 %package -n kolab-saslauthd
 Summary:            Kolab SASL Authentication Daemon
 Group:              Applications/System
-BuildRequires:      intltool, gettext, python
 Requires:           %{name} = %{version}-%{release}
 Requires:           cyrus-sasl
 Requires:           cyrus-sasl-plain
@@ -100,7 +101,6 @@ Kolab SASL Authentication Daemon for multi-domain, multi-authn database deployme
 %package -n kolab-server
 Summary:            Kolab Server implemented in Python
 Group:              Applications/System
-BuildRequires:      intltool, gettext, python
 Requires:           %{name} = %{version}-%{release}
 
 %description -n kolab-server
@@ -112,7 +112,6 @@ Kolab Server implemented in Python
 %package -n postfix-kolab
 Summary:            Kolab SMTP Access Policy for Postfix
 Group:              Applications/System
-BuildRequires:      intltool, gettext, python
 Requires:           postfix
 Requires:           %{name} = %{version}-%{release}
 Requires:           python-sqlalchemy





More information about the commits mailing list