123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050 |
- ########################################################################
- # Searx-Qt - Lightweight desktop application for Searx.
- # Copyright (C) 2020-2022 CYBERDEViL
- #
- # This file is part of Searx-Qt.
- #
- # Searx-Qt is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # Searx-Qt is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
- #
- ########################################################################
- from copy import deepcopy
- import random
- from operator import itemgetter
- from PyQt5.QtCore import (
- QObject,
- pyqtSignal,
- QAbstractTableModel,
- QTimer,
- QVariant,
- Qt
- )
- from searxqt import http as Http
- from searxqt.core.instances import Instance, Stats2
- from searxqt.core.engines import Stats2Engines, EnginesModel
- from searxqt.core.instanceVersions import (
- InstanceVersion,
- VersionFlags
- )
- from searxqt.core.http import ErrorType
- from searxqt.utils.string import boolToStr, listToStr
- from searxqt.utils.time import nowInMinutes
- from searxqt.translations import _, timeToString
- from searxqt.core import log
- class InstancesModelTypes:
- NotDefined = 0
- Stats2 = 1
- User = 2
- class PersistentEnginesModel(EnginesModel, QObject):
- changed = pyqtSignal()
- def __init__(self, enginesModel=None, parent=None):
- EnginesModel.__init__(self)
- QObject.__init__(self, parent)
- self._currentModel = None
- if enginesModel:
- self.setModel(enginesModel)
- def hasModel(self):
- return False if self._currentModel is None else True
- def setModel(self, enginesModel):
- if self._currentModel:
- self._currentModel.deleteLater()
- self._currentModel.changed.disconnect(self.changed)
- self._currentModel = enginesModel
- self._currentModel.changed.connect(self.changed)
- self._data = self._currentModel.data()
- self.changed.emit()
- class UserEnginesModel(EnginesModel, QObject):
- changed = pyqtSignal()
- def __init__(self, handler, parent=None):
- QObject.__init__(self, parent)
- EnginesModel.__init__(self, handler)
- handler.changed.connect(self.changed)
- class Stats2EnginesModel(Stats2Engines, QObject):
- changed = pyqtSignal()
- def __init__(self, handler, parent=None):
- """
- @param handler: Object containing engines data.
- @type handler: searxqt.models.instances.Stats2Model
- """
- QObject.__init__(self, parent)
- Stats2Engines.__init__(self, handler)
- handler.changed.connect(self.changed)
- class Stats2Model(Stats2, QObject):
- changed = pyqtSignal()
- # int core.http.ErrorType, str errorMsg
- updateFinished = pyqtSignal(int, str)
- def __init__(self, httpSettings, parent=None):
- """
- @param httpSettings:
- @type httpSettings: core.http.HttpRequestSettings
- """
- Stats2.__init__(self, Http.Thread, httpSettings)
- QObject.__init__(self, parent=parent)
- def setData(self, data):
- Stats2.setData(self, data)
- self.changed.emit()
- def updateFinishedCb(self, response):
- if response.error != ErrorType.Success:
- log.error(f'Updating instances failed! Error: {response.error}', self)
- else:
- log.info('Instances updated!', self)
- self.changed.emit()
- self.updateFinished.emit(response.error, response.errorMessage)
- class InstanceModel(Instance):
- def __init__(self, url, data, parent=None):
- Instance.__init__(self, url, data)
- class UserInstanceModel(InstanceModel, QObject):
- def __init__(self, url, data, parent=None):
- QObject.__init__(self, parent=parent)
- InstanceModel.__init__(self, url, data, parent=parent)
- @property
- def lastUpdated(self):
- return self._data.get('lastUpdated', 0)
- class Stats2InstanceModel(InstanceModel, QObject):
- def __init__(self, url, data, parent=None):
- QObject.__init__(self, parent=parent)
- InstanceModel.__init__(self, url, data, parent=parent)
- @property
- def analytics(self):
- """ When this is True, the instance has known tracking.
- @return: True when instance has known tracking, False otherwise.
- @rtype: bool
- """
- return self._data.get('analytics', False)
- class InstancesModel(QObject):
- InstanceType = InstanceModel
- Type = InstancesModelTypes.NotDefined
- changed = pyqtSignal()
- def __init__(self, handler=None, parent=None):
- """
- """
- QObject.__init__(self, parent=parent)
- self._instances = {}
- self._modelHandler = handler
- if handler:
- self._instances = handler.instances
- handler.changed.connect(self.changed)
- def __contains__(self, url):
- return bool(url in self._instances)
- def __getitem__(self, url):
- return self.InstanceType(url, self._instances[url])
- def __str__(self): return str([url for url in self._instances])
- def __repr__(self): return str(self)
- def __len__(self): return len(self._instances)
- def data(self):
- return self._instances
- def items(self):
- return [
- (url, self.InstanceType(url, data))
- for url, data in self._instances.items()
- ]
- def keys(self): return self._instances.keys()
- def values(self):
- return [
- self.InstanceType(url, data)
- for url, data in self._instances.items()
- ]
- def copy(self):
- return self._instances.copy()
- class PersistentInstancesModel(InstancesModel):
- """ This will either hold a Stats2InstancesModel or a UserInstancesModel
- It can be switched during run-time.
- This ensures that no references are made to the underlying model
- outside of this object.
- """
- typeChanged = pyqtSignal(int) # InstancesModelTypes
- def __init__(self, instancesModel=None, parent=None):
- InstancesModel.__init__(self, parent=parent)
- self.__currentModel = None
- self._modelHandler = None # Object that manages the model
- if instancesModel:
- self.setModel(instancesModel)
- def hasModel(self):
- return False if self.__currentModel is None else True
- def hasHandler(self):
- return False if self._modelHandler is None else True
- def handler(self):
- # Do not store references to the returned object!
- return self._modelHandler
- def setModel(self, instancesModel):
- if self.__currentModel:
- self.__currentModel.changed.disconnect(self.changed)
- self.__currentModel.deleteLater()
- self.InstanceType = instancesModel.InstanceType
- self.__currentModel = instancesModel
- self.__currentModel.changed.connect(self.changed)
- self._instances = self.__currentModel.data()
- self._modelHandler = instancesModel._modelHandler
- if self.Type != instancesModel.Type:
- self.Type = instancesModel.Type
- self.typeChanged.emit(self.Type)
- self.changed.emit()
- class UserInstancesModel(InstancesModel, QObject):
- InstanceType = UserInstanceModel
- Type = InstancesModelTypes.User
- def __init__(self, handler, parent=None):
- InstancesModel.__init__(self, handler, parent=parent)
- class Stats2InstancesModel(InstancesModel):
- InstanceType = Stats2InstanceModel
- Type = InstancesModelTypes.Stats2
- def __init__(self, handler, parent=None):
- """
- @param handler:
- @type handler: Stats2Model
- """
- InstancesModel.__init__(self, handler, parent=parent)
- class InstanceModelFilter(QObject):
- changed = pyqtSignal()
- VERSION_FILTER_TEMPLATE = {
- 'min': "", # just the version string
- 'git': True, # Allow git versions by default
- 'dirty': False, # Don't allow dirty versions by default
- 'extra': False, # Don't allow 'extra' versions by default
- 'unknown': False, # Don't allow unknown git commits by default
- 'invalid': False # Don't allow invalid versions by default
- }
- def __init__(self, model, parent=None):
- QObject.__init__(self, parent=parent)
- """
- @type model: searxqt.models.instances.PersistentInstancesModel
- """
- self._model = model
- self._current = model.copy()
- self._filter = {
- 'networkTypes': [],
- 'version': deepcopy(self.VERSION_FILTER_TEMPLATE),
- 'whitelist': [],
- # key: url (str), value: (time (uint), reason (str))
- 'blacklist': {},
- 'asnPrivacy': True,
- 'ipv6': False,
- 'engines': [],
- # A dict for temp blacklisting a instance url. This won't be stored on
- # disk, only in RAM. So on restart of searx-qt this will be empty.
- # It is used to put failing instances on a timeout.
- # key: instance url, value: tuple (QTimer object, str reason)
- 'timeout': {},
- # Skip instances that have analytics set to True
- 'analytics': True
- }
- self._model.changed.connect(self.apply)
- @property
- def timeoutList(self):
- return self._filter['timeout']
- @property
- def blacklist(self):
- return self._filter['blacklist']
- @property
- def whitelist(self):
- return self._filter['whitelist']
- def __delInstanceFromTimeout(self, url):
- """ Internal method for removing instance from timeout and apply the
- filter to the model afterwards.
- @param url: Instance URL to remove from timeout.
- @type url: str
- """
- self.delInstanceFromTimeout(url)
- self.apply()
- def putInstanceOnTimeout(self, url, duration=0, reason=''):
- """ Put a instance url on a timeout.
- When 'duration' is '0' it won't timeout, and the url will be
- blacklisted until restart of searx-qt or possible manual removal
- from the list.
- @param url: Instance url
- @type url: str
- @param duration: The duration of the blacklist in minutes.
- @type duration: int
- """
- timer = None
- if duration:
- timer = QTimer(self)
- timer.setSingleShot(True)
- timer.timeout.connect(
- lambda url=url: self.__delInstanceFromTimeout(url)
- )
- timer.start(duration * 60000)
- self.timeoutList.update({url: (timer, reason)})
- def delInstanceFromTimeout(self, url):
- """ Remove instance url from timeout.
- @param url: Instance URL to remove from timeout.
- @type url: str
- """
- if self.timeoutList[url][0]:
- # a QTimer is set, delete it.
- self.timeoutList[url][0].deleteLater() # Delete the QTimer.
- del self.timeoutList[url] # Remove from filter.
- def putInstanceOnBlacklist(self, url, reason=''):
- """ Put instance url on blacklist.
- @param url: Instance URL to blacklist.
- @type url: str
- @param reason: Optional reason for the blacklisting.
- @type reason: str
- """
- if url not in self.blacklist:
- self.blacklist.update({url: (nowInMinutes(), reason)})
- def delInstanceFromBlacklist(self, url):
- """ Delete instance url from blacklist.
- @param url: Instance URL remove from blacklist.
- @type url: str
- """
- del self.blacklist[url]
- def putInstanceOnWhitelist(self, url):
- """ Put instance url from whitelist.
- @param url: Instance URL to whitelist.
- @type url: str
- """
- if url not in self.whitelist:
- self.whitelist.append(url)
- def delInstanceFromWhitelist(self, url):
- """ Delete instance url from whitelist.
- @param url: Instance URL remove from whitelist.
- @type url: str
- """
- self.whitelist.remove(url)
- def loadSettings(self, data):
- defaultAsnPrivacy = bool(self._model.Type != InstancesModelTypes.User)
- defaultAnalytics = bool(self._model.Type != InstancesModelTypes.User)
- # Clear the temporary blacklist which maybe populated when switched
- # from profile.
- for timer, reason in self.timeoutList.values():
- if timer:
- timer.deleteLater()
- self.timeoutList.clear()
- # Restore timeouts
- timeouts = data.get('timeout', {})
- for url in timeouts:
- until, reason = timeouts[url]
- delta = until - nowInMinutes()
- if delta > 0:
- self.putInstanceOnTimeout(url, delta, reason)
- self.updateKwargs(
- {
- 'networkTypes': data.get('networkTypes', []),
- 'version': data.get(
- 'version',
- deepcopy(self.VERSION_FILTER_TEMPLATE)
- ),
- 'whitelist': data.get('whitelist', []),
- 'blacklist': data.get('blacklist', {}),
- 'asnPrivacy': data.get('asnPrivacy', defaultAsnPrivacy),
- 'ipv6': data.get('ipv6', False),
- 'analytics': data.get('analytics', defaultAnalytics)
- }
- )
- def saveSettings(self):
- filter_ = self.filter()
- # Store timeouts
- timeout = {}
- for url in self.timeoutList:
- timer, reason = self.timeoutList[url]
- if timer:
- until = nowInMinutes() + int((timer.remainingTime() / 1000) / 60)
- timeout.update({url: (until, reason)})
- return {
- 'networkTypes': filter_['networkTypes'],
- 'version': filter_['version'],
- 'whitelist': self.whitelist,
- 'blacklist': self.blacklist,
- 'asnPrivacy': filter_['asnPrivacy'],
- 'ipv6': filter_['ipv6'],
- 'timeout': timeout,
- 'analytics': filter_['analytics']
- }
- def filter(self): return self._filter
- def parentModel(self): return self._model
- def updateKwargs(self, kwargs, commit=True):
- for key in kwargs:
- if type(self._filter[key]) is dict: # TODO this is stupid
- for key2 in kwargs[key]:
- self._filter[key][key2] = kwargs[key][key2]
- else:
- self._filter[key] = kwargs[key]
- if commit:
- self.apply()
- def apply(self):
- self._current.clear()
- minimumVersion = InstanceVersion(self._filter['version']['min'])
- for url, instance in self._model.items():
- # Skip temporary blacklisted instances.
- if url in self.timeoutList:
- continue
- if url not in self.whitelist: # Url whitelisted
- # Url blacklist
- if self.blacklist:
- if instance.url in self.blacklist:
- continue
- # Stats2 only.
- if self._model.Type == InstancesModelTypes.Stats2:
- # Analytics
- if self._filter['analytics']:
- if instance.analytics:
- continue
- # Network
- if self._filter['networkTypes']:
- if (
- instance.networkType not in
- self._filter['networkTypes']
- ):
- continue
- # Version
- instanceVersion = instance.version
- # Filter out instances with an invalid version, maybe its
- # malformed or the format may be unknown to us.
- if not self._filter['version']['invalid']:
- if not instanceVersion.isValid():
- continue
- ## Minimum version
- if minimumVersion.isValid():
- # Cannot compare date-version with a semantic-version, so
- # filter out the other.
- if instanceVersion.type() != minimumVersion.type():
- continue
- # Filter out instances that don't meet the minimum version.
- if instance.version < minimumVersion:
- continue
- ## Non-development versions
- if not self._filter['version']['git']:
- # Condition where the evaluated instance it's version is a
- # git version (development) and the git checkbox is
- # unchecked, so we want to filter it out.
- if (instanceVersion.flags() & VersionFlags.Git):
- continue
- ## Development versions
- else:
- ## Dirty development versions
- if not self._filter['version']['dirty']:
- # Filter out instances with 'dirty' flag when filter is not
- # enabled.
- if (instanceVersion.flags() & VersionFlags.Dirty):
- continue
- ## Extra development versions
- if not self._filter['version']['extra']:
- # Filter out instances with 'extra' flag when filter is not
- # enabled.
- if (instanceVersion.flags() & VersionFlags.Extra):
- continue
- ## Extra development versions
- if not self._filter['version']['unknown']:
- # Filter out instances with 'unknown' flag when filter is not
- # enabled.
- if (instanceVersion.flags() & VersionFlags.Unknown):
- continue
- # ASN privacy
- if self._filter['asnPrivacy']:
- if instance.network.asnPrivacy != 0:
- continue
- # IPv6
- if self._filter['ipv6']:
- if not instance.network.ipv6:
- continue
- # Engines
- if self._filter['engines']:
- # TODO when engine(s) are set and also a language, we should
- # check if the engine has language support before allowing
- # it.
- #
- # TODO when engine(s) are set and also a time-range we
- # should check if the engine has time-range support.
- #
- # When the user has set specific search engines set to be
- # searched on we filter out all instanes that don't atleast
- # support one of the set engines available.
- found = False
- for engine in self._filter['engines']:
- for e in instance.engines:
- if e.name == engine:
- found = True
- break
- if not found:
- # This instance doesn't have one of the set engines so
- # we filter it out.
- continue
- self._current.update({url: instance})
- self.changed.emit()
- def __contains__(self, url): return bool(url in self._current)
- def __iter__(self): return iter(self._current)
- def __getitem__(self, url): return self._current[url]
- def __str__(self): return str([url for url in self])
- def __repr__(self): return str(self)
- def __len__(self): return len(self._current)
- def items(self): return self._current.items()
- def keys(self): return self._current.keys()
- def values(self): return self._current.values()
- def copy(self): return self._current.copy()
- class InstanceSelecterModel(QObject):
- optionsChanged = pyqtSignal()
- instanceChanged = pyqtSignal(str) # instance url
- def __init__(self, model, parent=None):
- QObject.__init__(self, parent=parent)
- """
- @type model: InstancesModelFilter
- """
- self._model = model
- self._currentInstanceUrl = ''
- self._model.changed.connect(self.__modelChanged)
- def __modelChanged(self):
- """ This can happen after example blacklisting all instances.
- """
- if self.currentUrl and self.currentUrl not in self._model:
- self.currentUrl = ""
- @property
- def currentUrl(self): return self._currentInstanceUrl
- @currentUrl.setter
- def currentUrl(self, url):
- self._currentInstanceUrl = url
- self.instanceChanged.emit(url)
- def loadSettings(self, data):
- self.currentUrl = data.get('currentInstance', '')
- self.instanceChanged.emit(self.currentUrl)
- def saveSettings(self):
- return {
- 'currentInstance': self.currentUrl
- }
- def getRandomInstances(self, amount=10):
- """ Returns a list of random instance urls.
- """
- return random.sample(list(self._model.keys()),
- min(amount, len(self._model.keys())))
- def randomInstance(self):
- if self._model.keys():
- self.currentUrl = random.choice(list(self._model.keys()))
- return self.currentUrl
- class EnginesTableModel(QAbstractTableModel):
- """ Model used to display engines with their data in a QTableView and
- for adding/removing engines to/from categories.
- """
- def __init__(self, enginesModel, parent):
- """
- @param enginesModel: Contains data about all engines.
- @type enginesModel: searxqt.models.instances.EnginesModel
- """
- QAbstractTableModel.__init__(self, parent)
- self._model = enginesModel # contains all engines
- self._userModel = None # see self.setUserModel method
- self._columns = [
- _('Enabled'),
- _('Name'),
- _('Categories'),
- _('Language support'),
- _('Paging'),
- _('SafeSearch'),
- _('Shortcut'),
- _('Time-range support')
- ]
- self._keyIndex = []
- self._catFilter = ""
- self._sort = (0, None)
- self.__genKeyIndexes()
- def setUserModel(self, model):
- """
- @param model: User category model
- @type model: searxqt.models.search.UserCategoryModel
- """
- self.layoutAboutToBeChanged.emit()
- self._userModel = model
- self.layoutChanged.emit()
- self.reSort()
- def __genKeyIndexes(self):
- self._keyIndex.clear()
- if self._catFilter:
- self._keyIndex = [
- key for key, engine in self._model.items()
- if self._catFilter in engine.categories
- ]
- else:
- self._keyIndex = list(self._model.keys())
- def setCatFilter(self, catKey=""):
- """ Filter engines on category.
- """
- self.layoutAboutToBeChanged.emit()
- self._catFilter = catKey
- self.__genKeyIndexes()
- self.reSort()
- self.layoutChanged.emit()
- def getValueByKey(self, key, columnIndex):
- if columnIndex == 0:
- if self._userModel:
- return boolToStr(bool(key in self._userModel.engines))
- return boolToStr(False)
- elif columnIndex == 1:
- return key
- elif columnIndex == 2:
- return listToStr(self._model[key].categories)
- elif columnIndex == 3:
- return boolToStr(self._model[key].languageSupport)
- elif columnIndex == 4:
- return boolToStr(self._model[key].paging)
- elif columnIndex == 5:
- return boolToStr(self._model[key].safesearch)
- elif columnIndex == 6:
- return self._model[key].shortcut
- elif columnIndex == 7:
- return boolToStr(self._model[key].timeRangeSupport)
- def __sort(self, columnIndex, order=Qt.AscendingOrder):
- unsortedList = [
- [key, self.getValueByKey(key, columnIndex)]
- for key in self._keyIndex
- ]
- reverse = False if order == Qt.AscendingOrder else True
- sortedList = sorted(
- unsortedList,
- key=itemgetter(1),
- reverse=reverse
- )
- self._keyIndex.clear()
- for key, value in sortedList:
- self._keyIndex.append(key)
- def reSort(self):
- if self._sort is not None:
- self.sort(self._sort[0], self._sort[1])
- """ QAbstractTableModel reimplementations below
- """
- def rowCount(self, parent): return len(self._keyIndex)
- def columnCount(self, parent):
- return len(self._columns)
- def headerData(self, col, orientation, role):
- if orientation == Qt.Horizontal and role == Qt.DisplayRole:
- return QVariant(self._columns[col])
- return QVariant()
- def sort(self, columnIndex, order=Qt.AscendingOrder):
- self.layoutAboutToBeChanged.emit()
- self._sort = (columnIndex, order) # backup current sorting
- self.__sort(columnIndex, order=order)
- self.layoutChanged.emit()
- def setData(self, index, value, role):
- if not index.isValid():
- return False
- if role == Qt.CheckStateRole:
- if self._userModel is not None:
- key = self._keyIndex[index.row()]
- if value:
- self._userModel.addEngine(key)
- else:
- self._userModel.removeEngine(key)
- self.reSort()
- return True
- return False
- def data(self, index, role):
- if not index.isValid():
- return QVariant()
- if role == Qt.DisplayRole:
- key = self._keyIndex[index.row()]
- return self.getValueByKey(key, index.column())
- elif index.column() == 0 and role == Qt.CheckStateRole:
- if self._userModel is not None:
- key = self._keyIndex[index.row()]
- if key in self._userModel.engines:
- return Qt.Checked
- return Qt.Unchecked
- return QVariant()
- def flags(self, index):
- flags = (
- Qt.ItemIsSelectable |
- Qt.ItemIsEnabled |
- Qt.ItemNeverHasChildren
- )
- if index.column() == 0:
- flags = flags | Qt.ItemIsUserCheckable
- return flags
- class InstanceTableModel(QAbstractTableModel):
- """ `InstancesModel` -> `QAbstractTableModel` adapter model
- """
- class Column:
- def __init__(self, name, route, type_):
- self._name = name
- self._route = route
- self._type = type_
- @property
- def type(self): return self._type
- @property
- def name(self): return self._name
- @property
- def route(self): return self._route
- def __init__(self, instancesModel, parent):
- """
- @param instancesModel: Resource model
- @type instancesModel: InstancesModel
- """
- QAbstractTableModel.__init__(self, parent)
- self._model = instancesModel
- self._currentModelType = instancesModel.parentModel().Type
- self._keyIndex = [] # [key, key, ..]
- self.__currentSorting = (0, Qt.AscendingOrder)
- self._columns = [
- InstanceTableModel.Column('url', 'url', str),
- InstanceTableModel.Column('version', 'version', str),
- InstanceTableModel.Column('engines', 'engines', list),
- InstanceTableModel.Column('tls.version', 'tls.version', str),
- InstanceTableModel.Column(
- 'tls.cert.version',
- 'tls.certificate.version',
- int),
- InstanceTableModel.Column(
- 'tls.countryName',
- 'tls.certificate.issuer.countryName',
- str),
- InstanceTableModel.Column(
- 'tls.commonName',
- 'tls.certificate.issuer.commonName',
- str),
- InstanceTableModel.Column(
- 'tls.organizationName',
- 'tls.certificate.issuer.organizationName',
- str),
- InstanceTableModel.Column(
- 'network.asnPrivacy',
- 'network.asnPrivacy',
- str),
- InstanceTableModel.Column(
- 'network.ipv6',
- 'network.ipv6',
- bool),
- InstanceTableModel.Column('network.ips', 'network.ips', dict),
- InstanceTableModel.Column('analytics', 'analytics', bool) # stats2
- ]
- instancesModel.changed.connect(self.__resourceModelChanged)
- instancesModel.parentModel().typeChanged.connect(
- self.__modelTypeChanged
- )
- def __modelTypeChanged(self, newType):
- previousType = self._currentModelType
- if (previousType != InstancesModelTypes.User and
- newType == InstancesModelTypes.User):
- del self._columns[-1]
- self._columns.append(
- InstanceTableModel.Column('lastUpdated', 'lastUpdated', int))
- elif (previousType == InstancesModelTypes.User and
- newType != InstancesModelTypes.User):
- del self._columns[-1]
- self._columns.append(
- InstanceTableModel.Column('analytics', 'analytics', bool))
- self._currentModelType = newType
- def __genKeyIndexes(self):
- self._keyIndex.clear()
- for key in self._model:
- self._keyIndex.append(key)
- def __resourceModelChanged(self):
- self.sort(*self.__currentSorting)
- def getColumns(self): return self._columns
- def getByIndex(self, index):
- """ Returns a Instance it's URL by index.
- @param index: Index of the instance it's url you like to get.
- @type index: int
- @return: Instance url
- @rtype: str
- """
- return self._keyIndex[index]
- def getByUrl(self, url):
- """ Returns a Instancs it's current index by url
- @param url: Url of the instance you want to get the current
- index of.
- @type url: str
- @returns: Instance index.
- @rtype: int
- """
- return self._keyIndex.index(url)
- def getPropertyValueByIndex(self, index, route):
- obj = self._model[self.getByIndex(index)]
- return self.getPropertyValue(obj, route)
- def getPropertyValue(self, obj, route):
- """ Returns the `Instance` it's desired property.
- @param obj: instance object
- @type obj: Instance
- @param route: traversel path to value through properties.
- @type route: str
- """
- routes = route.split('.')
- propValue = None
- for propName in routes:
- propValue = getattr(obj, propName)
- obj = propValue
- return propValue
- """ QAbstractTableModel reimplementations below
- """
- def rowCount(self, parent): return len(self._model)
- def columnCount(self, parent): return len(self._columns)
- def sort(self, col, order=Qt.AscendingOrder):
- self.layoutAboutToBeChanged.emit()
- route = self._columns[col].route
- unsortedList = []
- for url, instance in self._model.items():
- value = str(
- self.getPropertyValue(
- instance,
- route
- )
- )
- unsortedList.append([url, value])
- reverse = False if order == Qt.AscendingOrder else True
- sortedList = sorted(
- unsortedList,
- key=itemgetter(1),
- reverse=reverse
- )
- self._keyIndex.clear()
- for url, value in sortedList:
- self._keyIndex.append(url)
- self.__currentSorting = (col, order)
- self.layoutChanged.emit()
- def headerData(self, col, orientation, role):
- if orientation == Qt.Horizontal and role == Qt.DisplayRole:
- return QVariant(self._columns[col].name)
- return QVariant()
- def data(self, index, role):
- if not index.isValid():
- return QVariant()
- if role == Qt.DisplayRole:
- value = self.getPropertyValueByIndex(
- index.row(),
- self._columns[index.column()].route)
- if index.column() == 1: # version
- return str(value)
- elif index.column() == 2: # engines
- newStr = ''
- for engine in value:
- if newStr:
- newStr += ', {0}'.format(engine.name)
- else:
- newStr = engine.name
- return newStr
- elif index.column() == 10: # ips
- return str(value)
- # stats2 profile type specific
- elif self._model.parentModel().Type == InstancesModelTypes.Stats2:
- if index.column() == 11: # analytics
- return str(value)
- # user profile type specific
- else:
- if index.column() == 11: # userInstances lastUpdated
- return timeToString(value)
- return value
- return QVariant()
|