|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module containing a base class for synchronization handlers. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 |
|
12 import os |
|
13 |
|
14 from PyQt5.QtCore import QObject, pyqtSignal, QByteArray |
|
15 |
|
16 import Preferences |
|
17 |
|
18 from Utilities.crypto import dataEncrypt, dataDecrypt |
|
19 |
|
20 |
|
21 class SyncHandler(QObject): |
|
22 """ |
|
23 Base class for synchronization handlers. |
|
24 |
|
25 @signal syncStatus(type_, message) emitted to indicate the synchronization |
|
26 status (string one of "bookmarks", "history", "passwords", |
|
27 "useragents" or "speeddial", string) |
|
28 @signal syncError(message) emitted for a general error with the error |
|
29 message (string) |
|
30 @signal syncMessage(message) emitted to send a message about |
|
31 synchronization (string) |
|
32 @signal syncFinished(type_, done, download) emitted after a |
|
33 synchronization has finished (string one of "bookmarks", "history", |
|
34 "passwords", "useragents" or "speeddial", boolean, boolean) |
|
35 """ |
|
36 syncStatus = pyqtSignal(str, str) |
|
37 syncError = pyqtSignal(str) |
|
38 syncMessage = pyqtSignal(str) |
|
39 syncFinished = pyqtSignal(str, bool, bool) |
|
40 |
|
41 def __init__(self, parent=None): |
|
42 """ |
|
43 Constructor |
|
44 |
|
45 @param parent reference to the parent object (QObject) |
|
46 """ |
|
47 super(SyncHandler, self).__init__(parent) |
|
48 |
|
49 self._firstTimeSynced = False |
|
50 |
|
51 self._remoteFiles = { |
|
52 "bookmarks": "Bookmarks", |
|
53 "history": "History", |
|
54 "passwords": "Logins", |
|
55 "useragents": "UserAgentSettings", |
|
56 "speeddial": "SpeedDial", |
|
57 } |
|
58 |
|
59 self._messages = { |
|
60 "bookmarks": { |
|
61 "RemoteExists": self.tr( |
|
62 "Remote bookmarks file exists! Syncing local copy..."), |
|
63 "RemoteMissing": self.tr( |
|
64 "Remote bookmarks file does NOT exists. Exporting" |
|
65 " local copy..."), |
|
66 "LocalNewer": self.tr( |
|
67 "Local bookmarks file is NEWER. Exporting local copy..."), |
|
68 "LocalMissing": self.tr( |
|
69 "Local bookmarks file does NOT exist. Skipping" |
|
70 " synchronization!"), |
|
71 "Uploading": self.tr("Uploading local bookmarks file..."), |
|
72 }, |
|
73 "history": { |
|
74 "RemoteExists": self.tr( |
|
75 "Remote history file exists! Syncing local copy..."), |
|
76 "RemoteMissing": self.tr( |
|
77 "Remote history file does NOT exists. Exporting" |
|
78 " local copy..."), |
|
79 "LocalNewer": self.tr( |
|
80 "Local history file is NEWER. Exporting local copy..."), |
|
81 "LocalMissing": self.tr( |
|
82 "Local history file does NOT exist. Skipping" |
|
83 " synchronization!"), |
|
84 "Uploading": self.tr("Uploading local history file..."), |
|
85 }, |
|
86 "passwords": { |
|
87 "RemoteExists": self.tr( |
|
88 "Remote logins file exists! Syncing local copy..."), |
|
89 "RemoteMissing": self.tr( |
|
90 "Remote logins file does NOT exists. Exporting" |
|
91 " local copy..."), |
|
92 "LocalNewer": self.tr( |
|
93 "Local logins file is NEWER. Exporting local copy..."), |
|
94 "LocalMissing": self.tr( |
|
95 "Local logins file does NOT exist. Skipping" |
|
96 " synchronization!"), |
|
97 "Uploading": self.tr("Uploading local logins file..."), |
|
98 }, |
|
99 "useragents": { |
|
100 "RemoteExists": self.tr( |
|
101 "Remote user agent settings file exists! Syncing local" |
|
102 " copy..."), |
|
103 "RemoteMissing": self.tr( |
|
104 "Remote user agent settings file does NOT exists." |
|
105 " Exporting local copy..."), |
|
106 "LocalNewer": self.tr( |
|
107 "Local user agent settings file is NEWER. Exporting" |
|
108 " local copy..."), |
|
109 "LocalMissing": self.tr( |
|
110 "Local user agent settings file does NOT exist." |
|
111 " Skipping synchronization!"), |
|
112 "Uploading": self.tr( |
|
113 "Uploading local user agent settings file..."), |
|
114 }, |
|
115 "speeddial": { |
|
116 "RemoteExists": self.tr( |
|
117 "Remote speed dial settings file exists! Syncing local" |
|
118 " copy..."), |
|
119 "RemoteMissing": self.tr( |
|
120 "Remote speed dial settings file does NOT exists." |
|
121 " Exporting local copy..."), |
|
122 "LocalNewer": self.tr( |
|
123 "Local speed dial settings file is NEWER. Exporting" |
|
124 " local copy..."), |
|
125 "LocalMissing": self.tr( |
|
126 "Local speed dial settings file does NOT exist." |
|
127 " Skipping synchronization!"), |
|
128 "Uploading": self.tr( |
|
129 "Uploading local speed dial settings file..."), |
|
130 }, |
|
131 } |
|
132 |
|
133 def syncBookmarks(self): |
|
134 """ |
|
135 Public method to synchronize the bookmarks. |
|
136 |
|
137 @exception NotImplementedError raised to indicate that this method |
|
138 must be implemented by subclasses |
|
139 """ |
|
140 raise NotImplementedError |
|
141 |
|
142 def syncHistory(self): |
|
143 """ |
|
144 Public method to synchronize the history. |
|
145 |
|
146 @exception NotImplementedError raised to indicate that this method |
|
147 must be implemented by subclasses |
|
148 """ |
|
149 raise NotImplementedError |
|
150 |
|
151 def syncPasswords(self): |
|
152 """ |
|
153 Public method to synchronize the passwords. |
|
154 |
|
155 @exception NotImplementedError raised to indicate that this method |
|
156 must be implemented by subclasses |
|
157 """ |
|
158 raise NotImplementedError |
|
159 |
|
160 def syncUserAgents(self): |
|
161 """ |
|
162 Public method to synchronize the user agents. |
|
163 |
|
164 @exception NotImplementedError raised to indicate that this method |
|
165 must be implemented by subclasses |
|
166 """ |
|
167 raise NotImplementedError |
|
168 |
|
169 def syncSpeedDial(self): |
|
170 """ |
|
171 Public method to synchronize the speed dial data. |
|
172 |
|
173 @exception NotImplementedError raised to indicate that this method |
|
174 must be implemented by subclasses |
|
175 """ |
|
176 raise NotImplementedError |
|
177 |
|
178 def initialLoadAndCheck(self, forceUpload): |
|
179 """ |
|
180 Public method to do the initial check. |
|
181 |
|
182 @keyparam forceUpload flag indicating a forced upload of the files |
|
183 (boolean) |
|
184 @exception NotImplementedError raised to indicate that this method |
|
185 must be implemented by subclasses |
|
186 """ |
|
187 raise NotImplementedError |
|
188 |
|
189 def shutdown(self): |
|
190 """ |
|
191 Public method to shut down the handler. |
|
192 |
|
193 @exception NotImplementedError raised to indicate that this method |
|
194 must be implemented by subclasses |
|
195 """ |
|
196 raise NotImplementedError |
|
197 |
|
198 def readFile(self, fileName, type_): |
|
199 """ |
|
200 Public method to read a file. |
|
201 |
|
202 If encrypted synchronization is enabled, the data will be encrypted |
|
203 using the relevant encryption key. |
|
204 |
|
205 @param fileName name of the file to be read (string) |
|
206 @param type_ type of the synchronization event (string one |
|
207 of "bookmarks", "history", "passwords", "useragents" or |
|
208 "speeddial") |
|
209 @return data of the file, optionally encrypted (QByteArray) |
|
210 """ |
|
211 if os.path.exists(fileName): |
|
212 try: |
|
213 inputFile = open(fileName, "rb") |
|
214 data = inputFile.read() |
|
215 inputFile.close() |
|
216 except IOError: |
|
217 return QByteArray() |
|
218 |
|
219 if Preferences.getWebBrowser("SyncEncryptData") and \ |
|
220 (not Preferences.getWebBrowser("SyncEncryptPasswordsOnly") or |
|
221 (Preferences.getWebBrowser("SyncEncryptPasswordsOnly") and |
|
222 type_ == "passwords")): |
|
223 key = Preferences.getWebBrowser("SyncEncryptionKey") |
|
224 if not key: |
|
225 return QByteArray() |
|
226 |
|
227 data, ok = dataEncrypt( |
|
228 data, key, |
|
229 keyLength=Preferences.getWebBrowser( |
|
230 "SyncEncryptionKeyLength"), |
|
231 hashIterations=100) |
|
232 if not ok: |
|
233 return QByteArray() |
|
234 |
|
235 return QByteArray(data) |
|
236 |
|
237 return QByteArray() |
|
238 |
|
239 def writeFile(self, data, fileName, type_, timestamp=0): |
|
240 """ |
|
241 Public method to write the data to a file. |
|
242 |
|
243 If encrypted synchronization is enabled, the data will be decrypted |
|
244 using the relevant encryption key. |
|
245 |
|
246 @param data data to be written and optionally decrypted (QByteArray) |
|
247 @param fileName name of the file the data is to be written to (string) |
|
248 @param type_ type of the synchronization event (string one |
|
249 of "bookmarks", "history", "passwords", "useragents" or |
|
250 "speeddial") |
|
251 @param timestamp timestamp to be given to the file (int) |
|
252 @return tuple giving a success flag and an error string (boolean, |
|
253 string) |
|
254 """ |
|
255 data = bytes(data) |
|
256 |
|
257 if Preferences.getWebBrowser("SyncEncryptData") and \ |
|
258 (not Preferences.getWebBrowser("SyncEncryptPasswordsOnly") or |
|
259 (Preferences.getWebBrowser("SyncEncryptPasswordsOnly") and |
|
260 type_ == "passwords")): |
|
261 key = Preferences.getWebBrowser("SyncEncryptionKey") |
|
262 if not key: |
|
263 return False, self.tr("Invalid encryption key given.") |
|
264 |
|
265 data, ok = dataDecrypt( |
|
266 data, key, |
|
267 keyLength=Preferences.getWebBrowser("SyncEncryptionKeyLength")) |
|
268 if not ok: |
|
269 return False, self.tr("Data cannot be decrypted.") |
|
270 |
|
271 try: |
|
272 outputFile = open(fileName, "wb") |
|
273 outputFile.write(data) |
|
274 outputFile.close() |
|
275 if timestamp > 0: |
|
276 os.utime(fileName, (timestamp, timestamp)) |
|
277 return True, "" |
|
278 except IOError as error: |
|
279 return False, str(error) |