Helpviewer/History/HistoryManager.py

changeset 0
de9c2efb9d02
child 7
c679fb30c8f3
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the history manager.
8 """
9
10 from PyQt4.QtCore import *
11 from PyQt4.QtGui import *
12 from PyQt4.QtWebKit import QWebHistoryInterface, QWebSettings
13
14 from HistoryModel import HistoryModel
15 from HistoryFilterModel import HistoryFilterModel
16 from HistoryTreeModel import HistoryTreeModel
17
18 from Utilities.AutoSaver import AutoSaver
19 import Utilities
20 import Preferences
21
22 HISTORY_VERSION = 42
23
24 class HistoryEntry(object):
25 """
26 Class implementing a history entry.
27 """
28 def __init__(self, url = None, dateTime = None, title = None):
29 """
30 Constructor
31
32 @param url URL of the history entry (string)
33 @param dateTime date and time this entry was created (QDateTime)
34 @param title title string for the history entry (string)
35 """
36 self.url = url and url or ""
37 self.dateTime = dateTime and dateTime or QDateTime()
38 self.title = title and title or ""
39
40 def __eq__(self, other):
41 """
42 Special method determining equality.
43
44 @param other reference to the history entry to compare against (HistoryEntry)
45 @return flag indicating equality (boolean)
46 """
47 return other.title == self.title and \
48 other.url == self.url and \
49 other.dateTime == self.dateTime
50
51 def __lt__(self, other):
52 """
53 Special method determining less relation.
54
55 Note: History is sorted in reverse order by date and time
56
57 @param other reference to the history entry to compare against (HistoryEntry)
58 @return flag indicating less (boolean)
59 """
60 return self.dateTime > other.dateTime
61
62 def userTitle(self):
63 """
64 Public method to get the title of the history entry.
65
66 @return title of the entry (string)
67 """
68 if not self.title:
69 page = QFileInfo(QUrl(self.url).path()).fileName()
70 if page:
71 return page
72 return self.url
73 return self.title
74
75 class HistoryManager(QWebHistoryInterface):
76 """
77 Class implementing the history manager.
78
79 @signal historyCleared() emitted after the history has been cleared
80 @signal historyReset() emitted after the history has been reset
81 @signal entryAdded emitted after a history entry has been added
82 @signal entryRemoved emitted after a history entry has been removed
83 @signal entryUpdated(int) emitted after a history entry has been updated
84 """
85 def __init__(self, parent = None):
86 """
87 Constructor
88
89 @param parent reference to the parent object (QObject)
90 """
91 QWebHistoryInterface.__init__(self, parent)
92
93 self.__saveTimer = AutoSaver(self, self.save)
94 self.__daysToExpire = Preferences.getHelp("HistoryLimit")
95 self.__history = []
96 self.__lastSavedUrl = ""
97
98 self.__expiredTimer = QTimer()
99 self.__expiredTimer.setSingleShot(True)
100 self.connect(self.__expiredTimer, SIGNAL("timeout()"),
101 self.__checkForExpired)
102
103 self.__frequencyTimer = QTimer()
104 self.__frequencyTimer.setSingleShot(True)
105 self.connect(self.__frequencyTimer, SIGNAL("timeout()"),
106 self.__refreshFrequencies)
107
108 self.connect(self, SIGNAL("entryAdded"),
109 self.__saveTimer.changeOccurred)
110 self.connect(self, SIGNAL("entryRemoved"),
111 self.__saveTimer.changeOccurred)
112
113 self.__load()
114
115 self.__historyModel = HistoryModel(self, self)
116 self.__historyFilterModel = HistoryFilterModel(self.__historyModel, self)
117 self.__historyTreeModel = HistoryTreeModel(self.__historyFilterModel, self)
118
119 QWebHistoryInterface.setDefaultInterface(self)
120 self.__startFrequencyTimer()
121
122 def close(self):
123 """
124 Public method to close the history manager.
125 """
126 # remove history items on application exit
127 if self.__daysToExpire == -2:
128 self.clear()
129 self.__saveTimer.saveIfNeccessary()
130
131 def history(self):
132 """
133 Public method to return the history.
134
135 @return reference to the list of history entries (list of HistoryEntry)
136 """
137 return self.__history
138
139 def setHistory(self, history, loadedAndSorted = False):
140 """
141 Public method to set a new history.
142
143 @param history reference to the list of history entries to be set
144 (list of HistoryEntry)
145 @param loadedAndSorted flag indicating that the list is sorted (boolean)
146 """
147 self.__history = history[:]
148 if not loadedAndSorted:
149 self.__history.sort()
150
151 self.__checkForExpired()
152
153 if loadedAndSorted:
154 try:
155 self.__lastSavedUrl = self.__history[0].url
156 except IndexError:
157 self.__lastSavedUrl = ""
158 else:
159 self.__lastSavedUrl = ""
160 self.__saveTimer.changeOccurred()
161 self.emit(SIGNAL("historyReset()"))
162
163 def historyContains(self, url):
164 """
165 Public method to check the history for an entry.
166
167 @param url URL to check for (string)
168 @return flag indicating success (boolean)
169 """
170 return self.__historyFilterModel.historyContains(url)
171
172 def _addHistoryEntry(self, itm):
173 """
174 Protected method to add a history item.
175
176 @param itm reference to the history item to add (HistoryEntry)
177 """
178 globalSettings = QWebSettings.globalSettings()
179 if globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled):
180 return
181
182 self.__history.insert(0, itm)
183 self.emit(SIGNAL("entryAdded"), itm)
184 if len(self.__history) == 1:
185 self.__checkForExpired()
186
187 def _removeHistoryEntry(self, itm):
188 """
189 Protected method to remove a history item.
190
191 @param itm reference to the history item to remove (HistoryEntry)
192 """
193 self.__lastSavedUrl = ""
194 self.__history.remove(itm)
195 self.emit(SIGNAL("entryRemoved"), itm)
196
197 def addHistoryEntry(self, url):
198 """
199 Public method to add a history entry.
200
201 @param url URL to be added (string)
202 """
203 cleanurl = QUrl(url)
204 cleanurl.setPassword("")
205 if cleanurl.host() is not None:
206 cleanurl.setHost(cleanurl.host().lower())
207 itm = HistoryEntry(cleanurl.toString(), QDateTime.currentDateTime())
208 self._addHistoryEntry(itm)
209
210 def updateHistoryEntry(self, url, title):
211 """
212 Public method to update a history entry.
213
214 @param url URL of the entry to update (string)
215 @param title title of the entry to update (string)
216 """
217 for index in range(len(self.__history)):
218 if url == self.__history[index].url:
219 self.__history[index].title = title
220 self.__saveTimer.changeOccurred()
221 if not self.__lastSavedUrl:
222 self.__lastSavedUrl = self.__history[index].url
223 self.emit(SIGNAL("entryUpdated(int)"), index)
224 break
225
226 def removeHistoryEntry(self, url, title = ""):
227 """
228 Public method to remove a history entry.
229
230 @param url URL of the entry to remove (QUrl)
231 @param title title of the entry to remove (string)
232 """
233 for index in range(len(self.__history)):
234 if url == QUrl(self.__history[index].url) and \
235 (not title or title == self.__history[index].title):
236 self._removeHistoryEntry(self.__history[index])
237 break
238
239 def historyModel(self):
240 """
241 Public method to get a reference to the history model.
242
243 @return reference to the history model (HistoryModel)
244 """
245 return self.__historyModel
246
247 def historyFilterModel(self):
248 """
249 Public method to get a reference to the history filter model.
250
251 @return reference to the history filter model (HistoryFilterModel)
252 """
253 return self.__historyFilterModel
254
255 def historyTreeModel(self):
256 """
257 Public method to get a reference to the history tree model.
258
259 @return reference to the history tree model (HistoryTreeModel)
260 """
261 return self.__historyTreeModel
262
263 def __checkForExpired(self):
264 """
265 Private slot to check entries for expiration.
266 """
267 if self.__daysToExpire < 0 or len(self.__history) == 0:
268 return
269
270 now = QDateTime.currentDateTime()
271 nextTimeout = 0
272
273 while self.__history:
274 checkForExpired = QDateTime(self.__history[-1].dateTime)
275 checkForExpired.setDate(checkForExpired.date().addDays(self.__daysToExpire))
276 if now.daysTo(checkForExpired) > 7:
277 nextTimeout = 7 * 86400
278 else:
279 nextTimeout = now.secsTo(checkForExpired)
280 if nextTimeout > 0:
281 break
282
283 itm = self.__history.pop(-1)
284 self.__lastSavedUrl = ""
285 self.emit(SIGNAL("entryRemoved"), itm)
286
287 if nextTimeout > 0:
288 self.__expiredTimer.start(nextTimeout * 1000)
289
290 def daysToExpire(self):
291 """
292 Public method to get the days for entry expiration.
293
294 @return days for entry expiration (integer)
295 """
296 return self.__daysToExpire
297
298 def setDaysToExpire(self, limit):
299 """
300 Public method to set the days for entry expiration.
301
302 @param limit days for entry expiration (integer)
303 """
304 if self.__daysToExpire == limit:
305 return
306
307 self.__daysToExpire = limit
308 self.__checkForExpired()
309 self.__saveTimer.changeOccurred()
310
311 def preferencesChanged(self):
312 """
313 Public method to indicate a change of preferences.
314 """
315 self.setDaysToExpire(Preferences.getHelp("HistoryLimit"))
316
317 def clear(self):
318 """
319 Public slot to clear the complete history.
320 """
321 self.__history = []
322 self.__lastSavedUrl = ""
323 self.__saveTimer.changeOccurred()
324 self.__saveTimer.saveIfNeccessary()
325 self.emit(SIGNAL("historyReset()"))
326 self.emit(SIGNAL("historyCleared()"))
327
328 def __load(self):
329 """
330 Private method to load the saved history entries from disk.
331 """
332 historyFile = QFile(Utilities.getConfigDir() + "/browser/history")
333 if not historyFile.exists():
334 return
335 if not historyFile.open(QIODevice.ReadOnly):
336 QMessageBox.warning(None,
337 self.trUtf8("Loading History"),
338 self.trUtf8("""Unable to open history file <b>{0}</b>.<br/>"""
339 """Reason: {1}""")\
340 .format(historyFile.fileName, historyFile.errorString()))
341 return
342
343 history = []
344 historyStream = QDataStream(historyFile)
345
346 # double check, that the history file is sorted as it is read
347 needToSort = False
348 lastInsertedItem = HistoryEntry()
349 data = QByteArray()
350 stream = QDataStream()
351 buffer = QBuffer()
352 stream.setDevice(buffer)
353 while not historyFile.atEnd():
354 historyStream >> data
355 buffer.close()
356 buffer.setBuffer(data)
357 buffer.open(QIODevice.ReadOnly)
358 ver = stream.readUInt32()
359 if ver != HISTORY_VERSION:
360 continue
361 itm = HistoryEntry()
362 stream.readString(itm.url)
363 stream.readString(itm.dateTime)
364 stream.readString(itm.title)
365
366 if not itm.dateTime.isValid():
367 continue
368
369 if itm == lastInsertedItem:
370 if not lastInsertedItem.title and len(history) > 0:
371 history[0].title = itm.title
372 continue
373
374 if not needToSort and history and lastInsertedItem < itm:
375 needToSort = True
376
377 history.insert(0, itm)
378 lastInsertedItem = itm
379
380 if needToSort:
381 history.sort()
382
383 self.setHistory(history, True)
384
385 # if the history had to be sorted, rewrite the history sorted
386 if needToSort:
387 self.__lastSavedUrl = ""
388 self.__saveTimer.changeOccurred()
389
390 def save(self):
391 """
392 Public slot to save the history entries to disk.
393 """
394 historyFile = QFile(Utilities.getConfigDir() + "/browser/history")
395 if not historyFile.exists():
396 self.__lastSavedUrl = ""
397
398 saveAll = self.__lastSavedUrl = ""
399 first = len(self.__history) - 1
400 if not saveAll:
401 # find the first one to save
402 for index in range(len(self.__history)):
403 if self.__history[index].url == self.__lastSavedUrl:
404 first = index - 1
405 break
406 if first == len(self.__history) - 1:
407 saveAll = True
408
409 # use a temporary file when saving everything
410 tempFile = QTemporaryFile()
411 if saveAll:
412 opened = tempFile.open()
413 else:
414 opened = historyFile.open(QIODevice.Append)
415
416 if not opened:
417 if saveAll:
418 f = tempFile
419 else:
420 f = historyFile
421 QMessageBox.warning(None,
422 self.trUtf8("Saving History"),
423 self.trUtf8("""Unable to open history file <b>{0}</b>.<br/>"""
424 """Reason: {1}""")\
425 .format(f.fileName, f.errorString()))
426 return
427
428 if saveAll:
429 historyStream = QDataStream(tempFile)
430 else:
431 historyStream = QDataStream(historyFile)
432 for index in range(first, -1, -1):
433 data = QByteArray()
434 stream = QDataStream(data, QIODevice.WriteOnly)
435 itm = self.__history[index]
436 stream.writeUInt32(HISTORY_VERSION)
437 stream.writeString(itm.url)
438 stream << itm.dateTime
439 stream.writeString(itm.title)
440 historyStream << data
441
442 if saveAll:
443 tempFile.close()
444 if historyFile.exists() and not historyFile.remove():
445 QMessageBox.warning(None,
446 self.trUtf8("Saving History"),
447 self.trUtf8("""Error removing old history file <b>{0}</b>."""
448 """<br/>Reason: {1}""")\
449 .format(historyFile.fileName, historyFile.errorString()))
450 if not tempFile.copy(historyFile.fileName()):
451 QMessageBox.warning(None,
452 self.trUtf8("Saving History"),
453 self.trUtf8("""Error moving new history file over old one """
454 """(<b>{0}</b>).<br/>Reason: {1}""")\
455 .format(historyFile.fileName(), tempFile.errorString()))
456 else:
457 historyFile.close()
458
459 try:
460 self.__lastSavedUrl = self.__history[0].url
461 except IndexError:
462 self.__lastSavedUrl = ""
463
464 def __refreshFrequencies(self):
465 """
466 Private slot to recalculate the refresh frequencies.
467 """
468 self.__historyFilterModel.recalculateFrequencies()
469 self.__startFrequencyTimer()
470
471 def __startFrequencyTimer(self):
472 """
473 Private method to start the timer to recalculate the frequencies.
474 """
475 tomorrow = QDateTime(QDate.currentDate().addDays(1), QTime(3, 0))
476 self.__frequencyTimer.start(QDateTime.currentDateTime().secsTo(tomorrow) * 1000)

eric ide

mercurial