discstation 0.1.13 → 0.1.15

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discstation",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "DiscStation optical-media appliance host — one cross-OS installer",
5
5
  "bin": {
6
6
  "discstation-setup": "scripts/setup.mjs",
@@ -571,6 +571,13 @@ def _record_web_status(msg):
571
571
  elif msg.startswith("CANCELLED:"):
572
572
  _web_status = msg[10:].strip() or "CANCELLED"
573
573
  _web_progress_active = False
574
+ elif msg.startswith(("STANDBY:", "HOME:")):
575
+ # idle again (tray open, insert disc, back to the menu) — clear any
576
+ # lingering "Ejecting..." / progress state on the web UI.
577
+ text = msg.split(":", 1)[1].strip()
578
+ _web_status = "READY" if text in ("", "DiscStation", "Select mode", "Starting...") else text
579
+ _web_progress = -1
580
+ _web_progress_active = False
574
581
  else:
575
582
  return
576
583
  _sse_publish(_status_snapshot())
@@ -2745,6 +2752,13 @@ def burn_data_flow(ser):
2745
2752
  if not files_to_burn:
2746
2753
  raise RuntimeError("No files to burn")
2747
2754
 
2755
+ # A single .iso (loose, a local path, or an upload folder holding just
2756
+ # the .iso) is written verbatim as a bootable image, not repackaged.
2757
+ if len(files_to_burn) == 1 and files_to_burn[0].is_dir():
2758
+ inner = [p for p in files_to_burn[0].iterdir() if p.is_file()]
2759
+ if len(inner) == 1 and inner[0].suffix.lower() == ".iso":
2760
+ files_to_burn = inner
2761
+
2748
2762
  total_bytes = sum(f.stat().st_size for f in files_to_burn if f.is_file())
2749
2763
  for d in files_to_burn:
2750
2764
  if d.is_dir():
@@ -3829,9 +3843,13 @@ def station_loop(ser, url, artist_hint=None, album_hint=None):
3829
3843
  apply_disc_line("Disc: reading...")
3830
3844
  elif st == "disc":
3831
3845
  _tray_open = False
3846
+ transient_words = ["none", "checking", "reading", "tray open"]
3847
+ if discstation_host.system_name() == "darwin":
3848
+ # macOS slot drives take a few seconds to mount; "unknown"
3849
+ # is a not-ready read, not a settled answer — keep re-polling.
3850
+ transient_words.append("unknown")
3832
3851
  have_line = last_disc_line and not any(
3833
- w in last_disc_line.lower()
3834
- for w in ("none", "checking", "reading", "tray open"))
3852
+ w in last_disc_line.lower() for w in transient_words)
3835
3853
  if not have_line and _disc_poll_future is None:
3836
3854
  _disc_poll_start = now
3837
3855
  last_disc_poll = now
@@ -295,23 +295,30 @@ def build_data_image(source_paths, output_path, label, video=False):
295
295
  # xorriso's mkisofs emulation is the same lineage as Linux's
296
296
  # genisoimage/growisofs; -dvd-video gives a set-top-compatible
297
297
  # VIDEO_TS layout (the arg is the dir *containing* VIDEO_TS).
298
+ # xorriso's mkisofs emulation does NOT accept `-udf`; -dvd-video alone
299
+ # gives a DVD-Video-compliant ISO9660 layout set-top players read.
298
300
  command = [tool("xorriso"), "-as", "mkisofs", "-V", label, "-o", str(output_path)]
299
301
  if video:
300
- command += ["-dvd-video", "-udf", str(source_paths[0])]
302
+ command += ["-dvd-video", str(source_paths[0])]
301
303
  else:
302
- command += ["-iso-level", "3", "-J", "-R", "-udf",
303
- *[str(path) for path in source_paths]]
304
+ command += ["-iso-level", "3", "-J", "-R", *[str(path) for path in source_paths]]
304
305
  else:
305
306
  raise RuntimeError("Image building is only used by non-Linux optical backends")
306
- subprocess.run(command, check=True, capture_output=True, text=True)
307
+ result = subprocess.run(command, capture_output=True, text=True)
308
+ if result.returncode != 0:
309
+ lines = [ln.strip() for ln in (result.stderr or result.stdout).splitlines() if ln.strip()]
310
+ tail = next((ln for ln in reversed(lines) if "FAILURE" in ln.upper()),
311
+ lines[-1] if lines else f"exit {result.returncode}")
312
+ raise RuntimeError(f"xorriso: {tail[:200]}")
307
313
  return output_path
308
314
 
309
315
 
310
316
  def iso_burn_command(device, image_path):
311
317
  system = system_name()
312
318
  if system == "darwin":
313
- # -puppetstrings emits machine-readable PERCENT: / MESSAGE: lines.
314
- return [tool("hdiutil"), "burn", "-puppetstrings", str(image_path)]
319
+ # -puppetstrings emits machine-readable PERCENT: / MESSAGE: lines;
320
+ # -nosynth writes the image verbatim so a bootable ISO stays bootable.
321
+ return [tool("hdiutil"), "burn", "-puppetstrings", "-nosynth", str(image_path)]
315
322
  if system == "windows":
316
323
  return [tool("isoburn.exe"), "/Q", device, str(image_path)]
317
324
  raise RuntimeError("ISO command requested on Linux; use growisofs backend")
@@ -424,6 +431,7 @@ def eject_device(device, close=False):
424
431
  command.append("-t")
425
432
  command.append(device)
426
433
  elif system == "darwin":
434
+ global _last_disc_device
427
435
  # `drutil tray open` is a no-op on slot-load drives; `drutil eject`
428
436
  # works on both. `diskutil eject` is the fallback for a mounted disc.
429
437
  if close:
@@ -435,6 +443,10 @@ def eject_device(device, close=False):
435
443
  for command in commands:
436
444
  try:
437
445
  if subprocess.run(command, capture_output=True, text=True, timeout=10).returncode == 0:
446
+ if not close:
447
+ # the /dev/diskN node is gone now — don't let disc_device()
448
+ # hand back this stale node when the next disc goes in.
449
+ _last_disc_device = None
438
450
  return True
439
451
  except (OSError, subprocess.TimeoutExpired):
440
452
  pass