1 from datetime
import datetime
2 from couchdbkit
import Server
, Document
4 from mygpo
.core
.models
import Podcast
, PodcastGroup
, Episode
, SubscriberData
5 from mygpo
.users
.models
import Rating
, EpisodeAction
, User
, Device
, SubscriptionAction
, EpisodeUserState
, PodcastUserState
6 from mygpo
.log
import log
7 from mygpo
import utils
8 from mygpo
.decorators
import repeat_on_conflict
11 This module contains methods for converting objects from the old
12 ORM-based backend to the CouchDB-based backend
16 def save_podcast_signal(sender
, instance
=False, **kwargs
):
18 Signal-handler for creating/updating a CouchDB-based podcast when
19 an ORM-based podcast has been saved
25 newp
= Podcast
.for_oldid(instance
.id)
27 update_podcast(oldp
=instance
, newp
=newp
)
29 create_podcast(instance
)
32 log('error while updating CouchDB-Podcast: %s' % repr(e
))
35 def delete_podcast_signal(sender
, instance
=False, **kwargs
):
37 Signal-handler for deleting a CouchDB-based podcast when an ORM-based
44 newp
= Podcast
.for_oldid(instance
.id)
49 log('error while deleting CouchDB-Podcast: %s' % repr(e
))
53 def save_device_signal(sender
, instance
=False, **kwargs
):
58 dev
= get_or_migrate_device(instance
)
59 d
= update_device(instance
, dev
)
60 user
= get_or_migrate_user(instance
.user
)
64 podcast_states
= PodcastUserState
.for_user(instance
.user
)
65 for state
in podcast_states
:
67 @repeat_on_conflict(['state'])
68 def update_state(state
):
70 podcast
= Podcast
.get(state
.podcast
)
71 if not podcast
or not podcast
.url
:
73 state
.ref_url
= podcast
.url
75 state
.set_device_state(dev
)
78 update_state(state
=state
)
81 def delete_device_signal(sender
, instance
=False, **kwargs
):
85 user
= get_or_migrate_user(instance
.user
)
86 dev
= get_or_migrate_device(instance
)
87 user
.remove_device(dev
)
92 def save_episode_signal(sender
, instance
=False, **kwargs
):
94 Signal-handler for creating/updating a CouchDB-based episode when
95 an ORM-based episode has been saved
101 newe
= Episode
.for_oldid(instance
.id)
102 newp
= Podcast
.get(newe
.podcast
)
105 update_episode(instance
, newe
, newp
)
107 create_episode(instance
)
110 log('error while updating CouchDB Episode: %s' % repr(e
))
114 @repeat_on_conflict(['newp'], reload_f
=lambda x
: Podcast
.get(x
.get_id()))
115 def update_podcast(oldp
, newp
):
117 Updates newp based on oldp and returns True if an update was necessary
121 # Update related podcasts
122 from mygpo
.data
.models
import RelatedPodcast
124 rel_podcast
= set([r
.rel_podcast
for r
in RelatedPodcast
.objects
.filter(ref_podcast
=oldp
)])
125 rel
= list(podcasts_to_ids(rel_podcast
))
126 if newp
.related_podcasts
!= rel
:
127 newp
.related_podcasts
= rel
130 # Update Group-assignment
132 group
= get_group(oldp
.group
)
133 if not newp
in list(group
.podcasts
):
134 newp
= group
.add_podcast(newp
)
137 # Update subscriber-data
138 from mygpo
.data
.models
import HistoricPodcastData
139 sub
= HistoricPodcastData
.objects
.filter(podcast
=oldp
).order_by('date')
140 if sub
.count() and len(newp
.subscribers
) != sub
.count():
141 transf
= lambda s
: SubscriberData(
142 timestamp
= datetime(s
.date
.year
, s
.date
.month
, s
.date
.day
),
143 subscriber_count
= s
.subscriber_count
)
144 check
= lambda s
: s
.date
.weekday() == 6
146 newp
.subscribers
= newp
.subscribers
+ map(transf
, filter(check
, sub
))
147 newp
.subscribers
= utils
.set_cmp(newp
.subscribers
, lambda x
: x
.timestamp
)
148 newp
.subscribers
= list(sorted(set(newp
.subscribers
), key
=lambda s
: s
.timestamp
))
151 PROPERTIES
= ('language', 'content_types', 'title',
152 'description', 'link', 'last_update', 'logo_url',
153 'author', 'group_member_name')
156 if getattr(newp
, p
, None) != getattr(oldp
, p
, None):
157 setattr(newp
, p
, getattr(oldp
, p
, None))
160 if not oldp
.url
in newp
.urls
:
161 newp
.urls
.append(oldp
.url
)
170 def create_podcast(oldp
, sparse
=False):
172 Creates a (CouchDB) Podcast document from a (ORM) Podcast object
178 update_podcast(oldp
=oldp
, newp
=p
)
184 group
= PodcastGroup
.for_oldid(oldg
.id)
186 group
= create_podcastgroup(oldg
)
191 def create_podcastgroup(oldg
):
193 Creates a (CouchDB) PodcastGroup document from a
194 (ORM) PodcastGroup object
198 update_podcastgroup(oldg
, g
)
204 @repeat_on_conflict(['newg'])
205 def update_podcastgroup(oldg
, newg
):
207 if newg
.title
!= oldg
.title
:
208 newg
.title
= oldg
.title
215 def get_blacklist(blacklist
):
217 Returns a list of Ids of all blacklisted podcasts
219 blacklisted
= [b
.podcast
for b
in blacklist
]
221 for p
in blacklisted
:
222 newp
= Podcast
.for_oldid(p
.id)
224 newp
= create_podcast(p
)
226 blacklist_ids
.append(newp
._id
)
230 def get_ratings(ratings
):
232 Returns a list of Rating-objects, based on the relational Ratings
234 conv
= lambda r
: Rating(rating
=r
.rating
, timestamp
=r
.timestamp
)
235 return map(conv
, ratings
)
238 def podcasts_to_ids(podcasts
):
240 podcast
= Podcast
.for_oldid(p
.id)
242 podcast
= create_podcast(p
, sparse
=True)
243 yield podcast
.get_id()
246 def get_or_migrate_podcast(oldp
):
247 return Podcast
.for_oldid(oldp
.id) or create_podcast(oldp
)
250 def create_episode_action(action
):
252 a
.action
= action
.action
253 a
.timestamp
= action
.timestamp
254 a
.device_oldid
= action
.device
.id if action
.device
else None
255 a
.started
= action
.started
256 a
.playmark
= action
.playmark
259 def create_episode(olde
, sparse
=False):
260 podcast
= get_or_migrate_podcast(olde
.podcast
)
263 e
.urls
.append(olde
.url
)
264 e
.podcast
= podcast
.get_id()
267 update_episode(olde
, e
, podcast
)
274 def get_or_migrate_episode(olde
):
275 return Episode
.for_oldid(olde
.id) or create_episode(olde
)
278 def update_episode(olde
, newe
, podcast
):
281 if not olde
.url
in newe
.urls
:
282 newe
.urls
.append(olde
.url
)
285 PROPERTIES
= ('title', 'description', 'link',
286 'author', 'duration', 'filesize', 'language',
290 if getattr(newe
, p
, None) != getattr(olde
, p
, None):
291 setattr(newe
, p
, getattr(olde
, p
, None))
294 if newe
.outdated
!= olde
.outdated
:
295 newe
.outdated
= bool(olde
.outdated
)
298 if newe
.released
!= olde
.timestamp
:
299 newe
.released
= olde
.timestamp
302 if olde
.mimetype
and not olde
.mimetype
in newe
.mimetypes
:
303 newe
.mimetypes
.append(olde
.mimetype
)
306 @repeat_on_conflict(['newe'])
316 def get_or_migrate_user(user
):
317 u
= User
.for_oldid(user
.id)
323 u
.username
= user
.username
328 def get_or_migrate_device(device
, user
=None):
329 return Device
.for_oldid(device
.id) or create_device(device
)
332 def create_device(oldd
, sparse
=False):
333 user
= get_or_migrate_user(oldd
.user
)
339 update_device(oldd
, d
)
341 user
.devices
.append(d
)
346 def update_device(oldd
, newd
):
348 newd
.name
= oldd
.name
349 newd
.type = oldd
.type
350 newd
.deleted
= bool(oldd
.deleted
)
354 def migrate_subscription_action(old_action
):
355 action
= SubscriptionAction()
356 action
.timestamp
= old_action
.timestamp
357 action
.action
= 'subscribe' if old_action
.action
== 1 else 'unsubscribe'
358 action
.device
= get_or_migrate_device(old_action
.device
).id
362 def get_episode_user_state(user
, episode
, podcast
):
363 e_state
= EpisodeUserState
.for_user_episode(user
.id, episode
._id
)
367 p_state
= PodcastUserState
.for_user_podcast(user
, podcast
)
368 e_state
= p_state
.episodes
.get(episode
._id
, None)
371 e_state
= EpisodeUserState()
372 e_state
.episode
= episode
._id
374 @repeat_on_conflict(['p_state'])
375 def remove_episode_status(p_state
):
376 del p_state
.episodes
[episode
._id
]
379 remove_episode_status(p_state
=p_state
)
382 if not e_state
.podcast_ref_url
:
383 e_state
.podcast_ref_url
= podcast
.url
385 if not e_state
.ref_url
:
386 e_state
.ref_url
= episode
.url
388 e_state
.podcast
= podcast
.get_id()
389 e_state
.user_oldid
= user
.id
395 def get_devices(user
):
396 from mygpo
.api
.models
import Device
397 return [get_or_migrate_device(dev
) for dev
in Device
.objects
.filter(user
=user
)]