User fetching and inbox message cleaning

This commit is contained in:
Andrew Godwin 2022-11-27 17:05:31 -07:00
parent 2f443414a7
commit 3b079526a2
7 changed files with 91 additions and 14 deletions

View File

@ -213,6 +213,7 @@ class Config(models.Model):
identity_min_length: int = 2 identity_min_length: int = 2
identity_max_per_user: int = 5 identity_max_per_user: int = 5
identity_max_age: int = 24 * 60 * 60 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" restricted_usernames: str = "admin\nadmins\nadministrator\nadministrators\nsystem\nroot\nannounce\nannouncement\nannouncements"

View File

@ -87,10 +87,14 @@ class State:
try_interval: Optional[float] = None, try_interval: Optional[float] = None,
handler_name: Optional[str] = None, handler_name: Optional[str] = None,
externally_progressed: bool = False, externally_progressed: bool = False,
attempt_immediately: bool = True,
force_initial: bool = False,
): ):
self.try_interval = try_interval self.try_interval = try_interval
self.handler_name = handler_name self.handler_name = handler_name
self.externally_progressed = externally_progressed self.externally_progressed = externally_progressed
self.attempt_immediately = attempt_immediately
self.force_initial = force_initial
self.parents: Set["State"] = set() self.parents: Set["State"] = set()
self.children: Set["State"] = set() self.children: Set["State"] = set()
@ -121,7 +125,7 @@ class State:
@property @property
def initial(self): def initial(self):
return not self.parents return self.force_initial or (not self.parents)
@property @property
def terminal(self): def terminal(self):

View File

@ -74,6 +74,10 @@ class StatorModel(models.Model):
def state_graph(cls) -> Type[StateGraph]: def state_graph(cls) -> Type[StateGraph]:
return cls._meta.get_field("state").graph return cls._meta.get_field("state").graph
@property
def state_age(self) -> int:
return (timezone.now() - self.state_changed).total_seconds()
@classmethod @classmethod
async def atransition_schedule_due(cls, now=None) -> models.QuerySet: async def atransition_schedule_due(cls, now=None) -> models.QuerySet:
""" """
@ -184,6 +188,8 @@ class StatorModel(models.Model):
state = state.name state = state.name
if state not in self.state_graph.states: if state not in self.state_graph.states:
raise ValueError(f"Invalid state {state}") raise ValueError(f"Invalid state {state}")
# 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( self.__class__.objects.filter(pk=self.pk).update(
state=state, state=state,
state_changed=timezone.now(), state_changed=timezone.now(),
@ -191,6 +197,14 @@ class StatorModel(models.Model):
state_locked_until=None, state_locked_until=None,
state_ready=True, 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) atransition_perform = sync_to_async(transition_perform)

View File

@ -65,7 +65,7 @@ class PasswordResetAdmin(admin.ModelAdmin):
@admin.register(InboxMessage) @admin.register(InboxMessage)
class InboxMessageAdmin(admin.ModelAdmin): 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"] search_fields = ["message"]
actions = ["reset_state"] actions = ["reset_state"]
readonly_fields = ["state_changed"] readonly_fields = ["state_changed"]

View File

@ -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),
),
]

View File

@ -23,19 +23,31 @@ from users.models.system_actor import SystemActor
class IdentityStates(StateGraph): 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) outdated.transitions_to(updated)
updated.transitions_to(outdated)
@classmethod @classmethod
async def handle_outdated(cls, identity: "Identity"): async def handle_outdated(cls, identity: "Identity"):
# Local identities never need fetching # Local identities never need fetching
if identity.local: if identity.local:
return "updated" return cls.updated
# Run the actor fetch and progress to updated if it succeeds # Run the actor fetch and progress to updated if it succeeds
if await identity.fetch_actor(): 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): class Identity(StatorModel):

View File

@ -1,14 +1,17 @@
from asgiref.sync import sync_to_async from asgiref.sync import sync_to_async
from django.db import models from django.db import models
from core.models import Config
from stator.models import State, StateField, StateGraph, StatorModel from stator.models import State, StateField, StateGraph, StatorModel
class InboxMessageStates(StateGraph): class InboxMessageStates(StateGraph):
received = State(try_interval=300) 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) received.transitions_to(processed)
processed.transitions_to(purged)
@classmethod @classmethod
async def handle_received(cls, instance: "InboxMessage"): async def handle_received(cls, instance: "InboxMessage"):
@ -80,6 +83,11 @@ class InboxMessageStates(StateGraph):
raise ValueError(f"Cannot handle activity of type {unknown}") raise ValueError(f"Cannot handle activity of type {unknown}")
return cls.processed 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): class InboxMessage(StatorModel):
""" """