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