@danhachuel/thunderbolt 0.7.12 → 0.7.14
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/app/main.py +7 -6
- package/hermes_ui/storage.py +23 -2
- package/package.json +1 -1
package/app/main.py
CHANGED
|
@@ -3096,7 +3096,7 @@ def render_channels():
|
|
|
3096
3096
|
channel_id = str(channel["id"])
|
|
3097
3097
|
edit_key = f"edit_channel_{channel_id}"
|
|
3098
3098
|
with st.container(border=True):
|
|
3099
|
-
header_cols = st.columns([0.7, 2.75, 1.1, 1.0, 1.35, 1.35])
|
|
3099
|
+
header_cols = st.columns([0.7, 2.75, 1.15, 1.1, 1.0, 1.35, 1.35])
|
|
3100
3100
|
channel_url = str(channel.get("url") or "").strip()
|
|
3101
3101
|
if not channel_url:
|
|
3102
3102
|
channel_id_or_handle = str(channel.get("youtube_channel_id") or channel.get("youtube_id") or channel.get("handle") or "").strip()
|
|
@@ -3119,14 +3119,15 @@ def render_channels():
|
|
|
3119
3119
|
st.caption(channel_niche_label(channel))
|
|
3120
3120
|
st.caption(f"{channel.get('handle') or channel.get('url') or 'sem URL'} · {channel.get('metrics_source', 'manual')}")
|
|
3121
3121
|
with header_cols[2]:
|
|
3122
|
-
|
|
3122
|
+
if channel_url:
|
|
3123
|
+
st.link_button("Abrir canal", channel_url, type="primary", width="content")
|
|
3123
3124
|
with header_cols[3]:
|
|
3124
|
-
st.metric("
|
|
3125
|
+
st.metric("Inscritos", _format_channel_count(channel.get("subscriber_count")))
|
|
3125
3126
|
with header_cols[4]:
|
|
3126
|
-
st.metric("
|
|
3127
|
+
st.metric("Vídeos", _format_channel_count(channel.get("video_count")))
|
|
3127
3128
|
with header_cols[5]:
|
|
3128
|
-
|
|
3129
|
-
|
|
3129
|
+
st.metric("Visualizações", _format_channel_count(channel.get("view_count")))
|
|
3130
|
+
with header_cols[6]:
|
|
3130
3131
|
if st.button("↻", key=f"refresh_youtube_metrics_{channel_id}", help="Actualizar Inscritos, Vídeos e Visualizações", width="stretch"):
|
|
3131
3132
|
with st.spinner("A actualizar métricas YouTube…"):
|
|
3132
3133
|
refreshed, message = _refresh_youtube_channel_metrics(channel, youtube)
|
package/hermes_ui/storage.py
CHANGED
|
@@ -448,7 +448,7 @@ def ensure_storage() -> None:
|
|
|
448
448
|
|
|
449
449
|
_LOCK_TIMEOUT_SECONDS = 30.0
|
|
450
450
|
_LOCK_POLL_SECONDS = 0.05
|
|
451
|
-
_LOCK_STALE_SECONDS =
|
|
451
|
+
_LOCK_STALE_SECONDS = 120
|
|
452
452
|
_PROTECTED_STATE_FILES = {"channels.json", "tasks.json", "batches.json", "queues.json", "uploads.json"}
|
|
453
453
|
|
|
454
454
|
|
|
@@ -466,6 +466,12 @@ def _state_lock(path: Path, *, read_only: bool = False) -> Iterator[None]:
|
|
|
466
466
|
"""
|
|
467
467
|
lock_path = path.with_name(f".{path.name}.lock")
|
|
468
468
|
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
469
|
+
if read_only:
|
|
470
|
+
# JSON writes use os.replace(), so a reader can safely read either the
|
|
471
|
+
# old complete file or the new complete file. Never make page loads
|
|
472
|
+
# wait for a writer/antivirus handle on Windows.
|
|
473
|
+
yield
|
|
474
|
+
return
|
|
469
475
|
deadline = time.monotonic() + _LOCK_TIMEOUT_SECONDS
|
|
470
476
|
descriptor: int | None = None
|
|
471
477
|
while descriptor is None:
|
|
@@ -483,7 +489,22 @@ def _state_lock(path: Path, *, read_only: bool = False) -> Iterator[None]:
|
|
|
483
489
|
return
|
|
484
490
|
except FileExistsError:
|
|
485
491
|
try:
|
|
486
|
-
|
|
492
|
+
lock_age = time.time() - lock_path.stat().st_mtime
|
|
493
|
+
owner_pid = ""
|
|
494
|
+
try:
|
|
495
|
+
owner_pid = lock_path.read_text(encoding="ascii").split("pid=", 1)[1].splitlines()[0].strip()
|
|
496
|
+
except (IndexError, OSError, UnicodeDecodeError):
|
|
497
|
+
pass
|
|
498
|
+
owner_alive = False
|
|
499
|
+
if owner_pid.isdigit():
|
|
500
|
+
try:
|
|
501
|
+
os.kill(int(owner_pid), 0)
|
|
502
|
+
owner_alive = True
|
|
503
|
+
except PermissionError:
|
|
504
|
+
owner_alive = True
|
|
505
|
+
except (ProcessLookupError, OSError):
|
|
506
|
+
owner_alive = False
|
|
507
|
+
if (owner_pid and not owner_alive) or (not owner_pid and lock_age > _LOCK_STALE_SECONDS):
|
|
487
508
|
lock_path.unlink()
|
|
488
509
|
continue
|
|
489
510
|
except OSError:
|
package/package.json
CHANGED