use stale=ok|update_after to increase performance
[mygpo.git] / mygpo / web / heatmap.py
blob610cca4514f6a28ace7aec6de57cce6f01c43198
2 # This file is part of my.gpodder.org.
4 # my.gpodder.org is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # my.gpodder.org is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 # License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with my.gpodder.org. If not, see <http://www.gnu.org/licenses/>.
18 from functools import wraps
20 from mygpo.users.models import EpisodeUserState
23 class EpisodeHeatmap(object):
24 """ Information about how often certain parts of Episodes are played """
26 def __init__(self, podcast_id, episode_id=None, user_id=None,
27 duration=None):
28 """ Initialize a new Episode heatmap
30 EpisodeHeatmap(podcast_id, [episode_id, [user_id]]) """
32 self.podcast_id = podcast_id
34 if episode_id is not None and podcast_id is None:
35 raise ValueError('episode_id can only be used '
36 'if podcast_id is not None')
38 self.episode_id = episode_id
40 if user_id is not None and episode_id is None:
41 raise ValueError('user_id can only be used '
42 'if episode_id is not None')
44 self.user_id = user_id
45 self.duration = duration
46 self.heatmap = None
47 self.borders = None
50 def _query(self):
51 """ Queries the database and stores the heatmap and its borders """
53 db = EpisodeUserState.get_db()
55 group_level = len(filter(None, [self.podcast_id,
56 self.episode_id, self.user_id]))
58 r = db.view('users/episode_heatmap',
59 startkey = [self.podcast_id, self.episode_id,
60 self.user_id],
61 endkey = [self.podcast_id, self.episode_id or {},
62 self.user_id or {}],
63 reduce = True,
64 group = True,
65 group_level = group_level,
66 stale = 'update_after',
69 if not r:
70 self.heatmap = []
71 self.borders = []
72 else:
73 res = r.first()['value']
74 self.heatmap = res['heatmap']
75 self.borders = res['borders']
77 # heatmap info doesn't reach until the end of the episode
78 # so we extend it with 0 listeners
79 if self.duration > self.borders[-1]:
80 self.heatmap.append(0)
81 self.borders.append(self.duration)
84 def query_if_required():
85 """ If required, queries the database before calling the function """
87 def decorator(f):
88 @wraps(f)
89 def tmp(self, *args, **kwargs):
90 if None in (self.heatmap, self.borders):
91 self._query()
93 return f(self, *args, **kwargs)
94 return tmp
95 return decorator
98 @property
99 @query_if_required()
100 def max_plays(self):
101 """ Returns the highest number of plays of all sections """
103 return max(self.heatmap)
106 @property
107 @query_if_required()
108 def sections(self):
109 """ Returns an iterator that emits (from, to, play-counts) tuples
111 Each tuple represents one part in the heatmap with a distinct
112 play-count. from and to indicate the range of section in seconds."""
114 for i in range(len(self.heatmap)):
115 yield (self.borders[i], self.borders[i+1], self.heatmap[i])
118 @query_if_required()
119 def __nonzero__(self):
120 return any(self.heatmap)