Helpviewer/History/HistoryManager.py

changeset 0
de9c2efb9d02
child 7
c679fb30c8f3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Helpviewer/History/HistoryManager.py	Mon Dec 28 16:03:33 2009 +0000
@@ -0,0 +1,476 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2009 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the history manager.
+"""
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+from PyQt4.QtWebKit import QWebHistoryInterface, QWebSettings
+
+from HistoryModel import HistoryModel
+from HistoryFilterModel import HistoryFilterModel
+from HistoryTreeModel import HistoryTreeModel
+
+from Utilities.AutoSaver import AutoSaver
+import Utilities
+import Preferences
+
+HISTORY_VERSION = 42
+
+class HistoryEntry(object):
+    """
+    Class implementing a history entry.
+    """
+    def __init__(self, url = None, dateTime = None, title = None):
+        """
+        Constructor
+        
+        @param url URL of the history entry (string)
+        @param dateTime date and time this entry was created (QDateTime)
+        @param title title string for the history entry (string)
+        """
+        self.url = url and url or ""
+        self.dateTime = dateTime and dateTime or QDateTime()
+        self.title = title and title or ""
+    
+    def __eq__(self, other):
+        """
+        Special method determining equality.
+        
+        @param other reference to the history entry to compare against (HistoryEntry)
+        @return flag indicating equality (boolean)
+        """
+        return other.title == self.title and \
+            other.url == self.url and \
+            other.dateTime == self.dateTime
+    
+    def __lt__(self, other):
+        """
+        Special method determining less relation.
+        
+        Note: History is sorted in reverse order by date and time
+        
+        @param other reference to the history entry to compare against (HistoryEntry)
+        @return flag indicating less (boolean)
+        """
+        return self.dateTime > other.dateTime
+    
+    def userTitle(self):
+        """
+        Public method to get the title of the history entry.
+        
+        @return title of the entry (string)
+        """
+        if not self.title:
+            page = QFileInfo(QUrl(self.url).path()).fileName()
+            if page:
+                return page
+            return self.url
+        return self.title
+
+class HistoryManager(QWebHistoryInterface):
+    """
+    Class implementing the history manager.
+    
+    @signal historyCleared() emitted after the history has been cleared
+    @signal historyReset() emitted after the history has been reset
+    @signal entryAdded emitted after a history entry has been added
+    @signal entryRemoved emitted after a history entry has been removed
+    @signal entryUpdated(int) emitted after a history entry has been updated
+    """
+    def __init__(self, parent = None):
+        """
+        Constructor
+        
+        @param parent reference to the parent object (QObject)
+        """
+        QWebHistoryInterface.__init__(self, parent)
+        
+        self.__saveTimer = AutoSaver(self, self.save)
+        self.__daysToExpire = Preferences.getHelp("HistoryLimit")
+        self.__history = []
+        self.__lastSavedUrl = ""
+        
+        self.__expiredTimer = QTimer()
+        self.__expiredTimer.setSingleShot(True)
+        self.connect(self.__expiredTimer, SIGNAL("timeout()"), 
+                     self.__checkForExpired)
+        
+        self.__frequencyTimer = QTimer()
+        self.__frequencyTimer.setSingleShot(True)
+        self.connect(self.__frequencyTimer, SIGNAL("timeout()"), 
+                     self.__refreshFrequencies)
+        
+        self.connect(self, SIGNAL("entryAdded"), 
+                     self.__saveTimer.changeOccurred)
+        self.connect(self, SIGNAL("entryRemoved"), 
+                     self.__saveTimer.changeOccurred)
+        
+        self.__load()
+        
+        self.__historyModel = HistoryModel(self, self)
+        self.__historyFilterModel = HistoryFilterModel(self.__historyModel, self)
+        self.__historyTreeModel = HistoryTreeModel(self.__historyFilterModel, self)
+        
+        QWebHistoryInterface.setDefaultInterface(self)
+        self.__startFrequencyTimer()
+    
+    def close(self):
+        """
+        Public method to close the history manager.
+        """
+        # remove history items on application exit
+        if self.__daysToExpire == -2:
+            self.clear()
+        self.__saveTimer.saveIfNeccessary()
+    
+    def history(self):
+        """
+        Public method to return the history.
+        
+        @return reference to the list of history entries (list of HistoryEntry)
+        """
+        return self.__history
+    
+    def setHistory(self, history, loadedAndSorted = False):
+        """
+        Public method to set a new history.
+        
+        @param history reference to the list of history entries to be set
+            (list of HistoryEntry)
+        @param loadedAndSorted flag indicating that the list is sorted (boolean)
+        """
+        self.__history = history[:]
+        if not loadedAndSorted:
+            self.__history.sort()
+        
+        self.__checkForExpired()
+        
+        if loadedAndSorted:
+            try:
+                self.__lastSavedUrl = self.__history[0].url
+            except IndexError:
+                self.__lastSavedUrl = ""
+        else:
+            self.__lastSavedUrl = ""
+            self.__saveTimer.changeOccurred()
+        self.emit(SIGNAL("historyReset()"))
+    
+    def historyContains(self, url):
+        """
+        Public method to check the history for an entry.
+        
+        @param url URL to check for (string)
+        @return flag indicating success (boolean)
+        """
+        return self.__historyFilterModel.historyContains(url)
+    
+    def _addHistoryEntry(self, itm):
+        """
+        Protected method to add a history item.
+        
+        @param itm reference to the history item to add (HistoryEntry)
+        """
+        globalSettings = QWebSettings.globalSettings()
+        if globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled):
+            return
+        
+        self.__history.insert(0, itm)
+        self.emit(SIGNAL("entryAdded"), itm)
+        if len(self.__history) == 1:
+            self.__checkForExpired()
+    
+    def _removeHistoryEntry(self, itm):
+        """
+        Protected method to remove a history item.
+        
+        @param itm reference to the history item to remove (HistoryEntry)
+        """
+        self.__lastSavedUrl = ""
+        self.__history.remove(itm)
+        self.emit(SIGNAL("entryRemoved"), itm)
+    
+    def addHistoryEntry(self, url):
+        """
+        Public method to add a history entry.
+        
+        @param url URL to be added (string)
+        """
+        cleanurl = QUrl(url)
+        cleanurl.setPassword("")
+        if cleanurl.host() is not None:
+            cleanurl.setHost(cleanurl.host().lower())
+        itm = HistoryEntry(cleanurl.toString(), QDateTime.currentDateTime())
+        self._addHistoryEntry(itm)
+    
+    def updateHistoryEntry(self, url, title):
+        """
+        Public method to update a history entry.
+        
+        @param url URL of the entry to update (string)
+        @param title title of the entry to update (string)
+        """
+        for index in range(len(self.__history)):
+            if url == self.__history[index].url:
+                self.__history[index].title = title
+                self.__saveTimer.changeOccurred()
+                if not self.__lastSavedUrl:
+                    self.__lastSavedUrl = self.__history[index].url
+                self.emit(SIGNAL("entryUpdated(int)"), index)
+                break
+    
+    def removeHistoryEntry(self, url, title = ""):
+        """
+        Public method to remove a history entry.
+        
+        @param url URL of the entry to remove (QUrl)
+        @param title title of the entry to remove (string)
+        """
+        for index in range(len(self.__history)):
+            if url == QUrl(self.__history[index].url) and \
+               (not title or title == self.__history[index].title):
+                self._removeHistoryEntry(self.__history[index])
+                break
+    
+    def historyModel(self):
+        """
+        Public method to get a reference to the history model.
+        
+        @return reference to the history model (HistoryModel)
+        """
+        return self.__historyModel
+    
+    def historyFilterModel(self):
+        """
+        Public method to get a reference to the history filter model.
+        
+        @return reference to the history filter model (HistoryFilterModel)
+        """
+        return self.__historyFilterModel
+    
+    def historyTreeModel(self):
+        """
+        Public method to get a reference to the history tree model.
+        
+        @return reference to the history tree model (HistoryTreeModel)
+        """
+        return self.__historyTreeModel
+    
+    def __checkForExpired(self):
+        """
+        Private slot to check entries for expiration.
+        """
+        if self.__daysToExpire < 0 or len(self.__history) == 0:
+            return
+        
+        now = QDateTime.currentDateTime()
+        nextTimeout = 0
+        
+        while self.__history:
+            checkForExpired = QDateTime(self.__history[-1].dateTime)
+            checkForExpired.setDate(checkForExpired.date().addDays(self.__daysToExpire))
+            if now.daysTo(checkForExpired) > 7:
+                nextTimeout = 7 * 86400
+            else:
+                nextTimeout = now.secsTo(checkForExpired)
+            if nextTimeout > 0:
+                break
+            
+            itm = self.__history.pop(-1)
+            self.__lastSavedUrl = ""
+            self.emit(SIGNAL("entryRemoved"), itm)
+        
+        if nextTimeout > 0:
+            self.__expiredTimer.start(nextTimeout * 1000)
+    
+    def daysToExpire(self):
+        """
+        Public method to get the days for entry expiration.
+        
+        @return days for entry expiration (integer)
+        """
+        return self.__daysToExpire
+    
+    def setDaysToExpire(self, limit):
+        """
+        Public method to set the days for entry expiration.
+        
+        @param limit days for entry expiration (integer)
+        """
+        if self.__daysToExpire == limit:
+            return
+        
+        self.__daysToExpire = limit
+        self.__checkForExpired()
+        self.__saveTimer.changeOccurred()
+    
+    def preferencesChanged(self):
+        """
+        Public method to indicate a change of preferences.
+        """
+        self.setDaysToExpire(Preferences.getHelp("HistoryLimit"))
+    
+    def clear(self):
+        """
+        Public slot to clear the complete history.
+        """
+        self.__history = []
+        self.__lastSavedUrl = ""
+        self.__saveTimer.changeOccurred()
+        self.__saveTimer.saveIfNeccessary()
+        self.emit(SIGNAL("historyReset()"))
+        self.emit(SIGNAL("historyCleared()"))
+    
+    def __load(self):
+        """
+        Private method to load the saved history entries from disk.
+        """
+        historyFile = QFile(Utilities.getConfigDir() + "/browser/history")
+        if not historyFile.exists():
+            return
+        if not historyFile.open(QIODevice.ReadOnly):
+            QMessageBox.warning(None,
+                self.trUtf8("Loading History"),
+                self.trUtf8("""Unable to open history file <b>{0}</b>.<br/>"""
+                            """Reason: {1}""")\
+                    .format(historyFile.fileName, historyFile.errorString()))
+            return
+        
+        history = []
+        historyStream = QDataStream(historyFile)
+        
+        # double check, that the history file is sorted as it is read
+        needToSort = False
+        lastInsertedItem = HistoryEntry()
+        data = QByteArray()
+        stream = QDataStream()
+        buffer = QBuffer()
+        stream.setDevice(buffer)
+        while not historyFile.atEnd():
+            historyStream >> data
+            buffer.close()
+            buffer.setBuffer(data)
+            buffer.open(QIODevice.ReadOnly)
+            ver = stream.readUInt32()
+            if ver != HISTORY_VERSION:
+                continue
+            itm = HistoryEntry()
+            stream.readString(itm.url)
+            stream.readString(itm.dateTime)
+            stream.readString(itm.title)
+            
+            if not itm.dateTime.isValid():
+                continue
+            
+            if itm == lastInsertedItem:
+                if not lastInsertedItem.title and len(history) > 0:
+                    history[0].title = itm.title
+                continue
+            
+            if not needToSort and history and lastInsertedItem < itm:
+                needToSort = True
+            
+            history.insert(0, itm)
+            lastInsertedItem = itm
+        
+        if needToSort:
+            history.sort()
+        
+        self.setHistory(history, True)
+        
+        # if the history had to be sorted, rewrite the history sorted
+        if needToSort:
+            self.__lastSavedUrl = ""
+            self.__saveTimer.changeOccurred()
+    
+    def save(self):
+        """
+        Public slot to save the history entries to disk.
+        """
+        historyFile = QFile(Utilities.getConfigDir() + "/browser/history")
+        if not historyFile.exists():
+            self.__lastSavedUrl = ""
+        
+        saveAll = self.__lastSavedUrl = ""
+        first = len(self.__history) - 1
+        if not saveAll:
+            # find the first one to save
+            for index in range(len(self.__history)):
+                if self.__history[index].url == self.__lastSavedUrl:
+                    first = index - 1
+                    break
+        if first == len(self.__history) - 1:
+            saveAll = True
+        
+        # use a temporary file when saving everything
+        tempFile = QTemporaryFile()
+        if saveAll:
+            opened = tempFile.open()
+        else:
+            opened = historyFile.open(QIODevice.Append)
+        
+        if not opened:
+            if saveAll:
+                f = tempFile
+            else:
+                f = historyFile
+            QMessageBox.warning(None,
+                self.trUtf8("Saving History"),
+                self.trUtf8("""Unable to open history file <b>{0}</b>.<br/>"""
+                            """Reason: {1}""")\
+                    .format(f.fileName, f.errorString()))
+            return
+        
+        if saveAll:
+            historyStream = QDataStream(tempFile)
+        else:
+            historyStream = QDataStream(historyFile)
+        for index in range(first, -1, -1):
+            data = QByteArray()
+            stream = QDataStream(data, QIODevice.WriteOnly)
+            itm = self.__history[index]
+            stream.writeUInt32(HISTORY_VERSION)
+            stream.writeString(itm.url)
+            stream << itm.dateTime
+            stream.writeString(itm.title)
+            historyStream << data
+        
+        if saveAll:
+            tempFile.close()
+            if historyFile.exists() and not historyFile.remove():
+                QMessageBox.warning(None,
+                    self.trUtf8("Saving History"),
+                    self.trUtf8("""Error removing old history file <b>{0}</b>."""
+                                """<br/>Reason: {1}""")\
+                        .format(historyFile.fileName, historyFile.errorString()))
+            if not tempFile.copy(historyFile.fileName()):
+                QMessageBox.warning(None,
+                    self.trUtf8("Saving History"),
+                    self.trUtf8("""Error moving new history file over old one """
+                                """(<b>{0}</b>).<br/>Reason: {1}""")\
+                        .format(historyFile.fileName(), tempFile.errorString()))
+        else:
+            historyFile.close()
+        
+        try:
+            self.__lastSavedUrl = self.__history[0].url
+        except IndexError:
+            self.__lastSavedUrl = ""
+    
+    def __refreshFrequencies(self):
+        """
+        Private slot to recalculate the refresh frequencies.
+        """
+        self.__historyFilterModel.recalculateFrequencies()
+        self.__startFrequencyTimer()
+    
+    def __startFrequencyTimer(self):
+        """
+        Private method to start the timer to recalculate the frequencies.
+        """
+        tomorrow = QDateTime(QDate.currentDate().addDays(1), QTime(3, 0))
+        self.__frequencyTimer.start(QDateTime.currentDateTime().secsTo(tomorrow) * 1000)

eric ide

mercurial