Skip to content

Commit

Permalink
Merge pull request #24 from gpodder/subscribe-task
Browse files Browse the repository at this point in the history
Async [un]subscribes
  • Loading branch information
SiqingYu authored Feb 20, 2020
2 parents 9ec7e84 + a04fead commit b93169c
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 217 deletions.
2 changes: 1 addition & 1 deletion mygpo/administration/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from mygpo.users.models import Client
from mygpo.history.models import EpisodeHistoryEntry
from mygpo.maintenance.merge import PodcastMerger
from mygpo.subscriptions import subscribe, unsubscribe
from mygpo.subscriptions.tasks import subscribe, unsubscribe


class SimpleTest(TestCase):
Expand Down
2 changes: 1 addition & 1 deletion mygpo/api/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from mygpo.api.opml import Importer, Exporter
from mygpo.api.backend import get_device
from mygpo.utils import normalize_feed_url
from mygpo.subscriptions import subscribe, unsubscribe
from mygpo.subscriptions.tasks import subscribe, unsubscribe

import logging

Expand Down
3 changes: 2 additions & 1 deletion mygpo/api/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
from mygpo.api.httpresponse import JsonResponse
from mygpo.directory.models import ExamplePodcast
from mygpo.api.advanced.directory import podcast_data
from mygpo.subscriptions import get_subscribed_podcasts, subscribe, unsubscribe
from mygpo.subscriptions import get_subscribed_podcasts
from mygpo.subscriptions.tasks import subscribe, unsubscribe
from mygpo.directory.search import search_podcasts
from mygpo.decorators import allowed_methods, cors_origin
from mygpo.utils import parse_range, normalize_feed_url
Expand Down
9 changes: 3 additions & 6 deletions mygpo/api/subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,9 @@
from mygpo.api.backend import get_device
from mygpo.utils import get_timestamp, normalize_feed_url, intersect
from mygpo.users.models import Client
from mygpo.subscriptions import (
subscribe,
unsubscribe,
get_subscription_history,
subscription_diff,
)
from mygpo.subscriptions.tasks import subscribe, unsubscribe
from mygpo.subscriptions import get_subscription_history, subscription_diff
from mygpo.api.basic_auth import require_valid_user, check_username

import logging

Expand Down
13 changes: 6 additions & 7 deletions mygpo/podcasts/views/podcast.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.contrib.sites.requests import RequestSite
from django.utils.translation import gettext as _
from django.contrib import messages
from django.views.decorators.vary import vary_on_cookie
from django.views.decorators.cache import never_cache, cache_control
Expand All @@ -16,12 +15,12 @@
from mygpo.podcasts.models import Podcast, PodcastGroup, Episode, Tag
from mygpo.users.models import SubscriptionException
from mygpo.subscriptions.models import Subscription
from mygpo.subscriptions import (
from mygpo.subscriptions import get_subscribe_targets
from mygpo.subscriptions.tasks import (
subscribe as subscribe_podcast,
unsubscribe as unsubscribe_podcast,
subscribe_all as subscribe_podcast_all,
unsubscribe_all as unsubscribe_podcast_all,
get_subscribe_targets,
)
from mygpo.history.models import HistoryEntry
from mygpo.utils import normalize_feed_url, to_maxlength
Expand Down Expand Up @@ -267,7 +266,7 @@ def subscribe(request, podcast):
for uid in device_uids:
try:
device = request.user.client_set.get(uid=uid)
subscribe_podcast(podcast, request.user, device)
subscribe_podcast.delay(podcast, request.user, device)

except Client.DoesNotExist as e:
messages.error(request, str(e))
Expand All @@ -285,7 +284,7 @@ def subscribe(request, podcast):
def subscribe_all(request, podcast):
""" subscribe all of the user's devices to the podcast """
user = request.user
subscribe_podcast_all(podcast, user)
subscribe_podcast_all.delay(podcast, user)
return HttpResponseRedirect(get_podcast_link_target(podcast))


Expand All @@ -307,7 +306,7 @@ def unsubscribe(request, podcast, device_uid):
return HttpResponseRedirect(return_to)

try:
unsubscribe_podcast(podcast, user, device)
unsubscribe_podcast.delay(podcast, user, device)
except SubscriptionException as e:
logger.exception(
'Web: %(username)s: could not unsubscribe from podcast %(podcast_url)s on device %(device_id)s'
Expand All @@ -327,7 +326,7 @@ def unsubscribe(request, podcast, device_uid):
def unsubscribe_all(request, podcast):
""" unsubscribe all of the user's devices from the podcast """
user = request.user
unsubscribe_podcast_all(podcast, user)
unsubscribe_podcast_all.delay(podcast, user)
return HttpResponseRedirect(get_podcast_link_target(podcast))


Expand Down
183 changes: 13 additions & 170 deletions mygpo/subscriptions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
from datetime import datetime
import collections

from django.db import transaction
from django.db.utils import IntegrityError

from mygpo.subscriptions.signals import subscription_changed
from mygpo.utils import to_maxlength

import logging

Expand All @@ -16,150 +10,13 @@
# (non-__init__) module


@transaction.atomic
def subscribe(podcast, user, client, ref_url=None):
""" subscribes user to the current podcast on one client
Takes syned devices into account. """
ref_url = ref_url or podcast.url
now = datetime.utcnow()
clients = _affected_clients(client)

# fully execute subscriptions, before firing events
changed = list(_perform_subscribe(podcast, user, clients, now, ref_url))
_fire_events(podcast, user, changed, True)


@transaction.atomic
def unsubscribe(podcast, user, client):
""" unsubscribes user from the current podcast on one client
Takes syned devices into account. """
now = datetime.utcnow()
clients = _affected_clients(client)

# fully execute unsubscriptions, before firing events
# otherwise the first fired event might revert the unsubscribe
changed = list(_perform_unsubscribe(podcast, user, clients, now))
_fire_events(podcast, user, changed, False)


@transaction.atomic
def subscribe_all(podcast, user, ref_url=None):
""" subscribes user to the current podcast on all clients """
ref_url = ref_url or podcast.url
now = datetime.utcnow()
clients = user.client_set.all()

# fully execute subscriptions, before firing events
changed = list(_perform_subscribe(podcast, user, clients, now, ref_url))
_fire_events(podcast, user, changed, True)


@transaction.atomic
def unsubscribe_all(podcast, user):
""" unsubscribes user from the current podcast on all clients """
now = datetime.utcnow()
clients = user.client_set.filter(subscription__podcast=podcast)

# fully execute subscriptions, before firing events
changed = list(_perform_unsubscribe(podcast, user, clients, now))
_fire_events(podcast, user, changed, False)


def _perform_subscribe(podcast, user, clients, timestamp, ref_url):
""" Subscribes to a podcast on multiple clients
Yields the clients on which a subscription was added, ie not those where
the subscription already existed. """

from mygpo.subscriptions.models import Subscription

for client in clients:
try:
with transaction.atomic():
subscription = Subscription.objects.create(
user=user,
client=client,
podcast=podcast,
ref_url=to_maxlength(Subscription, 'ref_url', ref_url),
created=timestamp,
modified=timestamp,
)

except IntegrityError as ie:
msg = str(ie)
if 'Key (user_id, client_id, podcast_id)' in msg:
# Subscription already exists -- skip
continue

else:
# unknown error
raise

logger.info(
'{user} subscribed to {podcast} on {client}'.format(
user=user, podcast=podcast, client=client
)
)

from mygpo.history.models import HistoryEntry

HistoryEntry.objects.create(
timestamp=timestamp,
podcast=podcast,
user=user,
client=client,
action=HistoryEntry.SUBSCRIBE,
)

yield client


def _perform_unsubscribe(podcast, user, clients, timestamp):
""" Unsubscribes from a podcast on multiple clients
Yields the clients on which a subscription was removed, ie not those where
the podcast was not subscribed. """

from mygpo.subscriptions.models import Subscription

for client in clients:

try:
subscription = Subscription.objects.get(
user=user, client=client, podcast=podcast
)
except Subscription.DoesNotExist:
continue

subscription.delete()

logger.info(
'{user} unsubscribed from {podcast} on {client}'.format(
user=user, podcast=podcast, client=client
)
)

from mygpo.history.models import HistoryEntry

HistoryEntry.objects.create(
timestamp=timestamp,
podcast=podcast,
user=user,
client=client,
action=HistoryEntry.UNSUBSCRIBE,
)

yield client


def get_subscribe_targets(podcast, user):
""" Clients / SyncGroup on which the podcast can be subscribed
This excludes all devices/syncgroups on which the podcast is already
subscribed """

# django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
from mygpo.users.models import Client

clients = (
Expand All @@ -184,15 +41,19 @@ def get_subscribed_podcasts(user, only_public=False):
The attribute "url" contains the URL that was used when subscribing to
the podcast """

from mygpo.usersettings.models import UserSettings
from mygpo.subscriptions.models import SubscribedPodcast, Subscription
# django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
from mygpo.subscriptions.models import Subscription, SubscribedPodcast

subscriptions = (
Subscription.objects.filter(user=user)
.order_by('podcast')
.distinct('podcast')
.select_related('podcast')
)

# django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
from mygpo.usersettings.models import UserSettings

private = UserSettings.objects.get_private_podcasts(user)

podcasts = []
Expand All @@ -218,7 +79,7 @@ def get_subscription_history(
Setting device_id restricts the actions to a certain device
"""

from mygpo.usersettings.models import UserSettings
# django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
from mygpo.history.models import SUBSCRIPTION_ACTIONS, HistoryEntry

logger.info('Subscription History for {user}'.format(user=user.username))
Expand All @@ -241,6 +102,9 @@ def get_subscription_history(
history = history.filter(timestamp__lte=until)

if public_only:
# django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
from mygpo.usersettings.models import UserSettings

logger.info('... only public')
private = UserSettings.objects.get_private_podcasts(user)
history = history.exclude(podcast__in=private)
Expand All @@ -261,6 +125,7 @@ def get_subscription_change_history(history):
``history``.
"""

# django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
from mygpo.history.models import HistoryEntry

subscriptions = collections.defaultdict(int)
Expand All @@ -284,6 +149,7 @@ def get_subscription_change_history(history):
def subscription_diff(history):
""" Calculates a diff of subscriptions based on a history (sub/unsub) """

# django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
from mygpo.history.models import HistoryEntry

subscriptions = collections.defaultdict(int)
Expand All @@ -299,26 +165,3 @@ def subscription_diff(history):
unsubscribe = [podcast for (podcast, value) in subscriptions.items() if value < 0]

return subscribe, unsubscribe


def _affected_clients(client):
""" the clients that are affected if the given one is to be changed """
if client.sync_group:
# if the client is synced, all are affected
return client.sync_group.client_set.all()

else:
# if its not synced, only the client is affected
return [client]


def _fire_events(podcast, user, clients, subscribed):
""" Fire the events for subscription / unsubscription """
for client in clients:
subscription_changed.send(
sender=podcast.__class__,
instance=podcast,
user=user,
client=client,
subscribed=subscribed,
)
14 changes: 0 additions & 14 deletions mygpo/subscriptions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,6 @@
from mygpo.podcasts.models import Podcast


class SubscriptionManager(models.Manager):
""" Manages subscriptions """

def subscribe(self, device, podcast):
# create subscription, add history
pass

def unsubscribe(self, device, podcast):
# delete subscription, add history
pass


class Subscription(DeleteableModel):
""" A subscription to a podcast on a specific client """

Expand All @@ -45,8 +33,6 @@ class Subscription(DeleteableModel):
created = models.DateTimeField()
modified = models.DateTimeField()

objects = SubscriptionManager()

class Meta:
unique_together = [['user', 'client', 'podcast']]

Expand Down
Loading

0 comments on commit b93169c

Please sign in to comment.