diff --git a/src/osdag/_version.py b/src/osdag/_version.py
index ab6e815b2..5e987e66b 100644
--- a/src/osdag/_version.py
+++ b/src/osdag/_version.py
@@ -1 +1,2 @@
-__version__ = "2025.01.a.2"
+__version__ = "2025.01.a.1"
+__installation_type__ = "conda"
\ No newline at end of file
diff --git a/src/osdag/osdagMainPage.py b/src/osdag/osdagMainPage.py
index 0cfc08b51..d1be6d56f 100644
--- a/src/osdag/osdagMainPage.py
+++ b/src/osdag/osdagMainPage.py
@@ -88,7 +88,7 @@
import time
from importlib.resources import files
import urllib.request
-from PyQt5.QtWidgets import QMessageBox,QApplication, QDialog, QMainWindow
+from PyQt5.QtWidgets import QMessageBox,QApplication, QDialog, QMainWindow, QTextEdit
from .update_version_check import Update
#from Thread import timer
from .get_DPI_scale import scale
@@ -509,12 +509,74 @@ def selection_change(self):
elif loc == "Ask Us a Question":
self.ask_question()
elif loc == "Check for Update":
- update_class = Update()
- msg = update_class.notifi()
- QMessageBox.information(self, 'Info',msg)
+ self.updater = Update()
+ try:
+ update_avl, msg = self.updater.notifi()
+ except ConnectionError as e:
+ QMessageBox.critical(self, "Network Error", str(e))
+ return
+ # QMessageBox.information(self, 'Info',msg)
+
+ box = QMessageBox(self)
+ box.setIcon(QMessageBox.Information)
+ box.setWindowTitle("Update Status")
+ box.setText(msg)
+
+ if update_avl:
+ update_now_btn = box.addButton("Update Now", QMessageBox.AcceptRole)
+ later_btn = box.addButton("Update Later", QMessageBox.RejectRole)
+ else:
+ ok_btn = box.addButton("OK", QMessageBox.AcceptRole)
+
+ box.exec_()
+
+ if update_avl and box.clickedButton() == update_now_btn:
+ confirm_update = QMessageBox(self)
+ confirm_update.setIcon(QMessageBox.Information)
+ confirm_update.setWindowTitle("Confirm Update")
+ confirm_update.setText("This may take some time....\nDo you want to continue?")
+ confirm_update.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
+ confirm_update.setDefaultButton(QMessageBox.No)
+
+ result = confirm_update.exec_()
+ if result == QMessageBox.Yes:
+ self.progress_dialog = QDialog(self)
+ self.progress_dialog.setWindowTitle("Updating Osdag")
+ layout = QVBoxLayout()
+
+ self.progress_label = QLabel("Updating, please wait...")
+ layout.addWidget(self.progress_label)
+
+ self.progress_text = QTextEdit()
+ self.progress_text.setReadOnly(True)
+ self.progress_text.verticalScrollBar().setValue(
+ self.progress_text.verticalScrollBar().maximum()
+ )
+ layout.addWidget(self.progress_text)
+
+ self.progress_dialog.setLayout(layout)
+ self.progress_dialog.setModal(True)
+ self.progress_dialog.setWindowFlags(self.progress_dialog.windowFlags() | Qt.WindowStaysOnTopHint)
+ self.progress_dialog.show()
+
+ self.updater.output_signal.connect(lambda text: self.progress_text.append(text))
+ self.updater.finished_signal.connect(self.handle_update_finished)
+ self.updater.update_to_latest()
+ # if update_status:
+ # QMessageBox.information(self, "Update", msg)
+ # else:
+ # QMessageBox.warning(self, "Update Failed", msg)
+
+
# elif loc == "FAQ":
# pass
+ def handle_update_finished(self,success, msg):
+ self.progress_dialog.close()
+ if success:
+ QMessageBox.information(self, "Update Completed", msg)
+ else:
+ QMessageBox.warning(self, "Update Failed", msg)
def select_workspace_folder(self):
# This function prompts the user to select the workspace folder and returns the name of the workspace folder
diff --git a/src/osdag/update_version_check.py b/src/osdag/update_version_check.py
index 312cff3a7..3f9184692 100644
--- a/src/osdag/update_version_check.py
+++ b/src/osdag/update_version_check.py
@@ -1,55 +1,169 @@
######################### UpDateNotifi ################
+
import urllib.request
-import requests
import re
-from PyQt5.QtWidgets import QMessageBox,QMainWindow
-import sys
+from pathlib import Path
+from packaging.version import Version, InvalidVersion
+from PyQt5.QtCore import QObject, QProcess, pyqtSignal
+from PyQt5.QtWidgets import QDialog, QTextEdit, QLabel
+import subprocess
+import sys, os
+import json
+
+
+version_file = Path(__file__).parent / "_version.py"
+version_var = {}
+exec(version_file.read_text(), version_var)
+curr_version = version_var["__version__"]
+install_type = version_var["__installation_type__"]
+
+
+
+class Update(QObject):
+ output_signal = pyqtSignal(str)
+ finished_signal = pyqtSignal(bool, str)
-class Update():
def __init__(self):
super().__init__()
- self.old_version=self.get_current_version()
- # msg = self.notifi()
+ self.old_version = curr_version
+ self.process = QProcess(self)
+
+ def _set_exec_paths(self):
+ env_path = sys.prefix
+ env_path = Path(env_path)
+ base_conda_path = env_path.parents[1]
+ if sys.platform.startswith("win"):
+ conda_path = base_conda_path / "Scripts" / "conda.exe"
+ pixi_path = env_path / "Scripts" / "pixi.exe"
+ else:
+ conda_path = base_conda_path / "bin/conda"
+ pixi_path = env_path / "bin/pixi"
+
+ self.conda_path = str(conda_path)
+ self.pixi_path = str(pixi_path)
- def notifi(self):
+ def fetch_latest_version(self) -> str:
+ """Fetch the latest version string from Osdag downloads page."""
+ self._set_exec_paths()
+ if install_type == "conda":
+ cmd = [self.conda_path, "search", "-c", "conda-forge", "osdag::osdag", "--info", "--json"]
+ try:
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
+ data = json.loads(result.stdout)
+ except subprocess.CalledProcessError as e:
+ raise ConnectionError(f"Failed to fetch version info: {e.stderr}")
+ if data:
+ versions = data.get("osdag")
+ if versions:
+ return sorted(versions, key=lambda x:x["version"])[-1]["version"]
+ else: return None
+
+ elif install_type == "pixi":
+ cmd = [self.pixi_path, "search", "osdag", "--channel", "osdag"]
+ try:
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
+ for line in result.stdout.splitlines():
+ if line.strip().startswith("Version"):
+ return line.split()[-1].strip()
+
+ else: return None
+
+ except subprocess.CalledProcessError as e:
+ raise ConnectionError(f"Failed to fetch version info: {e.stderr}")
+
+ def notifi(self) -> str:
+ """Compare current version with latest version and return update message."""
try:
- url = "https://raw.githubusercontent.com/osdag-admin/Osdag/master/README.md"
- file = urllib.request.urlopen(url)
- version = 'not found'
- for line in file:
- decoded_line = line.decode("utf-8")
- match = re.search(r'Download the latest release version (\S+)', decoded_line)
- if match:
- version = match.group(1)
- version = version.split("<")[0]
- break
- # decoded_line = line.decode("utf-8")
- # new_version = decoded_line.split("=")[1]
- if version != self.old_version:
- msg = 'Current version: '+ self.old_version+'
'+'Latest version '+ str(version)+'
'+\
- 'Update will be available here '
+ latest_version = self.fetch_latest_version()
+ if latest_version is None:
+ return False, "Could not determine latest version."
+
+ latest_version = latest_version.lstrip("v").replace("_", ".")
+ if Version(latest_version) > Version(self.old_version):
+ return True, (
+ f"Current version: {self.old_version}\n"
+ f"Latest version: {latest_version}\n"
+ )
else:
- msg = 'Already up to date'
- return msg
- except:
- return "No internet connection"
+ return False, "Already up to date"
+
+ except InvalidVersion:
+ return False, "Could not parse version string."
+
+ def update_to_latest(self):
+ """Run conda update in background using QProcess."""
+ try:
+ latest_version = self.fetch_latest_version()
+ if latest_version:
+ latest_version = latest_version.lstrip("v").replace("_", ".")
+ except Exception as e:
+ return
+ try:
+ # cmd = ["conda", "install", "-y", f"osdag=={latest_version}"]
+
+ if install_type == "conda":
+ if not Path(self.conda_path).exists():
+ self.finished_signal.emit(False, f"conda not found at {self.conda_path}")
+ return
+ cmd = [self.conda_path, "update", "-y", "osdag", "--channel", "osdag"]
+ elif install_type == "pixi":
+ if not Path(self.pixi_path).exists():
+ self.finished_signal.emit(False, f"pixi not found at {self.pixi_path}")
+ return
+ cmd = [self.pixi_path, "update", "-y", "osdag", "--channel", "osdag"]
- def get_current_version(self):
- version_file = "_version.py"
- rel_path = str(sys.path[0])
- rel_path = rel_path.replace("\\", "/")
- VERSIONFILE = rel_path +'/'+ version_file
+ # Create QProcess
+ self.process.setProgram(cmd[0])
+ self.process.setArguments(cmd[1:])
- try:
- verstrline = open(VERSIONFILE, "rt").read()
- except EnvironmentError:
- pass # Okay, there is no version file.
+ # Connect signals for output
+ self.process.readyReadStandardOutput.connect(self.handle_stdout)
+ self.process.readyReadStandardError.connect(self.handle_stderr)
+ self.process.finished.connect(self.handle_finished)
+
+ # Start the process
+ self.process.start()
+
+ # result = subprocess.run(cmd, capture_output=False, text=True)
+ # if result.returncode == 0:
+ # self.finished_signal.emit(True, "Update successful! Please restart Osdag.")
+ # else:
+ # self.finished_signal.emit(False, f"Update failed.\nError: {result.stderr}\n"
+ # "Please retry or run:\nconda install --force-reinstall osdag::osdag")
+
+
+ except Exception as e:
+ self.finished_signal.emit(False, f"Update failed: {e}")
+ return
+
+
+ def handle_stdout(self):
+ """Read stdout and print it, also emit signal for GUI."""
+ if self.process:
+ output = self.process.readAllStandardOutput().data().decode("utf-8")
+ self.output_signal.emit(output)
+ print(output, end="")
+ # self.progress_text.append(output)
+ # self.progress_text.verticalScrollBar().setValue(
+ # self.progress_text.verticalScrollBar().maximum()
+ # )
+
+ def handle_stderr(self):
+ """Read stderr and print it."""
+ if self.process:
+ error = self.process.readAllStandardError().data().decode("utf-8")
+ self.output_signal.emit(error)
+ print(error, end="")
+ # self.output_signal.emit(error)
+ # self.progress_text.append(error)
+ # self.progress_text.verticalScrollBar().setValue(
+ # self.progress_text.verticalScrollBar().maximum()
+ # )
+
+ def handle_finished(self, exit_code, exit_status):
+ """Handle when the process finishes."""
+ if exit_code == 0:
+ self.finished_signal.emit(True, "Update successful! Please restart Osdag.")
else:
- VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
- mo = re.search(VSRE, verstrline, re.M)
- if mo:
- verstr = mo.group(1)
- return verstr
- else:
- print("unable to find version in %s" % (VERSIONFILE,))
- raise RuntimeError("if %s.py exists, it is required to be well-formed" % (VERSIONFILE,))
+ self.finished_signal.emit(False, f"Update failed with code {exit_code}")
+ self.process = None