2020-11-12 14:47:29 +01:00

262 lines
7.3 KiB
Python

# This file is part of the Indico plugins.
# Copyright (C) 2002 - 2020 CERN
#
# The Indico plugins are free software; you can redistribute
# them and/or modify them under the terms of the MIT License;
# see the LICENSE file for more details.
from flask import g
from werkzeug.datastructures import ImmutableDict
from indico.core.db.sqlalchemy import PyIntEnum, UTCDateTime, db
from indico.modules.categories.models.categories import Category
from indico.modules.events.models.events import Event
from indico.util.date_time import now_utc
from indico.util.string import format_repr, return_ascii
from indico.util.struct.enum import IndicoEnum
from indico_livesync.models.agents import LiveSyncAgent
from indico_livesync.util import obj_deref
class ChangeType(int, IndicoEnum):
created = 1
deleted = 2
moved = 3
data_changed = 4
protection_changed = 5
class EntryType(int, IndicoEnum):
category = 1
event = 2
contribution = 3
subcontribution = 4
session = 5
_column_for_types = {
EntryType.category: 'category_id',
EntryType.event: 'event_id',
EntryType.contribution: 'contribution_id',
EntryType.subcontribution: 'subcontribution_id',
EntryType.session: 'session_id'
}
def _make_checks():
available_columns = set(_column_for_types.values())
for link_type in EntryType:
required_col = _column_for_types[link_type]
forbidden_cols = available_columns - {required_col}
criteria = [f'{col} IS NULL' for col in sorted(forbidden_cols)]
criteria += [f'{required_col} IS NOT NULL']
condition = 'type != {} OR ({})'.format(link_type, ' AND '.join(criteria))
yield db.CheckConstraint(condition, f'valid_{link_type.name}_entry')
class LiveSyncQueueEntry(db.Model):
__tablename__ = 'queues'
__table_args__ = tuple(_make_checks()) + ({'schema': 'plugin_livesync'},)
#: Entry ID
id = db.Column(
db.Integer,
primary_key=True
)
#: ID of the agent this entry belongs to
agent_id = db.Column(
db.Integer,
db.ForeignKey('plugin_livesync.agents.id'),
nullable=False,
index=True
)
#: Timestamp of the change
timestamp = db.Column(
UTCDateTime,
nullable=False,
default=now_utc
)
#: if this record has already been processed
processed = db.Column(
db.Boolean,
nullable=False,
default=False
)
#: the change type, a :class:`ChangeType`
change = db.Column(
PyIntEnum(ChangeType),
nullable=False
)
#: The type of the changed object
type = db.Column(
PyIntEnum(EntryType),
nullable=False
)
#: The ID of the changed category
category_id = db.Column(
db.Integer,
db.ForeignKey('categories.categories.id'),
index=True,
nullable=True
)
#: ID of the changed event
event_id = db.Column(
db.Integer,
db.ForeignKey('events.events.id'),
index=True,
nullable=True
)
#: ID of the changed contribution
contrib_id = db.Column(
'contribution_id',
db.Integer,
db.ForeignKey('events.contributions.id'),
index=True,
nullable=True
)
#: ID of the changed session
session_id = db.Column(
'session_id',
db.Integer,
db.ForeignKey('events.sessions.id'),
index=True,
nullable=True
)
#: ID of the changed subcontribution
subcontrib_id = db.Column(
'subcontribution_id',
db.Integer,
db.ForeignKey('events.subcontributions.id'),
index=True,
nullable=True
)
#: The associated :class:LiveSyncAgent
agent = db.relationship(
'LiveSyncAgent',
backref=db.backref('queue', cascade='all, delete-orphan', lazy='dynamic')
)
category = db.relationship(
'Category',
lazy=True,
backref=db.backref(
'livesync_queue_entries',
cascade='all, delete-orphan',
lazy=True
)
)
event = db.relationship(
'Event',
lazy=True,
backref=db.backref(
'livesync_queue_entries',
cascade='all, delete-orphan',
lazy=True
)
)
session = db.relationship(
'Session',
lazy=False,
backref=db.backref(
'livesync_queue_entries',
cascade='all, delete-orphan',
lazy='dynamic'
)
)
contribution = db.relationship(
'Contribution',
lazy=False,
backref=db.backref(
'livesync_queue_entries',
cascade='all, delete-orphan',
lazy='dynamic'
)
)
subcontribution = db.relationship(
'SubContribution',
lazy=False,
backref=db.backref(
'livesync_queue_entries',
cascade='all, delete-orphan',
lazy='dynamic'
)
)
@property
def object(self):
"""Return the changed object."""
if self.type == EntryType.category:
return self.category
elif self.type == EntryType.event:
return self.event
elif self.type == EntryType.session:
return self.session
elif self.type == EntryType.contribution:
return self.contribution
elif self.type == EntryType.subcontribution:
return self.subcontribution
@property
def object_ref(self):
"""Return the reference of the changed object."""
return ImmutableDict(type=self.type, category_id=self.category_id, event_id=self.event_id,
session_id=self.session_id, contrib_id=self.contrib_id, subcontrib_id=self.subcontrib_id)
@return_ascii
def __repr__(self):
return format_repr(self, 'id', 'agent_id', 'change', 'type',
category_id=None, event_id=None, session_id=None, contrib_id=None, subcontrib_id=None)
@classmethod
def create(cls, changes, ref, excluded_categories=set()):
"""Create a new change in all queues.
:param changes: the change types, an iterable containing
:class:`ChangeType`
:param ref: the object reference (returned by `obj_ref`)
of the changed object
:param excluded_categories: set of categories (IDs) whose items
will not be tracked
"""
ref = dict(ref)
obj = obj_deref(ref)
if isinstance(obj, Category):
if any(c.id in excluded_categories for c in obj.chain_query):
return
else:
event = obj if isinstance(obj, Event) else obj.event
if event.category not in g.setdefault('livesync_excluded_categories_checked', {}):
g.livesync_excluded_categories_checked[event.category] = excluded_categories & set(event.category_chain)
if g.livesync_excluded_categories_checked[event.category]:
return
try:
agents = g.livesync_agents
except AttributeError:
agents = g.livesync_agents = LiveSyncAgent.query.all()
for change in changes:
for agent in agents:
entry = cls(agent=agent, change=change, **ref)
db.session.add(entry)
db.session.flush()