diff --git a/iblrig/base_tasks.py b/iblrig/base_tasks.py index 4ff8e4a53..3e4304f43 100644 --- a/iblrig/base_tasks.py +++ b/iblrig/base_tasks.py @@ -36,6 +36,7 @@ import iblrig.spacer import iblrig.alyx import iblrig.graphic as graph +from iblrig.version_management import check_for_updates import ibllib.io.session_params as ses_params from iblrig.transfer_experiments import SessionCopier @@ -48,11 +49,12 @@ class BaseSession(ABC): base_parameters_file = None is_mock = False extractor_tasks = None + checked_for_update = False def __init__(self, subject=None, task_parameter_file=None, file_hardware_settings=None, hardware_settings=None, file_iblrig_settings=None, iblrig_settings=None, one=None, interactive=True, projects=None, procedures=None, stub=None, - append=False, log_level='INFO'): + append=False, log_level='INFO', wizard=False): """ :param subject: The subject nickname. Required. :param task_parameter_file: an optional path to the task_parameters.yaml file @@ -72,6 +74,25 @@ def __init__(self, subject=None, task_parameter_file=None, file_hardware_setting self.logger = None self._setup_loggers(level=log_level) self.logger.info(f"Running iblrig {iblrig.__version__}, pybpod version {pybpodapi.__version__}") + + # check for update + if not wizard and not BaseSession.checked_for_update: + BaseSession.checked_for_update = True + update_status, remote_version = check_for_updates() + if update_status == True: + print(f"\nUpdate to iblrig {remote_version} is available! Please update using 'git pull'.\n") + + while True: + print("- Press [Enter] to exit IBL Rig and perform the update right away.\n" + "- Enter 'I will update later' to continue without updating.") + response = input('Your response: ') + if response == '': + print("\nEnter 'git pull' - then restart iblrig. Thanks for keeping iblrig up to date!") + exit() + elif response == 'I will update later': + print("\nPlease do so!") + break + self.interactive = False if append else interactive self._one = one self.init_datetime = datetime.datetime.now() diff --git a/iblrig/gui/wizard.py b/iblrig/gui/wizard.py index 2c8e0129c..6ca2c3bed 100644 --- a/iblrig/gui/wizard.py +++ b/iblrig/gui/wizard.py @@ -18,6 +18,7 @@ import iblrig.path_helper from iblrig.base_tasks import BaseSession from iblrig.hardware import Bpod +from iblrig.version_management import check_for_updates from pybpodapi import exceptions PROCEDURES = [ @@ -135,6 +136,22 @@ def __init__(self, *args, **kwargs): self.uiPushConnect.clicked.connect(self.alyx_connect) self.lineEditSubject.textChanged.connect(self._filter_subjects) self.running_task_process = None + self.setDisabled(True) + QtCore.QTimer.singleShot(100, self.check_for_update) + + def check_for_update(self): + update_available, remote_version = check_for_updates() + if update_available == 1: + msgBox = QtWidgets.QMessageBox(parent=self) + msgBox.setWindowTitle("Update Notice") + msgBox.setText(f"Update toiblrig {remote_version} is available.") + msgBox.setInformativeText("Please update using 'git pull'.") + msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok) + msgBox.setIcon(QtWidgets.QMessageBox().Information) + msgBox.findChild(QtWidgets.QPushButton).setText('Yes, I promise!') + msgBox.exec_() + self.setDisabled(False) + self.update() pixmapi = QStyle.SP_MediaPlay self.uiPushStart.setIcon(self.style().standardIcon(pixmapi)) @@ -194,7 +211,7 @@ def startstop(self): match self.uiPushStart.text(): case 'Start': self.controller2model() - task = EmptySession(subject=self.model.subject, append=self.uiCheckAppend.isChecked()) + task = EmptySession(subject=self.model.subject, append=self.uiCheckAppend.isChecked(), wizard=True) self.model.session_folder = task.paths['SESSION_FOLDER'] if self.model.session_folder.joinpath('.stop').exists(): self.model.session_folder.joinpath('.stop').unlink() @@ -212,6 +229,7 @@ def startstop(self): cmd.extend(['--projects', *self.model.projects]) if self.uiCheckAppend.isChecked(): cmd.append('--append') + cmd.append('--wizard') if self.running_task_process is None: self.running_task_process = subprocess.Popen(cmd) self.uiPushStart.setText('Stop') diff --git a/iblrig/misc.py b/iblrig/misc.py index 61458b5fb..e8d46677d 100644 --- a/iblrig/misc.py +++ b/iblrig/misc.py @@ -50,6 +50,7 @@ def _get_task_argument_parser(parents=None): parser.add_argument('--append', dest='append', action='store_true', default=False) parser.add_argument('--stub', type=Path, help="Path to _ibl_experiment.description.yaml stub file.") parser.add_argument('--log-level', type=str, help="Logger level", default="INFO") + parser.add_argument('--wizard', action='store_true', default=False) return parser diff --git a/iblrig/version_management.py b/iblrig/version_management.py new file mode 100644 index 000000000..344ca9c76 --- /dev/null +++ b/iblrig/version_management.py @@ -0,0 +1,45 @@ +from packaging import version +from pathlib import Path +from re import sub +from subprocess import check_output, check_call, SubprocessError + +import iblrig +from iblutil.util import setup_logger + +log = setup_logger('iblrig') + +def check_for_updates(): + log.info('Checking for updates ...') + + # assert that git is being used + dir_base = Path(iblrig.__file__).parents[1] + if not dir_base.joinpath('.git').exists(): + log.debug('iblrig does not seem to be managed through git') + return -1, '' + + # get newest remote tag + try: + branch = check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], + cwd=dir_base) + branch = sub(r'\n', '', branch.decode()) + check_call(["git", "fetch", "origin", branch, "-q"], cwd=dir_base, timeout=5) + version_remote_str = check_output(["git", "describe", "--tags", "--abbrev=0"], + cwd=dir_base) + version_remote_str = sub(r'[^\d\.]', '', version_remote_str.decode()) + except (SubprocessError, FileNotFoundError): + log.debug(f'Could not fetch remote tags') + return -1, '' + + # parse version information + try: + version_local = version.parse(iblrig.__version__) + version_remote = version.parse(version_remote_str) + except version.InvalidVersion: + log.debug(f'Invalid version string') + return -1, '' + + if version_remote > version_local: + log.info(f'Update to iblrig {version_remote_str} found.') + else: + log.info(f'No update found.') + return version_remote > version_local, version_remote_str