summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/models/config.py1
-rw-r--r--stator/graph.py6
-rw-r--r--stator/models.py28
-rw-r--r--users/admin.py2
-rw-r--r--users/migrations/0003_identity_followers_etc.py38
-rw-r--r--users/models/identity.py20
-rw-r--r--users/models/inbox_message.py10
7 files changed, 91 insertions, 14 deletions
diff --git a/core/models/config.py b/core/models/config.py
index 6c31658..dab0059 100644
--- a/core/models/config.py
+++ b/core/models/config.py
@@ -213,6 +213,7 @@ class Config(models.Model):
identity_min_length: int = 2
identity_max_per_user: int = 5
identity_max_age: int = 24 * 60 * 60
+ inbox_message_purge_after: int = 24 * 60 * 60
restricted_usernames: str = "admin\nadmins\nadministrator\nadministrators\nsystem\nroot\nannounce\nannouncement\nannouncements"
diff --git a/stator/graph.py b/stator/graph.py
index 424ea49..5c71d4a 100644
--- a/stator/graph.py
+++ b/stator/graph.py
@@ -87,10 +87,14 @@ class State:
try_interval: Optional[float] = None,
handler_name: Optional[str] = None,
externally_progressed: bool = False,
+ attempt_immediately: bool = True,
+ force_initial: bool = False,
):
self.try_interval = try_interval
self.handler_name = handler_name
self.externally_progressed = externally_progressed
+ self.attempt_immediately = attempt_immediately
+ self.force_initial = force_initial
self.parents: Set["State"] = set()
self.children: Set["State"] = set()
@@ -121,7 +125,7 @@ class State:
@property
def initial(self):
- return not self.parents
+ return self.force_initial or (not self.parents)
@property
def terminal(self):
diff --git a/stator/models.py b/stator/models.py
index bbff395..5257ac9 100644
--- a/stator/models.py
+++ b/stator/models.py
@@ -74,6 +74,10 @@ class StatorModel(models.Model):
def state_graph(cls) -> Type[StateGraph]:
return cls._meta.get_field("state").graph
+ @property
+ def state_age(self) -> int:
+ return (timezone.now() - self.state_changed).total_seconds()
+
@classmethod
async def atransition_schedule_due(cls, now=None) -> models.QuerySet:
"""
@@ -184,13 +188,23 @@ class StatorModel(models.Model):
state = state.name
if state not in self.state_graph.states:
raise ValueError(f"Invalid state {state}")
- self.__class__.objects.filter(pk=self.pk).update(
- state=state,
- state_changed=timezone.now(),
- state_attempted=None,
- state_locked_until=None,
- state_ready=True,
- )
+ # See if it's ready immediately (if not, delay until first try_interval)
+ if self.state_graph.states[state].attempt_immediately:
+ self.__class__.objects.filter(pk=self.pk).update(
+ state=state,
+ state_changed=timezone.now(),
+ state_attempted=None,
+ state_locked_until=None,
+ state_ready=True,
+ )
+ else:
+ self.__class__.objects.filter(pk=self.pk).update(
+ state=state,
+ state_changed=timezone.now(),
+ state_attempted=timezone.now(),
+ state_locked_until=None,
+ state_ready=False,
+ )
atransition_perform = sync_to_async(transition_perform)
diff --git a/users/admin.py b/users/admin.py
index f0d484d..235b0db 100644
--- a/users/admin.py
+++ b/users/admin.py
@@ -65,7 +65,7 @@ class PasswordResetAdmin(admin.ModelAdmin):
@admin.register(InboxMessage)
class InboxMessageAdmin(admin.ModelAdmin):
- list_display = ["id", "state", "state_attempted", "message_type", "message_actor"]
+ list_display = ["id", "state", "state_changed", "message_type", "message_actor"]
search_fields = ["message"]
actions = ["reset_state"]
readonly_fields = ["state_changed"]
diff --git a/users/migrations/0003_identity_followers_etc.py b/users/migrations/0003_identity_followers_etc.py
new file mode 100644
index 0000000..ffb6272
--- /dev/null
+++ b/users/migrations/0003_identity_followers_etc.py
@@ -0,0 +1,38 @@
+# Generated by Django 4.1.3 on 2022-11-27 22:58
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("users", "0002_identity_discoverable"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="identity",
+ name="followers_uri",
+ field=models.CharField(blank=True, max_length=500, null=True),
+ ),
+ migrations.AddField(
+ model_name="identity",
+ name="following_uri",
+ field=models.CharField(blank=True, max_length=500, null=True),
+ ),
+ migrations.AddField(
+ model_name="identity",
+ name="metadata",
+ field=models.JSONField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name="identity",
+ name="pinned",
+ field=models.JSONField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name="identity",
+ name="shared_inbox_uri",
+ field=models.CharField(blank=True, max_length=500, null=True),
+ ),
+ ]
diff --git a/users/models/identity.py b/users/models/identity.py
index 805755a..6957526 100644
--- a/users/models/identity.py
+++ b/users/models/identity.py
@@ -23,19 +23,31 @@ from users.models.system_actor import SystemActor
class IdentityStates(StateGraph):
- outdated = State(try_interval=3600)
- updated = State()
+ """
+ There are only two states in a cycle.
+ Identities sit in "updated" for up to system.identity_max_age, and then
+ go back to "outdated" for refetching.
+ """
+
+ outdated = State(try_interval=3600, force_initial=True)
+ updated = State(try_interval=86400, attempt_immediately=False)
outdated.transitions_to(updated)
+ updated.transitions_to(outdated)
@classmethod
async def handle_outdated(cls, identity: "Identity"):
# Local identities never need fetching
if identity.local:
- return "updated"
+ return cls.updated
# Run the actor fetch and progress to updated if it succeeds
if await identity.fetch_actor():
- return "updated"
+ return cls.updated
+
+ @classmethod
+ async def handle_updated(cls, instance: "Identity"):
+ if instance.state_age > Config.system.identity_max_age:
+ return cls.outdated
class Identity(StatorModel):
diff --git a/users/models/inbox_message.py b/users/models/inbox_message.py
index 589f933..079c572 100644
--- a/users/models/inbox_message.py
+++ b/users/models/inbox_message.py
@@ -1,14 +1,17 @@
from asgiref.sync import sync_to_async
from django.db import models
+from core.models import Config
from stator.models import State, StateField, StateGraph, StatorModel
class InboxMessageStates(StateGraph):
received = State(try_interval=300)
- processed = State()
+ processed = State(try_interval=86400, attempt_immediately=False)
+ purged = State() # This is actually deletion, it will never get here
received.transitions_to(processed)
+ processed.transitions_to(purged)
@classmethod
async def handle_received(cls, instance: "InboxMessage"):
@@ -80,6 +83,11 @@ class InboxMessageStates(StateGraph):
raise ValueError(f"Cannot handle activity of type {unknown}")
return cls.processed
+ @classmethod
+ async def handle_processed(cls, instance: "InboxMessage"):
+ if instance.state_age > Config.system.inbox_message_purge_after:
+ await InboxMessage.objects.filter(pk=instance.pk).adelete()
+
class InboxMessage(StatorModel):
"""