<NME> handle_mailbox_operations.py
<BEF> """A management command to apply mailbox operations."""

import logging
import os
import shutil

from django.core.management.base import BaseCommand

from modoboa.lib.exceptions import InternalError
from modoboa.lib.sysutils import exec_cmd
from modoboa.parameters import tools as param_tools
from ...app_settings import load_admin_settings
from ...models import MailboxOperation


class Command(BaseCommand):
    args = ''
    help = 'Handles rename and delete operations on mailboxes'

    def __init__(self, *args, **kwargs):
        super(Command, self).__init__(*args, **kwargs)
        self.logger = logging.getLogger('modoboa.admin')
        self.logger = logging.getLogger("modoboa.admin")

    def add_arguments(self, parser):
        """Add extra arguments to command line."""
        parser.add_argument(
            "--pidfile", default="/tmp/handle_mailbox_operations.pid",
            help="Path to the file that will contain the PID of this process"
        )

            except os.error as e:
                raise OperationError(str(e))
        code, output = exec_cmd(
            "mv %s %s" % (ope.argument, new_mail_home)
        )
        if code:
            raise OperationError(output)
                os.makedirs(dirname)
            except os.error as e:
                raise OperationError(str(e))
        code, output = exec_cmd(
            "mv %s %s" % (operation.argument, new_mail_home)
        )
        if code:
        if code:
            raise OperationError(output)

    def handle(self, *args, **options):
        AdminConsole().load()
        if parameters.get_admin("HANDLE_MAILBOXES") == 'no':
            return
        for ope in MailboxOperation.objects.all():
            try:
                f = getattr(self, '%s_mailbox' % ope.type)
        return True

            try:
                f(ope)
            except (OperationError, AdminError) as e:
                self.logger.critical('%s failed (reason: %s)', ope, e)
            else:
                self.logger.info('%s succeed', ope)
                ope.delete()
                f = getattr(self, "%s_mailbox" % ope.type)
            except AttributeError:
                continue
            try:
                f(ope)
            except (OperationError, InternalError) as e:
                self.logger.critical(
                    "%s failed (reason: %s)", ope, str(e).decode("utf-8"))
            else:
                self.logger.info("%s succeed", ope)
                ope.delete()
        os.unlink(options["pidfile"])

<MSG> Prevent multiple executions.

see #482

<DFF> @@ -14,9 +14,15 @@ class OperationError(Exception):
 
 
 class Command(BaseCommand):
-    args = ''
     help = 'Handles rename and delete operations on mailboxes'
 
+    option_list = BaseCommand.option_list + (
+        make_option(
+            '--pidfile', default='/tmp/handle_mailbox_operations.pid',
+            help='Path to the file that will contain the PID of this process'
+        ),
+    )
+
     def __init__(self, *args, **kwargs):
         super(Command, self).__init__(*args, **kwargs)
         self.logger = logging.getLogger('modoboa.admin')
@@ -32,7 +38,7 @@ class Command(BaseCommand):
             except os.error as e:
                 raise OperationError(str(e))
         code, output = exec_cmd(
-            "mv %s %s" % (ope.argument, new_mail_home)
+            "mv %s %s" % (operation.argument, new_mail_home)
         )
         if code:
             raise OperationError(output)
@@ -46,10 +52,30 @@ class Command(BaseCommand):
         if code:
             raise OperationError(output)
 
+    def check_pidfile(self, path):
+        """Check if this command is already running
+
+        :param str path: path to the file containing the PID
+        :return: a boolean, True means we can go further
+        """
+        if os.path.exists(path):
+            with open(path) as fp:
+                pid = fp.read().strip()
+            code, output = exec_cmd(
+                "grep handle_mailbox_operations /proc/%s/cmdline" % pid
+            )
+            if not code:
+                return False
+        with open(path, 'w') as fp:
+            print >> fp, os.getpid()
+        return True
+
     def handle(self, *args, **options):
         AdminConsole().load()
         if parameters.get_admin("HANDLE_MAILBOXES") == 'no':
             return
+        if not self.check_pidfile(options['pidfile']):
+            return
         for ope in MailboxOperation.objects.all():
             try:
                 f = getattr(self, '%s_mailbox' % ope.type)
@@ -58,7 +84,9 @@ class Command(BaseCommand):
             try:
                 f(ope)
             except (OperationError, AdminError) as e:
-                self.logger.critical('%s failed (reason: %s)', ope, e)
+                self.logger.critical('%s failed (reason: %s)',
+                                     ope, str(e).encode('utf-8'))
             else:
                 self.logger.info('%s succeed', ope)
                 ope.delete()
+        os.unlink(options['pidfile'])

Prevent multiple executions.