Helpviewer/Sync/FtpSyncHandler.py

Sat, 18 Feb 2012 18:08:54 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 18 Feb 2012 18:08:54 +0100
changeset 1638
cd2f9e526710
parent 1626
a77c8ea8582c
child 1680
28e57079dab5
permissions
-rw-r--r--

Added an idle timeout to the FTP synchronization handler to prevent server disconnects.

# -*- coding: utf-8 -*-

# Copyright (c) 2012 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a synchronization handler using FTP.
"""

from PyQt4.QtCore import pyqtSignal, QUrl, QFile, QIODevice, QTime, QThread, QTimer
from PyQt4.QtNetwork import QFtp, QNetworkProxyQuery, QNetworkProxy, QNetworkProxyFactory

from .SyncHandler import SyncHandler

import Helpviewer.HelpWindow

import Preferences


class FtpSyncHandler(SyncHandler):
    """
    Class implementing a synchronization handler using FTP.
    
    @signal syncStatus(type_, done, message) emitted to indicate the synchronization
        status (string one of "bookmarks", "history", "passwords" or "useragents",
        boolean, string)
    @signal syncError(message) emitted for a general error with the error message (string)
    @signal syncFinished(type_, done, download) emitted after a synchronization has
        finished (string one of "bookmarks", "history", "passwords" or "useragents",
        boolean, boolean)
    """
    syncStatus = pyqtSignal(str, bool, str)
    syncError = pyqtSignal(str)
    syncFinished = pyqtSignal(str, bool, bool)
    
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent object (QObject)
        """
        super().__init__(parent)
        
        self.__state = "idle"
        
        self.__remoteFiles = {
            "bookmarks": "Bookmarks",
            "history": "History",
            "passwords": "Logins",
            "useragents": "UserAgentSettings"
        }
        self.__remoteFilesFound = []
        
        self.__messages = {
            "bookmarks": {
                "RemoteExists": self.trUtf8(
                    "Remote bookmarks file exists! Syncing local copy..."),
                "RemoteMissing": self.trUtf8(
                    "Remote bookmarks file does NOT exists. Exporting local copy..."),
                "LocalMissing": self.trUtf8(
                    "Local bookmarks file does NOT exist. Skipping synchronization!"),
            },
            "history": {
                "RemoteExists": self.trUtf8(
                    "Remote history file exists! Syncing local copy..."),
                "RemoteMissing": self.trUtf8(
                    "Remote history file does NOT exists. Exporting local copy..."),
                "LocalMissing": self.trUtf8(
                    "Local history file does NOT exist. Skipping synchronization!"),
            },
            "passwords": {
                "RemoteExists": self.trUtf8(
                    "Remote logins file exists! Syncing local copy..."),
                "RemoteMissing": self.trUtf8(
                    "Remote logins file does NOT exists. Exporting local copy..."),
                "LocalMissing": self.trUtf8(
                    "Local logins file does NOT exist. Skipping synchronization!"),
            },
            "useragents": {
                "RemoteExists": self.trUtf8(
                    "Remote user agent settings file exists! Syncing local copy..."),
                "RemoteMissing": self.trUtf8(
                    "Remote user agent settings file does NOT exists."
                    " Exporting local copy..."),
                "LocalMissing": self.trUtf8(
                    "Local user agent settings file does NOT exist."
                    " Skipping synchronization!"),
            },
        }
    
    def initialLoadAndCheck(self):
        """
        Public method to do the initial check.
        """
        if not Preferences.getHelp("SyncEnabled"):
            return
        
        self.__state = "initializing"
        
        self.__remoteFilesFound = []
        self.__syncIDs = {}
        
        self.__idleTimer = QTimer(self)
        self.__idleTimer.setInterval(Preferences.getHelp("SyncFtpIdleTimeout") * 1000)
        self.__idleTimer.timeout.connect(self.__idleTimeout)
        
        self.__ftp = QFtp(self)
        self.__ftp.commandFinished.connect(self.__commandFinished)
        self.__ftp.listInfo.connect(self.__checkSyncFiles)
        
        # do proxy setup
        url = QUrl("ftp://{0}:{1}".format(
            Preferences.getHelp("SyncFtpServer"),
            Preferences.getHelp("SyncFtpPort")
        ))
        query = QNetworkProxyQuery(url)
        proxyList = QNetworkProxyFactory.proxyForQuery(query)
        ftpProxy = QNetworkProxy()
        for proxy in proxyList:
            if proxy.type() == QNetworkProxy.NoProxy or \
               proxy.type() == QNetworkProxy.FtpCachingProxy:
                ftpProxy = proxy
                break
        if ftpProxy.type() == QNetworkProxy.DefaultProxy:
            self.syncError.emit(self.trUtf8("No suitable proxy found."))
            return
        elif ftpProxy.type() == QNetworkProxy.FtpCachingProxy:
            self.__ftp.setProxy(ftpProxy.hostName(), ftpProxy.port())
        
        self.__ftp.connectToHost(Preferences.getHelp("SyncFtpServer"),
                                 Preferences.getHelp("SyncFtpPort"))
        self.__ftp.login(Preferences.getHelp("SyncFtpUser"),
                         Preferences.getHelp("SyncFtpPassword"))
    
    def __changeToStore(self):
        """
        Private slot to change to the storage directory.
        
        This action might cause the storage path to be created on the server.
        """
        self.__storePathList = \
            Preferences.getHelp("SyncFtpPath").replace("\\", "/").split("/")
        if self.__storePathList[0] == "":
            del self.__storePathList[0]
            self.__ftp.cd(self.__storePathList[0])
    
    def __commandFinished(self, id, error):
        """
        Private slot handling the end of a command.
        
        @param id id of the finished command (integer)
        @param error flag indicating an error situation (boolean)
        """
        if error:
            if self.__ftp.currentCommand() in [
                QFtp.ConnectToHost, QFtp.Login, QFtp.Mkdir, QFtp.List]:
                self.syncError.emit(self.__ftp.errorString())
            elif self.__ftp.currentCommand() == QFtp.Cd:
                self.__ftp.mkdir(self.__storePathList[0])
                self.__ftp.cd(self.__storePathList[0])
            else:
                if id in self.__syncIDs:
                    self.__syncIDs[id][1].close()
                    self.syncStatus.emit(self.__syncIDs[id][0], False,
                        self.__ftp.errorString())
                    self.syncFinished.emit(self.__syncIDs[id][0], False,
                        self.__syncIDs[id][2])
                    del self.__syncIDs[id]
        else:
            if self.__ftp.currentCommand() == QFtp.Login:
                self.__changeToStore()
            elif self.__ftp.currentCommand() == QFtp.Cd:
                del self.__storePathList[0]
                if self.__storePathList:
                    self.__ftp.cd(self.__storePathList[0])
                else:
                    self.__storeReached()
            elif self.__ftp.currentCommand() == QFtp.List:
                self.__initialSync()
            else:
                if id in self.__syncIDs:
                    self.__syncIDs[id][1].close()
                    self.syncFinished.emit(self.__syncIDs[id][0], True,
                        self.__syncIDs[id][2])
                    del self.__syncIDs[id]
    
    def __storeReached(self):
        """
        Private slot executed, when the storage directory was reached.
        """
        if self.__state == "initializing":
            self.__ftp.list()
            self.__idleTimer.start()
    
    def __checkSyncFiles(self, info):
        """
        Private slot called for each entry sent by the FTP list command.
        
        @param info info about the entry (QUrlInfo)
        """
        if info.isValid() and info.isFile():
            if info.name() in self.__remoteFiles.values():
                self.__remoteFilesFound.append(info.name())
    
    def __initialSyncFile(self, type_, fileName):
        """
        Private method to do the initial synchronization of the given file.
        
        @param type_ type of the synchronization event (string one
            of "bookmarks", "history", "passwords" or "useragents")
        @param fileName name of the file to be synchronized (string)
        """
        f = QFile(fileName)
        if self.__remoteFiles[type_] in self.__remoteFilesFound:
            self.syncStatus.emit(type_, True,
                self.__messages[type_]["RemoteExists"])
            f.open(QIODevice.WriteOnly)
            id = self.__ftp.get(self.__remoteFiles[type_], f)
            self.__syncIDs[id] = (type_, f, True)
        else:
            if f.exists():
                self.syncStatus.emit(type_, True,
                    self.__messages[type_]["RemoteMissing"])
                f.open(QIODevice.ReadOnly)
                id = self.__ftp.put(f, self.__remoteFiles[type_])
                self.__syncIDs[id] = (type_, f, False)
            else:
                self.syncStatus.emit(type_, True,
                    self.__messages[type_]["LocalMissing"])
    
    def __initialSync(self):
        """
        Private slot to do the initial synchronization.
        """
        # Bookmarks
        if Preferences.getHelp("SyncBookmarks"):
            self.__initialSyncFile("bookmarks",
                Helpviewer.HelpWindow.HelpWindow.bookmarksManager().getFileName())
        
        # History
        if Preferences.getHelp("SyncHistory"):
            self.__initialSyncFile("history",
                Helpviewer.HelpWindow.HelpWindow.historyManager().getFileName())
        
        # Passwords
        if Preferences.getHelp("SyncPasswords"):
            self.__initialSyncFile("passwords",
                Helpviewer.HelpWindow.HelpWindow.passwordManager().getFileName())
        
        # User Agent Settings
        if Preferences.getHelp("SyncUserAgents"):
            self.__initialSyncFile("useragents",
                Helpviewer.HelpWindow.HelpWindow.userAgentsManager().getFileName())
    
    def __syncFile(self, type_, fileName):
        """
        Private method to synchronize the given file.
        
        @param type_ type of the synchronization event (string one
            of "bookmarks", "history", "passwords" or "useragents")
        @param fileName name of the file to be synchronized (string)
        """
        self.__state = "uploading"
        f = QFile(fileName)
        if f.exists():
            f.open(QIODevice.ReadOnly)
            id = self.__ftp.put(f, self.__remoteFiles[type_])
            self.__syncIDs[id] = (type_, f, False)
    
    def syncBookmarks(self):
        """
        Public method to synchronize the bookmarks.
        """
        self.__syncFile("bookmarks",
            Helpviewer.HelpWindow.HelpWindow.bookmarksManager().getFileName())
    
    def syncHistory(self):
        """
        Public method to synchronize the history.
        """
        self.__syncFile("history",
            Helpviewer.HelpWindow.HelpWindow.historyManager().getFileName())
    
    def syncPasswords(self):
        """
        Public method to synchronize the passwords.
        """
        self.__syncFile("passwords",
            Helpviewer.HelpWindow.HelpWindow.passwordManager().getFileName())
    
    def syncUserAgents(self):
        """
        Public method to synchronize the user agents.
        """
        self.__syncFile("useragents",
            Helpviewer.HelpWindow.HelpWindow.userAgentsManager().getFileName())
    
    def shutdown(self):
        """
        Public method to shut down the handler.
        """
        if self.__idleTimer.isActive():
            self.__idleTimer.stop()
        
        t = QTime.currentTime()
        t.start()
        while t.elapsed() < 5000 and self.__ftp.hasPendingCommands():
            QThread.msleep(200)
        if self.__ftp.hasPendingCommands():
            self.__ftp.clearPendingCommands()
        if self.__ftp.currentCommand() != 0:
            self.__ftp.abort()
    
    def __idleTimeout(self):
        """
        Private slot to prevent a disconnect from the server.
        """
        self.__ftp.rawCommand("NOOP")

eric ide

mercurial