~facundo/encuentro/trunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# -*- coding: utf8 -*-

# Copyright 2011-2014 Facundo Batista
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# For further info, check  https://launchpad.net/encuentro

"""Update the episodes metadata."""

import bz2
import json
import logging
from datetime import datetime
from encuentro.config import config

import defer

from encuentro import utils
from encuentro.ui import dialogs

BACKENDS_URL = "http://www.taniquetil.com.ar/encuentro/backends-v04.list"

logger = logging.getLogger('encuentro.update')


class UpdateEpisodes(object):
    """Update the episodes info."""

    def __init__(self, main_window):
        self.main_window = main_window

    def background(self):
        """Trigger an update in background."""
        self._update()

    def interactive(self):
        """Update episodes interactively."""
        dialog = dialogs.UpdateDialog()
        dialog.show()
        self._update(dialog)

    @defer.inline_callbacks
    def _update(self, dialog=None):
        """Update the content from server.

        If we have a dialog (interactive update), check frequently if
        it was closed, so we stop working for that request.
        """
        if dialog:
            tell_user = lambda *t: dialog.append(u" ".join(map(unicode, t)))
        else:
            tell_user = lambda *t: None

        logger.info("Downloading backend list")
        tell_user("Descargando la lista de backends...")
        try:
            _, backends_file = yield utils.download(BACKENDS_URL)
        except Exception, e:
            logger.error("Problem when downloading backends: %s", e)
            tell_user("Hubo un PROBLEMA al bajar la lista de backends:", e)
            return
        if dialog and dialog.closed:
            return
        backends_list = [l.strip().split() for l in backends_file.split("\n")
                         if l and l[0] != '#']

        backends = {}
        for b_name, b_dloader, b_url in backends_list:
            logger.info("Downloading backend metadata for %r", b_name)
            tell_user("Descargando la lista de episodios para backend %r..." %
                      (b_name,))
            try:
                _, compressed = yield utils.download(b_url)
            except Exception, e:
                logger.error("Problem when downloading episodes: %s", e)
                tell_user("Hubo un PROBLEMA al bajar los episodios: ", e)
                return
            if dialog and dialog.closed:
                return

            tell_user("Descomprimiendo el archivo....")
            new_content = bz2.decompress(compressed)
            logger.debug("Downloaded data decompressed ok")

            content = json.loads(new_content)
            for item in content:
                item['downtype'] = b_dloader
            backends[b_name] = content

        if dialog and dialog.closed:
            return
        tell_user("Conciliando datos de diferentes backends")
        logger.debug("Merging backends data")
        new_data = self._merge(backends)

        tell_user("Actualizando los datos internos....")
        logger.debug("Updating internal metadata (%d)", len(new_data))
        self.main_window.programs_data.merge(
            new_data, self.main_window.big_panel.episodes)

        config.update({'autorefresh_last_time': datetime.now()})
        config.save()

        tell_user(u"¡Todo terminado bien!")

        if dialog:
            dialog.accept()

    def _merge(self, backends):
        """Merge content from all backends.

        This is for v03, with only 'encuentro' and 'conectar' data to be
        really merged, other data just appended.
        """
        raw_encuentro_data = backends.pop('encuentro')
        raw_conectar_data = backends.pop('conectar')
        enc_data = dict((x['episode_id'], x) for x in raw_encuentro_data)
        con_data = dict((x['episode_id'], x) for x in raw_conectar_data)
        common = set(enc_data) & set(con_data)
        logger.debug("Merging: encuentro=%d conectar=%d (common=%d)",
                     len(enc_data), len(con_data), len(common))

        # what is in not common in both goes untouched
        final_data = ([enc_data[epid] for epid in set(enc_data) - common] +
                      [con_data[epid] for epid in set(con_data) - common])

        # what is common, we need to do the merge
        for epid in common:
            enc_ep = enc_data[epid]
            con_ep = con_data[epid]

            enc_desc = enc_ep['description']
            con_desc = con_ep['description']
            if enc_desc == con_desc:
                description = enc_desc
            elif enc_desc is None:
                description = con_desc
            elif con_desc is None:
                description = enc_desc
            else:
                # not None, or they would have been the same, let's concat
                # both, shorter first
                if len(con_desc) < len(enc_desc):
                    description = con_desc + ' ' + enc_desc
                else:
                    description = enc_desc + ' ' + con_desc

            # if both are equal (None or not), it also works
            if enc_ep['duration'] is None:
                duration = con_ep['duration']
            else:
                duration = enc_ep['duration']
            if enc_ep['image_url'] is None:
                image_url = con_ep['image_url']
            else:
                image_url = enc_ep['image_url']
            if enc_ep['season'] is None:
                season = con_ep['season']
            else:
                season = enc_ep['season']

            d = dict(episode_id=epid, description=description,
                     duration=duration, url=con_ep['url'],
                     channel=con_ep['channel'], title=con_ep['title'],
                     section=con_ep['section'], image_url=image_url,
                     downtype=con_ep['downtype'], season=season)
            final_data.append(d)

        logger.debug("Merging: appending other data: %s", backends.keys())
        for data in backends.itervalues():
            final_data.extend(data)
        logger.debug("Merged, final: %d", len(final_data))
        return final_data