123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- ########################################################################
- # 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 PyQt5.QtCore import pyqtSignal, QObject
- from searxqt.core.searx import SearX, SearxConfigHandler
- from searxqt.core import log
- from searxqt.thread import Thread, ThreadManagerProto
- from searxqt.translations import _
- class SearchStatus:
- Done = 0
- Busy = 1
- class SearchBehaviour:
- Normal = 0
- RandomEvery = 1
- class CategoryModel(QObject):
- """ stateChanged; emitted when this category gets enabled or disabled.
- str: category key
- bool: state
- """
- stateChanged = pyqtSignal(str, bool)
- """ changed; emitted when a engine is added or removed from this category.
- str: category key
- """
- changed = pyqtSignal(str)
- def __init__(self, key, name, checked=False, parent=None):
- QObject.__init__(self, parent=parent)
- self.__key = key
- self.__name = name
- self.__checked = checked
- @property
- def name(self): return self.__name
- @property
- def key(self): return self.__key
- def isChecked(self):
- return self.__checked
- def check(self):
- self.__checked = True
- self.stateChanged.emit(self.key, True)
- def uncheck(self):
- self.__checked = False
- self.stateChanged.emit(self.key, False)
- class UserCategoryModel(CategoryModel):
- def __init__(self, key, name, checked=False, engines=None, parent=None):
- CategoryModel.__init__(self, key, name, checked=checked, parent=parent)
- # https://docs.python.org/3/faq/programming.html?highlight=default%20shared%20values%20objects#why-are-default-values-shared-between-objects
- self.__engines = engines if engines is not None else []
- @property
- def engines(self): return self.__engines
- def addEngine(self, engineStr):
- engineStr = engineStr.lower()
- if engineStr in self.__engines:
- log.error(f"Attempt to add a engine `{engineStr}` to user " \
- f"category `{self.name}` that is already there. " \
- "This should not happen.", self)
- return
- self.__engines.append(engineStr)
- self.changed.emit(self.key)
- def removeEngine(self, engineStr):
- self.__engines.remove(engineStr)
- self.changed.emit(self.key)
- class CategoriesModel(QObject): # generic
- CatModel = CategoryModel
- """ stateChanged; emitted when this category gets enabled/disabled.
- str: category key
- bool: state (enabled/disabled)
- """
- stateChanged = pyqtSignal(str, bool)
- """ changed; emitted when a category has changed (engine added or removed).
- str: category key
- """
- changed = pyqtSignal(str)
- """ removed; emitted when a category has been removed.
- str: category key
- """
- removed = pyqtSignal(str)
- """ dataChanged; emitted on setData (views must re-generate labels)
- """
- dataChanged = pyqtSignal()
- def __init__(self, parent=None):
- QObject.__init__(self, parent=parent)
- self._categories = {}
- def __contains__(self, key): return bool(key in self._categories)
- def __iter__(self): return iter(self._categories)
- def __len__(self): return len(self._categories)
- def __getitem__(self, key): return self._categories[key]
- def clear(self):
- for cat in list(self._categories.values()):
- self.removeCategory(cat.key)
- def data(self):
- return [cat.key for cat in self._categories.values()
- if cat.isChecked()]
- def setData(self, data):
- for catKey in data:
- if catKey in self:
- self[catKey].check()
- self.dataChanged.emit()
- def addCategory(self, catKey, name, checked=False):
- newCat = self.CatModel(catKey, name, checked=checked, parent=self)
- newCat.stateChanged.connect(self.stateChanged)
- newCat.changed.connect(self.changed)
- self._categories.update({catKey: newCat})
- return True
- def removeCategory(self, key):
- self._categories[key].stateChanged.disconnect(self.stateChanged)
- self._categories[key].deleteLater()
- del self._categories[key]
- self.removed.emit(key)
- def isChecked(self, key):
- return self._categories[key].isChecked()
- def checkedCategories(self):
- return [key for key in self._categories
- if self._categories[key].isChecked()]
- def items(self):
- return self._categories.items()
- def keys(self):
- return self._categories.keys()
- def values(self):
- return self._categories.values()
- def copy(self):
- return self._categories.copy()
- class UserCategoriesModel(CategoriesModel):
- CatModel = UserCategoryModel
- def __init__(self, parent=None):
- CategoriesModel.__init__(self, parent=parent)
- def data(self):
- data = {}
- for catKey, cat in self._categories.items():
- data.update({catKey: (cat.name, cat.isChecked(), cat.engines)})
- return data
- def setData(self, data):
- self.clear()
- for catKey, catData in data.items():
- self.addCategory(
- catKey,
- catData[0], # name
- checked=catData[1],
- engines=catData[2]
- )
- if catData[1]:
- self.stateChanged.emit(catKey, True)
- self.dataChanged.emit()
- def addCategory(self, catKey, name, checked=False, engines=None):
- newCat = self.CatModel(
- catKey,
- name,
- checked=checked,
- engines=engines,
- parent=self
- )
- newCat.stateChanged.connect(self.stateChanged)
- newCat.changed.connect(self.changed)
- self._categories.update({catKey: newCat})
- return True
- class SearchModel(SearX, QObject):
- statusChanged = pyqtSignal(int) # SearchStatus
- optionsChanged = pyqtSignal()
- def __init__(self, requestHandler, parent=None):
- SearX.__init__(self, requestHandler)
- QObject.__init__(self, parent=parent)
- self._status = SearchStatus.Done
- self._randomEveryRequest = False
- self._useFallback = True
- # Options
- @property
- def useFallback(self):
- """
- @rtype: bool
- """
- return self._useFallback
- @useFallback.setter
- def useFallback(self, state):
- """
- @type state: bool
- """
- self._useFallback = state
- self.optionsChanged.emit()
- @property
- def randomEvery(self):
- """
- @rtype: bool
- """
- return self._randomEveryRequest
- @randomEvery.setter
- def randomEvery(self, state):
- """
- @type state: bool
- """
- self._randomEveryRequest = state
- self.optionsChanged.emit()
- # End options
- def status(self): return self._status
- def saveSettings(self):
- """ Returns current state
- """
- return {
- 'fallback': self.useFallback,
- 'randomEvery': self.randomEvery,
- 'parseHtml': self.parseHtml,
- 'safeSearch': self.safeSearch
- }
- def loadSettings(self, data):
- """ Restore current state
- @type data: dict
- """
- self.useFallback = data.get('fallback', True)
- self.randomEvery = data.get('randomEvery', False)
- self.parseHtml = data.get('parseHtml', True)
- self.safeSearch = data.get('safeSearch', False)
- """ SearX re-implementations below
- """
- def search(self, requestKwargs={}):
- self.statusChanged.emit(SearchStatus.Busy)
- result = SearX.search(self)
- self.statusChanged.emit(SearchStatus.Done)
- return result
- class UserInstancesHandler(SearxConfigHandler, ThreadManagerProto):
- """
- """
- changed = pyqtSignal()
- def __init__(self, requestsHandler, parent=None):
- """
- @param requestsHandler:
- @type requestsHandler: core.requests.RequestsHandler
- """
- SearxConfigHandler.__init__(self, requestsHandler)
- ThreadManagerProto.__init__(self, parent=parent)
- self._currentThreadUrl = ""
- # ThreadManagerProto override
- def currentJobStr(self):
- if self.hasActiveJobs():
- url = self._currentThreadUrl
- queueCount = self.queueCount()
- return _(f"<b>Updating data:</b> {url} ({queueCount} left)")
- return ""
- # HandlerProto override
- def setData(self, data):
- self._threadQueue.clear()
- SearxConfigHandler.setData(self, data)
- self.changed.emit()
- def addInstance(self, url):
- if SearxConfigHandler.addInstance(self, url):
- self.changed.emit()
- return True
- return False
- def removeMultiInstances(self, urls):
- SearxConfigHandler.removeMultiInstances(self, urls)
- self.changed.emit()
- def updateInstance(self, url):
- if self._thread:
- if url not in self._threadQueue:
- self._threadQueue.append(url)
- else:
- self._thread = Thread(
- SearxConfigHandler.updateInstance,
- args=[self, url],
- parent=self
- )
- self._currentThreadUrl = url
- self._thread.finished.connect(
- self.__updateInstanceThreadFinished
- )
- self.threadStarted.emit()
- self._thread.start()
- def __clearUpdateThread(self):
- self._thread.finished.disconnect(
- self.__updateInstanceThreadFinished
- )
- # Wait before deleting because the `finished` signal is emited
- # from the thread itself, so this method could be called before the
- # thread is actually finished and result in a crash.
- self._thread.wait()
- self._thread.deleteLater()
- self._thread = None
- def __updateInstanceThreadFinished(self):
- result = self._thread.result()
- self.__clearUpdateThread()
- if result:
- self.changed.emit()
- self._currentThreadUrl = ""
- self.threadFinished.emit()
- if self._threadQueue:
- url = self._threadQueue.pop(0)
- self.updateInstance(url)
|