setiastrosuitepro 1.6.1__py3-none-any.whl
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.
Potentially problematic release.
This version of setiastrosuitepro might be problematic. Click here for more details.
- setiastro/__init__.py +2 -0
- setiastro/data/SASP_data.fits +0 -0
- setiastro/data/catalogs/List_of_Galaxies_with_Distances_Gly.csv +488 -0
- setiastro/data/catalogs/astrobin_filters.csv +890 -0
- setiastro/data/catalogs/astrobin_filters_page1_local.csv +51 -0
- setiastro/data/catalogs/cali2.csv +63 -0
- setiastro/data/catalogs/cali2color.csv +65 -0
- setiastro/data/catalogs/celestial_catalog - original.csv +16471 -0
- setiastro/data/catalogs/celestial_catalog.csv +24031 -0
- setiastro/data/catalogs/detected_stars.csv +24784 -0
- setiastro/data/catalogs/fits_header_data.csv +46 -0
- setiastro/data/catalogs/test.csv +8 -0
- setiastro/data/catalogs/updated_celestial_catalog.csv +16471 -0
- setiastro/images/Astro_Spikes.png +0 -0
- setiastro/images/HRDiagram.png +0 -0
- setiastro/images/LExtract.png +0 -0
- setiastro/images/LInsert.png +0 -0
- setiastro/images/Oxygenation-atm-2.svg.png +0 -0
- setiastro/images/RGB080604.png +0 -0
- setiastro/images/abeicon.png +0 -0
- setiastro/images/aberration.png +0 -0
- setiastro/images/andromedatry.png +0 -0
- setiastro/images/andromedatry_satellited.png +0 -0
- setiastro/images/annotated.png +0 -0
- setiastro/images/aperture.png +0 -0
- setiastro/images/astrosuite.ico +0 -0
- setiastro/images/astrosuite.png +0 -0
- setiastro/images/astrosuitepro.icns +0 -0
- setiastro/images/astrosuitepro.ico +0 -0
- setiastro/images/astrosuitepro.png +0 -0
- setiastro/images/background.png +0 -0
- setiastro/images/background2.png +0 -0
- setiastro/images/benchmark.png +0 -0
- setiastro/images/big_moon_stabilizer_timeline.png +0 -0
- setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
- setiastro/images/blaster.png +0 -0
- setiastro/images/blink.png +0 -0
- setiastro/images/clahe.png +0 -0
- setiastro/images/collage.png +0 -0
- setiastro/images/colorwheel.png +0 -0
- setiastro/images/contsub.png +0 -0
- setiastro/images/convo.png +0 -0
- setiastro/images/copyslot.png +0 -0
- setiastro/images/cosmic.png +0 -0
- setiastro/images/cosmicsat.png +0 -0
- setiastro/images/crop1.png +0 -0
- setiastro/images/cropicon.png +0 -0
- setiastro/images/curves.png +0 -0
- setiastro/images/cvs.png +0 -0
- setiastro/images/debayer.png +0 -0
- setiastro/images/denoise_cnn_custom.png +0 -0
- setiastro/images/denoise_cnn_graph.png +0 -0
- setiastro/images/disk.png +0 -0
- setiastro/images/dse.png +0 -0
- setiastro/images/exoicon.png +0 -0
- setiastro/images/eye.png +0 -0
- setiastro/images/fliphorizontal.png +0 -0
- setiastro/images/flipvertical.png +0 -0
- setiastro/images/font.png +0 -0
- setiastro/images/freqsep.png +0 -0
- setiastro/images/functionbundle.png +0 -0
- setiastro/images/graxpert.png +0 -0
- setiastro/images/green.png +0 -0
- setiastro/images/gridicon.png +0 -0
- setiastro/images/halo.png +0 -0
- setiastro/images/hdr.png +0 -0
- setiastro/images/histogram.png +0 -0
- setiastro/images/hubble.png +0 -0
- setiastro/images/imagecombine.png +0 -0
- setiastro/images/invert.png +0 -0
- setiastro/images/isophote.png +0 -0
- setiastro/images/isophote_demo_figure.png +0 -0
- setiastro/images/isophote_demo_image.png +0 -0
- setiastro/images/isophote_demo_model.png +0 -0
- setiastro/images/isophote_demo_residual.png +0 -0
- setiastro/images/jwstpupil.png +0 -0
- setiastro/images/linearfit.png +0 -0
- setiastro/images/livestacking.png +0 -0
- setiastro/images/mask.png +0 -0
- setiastro/images/maskapply.png +0 -0
- setiastro/images/maskcreate.png +0 -0
- setiastro/images/maskremove.png +0 -0
- setiastro/images/morpho.png +0 -0
- setiastro/images/mosaic.png +0 -0
- setiastro/images/multiscale_decomp.png +0 -0
- setiastro/images/nbtorgb.png +0 -0
- setiastro/images/neutral.png +0 -0
- setiastro/images/nuke.png +0 -0
- setiastro/images/openfile.png +0 -0
- setiastro/images/pedestal.png +0 -0
- setiastro/images/pen.png +0 -0
- setiastro/images/pixelmath.png +0 -0
- setiastro/images/platesolve.png +0 -0
- setiastro/images/ppp.png +0 -0
- setiastro/images/pro.png +0 -0
- setiastro/images/project.png +0 -0
- setiastro/images/psf.png +0 -0
- setiastro/images/redo.png +0 -0
- setiastro/images/redoicon.png +0 -0
- setiastro/images/rescale.png +0 -0
- setiastro/images/rgbalign.png +0 -0
- setiastro/images/rgbcombo.png +0 -0
- setiastro/images/rgbextract.png +0 -0
- setiastro/images/rotate180.png +0 -0
- setiastro/images/rotateclockwise.png +0 -0
- setiastro/images/rotatecounterclockwise.png +0 -0
- setiastro/images/satellite.png +0 -0
- setiastro/images/script.png +0 -0
- setiastro/images/selectivecolor.png +0 -0
- setiastro/images/simbad.png +0 -0
- setiastro/images/slot0.png +0 -0
- setiastro/images/slot1.png +0 -0
- setiastro/images/slot2.png +0 -0
- setiastro/images/slot3.png +0 -0
- setiastro/images/slot4.png +0 -0
- setiastro/images/slot5.png +0 -0
- setiastro/images/slot6.png +0 -0
- setiastro/images/slot7.png +0 -0
- setiastro/images/slot8.png +0 -0
- setiastro/images/slot9.png +0 -0
- setiastro/images/spcc.png +0 -0
- setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
- setiastro/images/spinner.gif +0 -0
- setiastro/images/stacking.png +0 -0
- setiastro/images/staradd.png +0 -0
- setiastro/images/staralign.png +0 -0
- setiastro/images/starnet.png +0 -0
- setiastro/images/starregistration.png +0 -0
- setiastro/images/starspike.png +0 -0
- setiastro/images/starstretch.png +0 -0
- setiastro/images/statstretch.png +0 -0
- setiastro/images/supernova.png +0 -0
- setiastro/images/uhs.png +0 -0
- setiastro/images/undoicon.png +0 -0
- setiastro/images/upscale.png +0 -0
- setiastro/images/viewbundle.png +0 -0
- setiastro/images/whitebalance.png +0 -0
- setiastro/images/wimi_icon_256x256.png +0 -0
- setiastro/images/wimilogo.png +0 -0
- setiastro/images/wims.png +0 -0
- setiastro/images/wrench_icon.png +0 -0
- setiastro/images/xisfliberator.png +0 -0
- setiastro/saspro/__init__.py +20 -0
- setiastro/saspro/__main__.py +809 -0
- setiastro/saspro/_generated/__init__.py +7 -0
- setiastro/saspro/_generated/build_info.py +2 -0
- setiastro/saspro/abe.py +1295 -0
- setiastro/saspro/abe_preset.py +196 -0
- setiastro/saspro/aberration_ai.py +694 -0
- setiastro/saspro/aberration_ai_preset.py +224 -0
- setiastro/saspro/accel_installer.py +218 -0
- setiastro/saspro/accel_workers.py +30 -0
- setiastro/saspro/add_stars.py +621 -0
- setiastro/saspro/astrobin_exporter.py +1007 -0
- setiastro/saspro/astrospike.py +153 -0
- setiastro/saspro/astrospike_python.py +1839 -0
- setiastro/saspro/autostretch.py +196 -0
- setiastro/saspro/backgroundneutral.py +560 -0
- setiastro/saspro/batch_convert.py +325 -0
- setiastro/saspro/batch_renamer.py +519 -0
- setiastro/saspro/blemish_blaster.py +488 -0
- setiastro/saspro/blink_comparator_pro.py +2926 -0
- setiastro/saspro/bundles.py +61 -0
- setiastro/saspro/bundles_dock.py +114 -0
- setiastro/saspro/cheat_sheet.py +178 -0
- setiastro/saspro/clahe.py +342 -0
- setiastro/saspro/comet_stacking.py +1377 -0
- setiastro/saspro/common_tr.py +107 -0
- setiastro/saspro/config.py +38 -0
- setiastro/saspro/config_bootstrap.py +40 -0
- setiastro/saspro/config_manager.py +316 -0
- setiastro/saspro/continuum_subtract.py +1617 -0
- setiastro/saspro/convo.py +1397 -0
- setiastro/saspro/convo_preset.py +414 -0
- setiastro/saspro/copyastro.py +187 -0
- setiastro/saspro/cosmicclarity.py +1564 -0
- setiastro/saspro/cosmicclarity_preset.py +407 -0
- setiastro/saspro/crop_dialog_pro.py +956 -0
- setiastro/saspro/crop_preset.py +189 -0
- setiastro/saspro/curve_editor_pro.py +2544 -0
- setiastro/saspro/curves_preset.py +375 -0
- setiastro/saspro/debayer.py +670 -0
- setiastro/saspro/debug_utils.py +29 -0
- setiastro/saspro/dnd_mime.py +35 -0
- setiastro/saspro/doc_manager.py +2641 -0
- setiastro/saspro/exoplanet_detector.py +2166 -0
- setiastro/saspro/file_utils.py +284 -0
- setiastro/saspro/fitsmodifier.py +745 -0
- setiastro/saspro/fix_bom.py +32 -0
- setiastro/saspro/free_torch_memory.py +48 -0
- setiastro/saspro/frequency_separation.py +1343 -0
- setiastro/saspro/function_bundle.py +1594 -0
- setiastro/saspro/generate_translations.py +2378 -0
- setiastro/saspro/ghs_dialog_pro.py +660 -0
- setiastro/saspro/ghs_preset.py +284 -0
- setiastro/saspro/graxpert.py +634 -0
- setiastro/saspro/graxpert_preset.py +287 -0
- setiastro/saspro/gui/__init__.py +0 -0
- setiastro/saspro/gui/main_window.py +8567 -0
- setiastro/saspro/gui/mixins/__init__.py +33 -0
- setiastro/saspro/gui/mixins/dock_mixin.py +263 -0
- setiastro/saspro/gui/mixins/file_mixin.py +443 -0
- setiastro/saspro/gui/mixins/geometry_mixin.py +403 -0
- setiastro/saspro/gui/mixins/header_mixin.py +441 -0
- setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
- setiastro/saspro/gui/mixins/menu_mixin.py +361 -0
- setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +1457 -0
- setiastro/saspro/gui/mixins/update_mixin.py +309 -0
- setiastro/saspro/gui/mixins/view_mixin.py +435 -0
- setiastro/saspro/halobgon.py +462 -0
- setiastro/saspro/header_viewer.py +448 -0
- setiastro/saspro/headless_utils.py +88 -0
- setiastro/saspro/histogram.py +753 -0
- setiastro/saspro/history_explorer.py +939 -0
- setiastro/saspro/i18n.py +156 -0
- setiastro/saspro/image_combine.py +414 -0
- setiastro/saspro/image_peeker_pro.py +1601 -0
- setiastro/saspro/imageops/__init__.py +37 -0
- setiastro/saspro/imageops/mdi_snap.py +292 -0
- setiastro/saspro/imageops/scnr.py +36 -0
- setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
- setiastro/saspro/imageops/stretch.py +244 -0
- setiastro/saspro/isophote.py +1179 -0
- setiastro/saspro/layers.py +208 -0
- setiastro/saspro/layers_dock.py +714 -0
- setiastro/saspro/lazy_imports.py +193 -0
- setiastro/saspro/legacy/__init__.py +2 -0
- setiastro/saspro/legacy/image_manager.py +2226 -0
- setiastro/saspro/legacy/numba_utils.py +3659 -0
- setiastro/saspro/legacy/xisf.py +1071 -0
- setiastro/saspro/linear_fit.py +534 -0
- setiastro/saspro/live_stacking.py +1830 -0
- setiastro/saspro/log_bus.py +5 -0
- setiastro/saspro/logging_config.py +460 -0
- setiastro/saspro/luminancerecombine.py +309 -0
- setiastro/saspro/main_helpers.py +201 -0
- setiastro/saspro/mask_creation.py +928 -0
- setiastro/saspro/masks_core.py +56 -0
- setiastro/saspro/mdi_widgets.py +353 -0
- setiastro/saspro/memory_utils.py +666 -0
- setiastro/saspro/metadata_patcher.py +75 -0
- setiastro/saspro/mfdeconv.py +3826 -0
- setiastro/saspro/mfdeconv_earlystop.py +71 -0
- setiastro/saspro/mfdeconvcudnn.py +3263 -0
- setiastro/saspro/mfdeconvsport.py +2382 -0
- setiastro/saspro/minorbodycatalog.py +567 -0
- setiastro/saspro/morphology.py +382 -0
- setiastro/saspro/multiscale_decomp.py +1290 -0
- setiastro/saspro/nbtorgb_stars.py +531 -0
- setiastro/saspro/numba_utils.py +3044 -0
- setiastro/saspro/numba_warmup.py +141 -0
- setiastro/saspro/ops/__init__.py +9 -0
- setiastro/saspro/ops/command_help_dialog.py +623 -0
- setiastro/saspro/ops/command_runner.py +217 -0
- setiastro/saspro/ops/commands.py +1594 -0
- setiastro/saspro/ops/script_editor.py +1102 -0
- setiastro/saspro/ops/scripts.py +1413 -0
- setiastro/saspro/ops/settings.py +679 -0
- setiastro/saspro/parallel_utils.py +554 -0
- setiastro/saspro/pedestal.py +121 -0
- setiastro/saspro/perfect_palette_picker.py +1070 -0
- setiastro/saspro/pipeline.py +110 -0
- setiastro/saspro/pixelmath.py +1600 -0
- setiastro/saspro/plate_solver.py +2444 -0
- setiastro/saspro/project_io.py +797 -0
- setiastro/saspro/psf_utils.py +136 -0
- setiastro/saspro/psf_viewer.py +549 -0
- setiastro/saspro/pyi_rthook_astroquery.py +95 -0
- setiastro/saspro/remove_green.py +314 -0
- setiastro/saspro/remove_stars.py +1625 -0
- setiastro/saspro/remove_stars_preset.py +404 -0
- setiastro/saspro/resources.py +477 -0
- setiastro/saspro/rgb_combination.py +207 -0
- setiastro/saspro/rgb_extract.py +19 -0
- setiastro/saspro/rgbalign.py +723 -0
- setiastro/saspro/runtime_imports.py +7 -0
- setiastro/saspro/runtime_torch.py +754 -0
- setiastro/saspro/save_options.py +72 -0
- setiastro/saspro/selective_color.py +1552 -0
- setiastro/saspro/sfcc.py +1430 -0
- setiastro/saspro/shortcuts.py +3043 -0
- setiastro/saspro/signature_insert.py +1099 -0
- setiastro/saspro/stacking_suite.py +18181 -0
- setiastro/saspro/star_alignment.py +7420 -0
- setiastro/saspro/star_alignment_preset.py +329 -0
- setiastro/saspro/star_metrics.py +49 -0
- setiastro/saspro/star_spikes.py +681 -0
- setiastro/saspro/star_stretch.py +470 -0
- setiastro/saspro/stat_stretch.py +506 -0
- setiastro/saspro/status_log_dock.py +78 -0
- setiastro/saspro/subwindow.py +3267 -0
- setiastro/saspro/supernovaasteroidhunter.py +1716 -0
- setiastro/saspro/swap_manager.py +99 -0
- setiastro/saspro/torch_backend.py +89 -0
- setiastro/saspro/torch_rejection.py +434 -0
- setiastro/saspro/translations/de_translations.py +3733 -0
- setiastro/saspro/translations/es_translations.py +3923 -0
- setiastro/saspro/translations/fr_translations.py +3842 -0
- setiastro/saspro/translations/integrate_translations.py +234 -0
- setiastro/saspro/translations/it_translations.py +3662 -0
- setiastro/saspro/translations/ja_translations.py +3585 -0
- setiastro/saspro/translations/pt_translations.py +3853 -0
- setiastro/saspro/translations/saspro_de.qm +0 -0
- setiastro/saspro/translations/saspro_de.ts +253 -0
- setiastro/saspro/translations/saspro_es.qm +0 -0
- setiastro/saspro/translations/saspro_es.ts +12520 -0
- setiastro/saspro/translations/saspro_fr.qm +0 -0
- setiastro/saspro/translations/saspro_fr.ts +12514 -0
- setiastro/saspro/translations/saspro_it.qm +0 -0
- setiastro/saspro/translations/saspro_it.ts +12520 -0
- setiastro/saspro/translations/saspro_ja.qm +0 -0
- setiastro/saspro/translations/saspro_ja.ts +257 -0
- setiastro/saspro/translations/saspro_pt.qm +0 -0
- setiastro/saspro/translations/saspro_pt.ts +257 -0
- setiastro/saspro/translations/saspro_zh.qm +0 -0
- setiastro/saspro/translations/saspro_zh.ts +12520 -0
- setiastro/saspro/translations/zh_translations.py +3659 -0
- setiastro/saspro/versioning.py +71 -0
- setiastro/saspro/view_bundle.py +1555 -0
- setiastro/saspro/wavescale_hdr.py +624 -0
- setiastro/saspro/wavescale_hdr_preset.py +101 -0
- setiastro/saspro/wavescalede.py +658 -0
- setiastro/saspro/wavescalede_preset.py +230 -0
- setiastro/saspro/wcs_update.py +374 -0
- setiastro/saspro/whitebalance.py +456 -0
- setiastro/saspro/widgets/__init__.py +48 -0
- setiastro/saspro/widgets/common_utilities.py +306 -0
- setiastro/saspro/widgets/graphics_views.py +122 -0
- setiastro/saspro/widgets/image_utils.py +518 -0
- setiastro/saspro/widgets/preview_dialogs.py +280 -0
- setiastro/saspro/widgets/spinboxes.py +275 -0
- setiastro/saspro/widgets/themed_buttons.py +13 -0
- setiastro/saspro/widgets/wavelet_utils.py +299 -0
- setiastro/saspro/window_shelf.py +185 -0
- setiastro/saspro/xisf.py +1123 -0
- setiastrosuitepro-1.6.1.dist-info/METADATA +267 -0
- setiastrosuitepro-1.6.1.dist-info/RECORD +342 -0
- setiastrosuitepro-1.6.1.dist-info/WHEEL +4 -0
- setiastrosuitepro-1.6.1.dist-info/entry_points.txt +6 -0
- setiastrosuitepro-1.6.1.dist-info/licenses/LICENSE +674 -0
- setiastrosuitepro-1.6.1.dist-info/licenses/license.txt +2580 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# pro/gui/mixins/update_mixin.py
|
|
2
|
+
"""
|
|
3
|
+
Update check mixin for AstroSuiteProMainWindow.
|
|
4
|
+
|
|
5
|
+
This mixin contains all functionality for checking for application updates,
|
|
6
|
+
downloading updates, and handling the update installation process.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
import webbrowser
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from PyQt6.QtCore import QUrl
|
|
15
|
+
from PyQt6.QtNetwork import QNetworkRequest, QNetworkReply
|
|
16
|
+
from PyQt6.QtWidgets import QMessageBox, QApplication
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class UpdateMixin:
|
|
23
|
+
"""
|
|
24
|
+
Mixin for application update functionality.
|
|
25
|
+
|
|
26
|
+
Provides methods for checking for updates, downloading updates,
|
|
27
|
+
and managing the update installation process.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# Default URL for update checks
|
|
31
|
+
_updates_url = "https://raw.githubusercontent.com/user/repo/main/updates.json"
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def _current_version_str(self) -> str:
|
|
35
|
+
"""
|
|
36
|
+
Return the current app version as a string.
|
|
37
|
+
|
|
38
|
+
Prefer an attribute set on the main window (self._version),
|
|
39
|
+
fall back to any module-level VERSION if present, then "0.0.0".
|
|
40
|
+
"""
|
|
41
|
+
v = getattr(self, "_version", None)
|
|
42
|
+
if not v:
|
|
43
|
+
v = globals().get("VERSION", None)
|
|
44
|
+
return str(v or "0.0.0")
|
|
45
|
+
|
|
46
|
+
def _ensure_network_manager(self):
|
|
47
|
+
"""Ensure the network access manager exists."""
|
|
48
|
+
from PyQt6.QtNetwork import QNetworkAccessManager
|
|
49
|
+
|
|
50
|
+
if not hasattr(self, "_nam") or self._nam is None:
|
|
51
|
+
self._nam = QNetworkAccessManager(self)
|
|
52
|
+
self._nam.finished.connect(self._on_update_reply)
|
|
53
|
+
|
|
54
|
+
def _kick_update_check(self, *, interactive: bool):
|
|
55
|
+
"""
|
|
56
|
+
Start an update check request.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
interactive: If True, show UI feedback for the check
|
|
60
|
+
"""
|
|
61
|
+
self._ensure_network_manager()
|
|
62
|
+
url_str = self.settings.value("updates/url", self._updates_url, type=str) or self._updates_url
|
|
63
|
+
req = QNetworkRequest(QUrl(url_str))
|
|
64
|
+
req.setRawHeader(
|
|
65
|
+
b"User-Agent",
|
|
66
|
+
f"SASPro/{self._current_version_str}".encode("utf-8")
|
|
67
|
+
)
|
|
68
|
+
reply = self._nam.get(req)
|
|
69
|
+
reply.setProperty("interactive", interactive)
|
|
70
|
+
|
|
71
|
+
def check_for_updates_now(self):
|
|
72
|
+
"""Check for updates interactively (show result to user)."""
|
|
73
|
+
if self.statusBar():
|
|
74
|
+
self.statusBar().showMessage(self.tr("Checking for updates..."))
|
|
75
|
+
self._kick_update_check(interactive=True)
|
|
76
|
+
|
|
77
|
+
def check_for_updates_startup(self):
|
|
78
|
+
"""Check for updates silently at startup."""
|
|
79
|
+
self._kick_update_check(interactive=False)
|
|
80
|
+
|
|
81
|
+
def _parse_version_tuple(self, v: str):
|
|
82
|
+
"""
|
|
83
|
+
Parse a version string into a tuple for comparison.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
v: Version string like "1.2.3"
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Tuple of integers, or None if parsing fails
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
parts = str(v).strip().split(".")
|
|
93
|
+
return tuple(int(p) for p in parts)
|
|
94
|
+
except Exception:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
def _on_update_reply(self, reply: QNetworkReply):
|
|
98
|
+
"""Handle network reply from update check or download."""
|
|
99
|
+
interactive = bool(reply.property("interactive"))
|
|
100
|
+
|
|
101
|
+
# Was this the second request (the actual installer download)?
|
|
102
|
+
if bool(reply.property("is_update_download")):
|
|
103
|
+
self._on_windows_update_download_finished(reply)
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
if reply.error() != QNetworkReply.NetworkError.NoError:
|
|
108
|
+
err = reply.errorString()
|
|
109
|
+
if self.statusBar():
|
|
110
|
+
self.statusBar().showMessage("Update check failed.", 5000)
|
|
111
|
+
if interactive:
|
|
112
|
+
QMessageBox.warning(self, self.tr("Update Check Failed"),
|
|
113
|
+
self.tr("Unable to check for updates.\n\n{err}").replace("{err}", err))
|
|
114
|
+
else:
|
|
115
|
+
print(f"[updates] check failed: {err}")
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
raw = bytes(reply.readAll())
|
|
119
|
+
try:
|
|
120
|
+
data = json.loads(raw.decode("utf-8"))
|
|
121
|
+
except Exception as je:
|
|
122
|
+
if self.statusBar():
|
|
123
|
+
self.statusBar().showMessage("Update check failed (bad JSON).", 5000)
|
|
124
|
+
if interactive:
|
|
125
|
+
QMessageBox.warning(self, self.tr("Update Check Failed"),
|
|
126
|
+
self.tr("Update JSON is invalid.\n\n{je}").replace("{je}", str(je)))
|
|
127
|
+
else:
|
|
128
|
+
print(f"[updates] bad JSON: {je}")
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
latest_str = str(data.get("version", "")).strip()
|
|
132
|
+
notes = str(data.get("notes", "") or "")
|
|
133
|
+
downloads = data.get("downloads", {}) or {}
|
|
134
|
+
|
|
135
|
+
if not latest_str:
|
|
136
|
+
if self.statusBar():
|
|
137
|
+
self.statusBar().showMessage("Update check failed (no 'version').", 5000)
|
|
138
|
+
if interactive:
|
|
139
|
+
QMessageBox.warning(self, self.tr("Update Check Failed"),
|
|
140
|
+
self.tr("Update JSON missing the 'version' field."))
|
|
141
|
+
else:
|
|
142
|
+
print("[updates] JSON missing 'version'")
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
cur_tuple = self._parse_version_tuple(self._current_version_str)
|
|
146
|
+
latest_tuple = self._parse_version_tuple(latest_str)
|
|
147
|
+
available = bool(latest_tuple and cur_tuple and latest_tuple > cur_tuple)
|
|
148
|
+
|
|
149
|
+
if available:
|
|
150
|
+
if self.statusBar():
|
|
151
|
+
self.statusBar().showMessage(self.tr("Update available: {0}").format(latest_str), 5000)
|
|
152
|
+
msg_box = QMessageBox(self)
|
|
153
|
+
msg_box.setIcon(QMessageBox.Icon.Information)
|
|
154
|
+
msg_box.setWindowTitle(self.tr("Update Available"))
|
|
155
|
+
msg_box.setText(self.tr("A new version ({0}) is available!").format(latest_str))
|
|
156
|
+
if notes:
|
|
157
|
+
msg_box.setInformativeText(self.tr("Release Notes:\n{0}").format(notes))
|
|
158
|
+
msg_box.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
|
159
|
+
msg_box.setDefaultButton(QMessageBox.StandardButton.Yes)
|
|
160
|
+
|
|
161
|
+
if downloads:
|
|
162
|
+
details = "\n".join([f"{k}: {v}" for k, v in downloads.items()])
|
|
163
|
+
msg_box.setDetailedText(details)
|
|
164
|
+
|
|
165
|
+
if msg_box.exec() == QMessageBox.StandardButton.Yes:
|
|
166
|
+
plat = sys.platform
|
|
167
|
+
link = downloads.get(
|
|
168
|
+
"Windows" if plat.startswith("win") else
|
|
169
|
+
"macOS" if plat.startswith("darwin") else
|
|
170
|
+
"Linux" if plat.startswith("linux") else "", ""
|
|
171
|
+
)
|
|
172
|
+
if not link:
|
|
173
|
+
QMessageBox.warning(self, self.tr("Download"), self.tr("No download link available for this platform."))
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
if plat.startswith("win"):
|
|
177
|
+
# Use in-app updater for Windows
|
|
178
|
+
self._start_windows_update_download(link)
|
|
179
|
+
else:
|
|
180
|
+
# Open browser for other platforms
|
|
181
|
+
webbrowser.open(link)
|
|
182
|
+
else:
|
|
183
|
+
if self.statusBar():
|
|
184
|
+
self.statusBar().showMessage("You're up to date.", 3000)
|
|
185
|
+
if interactive:
|
|
186
|
+
QMessageBox.information(self, self.tr("Up to Date"),
|
|
187
|
+
self.tr("You're already running the latest version."))
|
|
188
|
+
finally:
|
|
189
|
+
reply.deleteLater()
|
|
190
|
+
|
|
191
|
+
def _is_windows(self) -> bool:
|
|
192
|
+
"""Check if running on Windows."""
|
|
193
|
+
return sys.platform.startswith("win")
|
|
194
|
+
|
|
195
|
+
def _start_windows_update_download(self, url: str):
|
|
196
|
+
"""
|
|
197
|
+
Download the update file for Windows.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
url: URL to download from
|
|
201
|
+
"""
|
|
202
|
+
from PyQt6.QtCore import QStandardPaths
|
|
203
|
+
from pathlib import Path
|
|
204
|
+
import os
|
|
205
|
+
|
|
206
|
+
self._ensure_network_manager()
|
|
207
|
+
|
|
208
|
+
downloads_dir = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.DownloadLocation)
|
|
209
|
+
if not downloads_dir:
|
|
210
|
+
import tempfile
|
|
211
|
+
downloads_dir = tempfile.gettempdir()
|
|
212
|
+
|
|
213
|
+
os.makedirs(downloads_dir, exist_ok=True)
|
|
214
|
+
|
|
215
|
+
# filename from URL
|
|
216
|
+
fname = url.split("/")[-1] or "setiastrosuitepro_windows.zip"
|
|
217
|
+
target_path = Path(downloads_dir) / fname
|
|
218
|
+
|
|
219
|
+
req = QNetworkRequest(QUrl(url))
|
|
220
|
+
req.setRawHeader(
|
|
221
|
+
b"User-Agent",
|
|
222
|
+
f"SASPro/{self._current_version_str}".encode("utf-8")
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
reply = self._nam.get(req)
|
|
226
|
+
# mark this reply as "this is the actual installer file, not updates.json"
|
|
227
|
+
reply.setProperty("is_update_download", True)
|
|
228
|
+
reply.setProperty("target_path", str(target_path))
|
|
229
|
+
|
|
230
|
+
reply.downloadProgress.connect(
|
|
231
|
+
lambda rec, tot: self.statusBar().showMessage(
|
|
232
|
+
self.tr("Downloading update... {0:.1f} KB / {1:.1f} KB").format(rec / 1024, tot / 1024) if tot > 0 else self.tr("Downloading update...")
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def _on_windows_update_download_finished(self, reply: QNetworkReply):
|
|
237
|
+
"""Handle completion of Windows update download."""
|
|
238
|
+
from pathlib import Path
|
|
239
|
+
import os
|
|
240
|
+
import zipfile
|
|
241
|
+
import subprocess
|
|
242
|
+
import tempfile
|
|
243
|
+
|
|
244
|
+
target_path = Path(reply.property("target_path"))
|
|
245
|
+
|
|
246
|
+
if reply.error() != QNetworkReply.NetworkError.NoError:
|
|
247
|
+
QMessageBox.warning(self, self.tr("Update Failed"),
|
|
248
|
+
self.tr("Could not download update:\n{0}").format(reply.errorString()))
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
# Write the .zip
|
|
252
|
+
data = bytes(reply.readAll())
|
|
253
|
+
try:
|
|
254
|
+
with open(target_path, "wb") as f:
|
|
255
|
+
f.write(data)
|
|
256
|
+
except Exception as e:
|
|
257
|
+
QMessageBox.warning(self, self.tr("Update Failed"),
|
|
258
|
+
self.tr("Could not save update to disk:\n{0}").format(e))
|
|
259
|
+
return
|
|
260
|
+
|
|
261
|
+
self.statusBar().showMessage(f"Update downloaded to {target_path}", 5000)
|
|
262
|
+
|
|
263
|
+
# Extract zip if needed
|
|
264
|
+
if target_path.suffix.lower() == ".zip":
|
|
265
|
+
extract_dir = Path(tempfile.mkdtemp(prefix="saspro-update-"))
|
|
266
|
+
try:
|
|
267
|
+
with zipfile.ZipFile(target_path, "r") as zf:
|
|
268
|
+
zf.extractall(extract_dir)
|
|
269
|
+
except Exception as e:
|
|
270
|
+
QMessageBox.warning(self, self.tr("Update Failed"),
|
|
271
|
+
self.tr("Could not extract update zip:\n{0}").format(e))
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
# Look recursively for an .exe
|
|
275
|
+
exe_cands = list(extract_dir.rglob("*.exe"))
|
|
276
|
+
if not exe_cands:
|
|
277
|
+
QMessageBox.warning(
|
|
278
|
+
self,
|
|
279
|
+
"Update Failed",
|
|
280
|
+
self.tr("Downloaded ZIP did not contain an .exe installer.\nFolder: {0}").format(extract_dir)
|
|
281
|
+
)
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
installer_path = exe_cands[0]
|
|
285
|
+
else:
|
|
286
|
+
# In case one day Windows points straight to .exe
|
|
287
|
+
installer_path = target_path
|
|
288
|
+
|
|
289
|
+
# Ask to run
|
|
290
|
+
ok = QMessageBox.question(
|
|
291
|
+
self,
|
|
292
|
+
self.tr("Run Installer"),
|
|
293
|
+
self.tr("The update has been downloaded.\n\nRun the installer now? (SAS will close.)"),
|
|
294
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
295
|
+
QMessageBox.StandardButton.Yes,
|
|
296
|
+
)
|
|
297
|
+
if ok != QMessageBox.StandardButton.Yes:
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
# Launch installer
|
|
301
|
+
try:
|
|
302
|
+
subprocess.Popen([str(installer_path)], shell=False)
|
|
303
|
+
except Exception as e:
|
|
304
|
+
QMessageBox.warning(self, self.tr("Update Failed"),
|
|
305
|
+
self.tr("Could not start installer:\n{0}").format(e))
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
# Close app so the installer can overwrite files
|
|
309
|
+
QApplication.instance().quit()
|