discstation 0.1.39 → 0.1.40

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.39",
3
+ "version": "0.1.40",
4
4
  "description": "DiscStation optical-media appliance host — one cross-OS installer",
5
5
  "bin": {
6
6
  "discstation-setup": "scripts/setup.mjs",
@@ -184,6 +184,22 @@ def _mac_optical_device():
184
184
 
185
185
  _last_disc_device = None
186
186
 
187
+
188
+ class NoMediaError(FileNotFoundError):
189
+ """The optical drive is present but has no disc it can see."""
190
+
191
+
192
+ def _mac_drive_name():
193
+ """Drive model from ioreg, which lists it whether or not a disc is loaded
194
+ (the /dev/diskN node only exists while media is)."""
195
+ try:
196
+ out = subprocess.run(["/usr/sbin/ioreg", "-r", "-c", "IODVDServices", "-l"],
197
+ capture_output=True, text=True, check=False, timeout=3).stdout
198
+ except (OSError, subprocess.TimeoutExpired):
199
+ return None
200
+ m = re.search(r'"Product Name"="([^"]+)"', out)
201
+ return m.group(1) if m else None
202
+
187
203
  # --- Windows: IMAPI2 / WMI probes via bundled PowerShell helpers ---------------
188
204
  _WIN_DIR = Path(__file__).resolve().parent / "win"
189
205
  _win_info_cache = (0.0, None)
@@ -259,6 +275,9 @@ def disc_device():
259
275
  # known node so the caller can still probe it (it just reports no media).
260
276
  if _last_disc_device:
261
277
  return _last_disc_device
278
+ drive = _mac_drive_name()
279
+ if drive:
280
+ raise NoMediaError(f"Drive found ({drive}) but no disc is readable")
262
281
  elif override:
263
282
  return override
264
283
 
@@ -272,7 +291,7 @@ def disc_device():
272
291
  return str(drives[0])
273
292
  elif system == "darwin":
274
293
  try:
275
- status = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True, text=True, check=False, timeout=3)
294
+ status = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True, text=True, check=False, timeout=15)
276
295
  except (OSError, subprocess.TimeoutExpired):
277
296
  status = None
278
297
  if status:
@@ -304,7 +323,7 @@ def drive_status():
304
323
  return "unknown"
305
324
  try:
306
325
  r = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True,
307
- text=True, check=False, timeout=4)
326
+ text=True, check=False, timeout=15)
308
327
  except (OSError, subprocess.TimeoutExpired):
309
328
  return "unknown"
310
329
  text = (r.stdout + r.stderr).lower()
@@ -330,7 +349,7 @@ def _tag_rewritable(props, media_type):
330
349
 
331
350
 
332
351
  def _drutil_media_properties():
333
- result = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True, text=True, check=False, timeout=3)
352
+ result = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True, text=True, check=False, timeout=15)
334
353
  text = result.stdout + result.stderr
335
354
  lowered = text.lower()
336
355
  if result.returncode != 0 or "no media" in lowered:
@@ -382,7 +401,9 @@ def media_properties(device):
382
401
  mount_point = fields.get("Mount Point", "")
383
402
  if mount_point and mount_point != "Not applicable":
384
403
  props["ID_MOUNT_POINT"] = mount_point
385
- if label.lower() == "audio cd":
404
+ if label.lower() == "audio cd" or props.get("ID_FS_TYPE") == "cddafs":
405
+ # cddafs is macOS's filesystem for Red Book audio - the only signal
406
+ # that works for a burned CD-R, whose media type ("CD-R") has an "r".
386
407
  props["ID_CDROM_MEDIA_TYPE"] = "audio"
387
408
  elif "cd" in optical.lower() and "r" not in optical.lower():
388
409
  props["ID_CDROM_MEDIA_TYPE"] = "audio"
@@ -430,7 +451,7 @@ def media_capacity_bytes(device):
430
451
  try:
431
452
  result = subprocess.run(["/usr/sbin/diskutil", "info", device], capture_output=True, text=True, check=False, timeout=3)
432
453
  except subprocess.TimeoutExpired:
433
- result = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True, text=True, check=False, timeout=3)
454
+ result = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True, text=True, check=False, timeout=15)
434
455
  match = re.search(r"blocks:\s*(\d+)\s*/", result.stdout + result.stderr, re.IGNORECASE)
435
456
  if match:
436
457
  return int(match.group(1)) * 2048