fix update function
This commit is contained in:
parent
bd26f574a5
commit
d041775e9a
@ -98,3 +98,4 @@ It forks from WordQuery, **multi-thread feature**, and some other features.
|
|||||||
- [mdict-query](https://github.com/mmjang/mdict-query)
|
- [mdict-query](https://github.com/mmjang/mdict-query)
|
||||||
- [pystardict](https://github.com/lig/pystardict)
|
- [pystardict](https://github.com/lig/pystardict)
|
||||||
- [WordQuery](https://github.com/finalion/WordQuery)
|
- [WordQuery](https://github.com/finalion/WordQuery)
|
||||||
|
- [AnkiHub](https://github.com/dayjaby/AnkiHub)
|
||||||
|
|||||||
@ -98,4 +98,9 @@ def window_shortcut(key_sequence):
|
|||||||
"""
|
"""
|
||||||
global my_shortcut
|
global my_shortcut
|
||||||
my_shortcut = key_sequence
|
my_shortcut = key_sequence
|
||||||
|
try:
|
||||||
|
from .ui import check_updates
|
||||||
|
check_updates()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
0
src/fastwq/libs/AnkiHub/__init__.py
Normal file
0
src/fastwq/libs/AnkiHub/__init__.py
Normal file
2584
src/fastwq/libs/AnkiHub/markdown2.py
Normal file
2584
src/fastwq/libs/AnkiHub/markdown2.py
Normal file
File diff suppressed because it is too large
Load Diff
68
src/fastwq/libs/AnkiHub/updates.py
Normal file
68
src/fastwq/libs/AnkiHub/updates.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Form implementation generated from reading ui file 'updates.ui'
|
||||||
|
#
|
||||||
|
# Created: Sat Sep 10 10:16:01 2016
|
||||||
|
# by: PyQt4 UI code generator 4.11.2
|
||||||
|
#
|
||||||
|
# WARNING! All changes made in this file will be lost!
|
||||||
|
|
||||||
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
|
try:
|
||||||
|
_fromUtf8 = QtCore.QString.fromUtf8
|
||||||
|
except AttributeError:
|
||||||
|
def _fromUtf8(s):
|
||||||
|
return s
|
||||||
|
|
||||||
|
try:
|
||||||
|
_encoding = QtGui.QApplication.UnicodeUTF8
|
||||||
|
def _translate(context, text, disambig):
|
||||||
|
return QtGui.QApplication.translate(context, text, disambig, _encoding)
|
||||||
|
except AttributeError:
|
||||||
|
def _translate(context, text, disambig):
|
||||||
|
return QtGui.QApplication.translate(context, text, disambig)
|
||||||
|
|
||||||
|
class Ui_DialogUpdates(object):
|
||||||
|
def setupUi(self, DialogUpdates):
|
||||||
|
DialogUpdates.setObjectName(_fromUtf8("DialogUpdates"))
|
||||||
|
DialogUpdates.resize(500, 400)
|
||||||
|
self.verticalLayout = QtGui.QVBoxLayout(DialogUpdates)
|
||||||
|
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
|
||||||
|
self.labelUpdates = QtGui.QLabel(DialogUpdates)
|
||||||
|
self.labelUpdates.setWordWrap(True)
|
||||||
|
self.labelUpdates.setOpenExternalLinks(True)
|
||||||
|
self.labelUpdates.setObjectName(_fromUtf8("labelUpdates"))
|
||||||
|
self.verticalLayout.addWidget(self.labelUpdates)
|
||||||
|
self.textBrowser = QtGui.QTextBrowser(DialogUpdates)
|
||||||
|
self.textBrowser.setObjectName(_fromUtf8("textBrowser"))
|
||||||
|
self.verticalLayout.addWidget(self.textBrowser)
|
||||||
|
self.horizontalLayout = QtGui.QHBoxLayout()
|
||||||
|
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
|
||||||
|
spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
|
||||||
|
self.horizontalLayout.addItem(spacerItem)
|
||||||
|
self.always = QtGui.QPushButton(DialogUpdates)
|
||||||
|
self.always.setObjectName(_fromUtf8("always"))
|
||||||
|
self.horizontalLayout.addWidget(self.always)
|
||||||
|
self.update = QtGui.QPushButton(DialogUpdates)
|
||||||
|
self.update.setObjectName(_fromUtf8("update"))
|
||||||
|
self.horizontalLayout.addWidget(self.update)
|
||||||
|
self.dont = QtGui.QPushButton(DialogUpdates)
|
||||||
|
self.dont.setObjectName(_fromUtf8("dont"))
|
||||||
|
self.horizontalLayout.addWidget(self.dont)
|
||||||
|
self.never = QtGui.QPushButton(DialogUpdates)
|
||||||
|
self.never.setObjectName(_fromUtf8("never"))
|
||||||
|
self.horizontalLayout.addWidget(self.never)
|
||||||
|
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||||
|
|
||||||
|
self.retranslateUi(DialogUpdates)
|
||||||
|
QtCore.QMetaObject.connectSlotsByName(DialogUpdates)
|
||||||
|
|
||||||
|
def retranslateUi(self, DialogUpdates):
|
||||||
|
DialogUpdates.setWindowTitle(_translate("DialogUpdates", "Update Checker", None))
|
||||||
|
self.labelUpdates.setText(_translate("DialogUpdates", "<html><head/><body><p>A new version of {0} is available for download! </p><p>Do you want to update {1}to version {2}?</p><p>Changes from your version are listed below:</p></body></html>", None))
|
||||||
|
self.always.setText(_translate("DialogUpdates", "Always update", None))
|
||||||
|
self.update.setText(_translate("DialogUpdates", "Update", None))
|
||||||
|
self.dont.setText(_translate("DialogUpdates", "Don\'t update", None))
|
||||||
|
self.never.setText(_translate("DialogUpdates", "Never", None))
|
||||||
|
|
||||||
@ -19,3 +19,4 @@
|
|||||||
|
|
||||||
from .mdict import IndexBuilder as MdxBuilder
|
from .mdict import IndexBuilder as MdxBuilder
|
||||||
from .pystardict import Dictionary as StardictBuilder
|
from .pystardict import Dictionary as StardictBuilder
|
||||||
|
import ankihub
|
||||||
|
|||||||
279
src/fastwq/libs/ankihub.py
Normal file
279
src/fastwq/libs/ankihub.py
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
from PyQt4 import QtCore,QtGui
|
||||||
|
import httplib
|
||||||
|
import urllib2
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import zipfile
|
||||||
|
import traceback
|
||||||
|
import io
|
||||||
|
from AnkiHub.updates import Ui_DialogUpdates
|
||||||
|
from AnkiHub.markdown2 import markdown
|
||||||
|
import aqt
|
||||||
|
from anki.hooks import addHook
|
||||||
|
from anki.utils import isMac, isWin
|
||||||
|
|
||||||
|
# taken from Anki's aqt/profiles.py
|
||||||
|
def defaultBase():
|
||||||
|
if isWin:
|
||||||
|
loc = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.DocumentsLocation)
|
||||||
|
return os.path.join(loc, "Anki")
|
||||||
|
elif isMac:
|
||||||
|
return os.path.expanduser("~/Documents/Anki")
|
||||||
|
else:
|
||||||
|
p = os.path.expanduser("~/Anki")
|
||||||
|
if os.path.exists(p):
|
||||||
|
return p
|
||||||
|
else:
|
||||||
|
loc = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.DocumentsLocation)
|
||||||
|
if loc[:-1] == QtGui.QDesktopServices.storageLocation(
|
||||||
|
QtGui.QDesktopServices.HomeLocation):
|
||||||
|
return os.path.expanduser("~/Documents/Anki")
|
||||||
|
else:
|
||||||
|
return os.path.join(loc, "Anki")
|
||||||
|
|
||||||
|
headers = {"User-Agent": "AnkiHub"}
|
||||||
|
dataPath = os.path.join(defaultBase(),'.ankihub.json')
|
||||||
|
|
||||||
|
|
||||||
|
class DialogUpdates(QtGui.QDialog, Ui_DialogUpdates):
|
||||||
|
def __init__(self, parent, data, oldData, callback, automaticAnswer=None,install=False):
|
||||||
|
QtGui.QDialog.__init__(self,parent)
|
||||||
|
self.setupUi(self)
|
||||||
|
totalSize = sum(map(lambda x:x['size'],data['assets']))
|
||||||
|
|
||||||
|
def answer(doUpdate,answ):
|
||||||
|
callback(doUpdate,answ,self.appendHtml,self.close,install)
|
||||||
|
|
||||||
|
self.html = u''
|
||||||
|
self.appendHtml(markdown(data['body']))
|
||||||
|
|
||||||
|
if not automaticAnswer:
|
||||||
|
self.connect(self.update,QtCore.SIGNAL('clicked()'),
|
||||||
|
lambda:answer(True,'ask'))
|
||||||
|
self.connect(self.dont,QtCore.SIGNAL('clicked()'),
|
||||||
|
lambda:answer(False,'ask'))
|
||||||
|
self.connect(self.always,QtCore.SIGNAL('clicked()'),
|
||||||
|
lambda:answer(True,'always'))
|
||||||
|
self.connect(self.never,QtCore.SIGNAL('clicked()'),
|
||||||
|
lambda:answer(False,'never'))
|
||||||
|
else:
|
||||||
|
self.update.setEnabled(False)
|
||||||
|
self.dont.setEnabled(False)
|
||||||
|
self.always.setEnabled(False)
|
||||||
|
self.never.setEnabled(False)
|
||||||
|
answer(True,automaticAnswer)
|
||||||
|
|
||||||
|
fromVersion = ''
|
||||||
|
if 'tag_name' in oldData:
|
||||||
|
fromVersion = u'from {0} '.format(oldData['tag_name'])
|
||||||
|
self.labelUpdates.setText(
|
||||||
|
unicode(self.labelUpdates.text()).format(
|
||||||
|
data['name'],
|
||||||
|
fromVersion,
|
||||||
|
data['tag_name']))
|
||||||
|
|
||||||
|
|
||||||
|
def appendHtml(self,html='',temp=''):
|
||||||
|
self.html += html
|
||||||
|
self.textBrowser.setHtml(u'<html><body>{0}{1}</body></html>'.format(self.html,temp))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def installZipFile(data, fname):
|
||||||
|
base = os.path.join(defaultBase(),'addons')
|
||||||
|
if fname.endswith(".py"):
|
||||||
|
path = os.path.join(base, fname)
|
||||||
|
open(path, "wb").write(data)
|
||||||
|
return True
|
||||||
|
# .zip file
|
||||||
|
try:
|
||||||
|
z = zipfile.ZipFile(io.BytesIO(data))
|
||||||
|
except zipfile.BadZipfile:
|
||||||
|
return False
|
||||||
|
for n in z.namelist():
|
||||||
|
if n.endswith("/"):
|
||||||
|
# folder; ignore
|
||||||
|
continue
|
||||||
|
# write
|
||||||
|
z.extract(n, base)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def asset(a):
|
||||||
|
return {
|
||||||
|
'url': a['browser_download_url'],
|
||||||
|
'size': a['size']
|
||||||
|
}
|
||||||
|
|
||||||
|
profileLoaded = True
|
||||||
|
def _profileLoaded():
|
||||||
|
profileLoaded = True
|
||||||
|
|
||||||
|
addHook("profileLoaded",_profileLoaded)
|
||||||
|
|
||||||
|
def updateSingle(repositories,path,data):
|
||||||
|
def callback(doUpdate,answer,appendHtml,onReady,install):
|
||||||
|
if doUpdate:
|
||||||
|
for asset in data['assets']:
|
||||||
|
code = asset['url']
|
||||||
|
p, fname = os.path.split(code)
|
||||||
|
response = urllib2.urlopen(code)
|
||||||
|
meta = response.info()
|
||||||
|
file_size = int(meta.getheaders("Content-Length")[0])
|
||||||
|
d = buffer('')
|
||||||
|
dl = 0
|
||||||
|
i = 0
|
||||||
|
lastPercent = None
|
||||||
|
while True:
|
||||||
|
dkb = response.read(1024)
|
||||||
|
if not dkb:
|
||||||
|
break
|
||||||
|
dl += len(dkb)
|
||||||
|
d += dkb
|
||||||
|
if dl*100/file_size>i:
|
||||||
|
lastPercent = int(dl*100/file_size)
|
||||||
|
i = lastPercent+1
|
||||||
|
appendHtml(temp='<br />Downloading {1}: {0}%<br/>'.format(lastPercent,fname))
|
||||||
|
QtGui.QApplication.instance().processEvents()
|
||||||
|
appendHtml('<br />Downloading {1}: 100%<br/>'.format(int(dl*100/file_size),fname))
|
||||||
|
def installData():
|
||||||
|
if install:
|
||||||
|
filesBefore = aqt.mw.addonManager.files()
|
||||||
|
#directoriesBefore = aqt.mw.addonManager.directories()
|
||||||
|
if not installZipFile(d,fname):
|
||||||
|
appendHtml('Corrupt file<br/>')
|
||||||
|
else:
|
||||||
|
repositories[path] = data
|
||||||
|
repositories[path]['update'] = answer
|
||||||
|
with open(dataPath,'w') as file:
|
||||||
|
json.dump(repositories,file,indent=2)
|
||||||
|
if install:
|
||||||
|
appendHtml('Executing new scripts...<br/>')
|
||||||
|
newFiles = set(aqt.mw.addonManager.files()) - set(filesBefore)
|
||||||
|
#newDirectories = set(aqt.mw.addonManager.directories()) - set(directoriesBefore)
|
||||||
|
onReady() # close the AnkiHub update window
|
||||||
|
for file in newFiles:
|
||||||
|
try:
|
||||||
|
__import__(file.replace(".py", ""))
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
#for directory in newDirectories:
|
||||||
|
# try:
|
||||||
|
# __import__(directory)
|
||||||
|
# except:
|
||||||
|
# traceback.print_exc()
|
||||||
|
aqt.mw.addonManager.rebuildAddonsMenu()
|
||||||
|
else:
|
||||||
|
onReady() # close the AnkiHub update window
|
||||||
|
|
||||||
|
installData()
|
||||||
|
else:
|
||||||
|
repositories[path]['update'] = answer
|
||||||
|
with open(dataPath,'w') as file:
|
||||||
|
json.dump(repositories,file,indent=2)
|
||||||
|
onReady()
|
||||||
|
return callback
|
||||||
|
|
||||||
|
datas = []
|
||||||
|
|
||||||
|
def update(add=[],install=False):
|
||||||
|
conn = httplib.HTTPSConnection("api.github.com")
|
||||||
|
try:
|
||||||
|
with open(dataPath,'r') as file:
|
||||||
|
repositories = json.load(file)
|
||||||
|
except:
|
||||||
|
repositories = {}
|
||||||
|
# 'dayjaby/AnkiHub': {
|
||||||
|
# 'id': 4089471,
|
||||||
|
# 'update': 'ask'
|
||||||
|
# }
|
||||||
|
#}
|
||||||
|
|
||||||
|
for a in add:
|
||||||
|
if a not in repositories:
|
||||||
|
repositories[a] = {
|
||||||
|
'id': 0,
|
||||||
|
'update': 'ask'
|
||||||
|
}
|
||||||
|
|
||||||
|
for path,repository in repositories.items():
|
||||||
|
username,repositoryName = path.split('/')
|
||||||
|
if repository['update'] != 'never':
|
||||||
|
try:
|
||||||
|
url = "https://api.github.com/repos/{0}/releases/latest".format(path)
|
||||||
|
responseData = urllib2.urlopen(url, timeout=10).read()
|
||||||
|
release = json.loads(responseData)
|
||||||
|
datas.append(responseData)
|
||||||
|
except Exception as e:
|
||||||
|
datas.append(e)
|
||||||
|
release = {}
|
||||||
|
|
||||||
|
if 'id' in release:
|
||||||
|
if release['id'] != repository['id']:
|
||||||
|
data = {
|
||||||
|
'id': release['id'],
|
||||||
|
'name': repositoryName,
|
||||||
|
'tag_name': release['tag_name'],
|
||||||
|
'body': '### {0}\n'.format(release['name']) + release['body'],
|
||||||
|
'assets': map(asset,release['assets']),
|
||||||
|
'update': 'ask'
|
||||||
|
}
|
||||||
|
if 'tag_name' in repository:
|
||||||
|
oldVersion = map(int,repository['tag_name'][1:].split('.'))
|
||||||
|
while len(oldVersion)<3:
|
||||||
|
oldVersion.append(0)
|
||||||
|
else:
|
||||||
|
oldVersion = [0,0,0]
|
||||||
|
newVersion = map(int,data['tag_name'][1:].split('.'))
|
||||||
|
isMinor = len(newVersion)>2 and newVersion[2]>0
|
||||||
|
while len(newVersion)<3:
|
||||||
|
newVersion.append(0)
|
||||||
|
i = oldVersion[2]+1
|
||||||
|
if oldVersion[0]<newVersion[0] or oldVersion[1]<newVersion[1]:
|
||||||
|
if isMinor:
|
||||||
|
i = 1
|
||||||
|
while i<newVersion[2]:
|
||||||
|
minorTagName = 'v{0}.{1}.{2}'.format(newVersion[0],oldVersion[1],i)
|
||||||
|
response = urllib2.urlopen("https://api.github.com/repos/{0}/releases/tags/{1}".format(path,minorTagName))
|
||||||
|
responseData = response.read()
|
||||||
|
minor = json.loads(responseData)
|
||||||
|
data['body'] += '\n\n### {0}\n'.format(minor['name']) + minor['body']
|
||||||
|
data['assets'] += map(asset,minor['assets'])
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
if oldVersion[0]<newVersion[0] or oldVersion[1]<newVersion[1]:
|
||||||
|
# new major release necessary!
|
||||||
|
if isMinor: # if the newest version is minor, fetch the additional assets from the major
|
||||||
|
majorTagName = 'v{0}.{1}'.format(newVersion[0],newVersion[1])
|
||||||
|
try:
|
||||||
|
response = urllib2.urlopen("https://api.github.com/repos/{0}/releases/tags/{1}".format(path,majorTagName))
|
||||||
|
except:
|
||||||
|
response = urllib2.urlopen("https://api.github.com/repos/{0}/releases/tags/{1}.0".format(path,majorTagName))
|
||||||
|
responseData = response.read()
|
||||||
|
major = json.loads(responseData)
|
||||||
|
data['body'] += '\n\n### {0}\n'.format(major['name']) + major['body']
|
||||||
|
data['assets'] += map(asset,major['assets'])
|
||||||
|
|
||||||
|
if repository['update'] == 'always':
|
||||||
|
dialog = DialogUpdates(None,data,repository,updateSingle(repositories,path,data),'always')
|
||||||
|
elif install:
|
||||||
|
dialog = DialogUpdates(None,data,repository,updateSingle(repositories,path,data),'ask',install=True)
|
||||||
|
else:
|
||||||
|
dialog = DialogUpdates(None,data,repository,updateSingle(repositories,path,data))
|
||||||
|
dialog.exec_()
|
||||||
|
with open(dataPath,'w') as file:
|
||||||
|
json.dump(repositories,file,indent=2)
|
||||||
|
|
||||||
|
#update()
|
||||||
|
|
||||||
|
#def addRepository():
|
||||||
|
# repo, ok = QtGui.QInputDialog.getText(aqt.mw,'Add GitHub repository',
|
||||||
|
# 'Path:',text='<name>/<repository>')
|
||||||
|
# if repo and ok:
|
||||||
|
# update([repo],install=True)
|
||||||
|
#
|
||||||
|
#firstAction = aqt.mw.form.menuPlugins.actions()[0]
|
||||||
|
#action = QtGui.QAction('From GitHub', aqt.mw)
|
||||||
|
#action.setIconVisibleInMenu(True)
|
||||||
|
#action.triggered.connect(addRepository)
|
||||||
|
#aqt.mw.form.menuPlugins.insertAction(firstAction,action)
|
||||||
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import json
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
import anki
|
import anki
|
||||||
@ -206,7 +207,7 @@ class OptionsDialog(QDialog):
|
|||||||
about_btn.clicked.connect(self.show_about)
|
about_btn.clicked.connect(self.show_about)
|
||||||
# about_btn.clicked.connect(self.show_paras)
|
# about_btn.clicked.connect(self.show_paras)
|
||||||
chk_update_btn = QPushButton(_('UPDATE'))
|
chk_update_btn = QPushButton(_('UPDATE'))
|
||||||
chk_update_btn.clicked.connect(self.check_updates)
|
chk_update_btn.clicked.connect(check_updates)
|
||||||
home_label = QLabel(
|
home_label = QLabel(
|
||||||
'<a href="{url}">User Guide</a>'.format(url=Endpoint.user_guide))
|
'<a href="{url}">User Guide</a>'.format(url=Endpoint.user_guide))
|
||||||
home_label.setOpenExternalLinks(True)
|
home_label.setOpenExternalLinks(True)
|
||||||
@ -458,45 +459,13 @@ class OptionsDialog(QDialog):
|
|||||||
data['last_model'] = self.current_model['id']
|
data['last_model'] = self.current_model['id']
|
||||||
config.update(data)
|
config.update(data)
|
||||||
|
|
||||||
def check_updates(self):
|
|
||||||
|
|
||||||
self.updater = Updater()
|
def check_updates():
|
||||||
self.updater.chk_finish_signal.connect(self._show_update_result)
|
try:
|
||||||
self.updater.start()
|
from .libs import ankihub
|
||||||
|
ankihub.update(['sth2018/FastWordQuery'])
|
||||||
@pyqtSlot(dict)
|
except:
|
||||||
def _show_update_result(self, data):
|
pass
|
||||||
if data['result'] == 'ok':
|
|
||||||
version = data['version']
|
|
||||||
if version > VERSION:
|
|
||||||
showInfo(Template.new_version.format(version=version))
|
|
||||||
elif version == VERSION:
|
|
||||||
showInfo(Template.latest_version)
|
|
||||||
else:
|
|
||||||
showInfo(Template.abnormal_version)
|
|
||||||
else:
|
|
||||||
showInfo(Template.check_failure.format(msg=data['msg']))
|
|
||||||
|
|
||||||
|
|
||||||
class Updater(QThread):
|
|
||||||
chk_finish_signal = pyqtSignal(dict)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(QThread, self).__init__()
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
import urllib2
|
|
||||||
try:
|
|
||||||
req = urllib2.Request(Endpoint.check_version)
|
|
||||||
req.add_header('Pragma', 'no-cache')
|
|
||||||
resp = urllib2.urlopen(req, timeout=10)
|
|
||||||
version = resp.read().strip()
|
|
||||||
data = {'result': 'ok', 'version': version}
|
|
||||||
except:
|
|
||||||
info = _('CHECK_FAILURE')
|
|
||||||
data = {'result': 'error', 'msg': info}
|
|
||||||
|
|
||||||
self.chk_finish_signal.emit(data)
|
|
||||||
|
|
||||||
|
|
||||||
def show_options():
|
def show_options():
|
||||||
@ -505,4 +474,3 @@ def show_options():
|
|||||||
opt_dialog.exec_()
|
opt_dialog.exec_()
|
||||||
opt_dialog.activateWindow()
|
opt_dialog.activateWindow()
|
||||||
opt_dialog.raise_()
|
opt_dialog.raise_()
|
||||||
# service_manager.fetch_headers()
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user