
| Current Path : /usr/lib/python3/dist-packages/meteo_qt/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : //usr/lib/python3/dist-packages/meteo_qt/searchcity.py |
import logging
import urllib.request
from socket import timeout
import json
import re
from lxml import etree
from PyQt5.QtCore import (QByteArray, QCoreApplication, QSettings, QThread,
QTimer, pyqtSignal)
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (QDialog, QHBoxLayout, QLabel, QLineEdit,
QListWidget, QPushButton, QVBoxLayout)
class SearchCity(QDialog):
id_signal = pyqtSignal([tuple])
city_signal = pyqtSignal([tuple])
country_signal = pyqtSignal([tuple])
def __init__(self, accurate_url, appid, parent=None):
super(SearchCity, self).__init__(parent)
self.settings = QSettings()
self.delay = 1000
self.search_string = self.tr('Searching...')
self.timer = QTimer()
self.accurate_url = accurate_url
self.suffix = '&type=like&mode=xml' + appid
self.layout = QVBoxLayout()
self.lineLayout = QHBoxLayout()
self.buttonSearch = QPushButton()
self.buttonSearch.setIcon(QIcon(':/find'))
self.buttonSearch.setToolTip(
QCoreApplication.translate(
'Search city button Tooltip',
'Search city',
'Search for the given place'
)
)
self.buttonSearch.clicked.connect(self.search)
self.buttonMyLocation = QPushButton()
self.buttonMyLocation.setIcon(QIcon(':/mylocation'))
self.buttonMyLocation.setToolTip(
QCoreApplication.translate(
'Search by geolocalisation button tooltip',
'Find my location',
'Automatic search of my place'
)
)
self.buttonMyLocation.clicked.connect(self.myLocation)
self.line_search = QLineEdit(
QCoreApplication.translate(
'Search city dialogue',
'Start typing the city or the ID or the geographic '
'coordinates "latitude, longitude"',
'Default message in the search field'
)
)
self.line_search.selectAll()
self.listWidget = QListWidget()
self.status = QLabel()
self.lineLayout.addWidget(self.line_search)
self.lineLayout.addWidget(self.buttonSearch)
self.lineLayout.addWidget(self.buttonMyLocation)
self.layout.addLayout(self.lineLayout)
self.layout.addWidget(self.listWidget)
self.layout.addWidget(self.status)
self.buttonLayout = QHBoxLayout()
self.buttonLayout.addStretch()
self.buttonOk = QPushButton(self.tr('&Ok'))
self.buttonOk.setEnabled(False)
self.buttonCancel = QPushButton(self.tr('&Cancel'))
self.buttonLayout.addWidget(self.buttonOk)
self.buttonLayout.addWidget(self.buttonCancel)
self.layout.addLayout(self.buttonLayout)
self.setMinimumWidth(800)
self.setLayout(self.layout)
self.line_search.returnPressed.connect(self.search)
self.line_search.textChanged.connect(self.timer_run)
self.buttonOk.clicked.connect(self.accept)
self.buttonCancel.clicked.connect(self.reject)
self.listWidget.itemSelectionChanged.connect(self.buttonCheck)
self.listWidget.itemDoubleClicked['QListWidgetItem *'].connect(self.accept)
self.restoreGeometry(
self.settings.value("SearchCity/Geometry", QByteArray())
)
self.timer_search = QTimer(self)
self.timer_search.timeout.connect(self.search)
self.setWindowTitle(
QCoreApplication.translate(
'Window title',
'Find a city',
'City search dialogue'
)
)
self.status.setText(
'ℹ ' + QCoreApplication.translate(
'Status message',
'Click on "Find my Location" button for automatic geolocation',
'City search dialogue'
)
)
def timer_run(self):
self.timer_search.start(1000)
self.status.setText(
"ℹ "
+ QCoreApplication.translate(
"Status message",
"Put the city's name, comma, 2-letter country code (ex: London, GB)",
"City search dialogue"
)
)
def closeEvent(self, event):
self.settings.setValue(
"SearchCity/Geometry", self.saveGeometry()
)
def moveEvent(self, event):
self.settings.setValue(
"SearchCity/Geometry", self.saveGeometry()
)
def resizeEvent(self, event):
self.settings.setValue(
"SearchCity/Geometry", self.saveGeometry()
)
def buttonCheck(self):
'''Enable OK button if an item is selected.'''
row = self.listWidget.currentRow()
item = self.listWidget.item(row)
if item is not None:
self.buttonOk.setEnabled(True)
def accept(self):
row = self.listWidget.currentRow()
item = self.listWidget.item(row)
if item is not None:
selected_city = item.text()
city_list = selected_city.split(' - ')
for c in range(len(city_list)):
city_list[c] = city_list[c].strip()
id_ = 'ID', city_list[0]
city = 'City', city_list[1]
country = 'Country', city_list[2]
self.id_signal[tuple].emit(id_)
self.city_signal[tuple].emit(city)
self.country_signal[tuple].emit(country)
QDialog.accept(self)
def thread_terminate(self):
if hasattr(self, 'workThread'):
if self.workThread.isRunning():
self.workThread.terminate()
def myLocation(self):
locFound = False
loc = QCoreApplication.translate(
'Search city',
'N/A',
'Inserted in search field when the automatic'
'geolocalisation is not available'
)
try:
page = urllib.request.urlopen('http://ipinfo.io/json', timeout=5)
rep = page.read().decode('utf-8')
locdic = json.loads(rep)
loc = locdic['loc']
locFound = True
except (KeyError, urllib.error.HTTPError, timeout) as e:
locFound = False
logging.critical(
'Error fetching geolocation from http://ipinfo.io/json: '
+ str(e)
)
if locFound is False:
try:
data = str(urllib.request.urlopen('http://checkip.dyndns.com/', timeout=5).read())
myip = re.compile(r'Address: (\d+\.\d+\.\d+\.\d+)').search(data).group(1)
url = (
'http://api.ipstack.com/{}?access_key=1f5f4144cd87a4eda32832dad469b586'
.format(myip)
)
page = urllib.request.urlopen(url, timeout=5)
rep = page.read().decode('utf-8')
locdic = json.loads(rep)
loc = str(locdic['latitude']) + ',' + str(locdic['longitude'])
except (KeyError, urllib.error.HTTPError, timeout) as e:
logging.critical(
'Error fetching geolocation from {}: '.format(url)
+ str(e)
)
self.line_search.setText(loc)
def search(self):
self.timer_search.stop()
self.city = (self.line_search.text())
self.thread_terminate()
if len(self.city) < 3:
self.status.setText(
QCoreApplication.translate(
'SearchCity window',
'Please type more than three characters',
'Message in the statusbar'
)
)
return
self.lista = []
self.errorStatus = False
self.buttonOk.setEnabled(False)
self.listWidget.clear()
self.status.setText(self.search_string)
self.workThread = WorkThread(self.accurate_url, self.city, self.suffix)
self.workThread.setTerminationEnabled(True)
self.workThread.city_signal['QString'].connect(self.addlist)
self.workThread.finished.connect(self.result)
self.workThread.error['QString'].connect(self.error)
self.workThread.searching['QString'].connect(self.searching)
self.workThread.started.connect(self.thread_started)
self.timer.singleShot(self.delay, self.threadstart)
def searching(self, message):
'''Display a status message when searching takes a while'''
self.status.setText(message)
def thread_started(self):
'''Force the "searching" status message'''
self.status.setText(self.search_string)
def threadstart(self):
self.workThread.start()
def addlist(self, city):
logging.debug('Found: ' + str(city))
if city not in self.lista:
self.lista.append(city)
self.errorStatus = False
def error(self, e):
self.delay = 2000
logging.error(e)
self.status.setText(e)
self.adjustSize()
self.errorStatus = True
def result(self):
if self.errorStatus:
return
if len(self.line_search.text()) < 3:
self.thread_terminate()
self.status.clear()
return
self.delay = 1000
# Clear the listWidget elements from an interrupted thread
self.listWidget.clear()
self.listWidget.addItems(self.lista)
number_cities = len(self.lista)
cities_text = ''
if number_cities == 0:
cities_text = self.tr('No results')
elif number_cities == 1:
cities_text = self.tr('Found {0} city').format(number_cities)
elif number_cities > 1:
cities_text = self.tr('Found {0} cities').format(number_cities)
self.status.setText(cities_text)
class WorkThread(QThread):
error = pyqtSignal(['QString'])
city_signal = pyqtSignal(['QString'])
searching = pyqtSignal(['QString'])
def __init__(self, accurate_url, city, suffix, parent=None):
QThread.__init__(self, parent)
self.accurate_url = accurate_url
if city.count(',') == 1:
ccity = city.split(',')[0]
ccode = city.split(',')[1]
self.city = ccity + ',' + ccode.upper()
else:
self.city = city
self.suffix = suffix
self.tentatives = 1
self.settings = QSettings()
coordinates = city.split(',')
try:
coordinates[0], coordinates[1] = float(coordinates[0]), float(coordinates[1])
self.city = 'lat=' + str(coordinates[0]) + '&lon=' + str(coordinates[1])
self.accurate_url = self.accurate_url.replace('q=', '')
logging.debug('Search by geographic coordinates' + str(self.city))
except (ValueError, IndexError) as e:
logging.debug('Cannot find geographic coordinates' + str(e))
logging.debug('Search by city name' + str(self.city))
self.search_by_id = False
if self.city.isdecimal():
self.accurate_url = self.accurate_url.replace('find?q=', 'weather?id=')
self.search_by_id = True
logging.debug('Search city by ID')
def run(self):
use_proxy = self.settings.value('Proxy') or 'False'
use_proxy = eval(use_proxy)
proxy_auth = self.settings.value(
'Use_proxy_authentification'
) or 'False'
proxy_auth = eval(proxy_auth)
if use_proxy:
proxy_url = self.settings.value('Proxy_url')
proxy_port = self.settings.value('Proxy_port')
proxy_tot = 'http://' + ':' + proxy_port
if proxy_auth:
proxy_user = self.settings.value('Proxy_user')
proxy_password = self.settings.value('Proxy_pass')
proxy_tot = (
'http://' + proxy_user + ':' + proxy_password + '@'
+ proxy_url + ':' + proxy_port
)
proxy = urllib.request.ProxyHandler({"http": proxy_tot})
auth = urllib.request.HTTPBasicAuthHandler()
opener = urllib.request.build_opener(
proxy, auth, urllib.request.HTTPHandler
)
urllib.request.install_opener(opener)
else:
proxy_handler = urllib.request.ProxyHandler({})
opener = urllib.request.build_opener(proxy_handler)
urllib.request.install_opener(opener)
error_message = self.tr(
'Data error, please try again later\n'
'or modify the name of the city'
)
self.lista = []
if self.city == '':
return
try:
logging.debug(
self.accurate_url + repr(self.city.encode('utf-8'))
.replace("b'", "")
.replace("\\x", "%")
.replace("'", "")
.replace(' ', '%20')
+ self.suffix
)
logging.debug(
'City before utf8 encode: ' + self.accurate_url
+ self.city + self.suffix
)
req = urllib.request.urlopen(
self.accurate_url + repr(self.city.encode('utf-8'))
.replace("b'", "")
.replace("\\x", "%")
.replace("'", "")
.replace(' ', '%20')
+ self.suffix, timeout=5
)
page = req.read()
tree = etree.fromstring(page)
except timeout:
if self.tentatives == 10:
logging.error(error_message)
return
else:
self.tentatives += 1
searching_message = self.tr('Please wait, searching...')
logging.debug(searching_message)
self.searching['QString'].emit(searching_message)
self.run()
except (urllib.error.HTTPError, urllib.error.URLError) as error:
code = ''
if hasattr(error, 'code'):
code = str(error.code)
m_error = (
self.tr('Error: ') + code + ' ' + str(error.reason)
+ self.tr('\nTry again later')
)
if self.tentatives == 10:
self.error['QString'].emit(m_error)
return
else:
self.tentatives += 1
logging.debug('Tries: ' + str(self.tentatives))
self.run()
# No result
if self.search_by_id is False:
try:
if int(tree[1].text) == 0:
logging.debug('Number of cities: 0')
if self.tentatives == 10:
return
else:
self.tentatives += 1
logging.debug('Tries: ' + str(self.tentatives))
logging.debug('Try to retreive city information...')
self.run()
except:
return
for i in range(int(tree[1].text)):
city = tree[3][i][0].get('name')
country = tree[3][i][0][1].text
if country is None:
country = ''
id_ = tree[3][i][0].get('id')
lon = tree[3][i][0][0].get('lon')
lat = tree[3][i][0][0].get('lat')
if int(id_) == 0:
logging.error('Error ID: ' + str(id_))
if self.tentatives == 10:
self.error['QString'].emit(error_message)
return
else:
self.tentatives += 1
logging.debug('Tries: ' + str(self.tentatives))
logging.debug('Try to retrieve city information...')
# Try with a fuzzy city name
if city != '':
logging.info('Change search to:' + city)
self.city = (
repr(city.encode('utf-8'))
.replace("b'", "")
.replace("\\x", "%")
.replace("'", "")
.replace(' ', '%20')
)
self.run()
if city == '':
if self.tentatives == 10:
self.error['QString'].emit(error_message)
return
else:
self.tentatives += 1
logging.debug('Tries: ' + str(self.tentatives))
logging.debug('Try to retrieve city information...')
self.run()
try:
if id_ == '0':
continue
place = (
id_
+ ' - '
+ city
+ ' - '
+ country
+ ' - '
+ ' {0}° '
+ ','
+ ' {1}°'
).format(lat, lon)
if place in self.lista:
continue
self.lista.append(place)
except:
logging.critical('An error has occured:')
logging.critical('ID' + str(id_))
logging.critical('City' + str(city))
logging.critical('Country' + str(country))
return
else:
try:
city = tree[0].get('name')
except UnboundLocalError:
logging.debug(f"Could'nt find any city with the ID {self.city}")
return
if city == '':
if self.tentatives == 10:
self.error['QString'].emit(error_message)
return
else:
self.tentatives += 1
logging.debug('Tries: ' + str(self.tentatives))
logging.debug('Try to retrieve city information...')
self.run()
country = tree[0][1].text
if country is None:
country = ''
id_ = tree[0].get('id')
lon = tree[0][0].get('lon')
lat = tree[0][0].get('lat')
place = (
id_
+ ' - '
+ city
+ ' - '
+ country
+ ' - '
+ ' {0}° '
+ '-'
+ ' {1}°'
).format(lat, lon)
self.lista.append(place)
for i in self.lista:
self.city_signal['QString'].emit(i)
logging.debug('City thread done')