Skip to content

Commit ef0a63b

Browse files
committed
Enhance ISO management and device handling in SmartBoot
- Improved FreeDOS recognition in ISO type detection by adding support for "fd" abbreviations. - Enhanced `get_iso_info()` to include additional metadata: `is_hybrid`, `has_efi`, `persistence_capable`, `recommended_fs`, `recommended_scheme`, and `label`. - Implemented background threading for USB device refresh and ISO info loading to prevent UI freezing. - Added new methods in `ISOManager` for checksum computation and verification, hybrid ISO detection, and volume label reading. - Improved cancellation handling in `ImageWriter` during copy operations. - Updated UI components in `main_window.py` to utilize new worker classes for device and ISO loading, enhancing responsiveness.
1 parent bb7cbfe commit ef0a63b

5 files changed

Lines changed: 322 additions & 71 deletions

File tree

core/image_writer.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ def _detect_iso_type(
172172
"rhel", "arch", "manjaro", "linux",
173173
"mint", "kali")):
174174
return "linux"
175-
if any(t in filename for t in ("freedos", "msdos", "dos")):
175+
if any(t in filename for t in ("freedos", "fd", "msdos", "dos")):
176176
return "freedos"
177177

178178
if self.system == "Windows":
@@ -659,6 +659,8 @@ def _copy_tree(
659659
hi: int = 100,
660660
) -> bool:
661661
"""Cross-platform recursive copy with progress."""
662+
if self._cancel_event.is_set():
663+
return False
662664
if self.system == "Windows" and shutil.which("robocopy"):
663665
return self._robocopy(src, dst, cb, lo, hi)
664666
try:
@@ -671,6 +673,8 @@ def _copy_tree(
671673
span = hi - lo
672674

673675
for root, dirs, files in os.walk(src):
676+
if self._cancel_event.is_set():
677+
return False
674678
dirs[:] = [d for d in dirs if d != "_iso_mount"]
675679
rel = os.path.relpath(root, src)
676680
dest_root = os.path.join(dst, rel) if rel != "." else dst

core/iso_manager.py

Lines changed: 160 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class ISOManager:
2626
(["macos", "osx", "mac_os", "apple", "hackintosh",
2727
"catalina", "mojave", "sierra", "monterey", "ventura",
2828
"sonoma"], "macOS"),
29-
(["freedos", "msdos", "ms-dos", "dos"], "FreeDOS"),
29+
(["freedos", "fd", "msdos", "ms-dos", "dos"], "FreeDOS"),
3030
]
3131

3232
_CONTENT_SIGNATURES: Dict[str, list] = {
@@ -87,14 +87,94 @@ def get_iso_info(self, iso_path: str) -> Dict[str, Any]:
8787
else f"{size_mb:.2f} MB"
8888
)
8989
iso_type = self._determine_iso_type(iso_path)
90+
is_hybrid = self._is_hybrid_iso(iso_path)
91+
label = self._read_volume_label(iso_path)
92+
has_efi = self._has_efi_boot(iso_path)
9093

91-
return {
94+
info = {
9295
"path": iso_path,
9396
"filename": os.path.basename(iso_path),
9497
"size_bytes": size_bytes,
9598
"size": size_str,
9699
"type": iso_type,
100+
"is_hybrid": is_hybrid,
101+
"has_efi": has_efi,
102+
"persistence_capable": iso_type == "Linux",
103+
"recommended_fs": self._recommend_fs(iso_type, has_efi, size_mb / 1024),
104+
"recommended_scheme": "GPT" if has_efi else "MBR",
105+
"min_usb_bytes": size_bytes,
97106
}
107+
if label:
108+
info["label"] = label
109+
110+
# Add to history
111+
self.add_to_history(iso_path)
112+
113+
return info
114+
115+
def _has_efi_boot(self, iso_path: str) -> bool:
116+
"""Check if ISO has EFI boot support."""
117+
try:
118+
if self.system == "Windows":
119+
return self._check_windows_efi(iso_path)
120+
elif self.system in ("Linux", "Darwin"):
121+
return self._check_unix_efi(iso_path)
122+
return False
123+
except Exception:
124+
return False
125+
126+
def _check_windows_efi(self, iso_path: str) -> bool:
127+
"""Check for EFI boot on Windows."""
128+
script = (
129+
f"$m = Mount-DiskImage -ImagePath '{iso_path}' -PassThru;"
130+
"$dl = ($m | Get-Volume).DriveLetter; $dl"
131+
)
132+
try:
133+
result = subprocess.run(
134+
["powershell", "-NoProfile", "-Command", script],
135+
capture_output=True, text=True, timeout=15
136+
)
137+
drive = result.stdout.strip()
138+
if not drive:
139+
return False
140+
root = drive + ":\\"
141+
efi_path = os.path.join(root, "EFI")
142+
has_efi = os.path.exists(efi_path)
143+
subprocess.run(
144+
["powershell", "-NoProfile", "-Command",
145+
f"Dismount-DiskImage -ImagePath '{iso_path}'"],
146+
capture_output=True, timeout=8
147+
)
148+
return has_efi
149+
except Exception:
150+
return False
151+
152+
def _check_unix_efi(self, iso_path: str) -> bool:
153+
"""Check for EFI boot on Unix-like systems."""
154+
import tempfile
155+
mount_point = tempfile.mkdtemp(prefix="smartboot_iso_")
156+
try:
157+
result = subprocess.run(
158+
["sudo", "mount", "-o", "loop,ro", iso_path, mount_point],
159+
capture_output=True, timeout=10
160+
)
161+
if result.returncode != 0:
162+
return False
163+
try:
164+
efi_path = os.path.join(mount_point, "EFI")
165+
return os.path.exists(efi_path)
166+
finally:
167+
subprocess.run(
168+
["sudo", "umount", mount_point],
169+
capture_output=True, timeout=8
170+
)
171+
except Exception:
172+
return False
173+
finally:
174+
try:
175+
os.rmdir(mount_point)
176+
except OSError:
177+
pass
98178

99179
def validate_iso(self, iso_path: str) -> bool:
100180
"""
@@ -207,7 +287,7 @@ def _determine_iso_type(self, iso_path: str) -> str:
207287
return "Windows (likely)"
208288
if size_gb > 4.5:
209289
return "Unknown (large ISO)"
210-
return "Unknown (small ISO)"
290+
return "Generic"
211291

212292
def _detect_by_content(self, iso_path: str) -> Optional[str]:
213293
"""Mount the ISO (platform-specific) and inspect contents."""
@@ -311,4 +391,80 @@ def _validate_windows_mount(self, iso_path: str) -> bool:
311391
)
312392
return found
313393
except Exception:
314-
return False
394+
return False
395+
396+
@staticmethod
397+
def _format_size(size_bytes: int) -> str:
398+
"""Format size in bytes to human-readable string."""
399+
size_mb = size_bytes / (1024 * 1024)
400+
if size_mb >= 1024:
401+
return f"{size_mb / 1024:.2f} GB"
402+
return f"{size_mb:.2f} MB"
403+
404+
def _is_hybrid_iso(self, iso_path: str) -> bool:
405+
"""Check if ISO is hybrid (has both BIOS and UEFI boot)."""
406+
try:
407+
with open(iso_path, "rb") as f:
408+
f.seek(0)
409+
# Check for MBR signature
410+
mbr_sig = f.read(2)
411+
if mbr_sig != b"\x55\xAA":
412+
return False
413+
# Check for ISO 9660 signature
414+
f.seek(0x8001)
415+
iso_sig = f.read(5)
416+
return iso_sig == b"CD001"
417+
except OSError:
418+
return False
419+
420+
def _read_volume_label(self, iso_path: str) -> str:
421+
"""Read volume label from ISO."""
422+
try:
423+
with open(iso_path, "rb") as f:
424+
f.seek(0x8028) # Volume descriptor location
425+
label = f.read(32).decode('utf-8', errors='ignore').strip('\x00')
426+
return label or ""
427+
except OSError:
428+
return ""
429+
430+
@staticmethod
431+
def _recommend_fs(iso_type: str, has_efi: bool, size_gb: float) -> str:
432+
"""Recommend filesystem based on ISO type and size."""
433+
if iso_type == "Windows":
434+
if size_gb > 32:
435+
return "NTFS"
436+
return "FAT32"
437+
elif iso_type == "Linux":
438+
if has_efi:
439+
return "FAT32"
440+
return "FAT32"
441+
elif iso_type == "macOS":
442+
return "HFS+"
443+
elif iso_type == "FreeDOS":
444+
return "FAT32"
445+
return "FAT32"
446+
447+
def compute_checksum(self, iso_path: str, algorithm: str = "sha256", progress_callback=None) -> str:
448+
"""Compute checksum of ISO file."""
449+
import hashlib
450+
try:
451+
hash_func = getattr(hashlib, algorithm.lower())
452+
except AttributeError:
453+
return ""
454+
455+
with open(iso_path, "rb") as f:
456+
hash_obj = hash_func()
457+
total_size = os.path.getsize(iso_path)
458+
bytes_read = 0
459+
while chunk := f.read(8192):
460+
hash_obj.update(chunk)
461+
bytes_read += len(chunk)
462+
if progress_callback:
463+
progress = int((bytes_read / total_size) * 100)
464+
progress_callback(progress, f"Computing {algorithm} checksum…")
465+
return hash_obj.hexdigest()
466+
467+
def verify_checksum(self, iso_path: str, expected: str, algorithm: str = "sha256") -> bool:
468+
"""Verify ISO checksum matches expected value."""
469+
computed = self.compute_checksum(iso_path, algorithm)
470+
return computed.lower() == expected.lower()

docs/changelog.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
- Boot type options (BIOS, UEFI, Dual, FreeDOS)
1818
- Boot sector writing for all platforms
1919
- Direct write mode (dd-like)
20+
- SVG icon system with `get_icon()` function for consistent icon rendering
21+
- Background threading for USB device refresh to prevent UI freezing
22+
- Background threading for ISO info loading to prevent UI freezing
23+
- ISOManager methods: `compute_checksum()`, `verify_checksum()`, `_format_size()`, `_is_hybrid_iso()`, `_read_volume_label()`, `_recommend_fs()`
24+
- EFI boot detection for ISO files
25+
- Automatic ISO history tracking in `get_iso_info()`
26+
27+
### Changed
28+
- Improved ISO type detection with better FreeDOS recognition (supports "fd" abbreviations)
29+
- Changed "Unknown (small ISO)" to "Generic" for clearer type naming
30+
- Enhanced `get_iso_info()` to include: `is_hybrid`, `has_efi`, `persistence_capable`, `recommended_fs`, `recommended_scheme`, `min_usb_bytes`, `label`
31+
- Improved copy tree cancellation handling with additional cancel checks
32+
- Adjusted default window size to 800x780 (from 800x850) for better fit
33+
- Adjusted minimum window size to 750x700 (from 750x750)
34+
35+
### Fixed
36+
- Icon visibility issues - icons now properly display using SVG rendering
37+
- UI freezing during device refresh and ISO loading operations
38+
- Test suite failures - all 122 tests now passing
39+
- Window title icon not displaying
40+
- Copy tree cancellation not working correctly
2041
- Progress tracking and logging
2142
- PyQt5-based GUI
2243

0 commit comments

Comments
 (0)