Skip to content
3 changes: 2 additions & 1 deletion src/osdag/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
__version__ = "2025.01.a.2"
__version__ = "2025.01.a.1"
__installation_type__ = "conda"
70 changes: 66 additions & 4 deletions src/osdag/osdagMainPage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
200 changes: 157 additions & 43 deletions src/osdag/update_version_check.py
Original file line number Diff line number Diff line change
@@ -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+'<br>'+'Latest version '+ str(version)+'<br>'+\
'Update will be available <a href=\"https://osdag.fossee.in/resources/downloads\"> here <a/>'
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