chgksuite_qt 0.0.3b0__tar.gz → 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chgksuite_qt
3
- Version: 0.0.3b0
3
+ Version: 0.0.4
4
4
  Summary: A GUI wrapper for chgksuite using PyQt6
5
5
  Author-email: Alexander Pecheny <ap@pecheny.me>
6
6
  License-Expression: MIT
@@ -5,13 +5,16 @@ from __future__ import unicode_literals
5
5
  import argparse
6
6
  import builtins
7
7
  import io
8
+ import json
8
9
  import os
10
+ import subprocess
9
11
  import sys
10
12
  import threading
13
+ import urllib.request
11
14
 
12
15
  try:
13
16
  from PyQt6 import QtWidgets, QtGui
14
- from PyQt6.QtCore import QTimer, QMetaObject, Qt, Q_ARG, pyqtSignal, QObject
17
+ from PyQt6.QtCore import QTimer, pyqtSignal, QObject
15
18
 
16
19
  PYQT = True
17
20
  except ImportError:
@@ -27,6 +30,63 @@ from chgksuite.common import (
27
30
  from chgksuite.version import __version__
28
31
  from chgksuite.cli import ArgparseBuilder, single_action
29
32
 
33
+
34
+ def get_pyapp_executable():
35
+ """Return the pyapp executable path if running inside pyapp, else None."""
36
+ pyapp_env = os.environ.get("PYAPP", "")
37
+ # PYAPP_PASS_LOCATION sets PYAPP to the executable path instead of "1"
38
+ if pyapp_env and pyapp_env != "1" and os.path.isfile(pyapp_env):
39
+ return pyapp_env
40
+ return None
41
+
42
+
43
+ def get_installed_version(package_name):
44
+ """Get installed version of a package."""
45
+ try:
46
+ from importlib.metadata import version
47
+
48
+ return version(package_name)
49
+ except Exception:
50
+ return None
51
+
52
+
53
+ def check_pypi_version(package_name):
54
+ """Get latest version of a package from PyPI."""
55
+ try:
56
+ url = f"https://pypi.org/pypi/{package_name}/json"
57
+ with urllib.request.urlopen(url, timeout=10) as response:
58
+ data = json.loads(response.read().decode())
59
+ return data["info"]["version"]
60
+ except Exception:
61
+ return None
62
+
63
+
64
+ def check_for_updates():
65
+ """Check PyPI for updates to chgksuite and chgksuite-qt. Returns (has_update, details_str, error)."""
66
+ packages = ["chgksuite", "chgksuite-qt"]
67
+ updates = []
68
+
69
+ for pkg in packages:
70
+ installed = get_installed_version(pkg)
71
+ latest = check_pypi_version(pkg)
72
+ if installed is None or latest is None:
73
+ continue
74
+ if installed != latest:
75
+ updates.append((pkg, installed, latest))
76
+
77
+ if updates:
78
+ details = "\n".join(f"{pkg}: {inst} → {lat}" for pkg, inst, lat in updates)
79
+ return True, details, None
80
+
81
+ # No updates - show current versions
82
+ current = ", ".join(
83
+ f"{pkg} {get_installed_version(pkg)}"
84
+ for pkg in packages
85
+ if get_installed_version(pkg)
86
+ )
87
+ return False, current, None
88
+
89
+
30
90
  LANGS = ["by", "by_tar", "en", "kz_cyr", "ru", "sr", "ua", "uz", "uz_cyr"] + ["custom"]
31
91
 
32
92
  debug = False
@@ -34,6 +94,7 @@ debug = False
34
94
 
35
95
  class InputRequester(QObject):
36
96
  """Helper to request input from main thread via signal."""
97
+
37
98
  input_requested = pyqtSignal(str)
38
99
 
39
100
  def __init__(self, parent_window):
@@ -86,7 +147,7 @@ class RadioGroupVar:
86
147
  for val, rb in self.radio_buttons:
87
148
  if rb.isChecked():
88
149
  return val
89
-
150
+
90
151
 
91
152
  def init_layout(frame, layout, spacing=0):
92
153
  layout.setSpacing(spacing)
@@ -246,6 +307,81 @@ class ParserWrapper(object):
246
307
  self.advanced_frame.hide()
247
308
  self.window.resize(self.window.minimumSizeHint())
248
309
 
310
+ def check_and_update(self):
311
+ """Check for updates and run self-update if available."""
312
+ self.update_button.setEnabled(False)
313
+ self.update_button.setText("Проверка обновлений...")
314
+
315
+ # Check for updates in a thread to avoid blocking UI
316
+ def check_thread():
317
+ has_update, details, error = check_for_updates()
318
+ # Schedule UI update on main thread
319
+ QTimer.singleShot(
320
+ 0, lambda: self._handle_update_check(has_update, details, error)
321
+ )
322
+
323
+ threading.Thread(target=check_thread, daemon=True).start()
324
+
325
+ def _handle_update_check(self, has_update, details, error):
326
+ """Handle update check result on main thread."""
327
+ self.update_button.setEnabled(True)
328
+ self.update_button.setText("Обновить chgksuite")
329
+
330
+ if has_update is None or (not has_update and not details):
331
+ QtWidgets.QMessageBox.warning(
332
+ self.window,
333
+ "Ошибка",
334
+ "Не удалось проверить обновления. Проверьте подключение к интернету.",
335
+ )
336
+ return
337
+
338
+ if not has_update:
339
+ QtWidgets.QMessageBox.information(
340
+ self.window,
341
+ "Обновления",
342
+ f"Уже установлена последняя версия.\n\n{details}",
343
+ )
344
+ return
345
+
346
+ # Update available - ask user
347
+ reply = QtWidgets.QMessageBox.question(
348
+ self.window,
349
+ "Доступно обновление",
350
+ f"Доступны обновления:\n{details}\n\n"
351
+ "Обновить сейчас? Приложение будет закрыто.",
352
+ QtWidgets.QMessageBox.StandardButton.Yes
353
+ | QtWidgets.QMessageBox.StandardButton.No,
354
+ )
355
+
356
+ if reply == QtWidgets.QMessageBox.StandardButton.Yes:
357
+ self._run_self_update()
358
+
359
+ def _run_self_update(self):
360
+ """Run pyapp self-update command and close the application."""
361
+ try:
362
+ # Start the update process detached from current process
363
+ if sys.platform == "win32":
364
+ # On Windows, use CREATE_NEW_PROCESS_GROUP to detach
365
+ subprocess.Popen(
366
+ [self.pyapp_executable, "self", "update"],
367
+ creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
368
+ | subprocess.DETACHED_PROCESS,
369
+ close_fds=True,
370
+ )
371
+ else:
372
+ # On Unix, use start_new_session to detach
373
+ subprocess.Popen(
374
+ [self.pyapp_executable, "self", "update"],
375
+ start_new_session=True,
376
+ close_fds=True,
377
+ )
378
+ # Close the application
379
+ self.app.quit()
380
+ except Exception as e:
381
+ QtWidgets.QMessageBox.critical(
382
+ self.window, "Ошибка", f"Не удалось запустить обновление: {e}"
383
+ )
384
+
249
385
  def init_qt(self):
250
386
  self.app = QtWidgets.QApplication(sys.argv)
251
387
  self.window = QtWidgets.QWidget()
@@ -267,7 +403,9 @@ class ParserWrapper(object):
267
403
  self.ok_button = QtWidgets.QPushButton("Запустить")
268
404
  self.ok_button.clicked.connect(self.ok_button_press)
269
405
  self.button_layout.addWidget(self.ok_button)
270
- self.advanced_checkbox_var = QtWidgets.QCheckBox("Показать дополнительные настройки")
406
+ self.advanced_checkbox_var = QtWidgets.QCheckBox(
407
+ "Показать дополнительные настройки"
408
+ )
271
409
  self.advanced_checkbox_var.stateChanged.connect(self.toggle_advanced_frame)
272
410
  self.button_layout.addWidget(self.advanced_checkbox_var)
273
411
  self.advanced_frame.hide()
@@ -279,6 +417,13 @@ class ParserWrapper(object):
279
417
  self.output_text.setMinimumHeight(150)
280
418
  self.window_layout.addWidget(self.output_text)
281
419
 
420
+ # Update button (only shown when running inside pyapp)
421
+ self.pyapp_executable = get_pyapp_executable()
422
+ if self.pyapp_executable:
423
+ self.update_button = QtWidgets.QPushButton("Обновить chgksuite")
424
+ self.update_button.clicked.connect(self.check_and_update)
425
+ self.window_layout.addWidget(self.update_button)
426
+
282
427
  def add_argument(self, *args, **kwargs):
283
428
  if kwargs.pop("advanced", False):
284
429
  frame = self.advanced_frame
@@ -432,6 +577,7 @@ class SubparsersWrapper(object):
432
577
  self.layout.addWidget(radio)
433
578
  return pw
434
579
 
580
+
435
581
  def app():
436
582
  _, resourcedir = get_source_dirs()
437
583
  ld = get_lastdir()
@@ -0,0 +1 @@
1
+ __version__ = "0.0.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chgksuite_qt
3
- Version: 0.0.3b0
3
+ Version: 0.0.4
4
4
  Summary: A GUI wrapper for chgksuite using PyQt6
5
5
  Author-email: Alexander Pecheny <ap@pecheny.me>
6
6
  License-Expression: MIT
@@ -3,12 +3,6 @@ pyproject.toml
3
3
  chgksuite_qt/__main__.py
4
4
  chgksuite_qt/gui.py
5
5
  chgksuite_qt/version.py
6
- chgksuite_qt.egg-info/._PKG-INFO
7
- chgksuite_qt.egg-info/._SOURCES.txt
8
- chgksuite_qt.egg-info/._dependency_links.txt
9
- chgksuite_qt.egg-info/._entry_points.txt
10
- chgksuite_qt.egg-info/._requires.txt
11
- chgksuite_qt.egg-info/._top_level.txt
12
6
  chgksuite_qt.egg-info/PKG-INFO
13
7
  chgksuite_qt.egg-info/SOURCES.txt
14
8
  chgksuite_qt.egg-info/dependency_links.txt
@@ -1 +0,0 @@
1
- __version__ = "0.0.3b0"
File without changes
File without changes