discstation 0.1.2 → 0.1.7
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 +1 -1
- package/src/discstation.py +24 -2
- package/src/discstation_host.py +38 -5
package/package.json
CHANGED
package/src/discstation.py
CHANGED
|
@@ -918,6 +918,8 @@ _tray_open_since = 0.0 # time.monotonic() of the last OLED-initiated eject
|
|
|
918
918
|
|
|
919
919
|
|
|
920
920
|
def _tray_closed_with_disc(device):
|
|
921
|
+
if not device:
|
|
922
|
+
return False
|
|
921
923
|
global _tray_open
|
|
922
924
|
if not Path(device).exists():
|
|
923
925
|
return False
|
|
@@ -932,6 +934,8 @@ def _tray_closed_with_disc(device):
|
|
|
932
934
|
|
|
933
935
|
|
|
934
936
|
def disc_present(device):
|
|
937
|
+
if not device:
|
|
938
|
+
return False
|
|
935
939
|
if _tray_closed_with_disc(device):
|
|
936
940
|
return True
|
|
937
941
|
if _tray_open:
|
|
@@ -968,6 +972,8 @@ def disc_present(device):
|
|
|
968
972
|
|
|
969
973
|
|
|
970
974
|
def is_blank_disc(device):
|
|
975
|
+
if not device:
|
|
976
|
+
return False
|
|
971
977
|
if not Path(device).exists():
|
|
972
978
|
return False
|
|
973
979
|
|
|
@@ -1017,6 +1023,8 @@ def is_blank_disc(device):
|
|
|
1017
1023
|
|
|
1018
1024
|
|
|
1019
1025
|
def is_rewritable_disc(device):
|
|
1026
|
+
if not device:
|
|
1027
|
+
return False
|
|
1020
1028
|
"""Return whether the inserted medium can be overwritten."""
|
|
1021
1029
|
if not Path(device).exists():
|
|
1022
1030
|
return False
|
|
@@ -1100,6 +1108,10 @@ def drive_status(device):
|
|
|
1100
1108
|
Returns 'disc' | 'no_disc' | 'open' | 'loading' | 'unknown'. Single ioctl on
|
|
1101
1109
|
an O_NONBLOCK fd — does not consult the (stale on this USB bridge) udev db and
|
|
1102
1110
|
does not disturb the tray. Never raises."""
|
|
1111
|
+
if not device:
|
|
1112
|
+
return "unknown"
|
|
1113
|
+
if discstation_host.system_name() != "linux":
|
|
1114
|
+
return discstation_host.drive_status()
|
|
1103
1115
|
try:
|
|
1104
1116
|
fd = os.open(device, os.O_RDONLY | os.O_NONBLOCK)
|
|
1105
1117
|
except OSError:
|
|
@@ -1118,6 +1130,8 @@ def drive_status(device):
|
|
|
1118
1130
|
|
|
1119
1131
|
def _media_quick_state(device, props):
|
|
1120
1132
|
"""Fast, cheap read of whether a disc is loaded. No long probes."""
|
|
1133
|
+
if not device:
|
|
1134
|
+
return "empty"
|
|
1121
1135
|
if _tray_open:
|
|
1122
1136
|
return "empty" # deliberate OLED eject — do not poke the drive
|
|
1123
1137
|
st = drive_status(device)
|
|
@@ -1402,6 +1416,8 @@ def detect_disc(device, settle=True, budget=None, force=False):
|
|
|
1402
1416
|
disc_status_line / disc_title / menu_items_for_disc in one refresh burst do a
|
|
1403
1417
|
single probe. `settle=False` skips the post-insert wait (used by the web
|
|
1404
1418
|
endpoint, which can just poll again)."""
|
|
1419
|
+
if not device:
|
|
1420
|
+
return _disc_info(False, "none")
|
|
1405
1421
|
if _tray_open:
|
|
1406
1422
|
# Tray was deliberately ejected from the OLED — report "no disc" without
|
|
1407
1423
|
# touching the drive (cdrom_id / wodim / open() would re-close the tray).
|
|
@@ -3654,7 +3670,10 @@ def rip_audio_cd(ser, device, artist_hint=None, album_hint=None):
|
|
|
3654
3670
|
def station_loop(ser, url, artist_hint=None, album_hint=None):
|
|
3655
3671
|
global _last_burn_result, _last_burn_result_time, _tray_open, _tray_open_since
|
|
3656
3672
|
discstation_burn.cleanup_old_jobs()
|
|
3657
|
-
|
|
3673
|
+
try:
|
|
3674
|
+
device = discstation_burn.disc_device()
|
|
3675
|
+
except Exception:
|
|
3676
|
+
device = None # empty drive (e.g. macOS after an eject) — keep the loop alive
|
|
3658
3677
|
|
|
3659
3678
|
for _ in range(50):
|
|
3660
3679
|
line = read_serial_line(ser, timeout=0.2)
|
|
@@ -3708,7 +3727,10 @@ def station_loop(ser, url, artist_hint=None, album_hint=None):
|
|
|
3708
3727
|
_disc_poll_start = 0
|
|
3709
3728
|
|
|
3710
3729
|
def _poll_disc():
|
|
3711
|
-
|
|
3730
|
+
try:
|
|
3731
|
+
current_device = discstation_burn.disc_device()
|
|
3732
|
+
except Exception:
|
|
3733
|
+
return "Disc: none", None # no drive/media visible right now
|
|
3712
3734
|
return disc_status_line(current_device), current_device
|
|
3713
3735
|
|
|
3714
3736
|
def apply_disc_line(new_line):
|
package/src/discstation_host.py
CHANGED
|
@@ -96,15 +96,25 @@ def _mac_optical_device():
|
|
|
96
96
|
return f"/dev/{match.group(1)}" if match else None
|
|
97
97
|
|
|
98
98
|
|
|
99
|
+
_last_disc_device = None
|
|
100
|
+
|
|
101
|
+
|
|
99
102
|
def disc_device():
|
|
103
|
+
global _last_disc_device
|
|
100
104
|
system = system_name()
|
|
101
105
|
override = os.environ.get("DISC_DEVICE") or os.environ.get("DVD_DEVICE")
|
|
102
106
|
if system == "darwin":
|
|
103
107
|
detected = _mac_optical_device()
|
|
104
108
|
if detected:
|
|
109
|
+
_last_disc_device = detected
|
|
105
110
|
return detected
|
|
106
|
-
if override
|
|
111
|
+
if override:
|
|
107
112
|
return override
|
|
113
|
+
# macOS: the /dev/diskN node only exists while media is loaded. Once a
|
|
114
|
+
# disc is ejected there is nothing to detect — keep returning the last
|
|
115
|
+
# known node so the caller can still probe it (it just reports no media).
|
|
116
|
+
if _last_disc_device:
|
|
117
|
+
return _last_disc_device
|
|
108
118
|
elif override:
|
|
109
119
|
return override
|
|
110
120
|
|
|
@@ -134,6 +144,25 @@ def disc_device():
|
|
|
134
144
|
raise FileNotFoundError("No optical disc drive found; set DISC_DEVICE explicitly")
|
|
135
145
|
|
|
136
146
|
|
|
147
|
+
def drive_status():
|
|
148
|
+
"""Non-Linux equivalent of the CDROM_DRIVE_STATUS ioctl.
|
|
149
|
+
Returns 'disc' | 'no_disc' | 'unknown'. macOS: parse `drutil status`."""
|
|
150
|
+
if system_name() != "darwin":
|
|
151
|
+
return "unknown"
|
|
152
|
+
try:
|
|
153
|
+
r = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True,
|
|
154
|
+
text=True, check=False, timeout=4)
|
|
155
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
156
|
+
return "unknown"
|
|
157
|
+
text = (r.stdout + r.stderr).lower()
|
|
158
|
+
if "no media" in text or "media is not present" in text:
|
|
159
|
+
return "no_disc"
|
|
160
|
+
if "name: /dev/disk" in text or "type: cd" in text or "type: dvd" in text \
|
|
161
|
+
or "type: bd" in text:
|
|
162
|
+
return "disc"
|
|
163
|
+
return "unknown"
|
|
164
|
+
|
|
165
|
+
|
|
137
166
|
def _drutil_media_properties():
|
|
138
167
|
result = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True, text=True, check=False, timeout=3)
|
|
139
168
|
text = result.stdout + result.stderr
|
|
@@ -364,10 +393,14 @@ def eject_device(device, close=False):
|
|
|
364
393
|
command.append("-t")
|
|
365
394
|
command.append(device)
|
|
366
395
|
elif system == "darwin":
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if
|
|
370
|
-
commands
|
|
396
|
+
# `drutil tray open` is a no-op on slot-load drives; `drutil eject`
|
|
397
|
+
# works on both. `diskutil eject` is the fallback for a mounted disc.
|
|
398
|
+
if close:
|
|
399
|
+
commands = [["/usr/bin/drutil", "tray", "close"]]
|
|
400
|
+
else:
|
|
401
|
+
commands = [["/usr/bin/drutil", "eject"]]
|
|
402
|
+
if device:
|
|
403
|
+
commands.append(["/usr/sbin/diskutil", "eject", device])
|
|
371
404
|
for command in commands:
|
|
372
405
|
try:
|
|
373
406
|
if subprocess.run(command, capture_output=True, text=True, timeout=10).returncode == 0:
|