Skip to content

Commit c040ae4

Browse files
committed
fixed addon downloads
1 parent 0b436b2 commit c040ae4

File tree

1 file changed

+169
-24
lines changed

1 file changed

+169
-24
lines changed

src/switchcraft/gui_modern/views/settings_view.py

Lines changed: 169 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,45 +1672,190 @@ def _select_addon_asset(self, assets, addon_id):
16721672

16731673
return asset
16741674

1675+
def _install_from_source_fallback(self, addon_id):
1676+
"""
1677+
Fallback: Download source code from main branch and extract specific addon folder.
1678+
"""
1679+
import requests
1680+
import tempfile
1681+
import zipfile
1682+
import shutil
1683+
from switchcraft.services.addon_service import AddonService
1684+
from pathlib import Path
1685+
1686+
repo = "FaserF/SwitchCraft"
1687+
# Download main branch zip
1688+
source_url = f"https://github.com/{repo}/archive/refs/heads/main.zip"
1689+
logger.info(f"Fallback: Downloading source from {source_url}...")
1690+
1691+
resp = requests.get(source_url, timeout=30)
1692+
resp.raise_for_status()
1693+
1694+
with tempfile.TemporaryDirectory() as temp_dir:
1695+
zip_path = Path(temp_dir) / "source.zip"
1696+
zip_path.write_bytes(resp.content)
1697+
1698+
extract_dir = Path(temp_dir) / "extracted"
1699+
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
1700+
zip_ref.extractall(extract_dir)
1701+
1702+
# Locate addon folder in source
1703+
# Usually: SwitchCraft-main/src/switchcraft_{addon_id} OR SwitchCraft-main/src/switchcraft/assets/addons/{addon_id}
1704+
# Or just search for manifest.json with matching id?
1705+
# Common structure: SwitchCraft-main/src/switchcraft_{addon_id}
1706+
1707+
root_folder = next(extract_dir.iterdir()) # SwitchCraft-main
1708+
1709+
# Potential locations
1710+
candidates = [
1711+
root_folder / "src" / f"switchcraft_{addon_id}",
1712+
root_folder / "src" / addon_id,
1713+
root_folder / "src" / "switchcraft" / "assets" / "addons" / addon_id
1714+
]
1715+
1716+
addon_src = None
1717+
for cand in candidates:
1718+
if cand.exists() and (cand / "manifest.json").exists():
1719+
addon_src = cand
1720+
break
1721+
1722+
if not addon_src:
1723+
raise Exception(f"Addon source for '{addon_id}' not found in main branch.")
1724+
1725+
# Zip the addon folder to simulate a release package
1726+
pkg_zip = Path(temp_dir) / f"{addon_id}_source.zip"
1727+
shutil.make_archive(str(pkg_zip.with_suffix('')), 'zip', addon_src)
1728+
1729+
logger.info(f"Installing {addon_id} from repackaged source...")
1730+
return AddonService().install_addon(str(pkg_zip))
1731+
16751732
def _download_and_install_github(self, addon_id):
16761733
"""Helper to download/install without UI code mixed in."""
16771734
import requests
16781735
import tempfile
16791736
from switchcraft.services.addon_service import AddonService
1737+
from switchcraft.utils.config import SwitchCraftConfig
1738+
from switchcraft import __version__
1739+
from pathlib import Path
16801740

16811741
repo = "FaserF/SwitchCraft"
1682-
api_url = f"https://api.github.com/repos/{repo}/releases/latest"
1742+
channel = SwitchCraftConfig.get_update_channel()
1743+
logger.info(f"Addon download strategy: channel={channel}, app_version={__version__}")
1744+
1745+
# Strategy Helpers
1746+
def try_matching_version():
1747+
# Try to find a release tag matching the current version
1748+
# Assuming tags are v<version>
1749+
tag = f"v{__version__}"
1750+
api_url = f"https://api.github.com/repos/{repo}/releases/tags/{tag}"
1751+
logger.info(f"Trying Matching Version Release: {api_url}")
1752+
resp = requests.get(api_url, timeout=10)
1753+
if resp.status_code == 404:
1754+
return None
1755+
resp.raise_for_status()
1756+
return _process_release(resp.json())
1757+
1758+
def try_latest():
1759+
api_url = f"https://api.github.com/repos/{repo}/releases/latest"
1760+
logger.info(f"Trying Latest Release: {api_url}")
1761+
resp = requests.get(api_url, timeout=10)
1762+
if resp.status_code == 404:
1763+
return None # No latest release
1764+
resp.raise_for_status()
1765+
return _process_release(resp.json())
1766+
1767+
def try_newest_release():
1768+
# Gets list of releases (including pre-releases) and picks first
1769+
api_url = f"https://api.github.com/repos/{repo}/releases"
1770+
logger.info(f"Trying Newest Release (inc. beta): {api_url}")
1771+
resp = requests.get(api_url, timeout=10)
1772+
resp.raise_for_status()
1773+
releases = resp.json()
1774+
if not releases:
1775+
return None
1776+
return _process_release(releases[0]) # First is newest
1777+
1778+
def try_source():
1779+
logger.info("Trying Source Code Fallback...")
1780+
return self._install_from_source_fallback(addon_id)
1781+
1782+
def _process_release(release_data):
1783+
assets = release_data.get("assets", [])
1784+
asset = self._select_addon_asset(assets, addon_id)
1785+
if not asset:
1786+
return None
1787+
1788+
download_url = asset["browser_download_url"]
1789+
asset_name = asset.get("name", f"{addon_id}.zip")
1790+
logger.info(f"Found {asset_name} in release {release_data.get('tag_name')}, downloading from: {download_url}")
1791+
1792+
with tempfile.TemporaryDirectory() as temp_dir:
1793+
dl_path = Path(temp_dir) / asset_name
1794+
with requests.get(download_url, stream=True, timeout=30) as r:
1795+
r.raise_for_status()
1796+
with open(dl_path, 'wb') as f:
1797+
for chunk in r.iter_content(chunk_size=8192):
1798+
f.write(chunk)
1799+
1800+
return AddonService().install_addon(str(dl_path))
1801+
1802+
# Execution Logic based on Channel
1803+
last_error = None
1804+
1805+
# 1. Stable Channel
1806+
if channel == "stable":
1807+
# Attempt 1: Stable Latest
1808+
try:
1809+
if res := try_latest(): return res
1810+
except Exception as e:
1811+
logger.warning(f"Stable download failed: {e}")
1812+
last_error = e
16831813

1684-
resp = requests.get(api_url, timeout=10)
1685-
resp.raise_for_status()
1814+
# Attempt 2: Beta/Pre-release Fallback
1815+
try:
1816+
if res := try_newest_release(): return res
1817+
except Exception as e:
1818+
logger.warning(f"Beta fallback failed: {e}")
1819+
last_error = e
16861820

1687-
assets = resp.json().get("assets", [])
1688-
asset = self._select_addon_asset(assets, addon_id)
1821+
# Attempt 3: Source Fallback
1822+
try:
1823+
return try_source()
1824+
except Exception as e:
1825+
logger.error(f"Source fallback failed: {e}")
1826+
raise e # Raise specific source error if all else fails
16891827

1690-
if not asset:
1691-
# List available assets for debugging
1692-
available_assets = [a["name"] for a in assets]
1693-
candidates = [f"switchcraft_{addon_id}.zip", f"{addon_id}.zip"]
1694-
logger.warning(f"Addon {addon_id} not found in latest release. Searched for: {candidates}. Available assets: {available_assets}")
1695-
raise Exception(f"Addon {addon_id} not found in latest release. Searched for: {', '.join(candidates)}. Available: {', '.join(available_assets[:10])}")
1828+
# 2. Beta Channel
1829+
elif channel == "beta":
1830+
# Attempt 1: Newest Release (Beta)
1831+
try:
1832+
if res := try_newest_release(): return res
1833+
except Exception as e:
1834+
logger.warning(f"Beta download failed: {e}")
1835+
last_error = e
16961836

1697-
download_url = asset["browser_download_url"]
1698-
asset_name = asset.get("name", f"{addon_id}.zip")
1699-
logger.info(f"Found {asset_name} in release, downloading from: {download_url}")
1837+
# Attempt 2: Source Fallback
1838+
try:
1839+
return try_source()
1840+
except Exception as e:
1841+
raise e
17001842

1701-
with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as tmp:
1702-
d_resp = requests.get(download_url, timeout=30)
1703-
d_resp.raise_for_status()
1704-
tmp.write(d_resp.content)
1705-
tmp_path = tmp.name
1843+
# 3. Dev Channel
1844+
else: # dev / nightly
1845+
# Attempt 1: Source (Preferred for Dev)
1846+
try:
1847+
return try_source()
1848+
except Exception as e:
1849+
logger.warning(f"Source download failed: {e}")
1850+
last_error = e
17061851

1707-
try:
1708-
return AddonService().install_addon(tmp_path)
1709-
finally:
1852+
# Attempt 2: Newest Release Fallback
17101853
try:
1711-
os.unlink(tmp_path)
1854+
if res := try_newest_release(): return res
17121855
except Exception as e:
1713-
logger.debug(f"Failed to cleanup temp file {tmp_path}: {e}")
1856+
raise last_error or e
1857+
1858+
raise Exception(f"Addon '{addon_id}' could not be found in any channel fallback.")
17141859

17151860
def _download_addon_from_github(self, addon_id):
17161861
"""

0 commit comments

Comments
 (0)