diff --git a/archivebox/cli/archivebox_archiveresult.py b/archivebox/cli/archivebox_archiveresult.py
index 545f7157..2cabd92c 100644
--- a/archivebox/cli/archivebox_archiveresult.py
+++ b/archivebox/cli/archivebox_archiveresult.py
@@ -152,7 +152,7 @@ def create_archiveresults(
config = get_config(crawl=snapshot.crawl, snapshot=snapshot)
hooks = discover_hooks("Snapshot", config=config)
for hook_path in hooks:
- hook_name = hook_path.name
+ hook_name = hook_path.stem
plugin_name = hook_path.parent.name
if not is_tty:
write_record(build_archiveresult_request(snapshot.id, plugin_name, hook_name=hook_name, status=status))
diff --git a/archivebox/core/forms.py b/archivebox/core/forms.py
index 32820d02..8a40b438 100644
--- a/archivebox/core/forms.py
+++ b/archivebox/core/forms.py
@@ -685,7 +685,7 @@ class AddLinkForm(PluginConfigFormMixin, forms.Form):
),
)
persona = forms.ModelChoiceField(
- label="Persona (authentication profile)",
+ label="Persona (configuration profile)",
required=False,
queryset=Persona.objects.none(),
empty_label=None,
diff --git a/archivebox/core/models.py b/archivebox/core/models.py
index 8702ae88..974046c3 100755
--- a/archivebox/core/models.py
+++ b/archivebox/core/models.py
@@ -2565,7 +2565,7 @@ class Snapshot(ModelWithDeleteAfter, ModelWithOutputDir, ModelWithConfig, ModelW
archiveresults = []
for hook_path in hooks:
- hook_name = hook_path.name # e.g., 'on_Snapshot__50_wget.py'
+ hook_name = hook_path.stem # e.g., 'on_Snapshot__50_wget'
plugin = hook_path.parent.name # e.g., 'wget'
# ArchiveResult output is one filesystem directory per plugin hook, so
diff --git a/archivebox/core/views.py b/archivebox/core/views.py
index 4b0c54ef..e2e809bc 100644
--- a/archivebox/core/views.py
+++ b/archivebox/core/views.py
@@ -1132,6 +1132,7 @@ class AddView(UserPassesTestMixin, FormView):
"effective_config": effective_config_json,
"binary_urls": binary_urls,
}
+ recent_personas = list(persona_queryset.order_by("-created_at", "name")[:5])
plugin_dependency_map = {}
if can_override_crawl_config:
plugin_dependency_map = {
@@ -1153,6 +1154,7 @@ class AddView(UserPassesTestMixin, FormView):
"required_search_plugin": required_search_plugin,
"plugin_dependency_map_json": json.dumps(plugin_dependency_map, sort_keys=True),
"persona_config_map_json": json.dumps(persona_config_map, sort_keys=True, default=str),
+ "recent_personas": recent_personas,
"can_override_crawl_config": can_override_crawl_config,
"stdout": "",
}
@@ -1621,22 +1623,20 @@ def live_progress_view(request):
snapshots_paused = snapshot_status_counts.get(Snapshot.StatusChoices.PAUSED, 0)
download_plugin_names, indexing_plugin_names = _live_progress_plugin_names()
- archiveresult_status_counts: dict[str, int] = {}
- download_status_counts: dict[str, int] = {}
- indexing_status_counts: dict[str, int] = {}
- for status in (
+ result_statuses = (
ArchiveResult.StatusChoices.QUEUED,
ArchiveResult.StatusChoices.STARTED,
ArchiveResult.StatusChoices.PAUSED,
- ):
- for row in archiveresult_scope.filter(status=status).values("plugin").annotate(count=Count("id")).order_by():
- plugin = row["plugin"]
- count = row["count"]
- archiveresult_status_counts[status] = archiveresult_status_counts.get(status, 0) + count
- if plugin in indexing_plugin_names:
- indexing_status_counts[status] = indexing_status_counts.get(status, 0) + count
- elif plugin in download_plugin_names:
- download_status_counts[status] = download_status_counts.get(status, 0) + count
+ )
+ archiveresult_status_counts = count_statuses(archiveresult_scope, result_statuses)
+ download_scope = archiveresult_scope.filter(
+ plugin__in=download_plugin_names,
+ snapshot__status__in=(Snapshot.StatusChoices.QUEUED, Snapshot.StatusChoices.STARTED),
+ snapshot__crawl__status__in=(Crawl.StatusChoices.QUEUED, Crawl.StatusChoices.STARTED),
+ )
+ indexing_scope = archiveresult_scope.filter(plugin__in=indexing_plugin_names)
+ download_status_counts = count_statuses(download_scope, result_statuses)
+ indexing_status_counts = count_statuses(indexing_scope, result_statuses)
archiveresults_pending = archiveresult_status_counts.get(ArchiveResult.StatusChoices.QUEUED, 0)
archiveresults_started = archiveresult_status_counts.get(ArchiveResult.StatusChoices.STARTED, 0)
archiveresults_paused = archiveresult_status_counts.get(ArchiveResult.StatusChoices.PAUSED, 0)
diff --git a/archivebox/services/runner.py b/archivebox/services/runner.py
index cb7c851a..d8b014dc 100644
--- a/archivebox/services/runner.py
+++ b/archivebox/services/runner.py
@@ -13,6 +13,7 @@ import time
from collections.abc import Mapping
from contextlib import nullcontext
from datetime import timedelta
+from functools import lru_cache
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Any
@@ -1185,19 +1186,43 @@ def run_binary(binary_id: str) -> None:
asyncio.run(_run_binary(binary_id))
+@lru_cache(maxsize=1)
+def _snapshot_hook_names_by_plugin() -> dict[str, frozenset[str]]:
+ return {plugin.name: frozenset(hook.name for hook in plugin.filter_hooks("Snapshot")) for plugin in discover_plugins().values()}
+
+
def queued_plugins_for_snapshot(snapshot_id: str) -> list[str] | None:
from archivebox.core.models import ArchiveResult
- queued_plugins = sorted(
- set(
- ArchiveResult.objects.filter(
- snapshot_id=snapshot_id,
- status=ArchiveResult.StatusChoices.QUEUED,
- )
- .exclude(plugin="")
- .values_list("plugin", flat=True),
- ),
+ queued_results = list(
+ ArchiveResult.objects.filter(
+ snapshot_id=snapshot_id,
+ status=ArchiveResult.StatusChoices.QUEUED,
+ )
+ .exclude(plugin="")
+ .only("id", "plugin", "hook_name"),
)
+ hooks_by_plugin = _snapshot_hook_names_by_plugin()
+ obsolete_result_ids = [
+ result.id
+ for result in queued_results
+ if result.hook_name and result.hook_name not in hooks_by_plugin.get(result.plugin, frozenset())
+ ]
+ if obsolete_result_ids:
+ # Hook names are the scheduler identity for ArchiveResults. If an old
+ # queued row names a hook that the current plugin model cannot run, mark
+ # only that row final so the Snapshot scheduler can drain normally instead
+ # of re-running the whole plugin forever.
+ ArchiveResult.objects.filter(
+ id__in=obsolete_result_ids,
+ status=ArchiveResult.StatusChoices.QUEUED,
+ ).update(
+ status=ArchiveResult.StatusChoices.SKIPPED,
+ output_str="Hook no longer exists in the current plugin set.",
+ modified_at=timezone.now(),
+ )
+
+ queued_plugins = sorted({result.plugin for result in queued_results if result.id not in obsolete_result_ids})
if queued_plugins:
return queued_plugins
return None
diff --git a/archivebox/templates/core/add.html b/archivebox/templates/core/add.html
index 487388bd..386e8534 100644
--- a/archivebox/templates/core/add.html
+++ b/archivebox/templates/core/add.html
@@ -100,9 +100,9 @@
{{ form.persona.errors }}
{% endif %}
- Authentication profile (Chrome profile, cookies, etc.) to use when accessing URLs.
+ Authentication + configuration settings to use when saving URLs (cookies, user agent, resolution, timeouts, etc.)
{% if can_override_crawl_config %}
- Create new persona / import from Chrome →
+ Create new profile / import from Chrome ->
{% endif %}