forked from openlp/openlp
Refactor version check threading
This commit is contained in:
parent
cd158b63fd
commit
a6324b6b7f
@ -20,13 +20,17 @@
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import sys
|
||||
"""
|
||||
The entrypoint for OpenLP
|
||||
"""
|
||||
import faulthandler
|
||||
import multiprocessing
|
||||
import sys
|
||||
|
||||
from openlp.core.common import is_win, is_macosx
|
||||
from openlp.core import main
|
||||
|
||||
faulthandler.enable()
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""
|
||||
|
@ -26,21 +26,19 @@ The :mod:`core` module provides all core application functions
|
||||
All the core functions of the OpenLP application including the GUI, settings,
|
||||
logging and a plugin framework are contained within the openlp.core module.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from traceback import format_exception
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, OpenLPMixin, AppLocation, LanguageManager, Settings, UiStrings, \
|
||||
check_directory_exists, is_macosx, is_win, translate
|
||||
from openlp.core.common.versionchecker import VersionThread, get_application_version
|
||||
from openlp.core.version import check_for_update, get_version
|
||||
from openlp.core.lib import ScreenList
|
||||
from openlp.core.resources import qInitResources
|
||||
from openlp.core.ui import SplashScreen
|
||||
@ -154,8 +152,8 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
|
||||
self.processEvents()
|
||||
if not has_run_wizard:
|
||||
self.main_window.first_time()
|
||||
version = VersionThread(self.main_window)
|
||||
version.start()
|
||||
if Settings().value('core/update check'):
|
||||
check_for_update(self.main_window)
|
||||
self.main_window.is_display_blank()
|
||||
self.main_window.app_startup()
|
||||
return self.exec()
|
||||
@ -183,22 +181,18 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
|
||||
data_folder_path = str(AppLocation.get_data_path())
|
||||
if not os.path.exists(data_folder_path):
|
||||
log.critical('Database was not found in: ' + data_folder_path)
|
||||
status = QtWidgets.QMessageBox.critical(None, translate('OpenLP', 'Data Directory Error'),
|
||||
translate('OpenLP', 'OpenLP data folder was not found in:\n\n{path}'
|
||||
'\n\nThe location of the data folder was '
|
||||
'previously changed from the OpenLP\'s '
|
||||
'default location. If the data was stored on '
|
||||
'removable device, that device needs to be '
|
||||
'made available.\n\nYou may reset the data '
|
||||
'location back to the default location, '
|
||||
'or you can try to make the current location '
|
||||
'available.\n\nDo you want to reset to the '
|
||||
'default data location? If not, OpenLP will be '
|
||||
'closed so you can try to fix the the problem.')
|
||||
.format(path=data_folder_path),
|
||||
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
|
||||
QtWidgets.QMessageBox.No),
|
||||
QtWidgets.QMessageBox.No)
|
||||
status = QtWidgets.QMessageBox.critical(
|
||||
None, translate('OpenLP', 'Data Directory Error'),
|
||||
translate('OpenLP', 'OpenLP data folder was not found in:\n\n{path}\n\nThe location of the data '
|
||||
'folder was previously changed from the OpenLP\'s default location. If the data was '
|
||||
'stored on removable device, that device needs to be made available.\n\nYou may reset '
|
||||
'the data location back to the default location, or you can try to make the current '
|
||||
'location available.\n\nDo you want to reset to the default data location? If not, '
|
||||
'OpenLP will be closed so you can try to fix the the problem.').format(
|
||||
path=data_folder_path),
|
||||
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
|
||||
QtWidgets.QMessageBox.No
|
||||
)
|
||||
if status == QtWidgets.QMessageBox.No:
|
||||
# If answer was "No", return "True", it will shutdown OpenLP in def main
|
||||
log.info('User requested termination')
|
||||
@ -239,7 +233,7 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
|
||||
:param can_show_splash: Should OpenLP show the splash screen
|
||||
"""
|
||||
data_version = Settings().value('core/application version')
|
||||
openlp_version = get_application_version()['version']
|
||||
openlp_version = get_version()['version']
|
||||
# New installation, no need to create backup
|
||||
if not has_run_wizard:
|
||||
Settings().setValue('core/application version', openlp_version)
|
||||
@ -415,7 +409,7 @@ def main(args=None):
|
||||
Registry.create()
|
||||
Registry().register('application', application)
|
||||
Registry().set_flag('no_web_server', args.no_web_server)
|
||||
application.setApplicationVersion(get_application_version()['version'])
|
||||
application.setApplicationVersion(get_version()['version'])
|
||||
# Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one
|
||||
if application.is_already_running():
|
||||
sys.exit()
|
||||
|
@ -27,7 +27,7 @@ import logging
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings
|
||||
from openlp.core.common.versionchecker import get_application_version
|
||||
from openlp.core.version import get_version
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -139,7 +139,7 @@ class Plugin(QtCore.QObject, RegistryProperties):
|
||||
if version:
|
||||
self.version = version
|
||||
else:
|
||||
self.version = get_application_version()['version']
|
||||
self.version = get_version()['version']
|
||||
self.settings_section = self.name
|
||||
self.icon = None
|
||||
self.media_item_class = media_item_class
|
||||
|
55
openlp/core/threading.py
Normal file
55
openlp/core/threading.py
Normal file
@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program 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; version 2 of the License. #
|
||||
# #
|
||||
# This program 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, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
The :mod:`openlp.core.threading` module contains some common threading code
|
||||
"""
|
||||
from PyQt5 import QtCore
|
||||
|
||||
|
||||
def run_thread(parent, worker, prefix='', auto_start=True):
|
||||
"""
|
||||
Create a thread and assign a worker to it. This removes a lot of boilerplate code from the codebase.
|
||||
|
||||
:param object parent: The parent object so that the thread and worker are not orphaned.
|
||||
:param QObject worker: A QObject-based worker object which does the actual work.
|
||||
:param str prefix: A prefix to be applied to the attribute names.
|
||||
:param bool auto_start: Automatically start the thread. Defaults to True.
|
||||
"""
|
||||
# Set up attribute names
|
||||
thread_name = 'thread'
|
||||
worker_name = 'worker'
|
||||
if prefix:
|
||||
thread_name = '_'.join([prefix, thread_name])
|
||||
worker_name = '_'.join([prefix, worker_name])
|
||||
# Create the thread and add the thread and the worker to the parent
|
||||
thread = QtCore.QThread()
|
||||
setattr(parent, thread_name, thread)
|
||||
setattr(parent, worker_name, worker)
|
||||
# Move the worker into the thread's context
|
||||
worker.moveToThread(thread)
|
||||
# Connect slots and signals
|
||||
parent.version_thread.started.connect(parent.version_worker.start)
|
||||
parent.version_worker.quit.connect(parent.version_thread.quit)
|
||||
parent.version_worker.quit.connect(parent.version_worker.deleteLater)
|
||||
parent.version_thread.finished.connect(parent.version_thread.deleteLater)
|
||||
if auto_start:
|
||||
parent.version_thread.start()
|
@ -26,7 +26,7 @@ import webbrowser
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common.versionchecker import get_application_version
|
||||
from openlp.core.version import get_version
|
||||
from openlp.core.lib import translate
|
||||
from .aboutdialog import UiAboutDialog
|
||||
|
||||
@ -49,7 +49,7 @@ class AboutForm(QtWidgets.QDialog, UiAboutDialog):
|
||||
Set up the dialog. This method is mocked out in tests.
|
||||
"""
|
||||
self.setup_ui(self)
|
||||
application_version = get_application_version()
|
||||
application_version = get_version()
|
||||
about_text = self.about_text_edit.toPlainText()
|
||||
about_text = about_text.replace('<version>', application_version['version'])
|
||||
if application_version['build']:
|
||||
|
@ -71,7 +71,7 @@ except ImportError:
|
||||
VLC_VERSION = '-'
|
||||
|
||||
from openlp.core.common import Settings, UiStrings, translate
|
||||
from openlp.core.common.versionchecker import get_application_version
|
||||
from openlp.core.version import get_version
|
||||
from openlp.core.common import RegistryProperties, is_linux
|
||||
|
||||
from .exceptiondialog import Ui_ExceptionDialog
|
||||
@ -110,7 +110,7 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties):
|
||||
"""
|
||||
Create an exception report.
|
||||
"""
|
||||
openlp_version = get_application_version()
|
||||
openlp_version = get_version()
|
||||
description = self.description_text_edit.toPlainText()
|
||||
traceback = self.exception_text_edit.toPlainText()
|
||||
system = translate('OpenLP.ExceptionForm', 'Platform: {platform}\n').format(platform=platform.platform())
|
||||
|
@ -164,7 +164,6 @@ class GeneralTab(SettingsTab):
|
||||
self.startup_layout.addWidget(self.show_splash_check_box)
|
||||
self.check_for_updates_check_box = QtWidgets.QCheckBox(self.startup_group_box)
|
||||
self.check_for_updates_check_box.setObjectName('check_for_updates_check_box')
|
||||
self.check_for_updates_check_box.setVisible(False)
|
||||
self.startup_layout.addWidget(self.check_for_updates_check_box)
|
||||
self.right_layout.addWidget(self.startup_group_box)
|
||||
# Logo
|
||||
|
@ -40,7 +40,6 @@ from openlp.core.api.http import server
|
||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, UiStrings, \
|
||||
check_directory_exists, translate, is_win, is_macosx, add_actions
|
||||
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||
from openlp.core.common.versionchecker import get_application_version
|
||||
from openlp.core.lib import Renderer, PluginManager, ImageManager, PluginStatus, ScreenList, build_icon
|
||||
from openlp.core.lib.ui import create_action
|
||||
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
|
||||
@ -51,6 +50,7 @@ from openlp.core.ui.printserviceform import PrintServiceForm
|
||||
from openlp.core.ui.projector.manager import ProjectorManager
|
||||
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
|
||||
from openlp.core.ui.lib.mediadockmanager import MediaDockManager
|
||||
from openlp.core.version import get_version
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -487,7 +487,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
"""
|
||||
The main window.
|
||||
"""
|
||||
openlp_version_check = QtCore.pyqtSignal(QtCore.QVariant)
|
||||
log.info('MainWindow loaded')
|
||||
|
||||
def __init__(self):
|
||||
@ -561,7 +560,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
self.application.set_busy_cursor()
|
||||
# Simple message boxes
|
||||
Registry().register_function('theme_update_global', self.default_theme_changed)
|
||||
self.openlp_version_check.connect(self.version_notice)
|
||||
Registry().register_function('config_screen_changed', self.screen_changed)
|
||||
Registry().register_function('bootstrap_post_set_up', self.bootstrap_post_set_up)
|
||||
# Reset the cursor
|
||||
@ -587,6 +585,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
if saved_plugin_id != -1:
|
||||
self.media_tool_box.setCurrentIndex(saved_plugin_id)
|
||||
|
||||
def on_new_version_number(self, version_number):
|
||||
"""
|
||||
Called when the version check thread completes and we need to check the version number
|
||||
|
||||
:param str version_number: The version number downloaded from the OpenLP server.
|
||||
"""
|
||||
|
||||
def on_search_shortcut_triggered(self):
|
||||
"""
|
||||
Called when the search shortcut has been pressed.
|
||||
@ -606,7 +611,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
if widget:
|
||||
widget.on_focus()
|
||||
|
||||
def version_notice(self, version):
|
||||
def on_new_version(self, version):
|
||||
"""
|
||||
Notifies the user that a newer version of OpenLP is available.
|
||||
Triggered by delay thread and cannot display popup.
|
||||
@ -616,7 +621,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
log.debug('version_notice')
|
||||
version_text = translate('OpenLP.MainWindow', 'Version {new} of OpenLP is now available for download (you are '
|
||||
'currently running version {current}). \n\nYou can download the latest version from '
|
||||
'http://openlp.org/.').format(new=version, current=get_application_version()[u'full'])
|
||||
'http://openlp.org/.').format(new=version, current=get_version()[u'full'])
|
||||
QtWidgets.QMessageBox.question(self, translate('OpenLP.MainWindow', 'OpenLP Version Updated'), version_text)
|
||||
|
||||
def show(self):
|
||||
@ -973,7 +978,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
# Add a header section.
|
||||
# This is to insure it's our conf file for import.
|
||||
now = datetime.now()
|
||||
application_version = get_application_version()
|
||||
application_version = get_version()
|
||||
# Write INI format using Qsettings.
|
||||
# Write our header.
|
||||
export_settings.beginGroup(self.header_section)
|
||||
|
@ -20,24 +20,21 @@
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
The :mod:`openlp.core.common` module downloads the version details for OpenLP.
|
||||
The :mod:`openlp.core.version` module downloads the version details for OpenLP.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from datetime import datetime
|
||||
from distutils.version import LooseVersion
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
import requests
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import AppLocation, Registry, Settings
|
||||
from openlp.core.common.httputils import ping
|
||||
from openlp.core.common import AppLocation, Settings
|
||||
from openlp.core.threading import run_thread
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -46,42 +43,87 @@ CONNECTION_TIMEOUT = 30
|
||||
CONNECTION_RETRIES = 2
|
||||
|
||||
|
||||
class VersionThread(QtCore.QThread):
|
||||
class VersionWorker(QtCore.QObject):
|
||||
"""
|
||||
A special Qt thread class to fetch the version of OpenLP from the website.
|
||||
This is threaded so that it doesn't affect the loading time of OpenLP.
|
||||
A worker class to fetch the version of OpenLP from the website. This is run from within a thread so that it
|
||||
doesn't affect the loading time of OpenLP.
|
||||
"""
|
||||
def __init__(self, main_window):
|
||||
"""
|
||||
Constructor for the thread class.
|
||||
new_version = QtCore.pyqtSignal(dict)
|
||||
no_internet = QtCore.pyqtSignal()
|
||||
quit = QtCore.pyqtSignal()
|
||||
|
||||
:param main_window: The main window Object.
|
||||
def __init__(self, last_check_date):
|
||||
"""
|
||||
log.debug("VersionThread - Initialise")
|
||||
super(VersionThread, self).__init__(None)
|
||||
self.main_window = main_window
|
||||
Constructor for the version check worker.
|
||||
|
||||
def run(self):
|
||||
:param string last_check_date: The last day we checked for a new version of OpenLP
|
||||
"""
|
||||
Run the thread.
|
||||
log.debug('VersionWorker - Initialise')
|
||||
super(VersionWorker, self).__init__(None)
|
||||
self.last_check_date = last_check_date
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
self.sleep(1)
|
||||
log.debug('Version thread - run')
|
||||
found = ping("openlp.io")
|
||||
Registry().set_flag('internet_present', found)
|
||||
update_check = Settings().value('core/update check')
|
||||
if found:
|
||||
Registry().execute('get_website_version')
|
||||
if update_check:
|
||||
app_version = get_application_version()
|
||||
version = check_latest_version(app_version)
|
||||
log.debug("Versions {version1} and {version2} ".format(version1=LooseVersion(str(version)),
|
||||
version2=LooseVersion(str(app_version['full']))))
|
||||
if LooseVersion(str(version)) > LooseVersion(str(app_version['full'])):
|
||||
self.main_window.openlp_version_check.emit('{version}'.format(version=version))
|
||||
Check the latest version of OpenLP against the version file on the OpenLP site.
|
||||
|
||||
**Rules around versions and version files:**
|
||||
|
||||
* If a version number has a build (i.e. -bzr1234), then it is a nightly.
|
||||
* If a version number's minor version is an odd number, it is a development release.
|
||||
* If a version number's minor version is an even number, it is a stable release.
|
||||
"""
|
||||
log.debug('VersionWorker - Start')
|
||||
# I'm not entirely sure why this was here, I'm commenting it out until I hit the same scenario
|
||||
# time.sleep(1)
|
||||
current_version = get_version()
|
||||
download_url = 'http://www.openlp.org/files/version.txt'
|
||||
if current_version['build']:
|
||||
download_url = 'http://www.openlp.org/files/nightly_version.txt'
|
||||
elif int(current_version['version'].split('.')[1]) % 2 != 0:
|
||||
download_url = 'http://www.openlp.org/files/dev_version.txt'
|
||||
headers = {
|
||||
'User-Agent', 'OpenLP/{version} {system}/{release}; '.format(version=current_version['full'],
|
||||
system=platform.system(),
|
||||
release=platform.release())
|
||||
}
|
||||
remote_version = None
|
||||
retries = 0
|
||||
while retries < 3:
|
||||
try:
|
||||
response = requests.get(download_url, headers=headers)
|
||||
remote_version = response.text
|
||||
log.debug('New version found: %s', remote_version)
|
||||
break
|
||||
except requests.exceptions.ConnectionError:
|
||||
log.exception('Unable to connect to OpenLP server to download version file')
|
||||
self.no_internet.emit()
|
||||
retries += 1
|
||||
except requests.exceptions.RequestException:
|
||||
log.exception('Error occurred while connecting to OpenLP server to download version file')
|
||||
retries += 1
|
||||
if remote_version and LooseVersion(remote_version) > LooseVersion(current_version['full']):
|
||||
self.new_version.emit(remote_version)
|
||||
self.quit.emit()
|
||||
|
||||
|
||||
def get_application_version():
|
||||
def check_for_update(parent):
|
||||
"""
|
||||
Run a thread to download and check the version of OpenLP
|
||||
|
||||
:param MainWindow parent: The parent object for the thread. Usually the OpenLP main window.
|
||||
"""
|
||||
last_check_date = Settings().value('core/last version test')
|
||||
if datetime.date().strftime('%Y-%m-%d') <= last_check_date:
|
||||
log.debug('Version check skipped, last checked today')
|
||||
return
|
||||
worker = VersionWorker(last_check_date)
|
||||
worker.new_version.connect(parent.on_new_version)
|
||||
# TODO: Use this to figure out if there's an Internet connection?
|
||||
# worker.no_internet.connect(parent.on_no_internet)
|
||||
run_thread(parent, worker, 'version')
|
||||
|
||||
|
||||
def get_version():
|
||||
"""
|
||||
Returns the application version of the running instance of OpenLP::
|
||||
|
||||
@ -150,55 +192,3 @@ def get_application_version():
|
||||
else:
|
||||
log.info('Openlp version {version}'.format(version=APPLICATION_VERSION['version']))
|
||||
return APPLICATION_VERSION
|
||||
|
||||
|
||||
def check_latest_version(current_version):
|
||||
"""
|
||||
Check the latest version of OpenLP against the version file on the OpenLP
|
||||
site.
|
||||
|
||||
**Rules around versions and version files:**
|
||||
|
||||
* If a version number has a build (i.e. -bzr1234), then it is a nightly.
|
||||
* If a version number's minor version is an odd number, it is a development release.
|
||||
* If a version number's minor version is an even number, it is a stable release.
|
||||
|
||||
:param current_version: The current version of OpenLP.
|
||||
"""
|
||||
version_string = current_version['full']
|
||||
# set to prod in the distribution config file.
|
||||
settings = Settings()
|
||||
settings.beginGroup('core')
|
||||
last_test = settings.value('last version test')
|
||||
this_test = str(datetime.now().date())
|
||||
settings.setValue('last version test', this_test)
|
||||
settings.endGroup()
|
||||
if last_test != this_test:
|
||||
if current_version['build']:
|
||||
req = urllib.request.Request('http://www.openlp.org/files/nightly_version.txt')
|
||||
else:
|
||||
version_parts = current_version['version'].split('.')
|
||||
if int(version_parts[1]) % 2 != 0:
|
||||
req = urllib.request.Request('http://www.openlp.org/files/dev_version.txt')
|
||||
else:
|
||||
req = urllib.request.Request('http://www.openlp.org/files/version.txt')
|
||||
req.add_header('User-Agent', 'OpenLP/{version} {system}/{release}; '.format(version=current_version['full'],
|
||||
system=platform.system(),
|
||||
release=platform.release()))
|
||||
remote_version = None
|
||||
retries = 0
|
||||
while True:
|
||||
try:
|
||||
remote_version = str(urllib.request.urlopen(req, None,
|
||||
timeout=CONNECTION_TIMEOUT).read().decode()).strip()
|
||||
except (urllib.error.URLError, ConnectionError):
|
||||
if retries > CONNECTION_RETRIES:
|
||||
log.exception('Failed to download the latest OpenLP version file')
|
||||
else:
|
||||
retries += 1
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
break
|
||||
if remote_version:
|
||||
version_string = remote_version
|
||||
return version_string
|
Loading…
Reference in New Issue
Block a user