Ejemplo n.º 1
0
    def syncrunner(self):
        self.ui.registerthread(self.name)
        accountmetadata = self.getaccountmeta()
        if not os.path.exists(accountmetadata):
            os.mkdir(accountmetadata, 0700)

        self.remoterepos = Repository(self, 'remote')
        self.localrepos = Repository(self, 'local')
        self.statusrepos = Repository(self, 'status')

        # Loop account sync if needed (bail out after 3 failures)
        looping = 3
        while looping:
            self.ui.acct(self)
            try:
                self.lock()
                self.sync()
            except (KeyboardInterrupt, SystemExit):
                raise
            except OfflineImapError, e:
                # Stop looping and bubble up Exception if needed.
                if e.severity >= OfflineImapError.ERROR.REPO:
                    if looping:
                        looping -= 1
                    if e.severity >= OfflineImapError.ERROR.CRITICAL:
                        raise
                self.ui.error(e, exc_info()[2])
            except Exception, e:
                self.ui.error(e,
                              exc_info()[2],
                              msg="While attempting to sync"
                              " account '%s'" % self)
Ejemplo n.º 2
0
    def syncrunner(self):
        self.ui.registerthread(self.name)
        self.ui.acct(self.name)
        accountmetadata = self.getaccountmeta()
        if not os.path.exists(accountmetadata):
            os.mkdir(accountmetadata, 0700)

        self.remoterepos = Repository(self, 'remote')
        self.localrepos = Repository(self, 'local')
        self.statusrepos = Repository(self, 'status')

        # Loop account sync if needed (bail out after 3 failures)
        looping = 3
        while looping:
            try:
                try:
                    self.sync()
                except (KeyboardInterrupt, SystemExit):
                    raise
                except OfflineImapError, e:
                    self.ui.warn(e.reason)
                    # Stop looping and bubble up Exception if needed.
                    if e.severity >= OfflineImapError.ERROR.REPO:
                        if looping:
                            looping -= 1
                        if e.severity >= OfflineImapError.ERROR.CRITICAL:
                            raise
                except:
                    self.ui.warn("Error occured attempting to sync account "\
                                 "'%s':\n%s"% (self, traceback.format_exc()))
                else:
                    # after success sync, reset the looping counter to 3
                    if self.refreshperiod:
                        looping = 3
            finally:
Ejemplo n.º 3
0
 def serverdiagnostics(self):
     """Output diagnostics for all involved repositories"""
     remote_repo = Repository(self, 'remote')
     local_repo = Repository(self, 'local')
     #status_repo = Repository(self, 'status')
     self.ui.serverdiagnostics(remote_repo, 'Remote')
     self.ui.serverdiagnostics(local_repo, 'Local')
Ejemplo n.º 4
0
 def __migratefmd5(self, options):
     for accountname in self._get_activeaccounts(options):
         account = accounts.Account(self.config, accountname)
         localrepo = Repository(account, 'local')
         if localrepo.getfoldertype() != folder.Maildir.MaildirFolder:
             continue
         folders = localrepo.getfolders()
         for f in folders:
             f.migratefmd5(options.dryrun)
Ejemplo n.º 5
0
 def __migratefmd5(self, options):
     for accountname in self._get_activeaccounts(options):
         account = accounts.Account(self.config, accountname)
         localrepo = Repository(account, 'local')
         if localrepo.getfoldertype() != folder.Maildir.MaildirFolder:
             continue
         folders = localrepo.getfolders()
         for f in folders:
             f.migratefmd5(options.dryrun)
Ejemplo n.º 6
0
    def deletefolder(self, foldername):
        remote_repo = Repository(self, 'remote')

        try:
            if self.dryrun:
                self.ui.info("would try to remove '%s' on remote of '%s' "
                             "account" % (foldername, self))
            else:
                remote_repo.deletefolder(foldername)
                self.ui.info("Folder '%s' deleted." % foldername)
            return 0
        except Exception as e:
            self.ui.error(e)
            return 1
Ejemplo n.º 7
0
    def deletefolder(self, foldername):
        remote_repo = Repository(self, 'remote')

        try:
            if self.dryrun:
                self.ui.info("would try to remove '%s' on remote of '%s' "
                    "account"% (foldername, self))
            else:
                remote_repo.deletefolder(foldername)
                self.ui.info("Folder '%s' deleted."% foldername)
            return 0
        except Exception as e:
            self.ui.error(e)
            return 1
Ejemplo n.º 8
0
    def syncrunner(self):
        """The target for both single and multi-threaded modes."""

        self.ui.registerthread(self)
        try:
            accountmetadata = self.getaccountmeta()
            if not os.path.exists(accountmetadata):
                os.mkdir(accountmetadata, 0o700)

            self.remoterepos = Repository(self, 'remote')
            self.localrepos = Repository(self, 'local')
            self.statusrepos = Repository(self, 'status')
        except OfflineImapError as e:
            self.ui.error(e, exc_info()[2])
            if e.severity >= OfflineImapError.ERROR.CRITICAL:
                raise
            return

        # Loop account sync if needed (bail out after 3 failures).
        looping = 3
        while looping:
            self.ui.acct(self)
            try:
                self.__lock()
                self.__sync()
            except (KeyboardInterrupt, SystemExit):
                raise
            except OfflineImapError as e:
                # Stop looping and bubble up Exception if needed.
                if e.severity >= OfflineImapError.ERROR.REPO:
                    if looping:
                        looping -= 1
                    if e.severity >= OfflineImapError.ERROR.CRITICAL:
                        raise
                self.ui.error(e, exc_info()[2])
            except Exception as e:
                self.ui.error(e,
                              exc_info()[2],
                              msg="While attempting to sync account '%s'" %
                              self)
            else:
                # After success sync, reset the looping counter to 3.
                if self.refreshperiod:
                    looping = 3
            finally:
                self.ui.acctdone(self)
                self._unlock()
                if looping and self._sleeper() >= 2:
                    looping = 0
Ejemplo n.º 9
0
    def syncrunner(self):
        """The target for both single and multi-threaded modes."""

        self.ui.registerthread(self)
        try:
            accountmetadata = self.getaccountmeta()
            if not os.path.exists(accountmetadata):
                os.mkdir(accountmetadata, 0o700)

            self.remoterepos = Repository(self, 'remote')
            self.localrepos  = Repository(self, 'local')
            self.statusrepos = Repository(self, 'status')
        except OfflineImapError as e:
            self.ui.error(e, exc_info()[2])
            if e.severity >= OfflineImapError.ERROR.CRITICAL:
                raise
            return

        # Loop account sync if needed (bail out after 3 failures).
        looping = 3
        while looping:
            self.ui.acct(self)
            try:
                self.__lock()
                self.__sync()
            except (KeyboardInterrupt, SystemExit):
                raise
            except OfflineImapError as e:
                # Stop looping and bubble up Exception if needed.
                if e.severity >= OfflineImapError.ERROR.REPO:
                    if looping:
                        looping -= 1
                    if e.severity >= OfflineImapError.ERROR.CRITICAL:
                        raise
                self.ui.error(e, exc_info()[2])
            except Exception as e:
                self.ui.error(e, exc_info()[2], msg=
                    "While attempting to sync account '%s'"% self)
            else:
                # After success sync, reset the looping counter to 3.
                if self.refreshperiod:
                    looping = 3
            finally:
                self.ui.acctdone(self)
                self._unlock()
                if looping and self._sleeper() >= 2:
                    looping = 0
Ejemplo n.º 10
0
    def __migratefmd5(self, options):
        activeaccounts = self.config.get("general", "accounts")
        if options.accounts:
            activeaccounts = options.accounts
        activeaccounts = activeaccounts.replace(" ", "")
        activeaccounts = activeaccounts.split(",")
        allaccounts = accounts.AccountListGenerator(self.config)

        for account in allaccounts:
            if account.name not in activeaccounts:
                continue
            localrepo = Repository(account, 'local')
            if localrepo.getfoldertype() != folder.Maildir.MaildirFolder:
                continue
            folders = localrepo.getfolders()
            for f in folders:
                f.migratefmd5(options.dryrun)
Ejemplo n.º 11
0
    def __migratefmd5(self, options):
        activeaccounts = self.config.get("general", "accounts")
        if options.accounts:
            activeaccounts = options.accounts
        activeaccounts = activeaccounts.replace(" ", "")
        activeaccounts = activeaccounts.split(",")
        allaccounts = accounts.AccountListGenerator(self.config)

        for account in allaccounts:
            if account.name not in activeaccounts:
                continue
            localrepo = Repository(account, 'local')
            if localrepo.getfoldertype() != folder.Maildir.MaildirFolder:
                continue
            folders = localrepo.getfolders()
            for f in folders:
                f.migratefmd5(options.dryrun)
Ejemplo n.º 12
0
    def syncrunner(self, siglistener):
        self.ui.registerthread(self.name)
        self.ui.acct(self.name)
        accountmetadata = self.getaccountmeta()
        if not os.path.exists(accountmetadata):
            os.mkdir(accountmetadata, 0700)

        # get all three repositories
        self.remoterepos = Repository(self, 'remote')
        self.localrepos = Repository(self, 'local')
        self.statusrepos = Repository(self, 'status')

        #might need changes here to ensure that one account sync does not crash others...
        if not self.refreshperiod:
            try:
                try:
                    self.sync(siglistener)
                except (KeyboardInterrupt, SystemExit):
                    raise
                except:
                    self.ui.warn("Error occured attempting to sync account " + self.name \
                                 + ": " + traceback.format_exc())
            finally:
                self.ui.acctdone(self.name)

            return

        looping = 1
        while looping:
            try:
                try:
                    self.sync(siglistener)
                except (KeyboardInterrupt, SystemExit):
                    raise
                except:
                    self.ui.warn("Error occured attempting to sync account " + self.name \
                                 + ": " + traceback.format_exc())
            finally:
                looping = self.sleeper(siglistener) != 2
                self.ui.acctdone(self.name)
Ejemplo n.º 13
0
    def syncrunner(self, siglistener):
        self.ui.registerthread(self.name)
        self.ui.acct(self.name)
        accountmetadata = self.getaccountmeta()
        if not os.path.exists(accountmetadata):
            os.mkdir(accountmetadata, 0700)            

        # get all three repositories
        self.remoterepos = Repository(self, 'remote')
        self.localrepos  = Repository(self, 'local')
        self.statusrepos = Repository(self, 'status')

        #might need changes here to ensure that one account sync does not crash others...
        if not self.refreshperiod:
            try:
                try:
                    self.sync(siglistener)
                except (KeyboardInterrupt, SystemExit):
                    raise
                except:
                    self.ui.warn("Error occured attempting to sync account " + self.name \
                                 + ": " + traceback.format_exc())
            finally:
                self.ui.acctdone(self.name)

            return


        looping = 1
        while looping:
            try:
                try:
                    self.sync(siglistener)
                except (KeyboardInterrupt, SystemExit):
                    raise
                except:
                    self.ui.warn("Error occured attempting to sync account " + self.name \
                                 + ": " + traceback.format_exc())
            finally:
                looping = self.sleeper(siglistener) != 2
                self.ui.acctdone(self.name)
Ejemplo n.º 14
0
    def syncrunner(self):
        self.ui.registerthread(self)
        accountmetadata = self.getaccountmeta()
        if not os.path.exists(accountmetadata):
            os.mkdir(accountmetadata, 0o700)

        self.remoterepos = Repository(self, "remote")
        self.localrepos = Repository(self, "local")
        self.statusrepos = Repository(self, "status")

        # Loop account sync if needed (bail out after 3 failures)
        looping = 3
        while looping:
            self.ui.acct(self)
            try:
                self.lock()
                self.sync()
            except (KeyboardInterrupt, SystemExit):
                raise
            except OfflineImapError as e:
                # Stop looping and bubble up Exception if needed.
                if e.severity >= OfflineImapError.ERROR.REPO:
                    if looping:
                        looping -= 1
                    if e.severity >= OfflineImapError.ERROR.CRITICAL:
                        raise
                self.ui.error(e, exc_info()[2])
            except Exception as e:
                self.ui.error(e, exc_info()[2], msg="While attempting to sync" " account '%s'" % self)
            else:
                # after success sync, reset the looping counter to 3
                if self.refreshperiod:
                    looping = 3
            finally:
                self.ui.acctdone(self)
                self.unlock()
                if looping and self.sleeper() >= 2:
                    looping = 0
Ejemplo n.º 15
0
class SyncableAccount(Account):
    """A syncable email account connecting 2 repositories

    Derives from :class:`accounts.Account` but contains the additional
    functions :meth:`syncrunner`, :meth:`sync`, :meth:`syncfolders`,
    used for syncing."""

    def __init__(self, *args, **kwargs):
        Account.__init__(self, *args, **kwargs)
        self._lockfd = None
        self._lockfilepath = os.path.join(self.config.getmetadatadir(),
                                          "%s.lock" % self)

    def lock(self):
        """Lock the account, throwing an exception if it is locked already"""
        self._lockfd = open(self._lockfilepath, 'w')
        try:
            fcntl.lockf(self._lockfd, fcntl.LOCK_EX|fcntl.LOCK_NB)
        except NameError:
            #fcntl not available (Windows), disable file locking... :(
            pass
        except IOError:
            self._lockfd.close()
            raise OfflineImapError("Could not lock account %s. Is another "
                                   "instance using this account?" % self,
                                   OfflineImapError.ERROR.REPO)

    def unlock(self):
        """Unlock the account, deleting the lock file"""
        #If we own the lock file, delete it
        if self._lockfd and not self._lockfd.closed:
            self._lockfd.close()
            try:
                os.unlink(self._lockfilepath)
            except OSError:
                pass #Failed to delete for some reason.

    def syncrunner(self):
        self.ui.registerthread(self)
        try:
            accountmetadata = self.getaccountmeta()
            if not os.path.exists(accountmetadata):
                os.mkdir(accountmetadata, 0o700)

            self.remoterepos = Repository(self, 'remote')
            self.localrepos  = Repository(self, 'local')
            self.statusrepos = Repository(self, 'status')
        except OfflineImapError as e:
            self.ui.error(e, exc_info()[2])
            if e.severity >= OfflineImapError.ERROR.CRITICAL:
                raise
            return

        # Loop account sync if needed (bail out after 3 failures)
        looping = 3
        while looping:
            self.ui.acct(self)
            try:
                self.lock()
                self.sync()
            except (KeyboardInterrupt, SystemExit):
                raise
            except OfflineImapError as e:
                # Stop looping and bubble up Exception if needed.
                if e.severity >= OfflineImapError.ERROR.REPO:
                    if looping:
                        looping -= 1
                    if e.severity >= OfflineImapError.ERROR.CRITICAL:
                        raise
                self.ui.error(e, exc_info()[2])
            except Exception as e:
                self.ui.error(e, exc_info()[2], msg = "While attempting to sync"
                    " account '%s'" % self)
            else:
                # after success sync, reset the looping counter to 3
                if self.refreshperiod:
                    looping = 3
            finally:
                self.ui.acctdone(self)
                self.unlock()
                if looping and self.sleeper() >= 2:
                    looping = 0

    def get_local_folder(self, remotefolder):
        """Return the corresponding local folder for a given remotefolder"""
        return self.localrepos.getfolder(
            remotefolder.getvisiblename().
            replace(self.remoterepos.getsep(), self.localrepos.getsep()))

    def sync(self):
        """Synchronize the account once, then return

        Assumes that `self.remoterepos`, `self.localrepos`, and
        `self.statusrepos` has already been populated, so it should only
        be called from the :meth:`syncrunner` function.
        """
        folderthreads = []

        hook = self.getconf('presynchook', '')
        self.callhook(hook)

        quickconfig = self.getconfint('quick', 0)
        if quickconfig < 0:
            quick = True
        elif quickconfig > 0:
            if self.quicknum == 0 or self.quicknum > quickconfig:
                self.quicknum = 1
                quick = False
            else:
                self.quicknum = self.quicknum + 1
                quick = True
        else:
            quick = False

        try:
            remoterepos = self.remoterepos
            localrepos = self.localrepos
            statusrepos = self.statusrepos

            # init repos with list of folders, so we have them (and the
            # folder delimiter etc)
            remoterepos.getfolders()
            localrepos.getfolders()

            remoterepos.sync_folder_structure(localrepos, statusrepos)
            # replicate the folderstructure between REMOTE to LOCAL
            if not localrepos.getconfboolean('readonly', False):
                self.ui.syncfolders(remoterepos, localrepos)

            # iterate through all folders on the remote repo and sync
            for remotefolder in remoterepos.getfolders():
                # check for CTRL-C or SIGTERM
                if Account.abort_NOW_signal.is_set(): break

                if not remotefolder.sync_this:
                    self.ui.debug('', "Not syncing filtered folder '%s'"
                                  "[%s]" % (remotefolder, remoterepos))
                    continue # Ignore filtered folder
                localfolder = self.get_local_folder(remotefolder)
                if not localfolder.sync_this:
                    self.ui.debug('', "Not syncing filtered folder '%s'"
                                 "[%s]" % (localfolder, localfolder.repository))
                    continue # Ignore filtered folder
                thread = InstanceLimitedThread(\
                    instancename = 'FOLDER_' + self.remoterepos.getname(),
                    target = syncfolder,
                    name = "Folder %s [acc: %s]" % (remotefolder, self),
                    args = (self, remotefolder, quick))
                thread.start()
                folderthreads.append(thread)
            # wait for all threads to finish
            for thr in folderthreads:
                thr.join()
            # Write out mailbox names if required and not in dry-run mode
            if not self.dryrun:
                mbnames.write()
            localrepos.forgetfolders()
            remoterepos.forgetfolders()
        except:
            #error while syncing. Drop all connections that we have, they
            #might be bogus by now (e.g. after suspend)
            localrepos.dropconnections()
            remoterepos.dropconnections()
            raise
        else:
            # sync went fine. Hold or drop depending on config
            localrepos.holdordropconnections()
            remoterepos.holdordropconnections()

        hook = self.getconf('postsynchook', '')
        self.callhook(hook)

    def callhook(self, cmd):
        # check for CTRL-C or SIGTERM and run postsynchook
        if Account.abort_NOW_signal.is_set():
            return
        if not cmd:
            return
        try:
            self.ui.callhook("Calling hook: " + cmd)
            if self.dryrun: # don't if we are in dry-run mode
                return
            p = Popen(cmd, shell=True,
                      stdin=PIPE, stdout=PIPE, stderr=PIPE,
                      close_fds=True)
            r = p.communicate()
            self.ui.callhook("Hook stdout: %s\nHook stderr:%s\n" % r)
            self.ui.callhook("Hook return code: %d" % p.returncode)
        except (KeyboardInterrupt, SystemExit):
            raise
        except Exception as e:
            self.ui.error(e, exc_info()[2], msg = "Calling hook")
Ejemplo n.º 16
0
class SyncableAccount(Account):
    """A syncable email account connecting 2 repositories.

    Derives from :class:`accounts.Account` but contains the additional
    functions :meth:`syncrunner`, :meth:`sync`, :meth:`syncfolders`,
    used for syncing.

    In multi-threaded mode, one instance of this object is run per "account"
    thread."""
    def __init__(self, *args, **kwargs):
        Account.__init__(self, *args, **kwargs)
        self._lockfd = None
        self._lockfilepath = os.path.join(self.config.getmetadatadir(),
                                          "%s.lock" % self)

    def __lock(self):
        """Lock the account, throwing an exception if it is locked already."""

        self._lockfd = open(self._lockfilepath, 'w')
        try:
            fcntl.lockf(self._lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
        except NameError:
            #fcntl not available (Windows), disable file locking... :(
            pass
        except IOError:
            self._lockfd.close()
            six.reraise(
                OfflineImapError,
                OfflineImapError(
                    "Could not lock account %s. Is another "
                    "instance using this account?" % self,
                    OfflineImapError.ERROR.REPO),
                exc_info()[2])

    def _unlock(self):
        """Unlock the account, deleting the lock file"""

        #If we own the lock file, delete it
        if self._lockfd and not self._lockfd.closed:
            self._lockfd.close()
            try:
                os.unlink(self._lockfilepath)
            except OSError:
                pass  # Failed to delete for some reason.

    def syncrunner(self):
        """The target for both single and multi-threaded modes."""

        self.ui.registerthread(self)
        try:
            accountmetadata = self.getaccountmeta()
            if not os.path.exists(accountmetadata):
                os.mkdir(accountmetadata, 0o700)

            self.remoterepos = Repository(self, 'remote')
            self.localrepos = Repository(self, 'local')
            self.statusrepos = Repository(self, 'status')
        except OfflineImapError as e:
            self.ui.error(e, exc_info()[2])
            if e.severity >= OfflineImapError.ERROR.CRITICAL:
                raise
            return

        # Loop account sync if needed (bail out after 3 failures).
        looping = 3
        while looping:
            self.ui.acct(self)
            try:
                self.__lock()
                self.__sync()
            except (KeyboardInterrupt, SystemExit):
                raise
            except OfflineImapError as e:
                # Stop looping and bubble up Exception if needed.
                if e.severity >= OfflineImapError.ERROR.REPO:
                    if looping:
                        looping -= 1
                    if e.severity >= OfflineImapError.ERROR.CRITICAL:
                        raise
                self.ui.error(e, exc_info()[2])
            except Exception as e:
                self.ui.error(e,
                              exc_info()[2],
                              msg="While attempting to sync account '%s'" %
                              self)
            else:
                # After success sync, reset the looping counter to 3.
                if self.refreshperiod:
                    looping = 3
            finally:
                self.ui.acctdone(self)
                self._unlock()
                if looping and self._sleeper() >= 2:
                    looping = 0

    def get_local_folder(self, remotefolder):
        """Return the corresponding local folder for a given remotefolder."""

        return self.localrepos.getfolder(remotefolder.getvisiblename().replace(
            self.remoterepos.getsep(), self.localrepos.getsep()))

    # The syncrunner will loop on this method. This means it is called more than
    # once during the run.
    def __sync(self):
        """Synchronize the account once, then return.

        Assumes that `self.remoterepos`, `self.localrepos`, and
        `self.statusrepos` has already been populated, so it should only
        be called from the :meth:`syncrunner` function."""

        folderthreads = []

        hook = self.getconf('presynchook', '')
        self.callhook(hook)

        if self.utf_8_support and self.remoterepos.getdecodefoldernames():
            raise OfflineImapError(
                "Configuration mismatch in account " +
                "'%s'. " % self.getname() +
                "\nAccount setting 'utf8foldernames' and repository " +
                "setting 'decodefoldernames'\nmay not be used at the " +
                "same time. This account has not been synchronized.\n" +
                "Please check the configuration and documentation.",
                OfflineImapError.ERROR.REPO)

        quickconfig = self.getconfint('quick', 0)
        if quickconfig < 0:
            quick = True
        elif quickconfig > 0:
            if self.quicknum == 0 or self.quicknum > quickconfig:
                self.quicknum = 1
                quick = False
            else:
                self.quicknum = self.quicknum + 1
                quick = True
        else:
            quick = False

        try:
            startedThread = False
            remoterepos = self.remoterepos
            localrepos = self.localrepos
            statusrepos = self.statusrepos

            # Init repos with list of folders, so we have them (and the
            # folder delimiter etc).
            remoterepos.getfolders()
            localrepos.getfolders()

            remoterepos.sync_folder_structure(localrepos, statusrepos)
            # Replicate the folderstructure between REMOTE to LOCAL.
            if not localrepos.getconfboolean('readonly', False):
                self.ui.syncfolders(remoterepos, localrepos)

            # Iterate through all folders on the remote repo and sync.
            for remotefolder in remoterepos.getfolders():
                # Check for CTRL-C or SIGTERM.
                if Account.abort_NOW_signal.is_set():
                    break

                if not remotefolder.sync_this:
                    self.ui.debug(
                        '', "Not syncing filtered folder '%s'"
                        "[%s]" % (remotefolder.getname(), remoterepos))
                    continue  # Ignore filtered folder.

                # The remote folder names must not have the local sep char in
                # their names since this would cause troubles while converting
                # the name back (from local to remote).
                sep = localrepos.getsep()
                if (sep != os.path.sep and sep != remoterepos.getsep()
                        and sep in remotefolder.getname()):
                    self.ui.warn(
                        '', "Ignoring folder '%s' due to unsupported "
                        "'%s' character serving as local separator." %
                        (remotefolder.getname(), localrepos.getsep()))
                    continue  # Ignore unsupported folder name.

                localfolder = self.get_local_folder(remotefolder)
                if not localfolder.sync_this:
                    self.ui.debug(
                        '', "Not syncing filtered folder '%s'"
                        "[%s]" %
                        (localfolder.getname(), localfolder.repository))
                    continue  # Ignore filtered folder.

                if not globals.options.singlethreading:
                    thread = InstanceLimitedThread(
                        limitNamespace="%s%s" %
                        (FOLDER_NAMESPACE, self.remoterepos.getname()),
                        target=syncfolder,
                        name="Folder %s [acc: %s]" %
                        (remotefolder.getexplainedname(), self),
                        args=(self, remotefolder, quick))
                    thread.start()
                    folderthreads.append(thread)
                else:
                    syncfolder(self, remotefolder, quick)
                startedThread = True
            # Wait for all threads to finish.
            for thr in folderthreads:
                thr.join()
            if startedThread is True:
                mbnames.writeIntermediateFile(
                    self.name)  # Write out mailbox names.
            else:
                msg = "Account {}: no folder to sync (folderfilter issue?)".format(
                    self)
                raise OfflineImapError(msg, OfflineImapError.ERROR.REPO)
            localrepos.forgetfolders()
            remoterepos.forgetfolders()
        except:
            # Error while syncing. Drop all connections that we have, they
            # might be bogus by now (e.g. after suspend).
            localrepos.dropconnections()
            remoterepos.dropconnections()
            raise
        else:
            # Sync went fine. Hold or drop depending on config.
            localrepos.holdordropconnections()
            remoterepos.holdordropconnections()

        hook = self.getconf('postsynchook', '')
        self.callhook(hook)

    def callhook(self, cmd):
        # Check for CTRL-C or SIGTERM and run postsynchook.
        if Account.abort_NOW_signal.is_set():
            return
        if not cmd:
            return
        try:
            self.ui.callhook("Calling hook: " + cmd)
            if self.dryrun:
                return
            p = Popen(cmd,
                      shell=True,
                      stdin=PIPE,
                      stdout=PIPE,
                      stderr=PIPE,
                      close_fds=True)
            r = p.communicate()
            self.ui.callhook("Hook stdout: %s\nHook stderr:%s\n" % r)
            self.ui.callhook("Hook return code: %d" % p.returncode)
        except (KeyboardInterrupt, SystemExit):
            raise
        except Exception as e:
            self.ui.error(e, exc_info()[2], msg="Calling hook")
Ejemplo n.º 17
0
class SyncableAccount(Account):
    """A syncable email account connecting 2 repositories

    Derives from :class:`accounts.Account` but contains the additional
    functions :meth:`syncrunner`, :meth:`sync`, :meth:`syncfolders`,
    used for syncing."""
    def __init__(self, *args, **kwargs):
        Account.__init__(self, *args, **kwargs)
        self._lockfd = None
        self._lockfilepath = os.path.join(self.config.getmetadatadir(),
                                          "%s.lock" % self)

    def lock(self):
        """Lock the account, throwing an exception if it is locked already"""
        self._lockfd = open(self._lockfilepath, 'w')
        try:
            fcntl.lockf(self._lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
        except NameError:
            #fcntl not available (Windows), disable file locking... :(
            pass
        except IOError:
            self._lockfd.close()
            raise OfflineImapError(
                "Could not lock account %s. Is another "
                "instance using this account?" % self,
                OfflineImapError.ERROR.REPO)

    def unlock(self):
        """Unlock the account, deleting the lock file"""
        #If we own the lock file, delete it
        if self._lockfd and not self._lockfd.closed:
            self._lockfd.close()
            try:
                os.unlink(self._lockfilepath)
            except OSError:
                pass  #Failed to delete for some reason.

    def syncrunner(self):
        self.ui.registerthread(self)
        try:
            accountmetadata = self.getaccountmeta()
            if not os.path.exists(accountmetadata):
                os.mkdir(accountmetadata, 0o700)

            self.remoterepos = Repository(self, 'remote')
            self.localrepos = Repository(self, 'local')
            self.statusrepos = Repository(self, 'status')
        except OfflineImapError as e:
            self.ui.error(e, exc_info()[2])
            if e.severity >= OfflineImapError.ERROR.CRITICAL:
                raise
            return

        # Loop account sync if needed (bail out after 3 failures)
        looping = 3
        while looping:
            self.ui.acct(self)
            try:
                self.lock()
                self.sync()
            except (KeyboardInterrupt, SystemExit):
                raise
            except OfflineImapError as e:
                # Stop looping and bubble up Exception if needed.
                if e.severity >= OfflineImapError.ERROR.REPO:
                    if looping:
                        looping -= 1
                    if e.severity >= OfflineImapError.ERROR.CRITICAL:
                        raise
                self.ui.error(e, exc_info()[2])
            except Exception as e:
                self.ui.error(e,
                              exc_info()[2],
                              msg="While attempting to sync"
                              " account '%s'" % self)
            else:
                # after success sync, reset the looping counter to 3
                if self.refreshperiod:
                    looping = 3
            finally:
                self.ui.acctdone(self)
                self.unlock()
                if looping and self.sleeper() >= 2:
                    looping = 0

    def get_local_folder(self, remotefolder):
        """Return the corresponding local folder for a given remotefolder"""
        return self.localrepos.getfolder(remotefolder.getvisiblename().replace(
            self.remoterepos.getsep(), self.localrepos.getsep()))

    def sync(self):
        """Synchronize the account once, then return

        Assumes that `self.remoterepos`, `self.localrepos`, and
        `self.statusrepos` has already been populated, so it should only
        be called from the :meth:`syncrunner` function.
        """
        folderthreads = []

        hook = self.getconf('presynchook', '')
        self.callhook(hook)

        quickconfig = self.getconfint('quick', 0)
        if quickconfig < 0:
            quick = True
        elif quickconfig > 0:
            if self.quicknum == 0 or self.quicknum > quickconfig:
                self.quicknum = 1
                quick = False
            else:
                self.quicknum = self.quicknum + 1
                quick = True
        else:
            quick = False

        try:
            remoterepos = self.remoterepos
            localrepos = self.localrepos
            statusrepos = self.statusrepos

            # init repos with list of folders, so we have them (and the
            # folder delimiter etc)
            remoterepos.getfolders()
            localrepos.getfolders()

            remoterepos.sync_folder_structure(localrepos, statusrepos)
            # replicate the folderstructure between REMOTE to LOCAL
            if not localrepos.getconfboolean('readonly', False):
                self.ui.syncfolders(remoterepos, localrepos)

            # iterate through all folders on the remote repo and sync
            for remotefolder in remoterepos.getfolders():
                # check for CTRL-C or SIGTERM
                if Account.abort_NOW_signal.is_set(): break

                if not remotefolder.sync_this:
                    self.ui.debug(
                        '', "Not syncing filtered folder '%s'"
                        "[%s]" % (remotefolder, remoterepos))
                    continue  # Ignore filtered folder
                localfolder = self.get_local_folder(remotefolder)
                if not localfolder.sync_this:
                    self.ui.debug(
                        '', "Not syncing filtered folder '%s'"
                        "[%s]" % (localfolder, localfolder.repository))
                    continue  # Ignore filtered folder
                if not globals.options.singlethreading:
                    thread = InstanceLimitedThread(\
                        instancename = 'FOLDER_' + self.remoterepos.getname(),
                        target = syncfolder,
                        name = "Folder %s [acc: %s]" % (remotefolder, self),
                        args = (self, remotefolder, quick))
                    thread.start()
                    folderthreads.append(thread)
                else:
                    syncfolder(self, remotefolder, quick)
            # wait for all threads to finish
            for thr in folderthreads:
                thr.join()
            # Write out mailbox names if required and not in dry-run mode
            if not self.dryrun:
                mbnames.write()
            localrepos.forgetfolders()
            remoterepos.forgetfolders()
        except:
            #error while syncing. Drop all connections that we have, they
            #might be bogus by now (e.g. after suspend)
            localrepos.dropconnections()
            remoterepos.dropconnections()
            raise
        else:
            # sync went fine. Hold or drop depending on config
            localrepos.holdordropconnections()
            remoterepos.holdordropconnections()

        hook = self.getconf('postsynchook', '')
        self.callhook(hook)

    def callhook(self, cmd):
        # check for CTRL-C or SIGTERM and run postsynchook
        if Account.abort_NOW_signal.is_set():
            return
        if not cmd:
            return
        try:
            self.ui.callhook("Calling hook: " + cmd)
            if self.dryrun:  # don't if we are in dry-run mode
                return
            p = Popen(cmd,
                      shell=True,
                      stdin=PIPE,
                      stdout=PIPE,
                      stderr=PIPE,
                      close_fds=True)
            r = p.communicate()
            self.ui.callhook("Hook stdout: %s\nHook stderr:%s\n" % r)
            self.ui.callhook("Hook return code: %d" % p.returncode)
        except (KeyboardInterrupt, SystemExit):
            raise
        except Exception as e:
            self.ui.error(e, exc_info()[2], msg="Calling hook")
Ejemplo n.º 18
0
class SyncableAccount(Account):
    """A syncable IMAP account.

    Derives from class:`Account`."""
    def syncrunner(self, siglistener):
        self.ui.registerthread(self.name)
        self.ui.acct(self.name)
        accountmetadata = self.getaccountmeta()
        if not os.path.exists(accountmetadata):
            os.mkdir(accountmetadata, 0700)

        # get all three repositories
        self.remoterepos = Repository(self, 'remote')
        self.localrepos = Repository(self, 'local')
        self.statusrepos = Repository(self, 'status')

        #might need changes here to ensure that one account sync does not crash others...
        if not self.refreshperiod:
            try:
                try:
                    self.sync(siglistener)
                except (KeyboardInterrupt, SystemExit):
                    raise
                except:
                    self.ui.warn("Error occured attempting to sync account " + self.name \
                                 + ": " + traceback.format_exc())
            finally:
                self.ui.acctdone(self.name)

            return

        looping = 1
        while looping:
            try:
                try:
                    self.sync(siglistener)
                except (KeyboardInterrupt, SystemExit):
                    raise
                except:
                    self.ui.warn("Error occured attempting to sync account " + self.name \
                                 + ": " + traceback.format_exc())
            finally:
                looping = self.sleeper(siglistener) != 2
                self.ui.acctdone(self.name)

    def getaccountmeta(self):
        return os.path.join(self.metadatadir, 'Account-' + self.name)

    def sync(self, siglistener):
        # We don't need an account lock because syncitall() goes through
        # each account once, then waits for all to finish.

        hook = self.getconf('presynchook', '')
        self.callhook(hook)

        quickconfig = self.getconfint('quick', 0)
        if quickconfig < 0:
            quick = True
        elif quickconfig > 0:
            if self.quicknum == 0 or self.quicknum > quickconfig:
                self.quicknum = 1
                quick = False
            else:
                self.quicknum = self.quicknum + 1
                quick = True
        else:
            quick = False

        try:
            remoterepos = self.remoterepos
            localrepos = self.localrepos
            statusrepos = self.statusrepos
            self.ui.syncfolders(remoterepos, localrepos)
            remoterepos.syncfoldersto(localrepos, [statusrepos])

            siglistener.addfolders(remoterepos.getfolders(),
                                   bool(self.refreshperiod), quick)

            while True:
                folderthreads = []
                for remotefolder, quick in siglistener.queuedfolders():
                    thread = InstanceLimitedThread(\
                        instancename = 'FOLDER_' + self.remoterepos.getname(),
                        target = syncfolder,
                        name = "Folder sync [%s]" % self.name,
                        args = (self.name, remoterepos, remotefolder, localrepos,
                                statusrepos, quick))
                    thread.setDaemon(1)
                    thread.start()
                    folderthreads.append(thread)
                threadutil.threadsreset(folderthreads)
                if siglistener.clearfolders():
                    break
            mbnames.write()
            localrepos.forgetfolders()
            remoterepos.forgetfolders()
        finally:
            localrepos.holdordropconnections()
            remoterepos.holdordropconnections()

        hook = self.getconf('postsynchook', '')
        self.callhook(hook)

    def callhook(self, cmd):
        if not cmd:
            return
        try:
            self.ui.callhook("Calling hook: " + cmd)
            p = Popen(cmd,
                      shell=True,
                      stdin=PIPE,
                      stdout=PIPE,
                      stderr=PIPE,
                      close_fds=True)
            r = p.communicate()
            self.ui.callhook("Hook stdout: %s\nHook stderr:%s\n" % r)
            self.ui.callhook("Hook return code: %d" % p.returncode)
        except:
            self.ui.warn("Exception occured while calling hook")
Ejemplo n.º 19
0
class SyncableAccount(Account):
    """A syncable IMAP account.

    Derives from class:`Account`."""

    def syncrunner(self, siglistener):
        self.ui.registerthread(self.name)
        self.ui.acct(self.name)
        accountmetadata = self.getaccountmeta()
        if not os.path.exists(accountmetadata):
            os.mkdir(accountmetadata, 0700)            

        # get all three repositories
        self.remoterepos = Repository(self, 'remote')
        self.localrepos  = Repository(self, 'local')
        self.statusrepos = Repository(self, 'status')

        #might need changes here to ensure that one account sync does not crash others...
        if not self.refreshperiod:
            try:
                try:
                    self.sync(siglistener)
                except (KeyboardInterrupt, SystemExit):
                    raise
                except:
                    self.ui.warn("Error occured attempting to sync account " + self.name \
                                 + ": " + traceback.format_exc())
            finally:
                self.ui.acctdone(self.name)

            return


        looping = 1
        while looping:
            try:
                try:
                    self.sync(siglistener)
                except (KeyboardInterrupt, SystemExit):
                    raise
                except:
                    self.ui.warn("Error occured attempting to sync account " + self.name \
                                 + ": " + traceback.format_exc())
            finally:
                looping = self.sleeper(siglistener) != 2
                self.ui.acctdone(self.name)


    def getaccountmeta(self):
        return os.path.join(self.metadatadir, 'Account-' + self.name)

    def sync(self, siglistener):
        # We don't need an account lock because syncitall() goes through
        # each account once, then waits for all to finish.

        hook = self.getconf('presynchook', '')
        self.callhook(hook)

        quickconfig = self.getconfint('quick', 0)
        if quickconfig < 0:
            quick = True
        elif quickconfig > 0:
            if self.quicknum == 0 or self.quicknum > quickconfig:
                self.quicknum = 1
                quick = False
            else:
                self.quicknum = self.quicknum + 1
                quick = True
        else:
            quick = False

        try:
            remoterepos = self.remoterepos
            localrepos = self.localrepos
            statusrepos = self.statusrepos
            self.ui.syncfolders(remoterepos, localrepos)
            remoterepos.syncfoldersto(localrepos, [statusrepos])

            siglistener.addfolders(remoterepos.getfolders(), bool(self.refreshperiod), quick)

            while True:
                folderthreads = []
                for remotefolder, quick in siglistener.queuedfolders():
                    thread = InstanceLimitedThread(\
                        instancename = 'FOLDER_' + self.remoterepos.getname(),
                        target = syncfolder,
                        name = "Folder sync [%s]" % self.name,
                        args = (self.name, remoterepos, remotefolder, localrepos,
                                statusrepos, quick))
                    thread.setDaemon(1)
                    thread.start()
                    folderthreads.append(thread)
                threadutil.threadsreset(folderthreads)
                if siglistener.clearfolders():
                    break
            mbnames.write()
            localrepos.forgetfolders()
            remoterepos.forgetfolders()
        finally:
            localrepos.holdordropconnections()
            remoterepos.holdordropconnections()

        hook = self.getconf('postsynchook', '')
        self.callhook(hook)

    def callhook(self, cmd):
        if not cmd:
            return
        try:
            self.ui.callhook("Calling hook: " + cmd)
            p = Popen(cmd, shell=True,
                      stdin=PIPE, stdout=PIPE, stderr=PIPE,
                      close_fds=True)
            r = p.communicate()
            self.ui.callhook("Hook stdout: %s\nHook stderr:%s\n" % r)
            self.ui.callhook("Hook return code: %d" % p.returncode)
        except:
            self.ui.warn("Exception occured while calling hook")