Skip to content

Commit 52b2420

Browse files
committed
small test fixes
1 parent d3997b0 commit 52b2420

File tree

12 files changed

+176
-38
lines changed

12 files changed

+176
-38
lines changed

.github/workflows/release.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ jobs:
5454
# Update file_version_info.txt
5555
python .github/scripts/update_version_info.py "$VERSION"
5656
57+
# Update .iss files (Inno Setup installer scripts)
58+
sed -i "s/#define MyAppVersion \".*\"/#define MyAppVersion \"$VERSION\"/" switchcraft_modern.iss
59+
sed -i "s/#define MyAppVersionNumeric \".*\"/#define MyAppVersionNumeric \"$VERSION\"/" switchcraft_modern.iss
60+
sed -i "s/#define MyAppVersion \".*\"/#define MyAppVersion \"$VERSION\"/" switchcraft_legacy.iss
61+
sed -i "s/#define MyAppVersionNumeric \".*\"/#define MyAppVersionNumeric \"$VERSION\"/" switchcraft_legacy.iss
62+
5763
- name: Generate Changelog
5864
id: changelog
5965
run: |
@@ -86,7 +92,7 @@ jobs:
8692
git config --global user.name "github-actions[bot]"
8793
git config --global user.email "github-actions[bot]@users.noreply.github.com"
8894
89-
git add pyproject.toml src/switchcraft/__init__.py file_version_info.txt
95+
git add pyproject.toml src/switchcraft/__init__.py file_version_info.txt switchcraft_modern.iss switchcraft_legacy.iss
9096
9197
# Only commit if there are changes
9298
if ! git diff --cached --quiet; then
@@ -141,8 +147,15 @@ jobs:
141147
sed -i "s/__version__ = \".*\"/__version__ = \"$DEV_VERSION\"/" src/switchcraft/__init__.py
142148
python .github/scripts/update_version_info.py "$DEV_VERSION"
143149
150+
# Update .iss files (Inno Setup installer scripts) - keep base version without -dev suffix for installer
151+
BASE_VERSION=$(echo $DEV_VERSION | sed 's/-dev$//')
152+
sed -i "s/#define MyAppVersion \".*\"/#define MyAppVersion \"$BASE_VERSION\"/" switchcraft_modern.iss
153+
sed -i "s/#define MyAppVersionNumeric \".*\"/#define MyAppVersionNumeric \"$BASE_VERSION\"/" switchcraft_modern.iss
154+
sed -i "s/#define MyAppVersion \".*\"/#define MyAppVersion \"$BASE_VERSION\"/" switchcraft_legacy.iss
155+
sed -i "s/#define MyAppVersionNumeric \".*\"/#define MyAppVersionNumeric \"$BASE_VERSION\"/" switchcraft_legacy.iss
156+
144157
# Commit
145-
git add pyproject.toml src/switchcraft/__init__.py file_version_info.txt
158+
git add pyproject.toml src/switchcraft/__init__.py file_version_info.txt switchcraft_modern.iss switchcraft_legacy.iss
146159
git commit -m "chore: bump version to $DEV_VERSION for development [skip ci]"
147160
git push origin main
148161

renovate.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
"**/venv/**",
2121
"**/__pycache__/**"
2222
],
23+
"schedule": [
24+
"before 4am on monday"
25+
],
26+
"timezone": "Europe/Berlin",
2327
"packageRules": [
2428
{
2529
"matchManagers": [
@@ -33,6 +37,16 @@
3337
"groupName": "flet packages",
3438
"description": "Group flet and flet-desktop updates together to keep them in sync"
3539
},
40+
{
41+
"matchManagers": [
42+
"pep621",
43+
"pip_requirements"
44+
],
45+
"matchPackageNames": [
46+
"pyinstaller"
47+
],
48+
"description": "PyInstaller build dependency - ensure updates are detected"
49+
},
3650
{
3751
"matchManagers": [
3852
"pep621",

src/switchcraft/gui_modern/views/intune_store_view.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,16 +147,20 @@ def _search_task():
147147
else:
148148
apps = self.intune_service.list_apps(token) # Top 50?
149149

150-
result_holder["apps"] = apps
150+
result_holder["apps"] = apps if apps else []
151151
result_holder["completed"] = True
152152
except requests.exceptions.Timeout:
153-
result_holder["error"] = "Request timed out. The server took too long to respond. Please try again."
153+
result_holder["error"] = "Request timed out after 30 seconds. Please check your connection and try again."
154154
result_holder["completed"] = True
155155
except requests.exceptions.RequestException as ex:
156-
result_holder["error"] = f"Network error: {str(ex)}"
156+
result_holder["error"] = f"Network error: {str(ex)}. Please check your connection."
157157
result_holder["completed"] = True
158158
except Exception as ex:
159-
result_holder["error"] = f"Error: {str(ex)}"
159+
error_msg = str(ex)
160+
if "timeout" in error_msg.lower():
161+
result_holder["error"] = "Request timed out. Please check your connection and try again."
162+
else:
163+
result_holder["error"] = f"Error: {error_msg}"
160164
result_holder["completed"] = True
161165

162166
# Start search in background thread

src/switchcraft/gui_modern/views/winget_view.py

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -329,13 +329,34 @@ def _show_list(self, results, filter_by="all", query=""):
329329
def _load_details(self, short_info):
330330
logger.info(f"Loading details for package: {short_info.get('Id', 'Unknown')}")
331331

332-
# Show loading immediately
333-
self.details_area.controls.clear()
334-
self.details_area.controls.append(ft.ProgressBar())
332+
# Create new loading area immediately
333+
loading_area = ft.Column(scroll=ft.ScrollMode.AUTO, expand=True)
334+
loading_area.controls.append(ft.ProgressBar())
335+
loading_area.controls.append(ft.Text("Loading package details...", color="GREY_500", italic=True))
336+
self.details_area = loading_area
337+
338+
# CRITICAL: Re-assign content to force container refresh
339+
self.right_pane.content = self.details_area
335340
self.right_pane.visible = True
336341

337-
# Force update
338-
self.update()
342+
# Force update of details area, row, and page
343+
try:
344+
self.details_area.update()
345+
except Exception as ex:
346+
logger.debug(f"Error updating details_area: {ex}")
347+
try:
348+
self.right_pane.update()
349+
except Exception as ex:
350+
logger.debug(f"Error updating right_pane: {ex}")
351+
try:
352+
self.update()
353+
except Exception as ex:
354+
logger.debug(f"Error updating row: {ex}")
355+
if hasattr(self, 'app_page'):
356+
try:
357+
self.app_page.update()
358+
except Exception as ex:
359+
logger.debug(f"Error updating app_page: {ex}")
339360

340361
def _fetch():
341362
try:
@@ -353,8 +374,8 @@ def _show_ui():
353374
except Exception as ex:
354375
logger.exception(f"Error in _show_details_ui: {ex}")
355376
# Show error in UI
356-
self.details_area.controls.clear()
357-
self.details_area.controls.append(
377+
error_area = ft.Column(scroll=ft.ScrollMode.AUTO, expand=True)
378+
error_area.controls.append(
358379
ft.Container(
359380
content=ft.Column([
360381
ft.Icon(ft.Icons.ERROR, color="RED", size=40),
@@ -364,7 +385,17 @@ def _show_ui():
364385
alignment=ft.Alignment(0, 0)
365386
)
366387
)
367-
self.update()
388+
self.details_area = error_area
389+
self.right_pane.content = self.details_area
390+
self.right_pane.visible = True
391+
try:
392+
self.details_area.update()
393+
self.right_pane.update()
394+
self.update()
395+
if hasattr(self, 'app_page'):
396+
self.app_page.update()
397+
except Exception:
398+
pass
368399

369400
if hasattr(self.app_page, 'run_task'):
370401
try:
@@ -385,8 +416,8 @@ def _show_ui():
385416

386417
# Update UI on main thread
387418
def _show_error_ui():
388-
self.details_area.controls.clear()
389-
self.details_area.controls.append(
419+
error_area = ft.Column(scroll=ft.ScrollMode.AUTO, expand=True)
420+
error_area.controls.append(
390421
ft.Container(
391422
content=ft.Column([
392423
ft.Icon(ft.Icons.ERROR, color="RED", size=40),
@@ -397,7 +428,17 @@ def _show_error_ui():
397428
alignment=ft.Alignment(0, 0)
398429
)
399430
)
400-
self.update()
431+
self.details_area = error_area
432+
self.right_pane.content = self.details_area
433+
self.right_pane.visible = True
434+
try:
435+
self.details_area.update()
436+
self.right_pane.update()
437+
self.update()
438+
if hasattr(self, 'app_page'):
439+
self.app_page.update()
440+
except Exception:
441+
pass
401442

402443
if hasattr(self.app_page, 'run_task'):
403444
self.app_page.run_task(_show_error_ui)

src/switchcraft/services/intune_service.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def create_intunewin(self, source_folder: str, setup_file: str, output_folder: s
106106
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
107107

108108
try:
109-
process = subprocess.Popen(
109+
with subprocess.Popen(
110110
cmd,
111111
stdout=subprocess.PIPE,
112112
stderr=subprocess.STDOUT,
@@ -115,19 +115,21 @@ def create_intunewin(self, source_folder: str, setup_file: str, output_folder: s
115115
startupinfo=startupinfo,
116116
bufsize=1,
117117
universal_newlines=True
118-
)
118+
) as process:
119119

120-
full_output = []
120+
full_output = []
121121

122-
# Stream output
123-
for line in process.stdout:
124-
line_str = line
125-
full_output.append(line_str)
126-
# If a callback is provided (e.g. for UI)
127-
if progress_callback:
128-
progress_callback(line_str)
122+
# Stream output
123+
for line in process.stdout:
124+
line_str = line
125+
full_output.append(line_str)
126+
# If a callback is provided (e.g. for UI)
127+
if progress_callback:
128+
progress_callback(line_str)
129129

130-
process.wait()
130+
# No need to call process.wait() explicitly as 'with' handles it,
131+
# but we can do it if we want the return code check immediately after loop
132+
# The loop ends when stdout closes (process exit usually)
131133

132134
output_str = "".join(full_output)
133135

@@ -385,7 +387,7 @@ def list_apps(self, token, filter_query=None):
385387
params["$select"] = "id,displayName,publisher,appType,largeIcon,iconUrl,logoUrl"
386388

387389
try:
388-
resp = requests.get(url, headers=headers, params=params, timeout=30)
390+
resp = requests.get(url, headers=headers, params=params, timeout=30, stream=False)
389391
resp.raise_for_status()
390392
data = resp.json()
391393
return data.get("value", [])
@@ -466,9 +468,15 @@ def get_app_details(self, token, app_id):
466468
base_url = f"https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/{app_id}"
467469

468470
try:
469-
resp = requests.get(base_url, headers=headers, timeout=30)
471+
resp = requests.get(base_url, headers=headers, timeout=30, stream=False)
470472
resp.raise_for_status()
471473
return resp.json()
474+
except requests.exceptions.Timeout:
475+
logger.error(f"Request timed out while getting app details for {app_id}")
476+
raise Exception("Request timed out. The server took too long to respond.")
477+
except requests.exceptions.RequestException as e:
478+
logger.error(f"Network error getting app details: {e}")
479+
raise Exception(f"Network error: {str(e)}")
472480
except Exception as e:
473481
logger.error(f"Failed to get app details: {e}")
474482
raise e

src/switchcraft/utils/logging_handler.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ def setup_file_logging(self, log_dir):
3232
filename = f"SwitchCraft_Session_{timestamp}.log"
3333
self.current_log_path = log_dir / filename
3434

35+
# Close existing handler if it exists
36+
if self.file_handler:
37+
try:
38+
self.file_handler.close()
39+
except Exception:
40+
pass
41+
3542
# Create FileHandler
3643
self.file_handler = logging.FileHandler(self.current_log_path, encoding='utf-8')
3744
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

src/switchcraft_winget/utils/winget.py

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,17 @@ def _search_via_powershell(self, query: str) -> List[Dict[str, str]]:
131131
cmd = ["powershell", "-NoProfile", "-NonInteractive", "-Command", ps_script]
132132
startupinfo = self._get_startup_info()
133133

134-
proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="ignore", startupinfo=startupinfo, timeout=45)
134+
# Hide CMD window on Windows
135+
kwargs = {}
136+
if startupinfo:
137+
kwargs['startupinfo'] = startupinfo
138+
import sys
139+
if sys.platform == "win32":
140+
if hasattr(subprocess, 'CREATE_NO_WINDOW'):
141+
kwargs['creationflags'] = subprocess.CREATE_NO_WINDOW
142+
else:
143+
kwargs['creationflags'] = 0x08000000 # CREATE_NO_WINDOW constant
144+
proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="ignore", timeout=45, **kwargs)
135145

136146
if proc.returncode != 0:
137147
logger.debug(f"PowerShell search failed: {proc.stderr[:200] if proc.stderr else 'No error'}")
@@ -317,7 +327,17 @@ def install_package(self, package_id: str, scope: str = "machine") -> bool:
317327
"--accept-source-agreements"
318328
]
319329
try:
320-
proc = subprocess.run(cmd, capture_output=True, text=True)
330+
startupinfo = self._get_startup_info()
331+
kwargs = {}
332+
if startupinfo:
333+
kwargs['startupinfo'] = startupinfo
334+
import sys
335+
if sys.platform == "win32":
336+
if hasattr(subprocess, 'CREATE_NO_WINDOW'):
337+
kwargs['creationflags'] = subprocess.CREATE_NO_WINDOW
338+
else:
339+
kwargs['creationflags'] = 0x08000000 # CREATE_NO_WINDOW constant
340+
proc = subprocess.run(cmd, capture_output=True, text=True, **kwargs)
321341
if proc.returncode != 0:
322342
logger.error(f"Winget install failed: {proc.stderr}")
323343
return False
@@ -330,7 +350,17 @@ def download_package(self, package_id: str, dest_dir: Path) -> Optional[Path]:
330350
"""Download a package installer to dest_dir. Returns path to installer if found."""
331351
cmd = ["winget", "download", "--id", package_id, "--dir", str(dest_dir), "--accept-source-agreements", "--accept-package-agreements"]
332352
try:
333-
proc = subprocess.run(cmd, capture_output=True, text=True)
353+
startupinfo = self._get_startup_info()
354+
kwargs = {}
355+
if startupinfo:
356+
kwargs['startupinfo'] = startupinfo
357+
import sys
358+
if sys.platform == "win32":
359+
if hasattr(subprocess, 'CREATE_NO_WINDOW'):
360+
kwargs['creationflags'] = subprocess.CREATE_NO_WINDOW
361+
else:
362+
kwargs['creationflags'] = 0x08000000 # CREATE_NO_WINDOW constant
363+
proc = subprocess.run(cmd, capture_output=True, text=True, **kwargs)
334364
if proc.returncode != 0:
335365
logger.error(f"Winget download failed: {proc.stderr}")
336366
return None
@@ -351,7 +381,17 @@ def _search_via_cli(self, query: str) -> List[Dict[str, str]]:
351381
cmd = ["winget", "search", query, "--accept-source-agreements"]
352382
startupinfo = self._get_startup_info()
353383

354-
proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="ignore", startupinfo=startupinfo)
384+
# Hide CMD window on Windows
385+
kwargs = {}
386+
if startupinfo:
387+
kwargs['startupinfo'] = startupinfo
388+
import sys
389+
if sys.platform == "win32":
390+
if hasattr(subprocess, 'CREATE_NO_WINDOW'):
391+
kwargs['creationflags'] = subprocess.CREATE_NO_WINDOW
392+
else:
393+
kwargs['creationflags'] = 0x08000000 # CREATE_NO_WINDOW constant
394+
proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="ignore", **kwargs)
355395
if proc.returncode != 0:
356396
return []
357397

switchcraft_legacy.iss

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55
; Debug Mode: /DEBUGMODE=1 (enables verbose logging)
66

77
#define MyAppName "SwitchCraft Legacy"
8-
#define MyAppVersion "2025.12.4"
9-
#define MyAppVersionNumeric "2025.12.4"
8+
#ifndef MyAppVersion
9+
#define MyAppVersion "2026.1.1"
10+
#endif
11+
#ifndef MyAppVersionNumeric
12+
#define MyAppVersionNumeric "2026.1.1"
13+
#endif
1014
#define MyAppPublisher "FaserF"
1115
#define MyAppURL "https://github.com/FaserF/SwitchCraft"
1216
#define MyAppExeName "SwitchCraft-Legacy.exe"

switchcraft_modern.iss

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55
; Debug Mode: /DEBUGMODE=1 (enables verbose logging)
66

77
#define MyAppName "SwitchCraft"
8-
#define MyAppVersion "2025.12.4"
9-
#define MyAppVersionNumeric "2025.12.4"
8+
#ifndef MyAppVersion
9+
#define MyAppVersion "2026.1.1"
10+
#endif
11+
#ifndef MyAppVersionNumeric
12+
#define MyAppVersionNumeric "2026.1.1"
13+
#endif
1014
#define MyAppPublisher "FaserF"
1115
#define MyAppURL "https://github.com/FaserF/SwitchCraft"
1216
#define MyAppExeName "SwitchCraft.exe"

tests/test_full_coverage.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def test_intune_packaging(mock_popen, mock_is_avail, intune_service, tmp_path):
6666
process_mock.stdout = ["Output line 1\n", "Output line 2\n"]
6767
process_mock.returncode = 0
6868
process_mock.wait.return_value = None
69+
process_mock.__enter__.return_value = process_mock
6970
mock_popen.return_value = process_mock
7071

7172
# Correct args: source_folder, setup_file, output_folder

0 commit comments

Comments
 (0)