WebBrowser/Sync/FtpSyncHandler.py

branch
QtWebEngine
changeset 4774
2c6ffa778c3b
parent 4631
5c1a96925da4
child 4868
985d275502c8
equal deleted inserted replaced
4773:cad470dfd807 4774:2c6ffa778c3b
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a synchronization handler using FTP.
8 """
9
10 from __future__ import unicode_literals
11
12 import ftplib
13 import io
14
15 from PyQt5.QtCore import pyqtSignal, QTimer, QFileInfo, QCoreApplication, \
16 QByteArray
17
18 from E5Network.E5Ftp import E5Ftp, E5FtpProxyType, E5FtpProxyError
19
20 from .SyncHandler import SyncHandler
21
22 from WebBrowser.WebBrowserWindow import WebBrowserWindow
23
24 import Preferences
25
26 from Utilities.FtpUtilities import FtpDirLineParser, FtpDirLineParserError
27
28
29 class FtpSyncHandler(SyncHandler):
30 """
31 Class implementing a synchronization handler using FTP.
32
33 @signal syncStatus(type_, message) emitted to indicate the synchronization
34 status (string one of "bookmarks", "history", "passwords",
35 "useragents" or "speeddial", string)
36 @signal syncError(message) emitted for a general error with the error
37 message (string)
38 @signal syncMessage(message) emitted to send a message about
39 synchronization (string)
40 @signal syncFinished(type_, done, download) emitted after a
41 synchronization has finished (string one of "bookmarks", "history",
42 "passwords", "useragents" or "speeddial", boolean, boolean)
43 """
44 syncStatus = pyqtSignal(str, str)
45 syncError = pyqtSignal(str)
46 syncMessage = pyqtSignal(str)
47 syncFinished = pyqtSignal(str, bool, bool)
48
49 def __init__(self, parent=None):
50 """
51 Constructor
52
53 @param parent reference to the parent object (QObject)
54 """
55 super(FtpSyncHandler, self).__init__(parent)
56
57 self.__state = "idle"
58 self.__forceUpload = False
59 self.__connected = False
60
61 self.__remoteFilesFound = {}
62
63 def initialLoadAndCheck(self, forceUpload):
64 """
65 Public method to do the initial check.
66
67 @keyparam forceUpload flag indicating a forced upload of the files
68 (boolean)
69 """
70 if not Preferences.getWebBrowser("SyncEnabled"):
71 return
72
73 self.__state = "initializing"
74 self.__forceUpload = forceUpload
75
76 self.__dirLineParser = FtpDirLineParser()
77 self.__remoteFilesFound = {}
78
79 self.__idleTimer = QTimer(self)
80 self.__idleTimer.setInterval(
81 Preferences.getWebBrowser("SyncFtpIdleTimeout") * 1000)
82 self.__idleTimer.timeout.connect(self.__idleTimeout)
83
84 self.__ftp = E5Ftp()
85
86 # do proxy setup
87 if not Preferences.getUI("UseProxy"):
88 proxyType = E5FtpProxyType.NoProxy
89 else:
90 proxyType = Preferences.getUI("ProxyType/Ftp")
91 if proxyType != E5FtpProxyType.NoProxy:
92 self.__ftp.setProxy(
93 proxyType,
94 Preferences.getUI("ProxyHost/Ftp"),
95 Preferences.getUI("ProxyPort/Ftp"))
96 if proxyType != E5FtpProxyType.NonAuthorizing:
97 self.__ftp.setProxyAuthentication(
98 Preferences.getUI("ProxyUser/Ftp"),
99 Preferences.getUI("ProxyPassword/Ftp"),
100 Preferences.getUI("ProxyAccount/Ftp"))
101
102 QTimer.singleShot(0, self.__doFtpCommands)
103
104 def __doFtpCommands(self):
105 """
106 Private slot executing the sequence of FTP commands.
107 """
108 try:
109 ok = self.__connectAndLogin()
110 if ok:
111 self.__changeToStore()
112 self.__ftp.retrlines("LIST", self.__dirListCallback)
113 self.__initialSync()
114 self.__state = "idle"
115 self.__idleTimer.start()
116 except (ftplib.all_errors + (E5FtpProxyError,)) as err:
117 self.syncError.emit(str(err))
118
119 def __connectAndLogin(self):
120 """
121 Private method to connect to the FTP server and log in.
122
123 @return flag indicating a successful log in (boolean)
124 """
125 self.__ftp.connect(
126 Preferences.getWebBrowser("SyncFtpServer"),
127 Preferences.getWebBrowser("SyncFtpPort"),
128 timeout=5)
129 self.__ftp.login(
130 Preferences.getWebBrowser("SyncFtpUser"),
131 Preferences.getWebBrowser("SyncFtpPassword"))
132 self.__connected = True
133 return True
134
135 def __changeToStore(self):
136 """
137 Private slot to change to the storage directory.
138
139 This action will create the storage path on the server, if it
140 does not exist. Upon return, the current directory of the server
141 is the sync directory.
142 """
143 storePathList = \
144 Preferences.getWebBrowser("SyncFtpPath")\
145 .replace("\\", "/").split("/")
146 if storePathList[0] == "":
147 storePathList.pop(0)
148 while storePathList:
149 path = storePathList[0]
150 try:
151 self.__ftp.cwd(path)
152 except ftplib.error_perm as err:
153 code = err.args[0].strip()[:3]
154 if code == "550":
155 # path does not exist, create it
156 self.__ftp.mkd(path)
157 self.__ftp.cwd(path)
158 else:
159 raise
160 storePathList.pop(0)
161
162 def __dirListCallback(self, line):
163 """
164 Private slot handling the receipt of directory listing lines.
165
166 @param line the received line of the directory listing (string)
167 """
168 try:
169 urlInfo = self.__dirLineParser.parseLine(line)
170 except FtpDirLineParserError:
171 # silently ignore parser errors
172 urlInfo = None
173
174 if urlInfo and urlInfo.isValid() and urlInfo.isFile():
175 if urlInfo.name() in self._remoteFiles.values():
176 self.__remoteFilesFound[urlInfo.name()] = \
177 urlInfo.lastModified()
178
179 QCoreApplication.processEvents()
180
181 def __downloadFile(self, type_, fileName, timestamp):
182 """
183 Private method to downlaod the given file.
184
185 @param type_ type of the synchronization event (string one
186 of "bookmarks", "history", "passwords", "useragents" or
187 "speeddial")
188 @param fileName name of the file to be downloaded (string)
189 @param timestamp time stamp in seconds of the file to be downloaded
190 (integer)
191 """
192 self.syncStatus.emit(type_, self._messages[type_]["RemoteExists"])
193 buffer = io.BytesIO()
194 try:
195 self.__ftp.retrbinary(
196 "RETR {0}".format(self._remoteFiles[type_]),
197 lambda x: self.__downloadFileCallback(buffer, x))
198 ok, error = self.writeFile(
199 QByteArray(buffer.getvalue()), fileName, type_, timestamp)
200 if not ok:
201 self.syncStatus.emit(type_, error)
202 self.syncFinished.emit(type_, ok, True)
203 except ftplib.all_errors as err:
204 self.syncStatus.emit(type_, str(err))
205 self.syncFinished.emit(type_, False, True)
206
207 def __downloadFileCallback(self, buffer, data):
208 """
209 Private method receiving the downloaded data.
210
211 @param buffer reference to the buffer (io.BytesIO)
212 @param data byte string to store in the buffer (bytes)
213 @return number of bytes written to the buffer (integer)
214 """
215 res = buffer.write(data)
216 QCoreApplication.processEvents()
217 return res
218
219 def __uploadFile(self, type_, fileName):
220 """
221 Private method to upload the given file.
222
223 @param type_ type of the synchronization event (string one
224 of "bookmarks", "history", "passwords", "useragents" or
225 "speeddial")
226 @param fileName name of the file to be uploaded (string)
227 @return flag indicating success (boolean)
228 """
229 res = False
230 data = self.readFile(fileName, type_)
231 if data.isEmpty():
232 self.syncStatus.emit(type_, self._messages[type_]["LocalMissing"])
233 self.syncFinished.emit(type_, False, False)
234 else:
235 buffer = io.BytesIO(data.data())
236 try:
237 self.__ftp.storbinary(
238 "STOR {0}".format(self._remoteFiles[type_]),
239 buffer,
240 callback=lambda x: QCoreApplication.processEvents())
241 self.syncFinished.emit(type_, True, False)
242 res = True
243 except ftplib.all_errors as err:
244 self.syncStatus.emit(type_, str(err))
245 self.syncFinished.emit(type_, False, False)
246 return res
247
248 def __initialSyncFile(self, type_, fileName):
249 """
250 Private method to do the initial synchronization of the given file.
251
252 @param type_ type of the synchronization event (string one
253 of "bookmarks", "history", "passwords", "useragents" or
254 "speeddial")
255 @param fileName name of the file to be synchronized (string)
256 """
257 if not self.__forceUpload and \
258 self._remoteFiles[type_] in self.__remoteFilesFound:
259 if QFileInfo(fileName).lastModified() < \
260 self.__remoteFilesFound[self._remoteFiles[type_]]:
261 self.__downloadFile(
262 type_, fileName,
263 self.__remoteFilesFound[self._remoteFiles[type_]]
264 .toTime_t())
265 else:
266 self.syncStatus.emit(
267 type_, self.tr("No synchronization required."))
268 self.syncFinished.emit(type_, True, True)
269 else:
270 if self._remoteFiles[type_] not in self.__remoteFilesFound:
271 self.syncStatus.emit(
272 type_, self._messages[type_]["RemoteMissing"])
273 else:
274 self.syncStatus.emit(
275 type_, self._messages[type_]["LocalNewer"])
276 self.__uploadFile(type_, fileName)
277
278 def __initialSync(self):
279 """
280 Private slot to do the initial synchronization.
281 """
282 # Bookmarks
283 if Preferences.getWebBrowser("SyncBookmarks"):
284 self.__initialSyncFile(
285 "bookmarks",
286 WebBrowserWindow.bookmarksManager().getFileName())
287
288 # History
289 if Preferences.getWebBrowser("SyncHistory"):
290 self.__initialSyncFile(
291 "history",
292 WebBrowserWindow.historyManager().getFileName())
293
294 # Passwords
295 if Preferences.getWebBrowser("SyncPasswords"):
296 self.__initialSyncFile(
297 "passwords",
298 WebBrowserWindow.passwordManager().getFileName())
299
300 # TODO: UserAgents
301 # User Agent Settings
302 ## if Preferences.getWebBrowser("SyncUserAgents"):
303 ## self.__initialSyncFile(
304 ## "useragents",
305 ## WebBrowserWindow.userAgentsManager().getFileName())
306
307 # TODO: SpeedDial
308 # Speed Dial Settings
309 ## if Preferences.getWebBrowser("SyncSpeedDial"):
310 ## self.__initialSyncFile(
311 ## "speeddial",
312 ## WebBrowserWindow.speedDial().getFileName())
313
314 self.__forceUpload = False
315
316 def __syncFile(self, type_, fileName):
317 """
318 Private method to synchronize the given file.
319
320 @param type_ type of the synchronization event (string one
321 of "bookmarks", "history", "passwords", "useragents" or
322 "speeddial")
323 @param fileName name of the file to be synchronized (string)
324 """
325 if self.__state == "initializing":
326 return
327
328 # use idle timeout to check, if we are still connected
329 if self.__connected:
330 self.__idleTimeout()
331 if not self.__connected or self.__ftp.sock is None:
332 ok = self.__connectAndLogin()
333 if not ok:
334 self.syncStatus.emit(
335 type_, self.tr("Cannot log in to FTP host."))
336 return
337
338 # upload the changed file
339 self.__state = "uploading"
340 self.syncStatus.emit(type_, self._messages[type_]["Uploading"])
341 if self.__uploadFile(type_, fileName):
342 self.syncStatus.emit(
343 type_, self.tr("Synchronization finished."))
344 self.__state = "idle"
345
346 def syncBookmarks(self):
347 """
348 Public method to synchronize the bookmarks.
349 """
350 self.__syncFile(
351 "bookmarks",
352 WebBrowserWindow.bookmarksManager().getFileName())
353
354 def syncHistory(self):
355 """
356 Public method to synchronize the history.
357 """
358 self.__syncFile(
359 "history",
360 WebBrowserWindow.historyManager().getFileName())
361
362 def syncPasswords(self):
363 """
364 Public method to synchronize the passwords.
365 """
366 self.__syncFile(
367 "passwords",
368 WebBrowserWindow.passwordManager().getFileName())
369
370 # TODO: UserAgents
371 def syncUserAgents(self):
372 """
373 Public method to synchronize the user agents.
374 """
375 ## self.__syncFile(
376 ## "useragents",
377 ## WebBrowserWindow.userAgentsManager().getFileName())
378
379 # TODO: SpeedDial
380 def syncSpeedDial(self):
381 """
382 Public method to synchronize the speed dial data.
383 """
384 ## self.__syncFile(
385 ## "speeddial",
386 ## WebBrowserWindow.speedDial().getFileName())
387
388 def shutdown(self):
389 """
390 Public method to shut down the handler.
391 """
392 if self.__idleTimer.isActive():
393 self.__idleTimer.stop()
394
395 try:
396 if self.__connected:
397 self.__ftp.quit()
398 except ftplib.all_errors:
399 pass # ignore FTP errors because we are shutting down anyway
400 self.__connected = False
401
402 def __idleTimeout(self):
403 """
404 Private slot to prevent a disconnect from the server.
405 """
406 if self.__state == "idle" and self.__connected:
407 try:
408 self.__ftp.voidcmd("NOOP")
409 except ftplib.Error as err:
410 code = err.args[0].strip()[:3]
411 if code == "421":
412 self.__connected = False
413 except IOError:
414 self.__connected = False

eric ide

mercurial