1 from collections
import namedtuple
3 from couchdbkit
.ext
.django
.schema
import *
5 from mygpo
.podcasts
.models
import Podcast
8 logger
= logging
.getLogger(__name__
)
11 GroupedDevices
= namedtuple('GroupedDevices', 'is_synced devices')
14 def get_grouped_devices(user
):
15 """ Returns groups of synced devices and a unsynced group """
17 from mygpo
.users
.models
import Client
18 clients
= Client
.objects
.filter(user
=user
, deleted
=False)\
19 .order_by('-sync_group')
24 for client
in clients
:
25 # check if we have just found a new group
26 if last_group
!= client
.sync_group
:
30 group
= GroupedDevices(client
.sync_group
is not None, [])
32 last_group
= client
.sync_group
33 group
.devices
.append(client
)
35 # yield remaining group
39 class SyncedDevicesMixin(DocumentSchema
):
40 """ Contains the device-syncing functionality of a user """
42 sync_groups
= ListProperty()
44 def get_device_sync_group(self
, device
):
45 """ Returns the sync-group Id of the device """
47 for n
, group
in enumerate(self
.sync_groups
):
48 if device
.id in group
:
51 def get_devices_in_group(self
, sg
):
52 """ Returns the devices in the group with the given Id """
54 ids
= self
.sync_groups
[sg
]
55 return map(self
.get_device
, ids
)
58 def sync_group(self
, device
):
59 """ Sync the group of the device """
61 group_index
= self
.get_device_sync_group(device
)
63 if group_index
is None:
66 group_state
= self
.get_group_state(group_index
)
68 for device
in self
.get_devices_in_group(group_index
):
69 sync_actions
= self
.get_sync_actions(device
, group_state
)
70 self
.apply_sync_actions(device
, sync_actions
)
73 def apply_sync_actions(self
, device
, sync_actions
):
74 """ Applies the sync-actions to the device """
76 from mygpo
.db
.couchdb
.podcast_state
import subscribe
, unsubscribe
77 from mygpo
.users
.models
import SubscriptionException
78 add
, rem
= sync_actions
80 podcasts
= Podcast
.objects
.filter(id__in
=(add
+rem
))
81 podcasts
= {podcast
.id: podcast
for podcast
in podcasts
}
83 for podcast_id
in add
:
84 podcast
= podcasts
.get(podcast_id
, None)
88 subscribe(podcast
, self
, device
)
89 except SubscriptionException
as e
:
90 logger
.warn('Web: %(username)s: cannot sync device: %(error)s' %
91 dict(username
=self
.username
, error
=repr(e
)))
93 for podcast_id
in rem
:
94 podcast
= podcasts
.get(podcast_id
, None)
99 unsubscribe(podcast
, self
, device
)
100 except SubscriptionException
as e
:
101 logger
.warn('Web: %(username)s: cannot sync device: %(error)s' %
102 dict(username
=self
.username
, error
=repr(e
)))
105 def get_group_state(self
, group_index
):
106 """ Returns the group's subscription state
108 The state is represented by the latest actions for each podcast """
110 device_ids
= self
.sync_groups
[group_index
]
111 devices
= self
.get_devices(device_ids
)
116 actions
= dict(d
.get_latest_changes())
117 for podcast_id
, action
in actions
.items():
118 if not podcast_id
in state
or \
119 action
.timestamp
> state
[podcast_id
].timestamp
:
120 state
[podcast_id
] = action
125 def get_sync_actions(self
, device
, group_state
):
126 """ Get the actions required to bring the device to the group's state
128 After applying the actions the device reflects the group's state """
130 sg
= self
.get_device_sync_group(device
)
134 # Filter those that describe actual changes to the current state
136 current_state
= dict(device
.get_latest_changes())
138 for podcast_id
, action
in group_state
.items():
140 # Sync-Actions must be newer than current state
141 if podcast_id
in current_state
and \
142 action
.timestamp
<= current_state
[podcast_id
].timestamp
:
145 # subscribe only what hasn't been subscribed before
146 if action
.action
== 'subscribe' and \
147 (podcast_id
not in current_state
or \
148 current_state
[podcast_id
].action
== 'unsubscribe'):
149 add
.append(podcast_id
)
151 # unsubscribe only what has been subscribed before
152 elif action
.action
== 'unsubscribe' and \
153 podcast_id
in current_state
and \
154 current_state
[podcast_id
].action
== 'subscribe':
155 rem
.append(podcast_id
)