2016-05-29 08:29:14 +02:00
|
|
|
import itertools
|
2016-03-02 00:46:19 +01:00
|
|
|
import json
|
2015-01-18 15:53:03 +01:00
|
|
|
|
2016-05-29 08:29:14 +02:00
|
|
|
from asgiref.inmemory import ChannelLayer
|
|
|
|
from channels import Channel, Group
|
|
|
|
from channels.auth import channel_session_user, channel_session_user_from_http
|
2016-08-08 07:48:11 +02:00
|
|
|
from django.db import transaction
|
2016-05-29 08:29:14 +02:00
|
|
|
from django.utils import timezone
|
2016-01-10 00:17:00 +01:00
|
|
|
|
2016-09-17 22:26:23 +02:00
|
|
|
from ..core.config import config
|
|
|
|
from ..core.models import Projector
|
2016-05-29 08:29:14 +02:00
|
|
|
from ..users.auth import AnonymousUser
|
|
|
|
from ..users.models import User
|
2016-09-17 22:26:23 +02:00
|
|
|
from .collection import CollectionElement
|
2013-02-27 18:22:24 +01:00
|
|
|
|
2015-01-17 14:01:44 +01:00
|
|
|
|
2016-05-29 08:29:14 +02:00
|
|
|
def get_logged_in_users():
|
2015-01-17 14:01:44 +01:00
|
|
|
"""
|
2016-05-29 08:29:14 +02:00
|
|
|
Helper to get all logged in users.
|
2013-02-27 18:22:24 +01:00
|
|
|
|
2016-05-29 08:29:14 +02:00
|
|
|
Only works with the OpenSlides session backend.
|
2013-10-03 21:49:51 +02:00
|
|
|
"""
|
2016-05-29 08:29:14 +02:00
|
|
|
return User.objects.exclude(session=None).filter(session__expire_date__gte=timezone.now()).distinct()
|
2013-08-04 12:59:11 +02:00
|
|
|
|
|
|
|
|
2016-09-17 22:26:23 +02:00
|
|
|
def get_projector_element_data(projector):
|
2016-05-29 08:29:14 +02:00
|
|
|
"""
|
2016-09-17 22:26:23 +02:00
|
|
|
Returns a list of dicts that are required for a specific projector.
|
2016-05-29 08:29:14 +02:00
|
|
|
|
2016-09-17 22:26:23 +02:00
|
|
|
The argument projector has to be a projector instance.
|
|
|
|
"""
|
|
|
|
output = []
|
|
|
|
for requirement in projector.get_all_requirements():
|
|
|
|
required_collection_element = CollectionElement.from_instance(requirement)
|
|
|
|
element_dict = required_collection_element.as_autoupdate_for_projector()
|
|
|
|
if element_dict is not None:
|
|
|
|
output.append(element_dict)
|
|
|
|
return output
|
2016-01-10 00:17:00 +01:00
|
|
|
|
2013-03-27 15:53:31 +01:00
|
|
|
|
2016-05-29 08:29:14 +02:00
|
|
|
@channel_session_user_from_http
|
2016-09-17 22:26:23 +02:00
|
|
|
def ws_add_site(message):
|
2016-05-29 08:29:14 +02:00
|
|
|
"""
|
|
|
|
Adds the websocket connection to a group specific to the connecting user.
|
2013-12-09 18:03:47 +01:00
|
|
|
|
2016-05-29 08:29:14 +02:00
|
|
|
The group with the name 'user-None' stands for all anonymous users.
|
|
|
|
"""
|
|
|
|
Group('user-{}'.format(message.user.id)).add(message.reply_channel)
|
2013-02-27 18:22:24 +01:00
|
|
|
|
2015-01-17 14:01:44 +01:00
|
|
|
|
2016-05-29 08:29:14 +02:00
|
|
|
@channel_session_user
|
2016-09-17 22:26:23 +02:00
|
|
|
def ws_disconnect_site(message):
|
|
|
|
"""
|
|
|
|
This function is called, when a client on the site disconnects.
|
|
|
|
"""
|
2016-05-29 08:29:14 +02:00
|
|
|
Group('user-{}'.format(message.user.id)).discard(message.reply_channel)
|
2016-01-10 00:17:00 +01:00
|
|
|
|
2015-01-17 14:01:44 +01:00
|
|
|
|
2016-09-17 22:26:23 +02:00
|
|
|
@channel_session_user_from_http
|
|
|
|
def ws_add_projector(message, projector_id):
|
2015-01-17 14:01:44 +01:00
|
|
|
"""
|
2016-09-17 22:26:23 +02:00
|
|
|
Adds the websocket connection to a group specific to the projector with the given id.
|
|
|
|
Also sends all data that are shown on the projector.
|
2015-01-17 14:01:44 +01:00
|
|
|
"""
|
2016-09-17 22:26:23 +02:00
|
|
|
user = message.user
|
|
|
|
# user is the django anonymous user. We have our own.
|
|
|
|
if user.is_anonymous:
|
|
|
|
user = AnonymousUser()
|
|
|
|
|
|
|
|
if not user.has_perm('core.can_see_projector'):
|
|
|
|
message.reply_channel.send({'text': 'No permissions to see this projector.'})
|
2015-01-17 14:01:44 +01:00
|
|
|
else:
|
2016-09-17 22:26:23 +02:00
|
|
|
try:
|
|
|
|
projector = Projector.objects.get(pk=projector_id)
|
|
|
|
except Projector.DoesNotExist:
|
|
|
|
message.reply_channel.send({'text': 'The projector {} does not exist.'.format(projector_id)})
|
|
|
|
else:
|
|
|
|
# At first, the client is added to the projector group, so it is
|
|
|
|
# informed if the data change.
|
|
|
|
Group('projector-{}'.format(projector_id)).add(message.reply_channel)
|
|
|
|
|
|
|
|
# Send all elements that are on the projector.
|
|
|
|
output = get_projector_element_data(projector)
|
2016-05-29 08:29:14 +02:00
|
|
|
|
2016-09-17 22:26:23 +02:00
|
|
|
# Send all config elements.
|
|
|
|
for key, value in config.items():
|
|
|
|
output.append({
|
|
|
|
'collection': config.get_collection_string(),
|
|
|
|
'id': key,
|
|
|
|
'action': 'changed',
|
|
|
|
'data': {'key': key, 'value': value}})
|
|
|
|
|
|
|
|
# Send the projector instance.
|
|
|
|
collection_element = CollectionElement.from_instance(projector)
|
|
|
|
output.append(collection_element.as_autoupdate_for_projector())
|
|
|
|
|
|
|
|
# Send all the data that was only collected before
|
|
|
|
message.reply_channel.send({'text': json.dumps(output)})
|
|
|
|
|
|
|
|
|
|
|
|
def ws_disconnect_projector(message, projector_id):
|
|
|
|
"""
|
|
|
|
This function is called, when a client on the projector disconnects.
|
|
|
|
"""
|
|
|
|
Group('projector-{}'.format(projector_id)).discard(message.reply_channel)
|
|
|
|
|
|
|
|
|
|
|
|
def send_data(message):
|
|
|
|
"""
|
|
|
|
Informs all users about changed data.
|
|
|
|
"""
|
|
|
|
collection_element = CollectionElement.from_values(**message)
|
2016-05-29 08:29:14 +02:00
|
|
|
|
|
|
|
# Loop over all logged in users and the anonymous user.
|
|
|
|
for user in itertools.chain(get_logged_in_users(), [AnonymousUser()]):
|
|
|
|
channel = Group('user-{}'.format(user.id))
|
2016-09-17 22:26:23 +02:00
|
|
|
output = collection_element.as_autoupdate_for_user(user)
|
|
|
|
if output is None:
|
|
|
|
# There are no data for the user so he can't see the object. Skip him.
|
|
|
|
continue
|
|
|
|
channel.send({'text': json.dumps([output])})
|
|
|
|
|
|
|
|
# Get the projector elements where data have to be sent and if whole projector
|
|
|
|
# has to be updated.
|
|
|
|
if collection_element.collection_string == config.get_collection_string():
|
|
|
|
# Config elements are always send to each projector
|
|
|
|
projectors = Projector.objects.all()
|
|
|
|
send_all = None # The decission is done later
|
|
|
|
elif collection_element.collection_string == Projector.get_collection_string():
|
|
|
|
# Update a projector, when the projector element is updated.
|
|
|
|
projectors = [collection_element.get_instance()]
|
|
|
|
send_all = True
|
|
|
|
elif collection_element.is_deleted():
|
|
|
|
projectors = Projector.objects.all()
|
|
|
|
send_all = False
|
|
|
|
else:
|
|
|
|
# Other elements are only send to the projector they are currently shown
|
2016-09-23 13:12:02 +02:00
|
|
|
projectors = Projector.get_projectors_that_show_this(collection_element)
|
2016-09-17 22:26:23 +02:00
|
|
|
send_all = None # The decission is done later
|
|
|
|
|
|
|
|
for projector in projectors:
|
|
|
|
if send_all is None:
|
2016-09-23 13:12:02 +02:00
|
|
|
send_all = projector.need_full_update_for_this(collection_element)
|
2016-09-17 22:26:23 +02:00
|
|
|
if send_all:
|
|
|
|
output = get_projector_element_data(projector)
|
|
|
|
else:
|
|
|
|
output = []
|
|
|
|
output.append(collection_element.as_autoupdate_for_projector())
|
|
|
|
if output:
|
|
|
|
Group('projector-{}'.format(projector.pk)).send(
|
|
|
|
{'text': json.dumps(output)})
|
2016-05-29 08:29:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
def inform_changed_data(instance, is_deleted=False):
|
|
|
|
try:
|
|
|
|
root_instance = instance.get_root_rest_element()
|
|
|
|
except AttributeError:
|
|
|
|
# Instance has no method get_root_rest_element. Just ignore it.
|
2015-01-17 14:01:44 +01:00
|
|
|
pass
|
2016-05-29 08:29:14 +02:00
|
|
|
else:
|
2016-09-17 22:26:23 +02:00
|
|
|
collection_element = CollectionElement.from_instance(
|
|
|
|
root_instance,
|
|
|
|
is_deleted=is_deleted and instance == root_instance)
|
2016-08-29 16:00:16 +02:00
|
|
|
|
2016-08-08 07:48:11 +02:00
|
|
|
# If currently there is an open database transaction, then the following
|
|
|
|
# function is only called, when the transaction is commited. If there
|
|
|
|
# is currently no transaction, then the function is called immediately.
|
2016-09-17 22:26:23 +02:00
|
|
|
def send_autoupdate():
|
2016-08-08 07:48:11 +02:00
|
|
|
try:
|
2016-09-17 22:26:23 +02:00
|
|
|
Channel('autoupdate.send_data').send(collection_element.as_channels_message())
|
2016-08-08 07:48:11 +02:00
|
|
|
except ChannelLayer.ChannelFull:
|
|
|
|
pass
|
2016-08-29 16:00:16 +02:00
|
|
|
|
2016-09-17 22:26:23 +02:00
|
|
|
transaction.on_commit(send_autoupdate)
|
2015-01-17 14:01:44 +01:00
|
|
|
|
|
|
|
|
|
|
|
def inform_changed_data_receiver(sender, instance, **kwargs):
|
|
|
|
"""
|
|
|
|
Receiver for the inform_changed_data function to use in a signal.
|
|
|
|
"""
|
2016-05-29 08:29:14 +02:00
|
|
|
inform_changed_data(instance)
|
2016-02-11 11:29:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
def inform_deleted_data_receiver(sender, instance, **kwargs):
|
|
|
|
"""
|
|
|
|
Receiver for the inform_changed_data function to use in a signal.
|
|
|
|
"""
|
2016-05-29 08:29:14 +02:00
|
|
|
inform_changed_data(instance, is_deleted=True)
|