~ubuntu-branches/ubuntu/maverick/coherence/maverick

« back to all changes in this revision

Viewing changes to coherence/backends/feed_storage.py

  • Committer: Bazaar Package Importer
  • Author(s): Charlie Smotherman
  • Date: 2010-01-02 10:57:15 UTC
  • mfrom: (1.1.7 upstream) (3.2.8 sid)
  • Revision ID: james.westby@ubuntu.com-20100102105715-sghzl2nw4lr5b1ob
Tags: 0.6.6.2-1
*  New  upstream release, summary of changes:
    - adding all necessary files to MANIFEST.in, to compensate for the
      gone 'auto-include-all-files-under-version-control' setuptools
      feature.
    - rearranging genre and genres attribute in DIDLLite - thx Caleb  
    - fix face_path typo, fixes #275.   

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Licensed under the MIT license
 
2
# http://opensource.org/licenses/mit-license.php
 
3
 
 
4
# Copyright 2009, Dominik Ruf <dominikruf at googlemail dot com>
 
5
 
 
6
from coherence.backend import BackendItem
 
7
from coherence.backend import BackendStore
 
8
from coherence.upnp.core import DIDLLite
 
9
from coherence.upnp.core.utils import ReverseProxyUriResource
 
10
 
 
11
from xml.etree.ElementTree import ElementTree
 
12
import urllib
 
13
import httplib
 
14
from urlparse import urlsplit
 
15
try:
 
16
    import feedparser
 
17
except:
 
18
    raise ImportError("""
 
19
        This backend depends on the feedparser modul.
 
20
        You can get it at http://www.feedparser.org/.""")
 
21
 
 
22
MIME_TYPES_EXTENTION_MAPPING = {'mp3': 'audio/mpeg',}
 
23
 
 
24
ROOT_CONTAINER_ID = 0
 
25
AUDIO_ALL_CONTAINER_ID = 51
 
26
AUDIO_ARTIST_CONTAINER_ID = 52
 
27
AUDIO_ALBUM_CONTAINER_ID = 53
 
28
VIDEO_FOLDER_CONTAINER_ID = 54
 
29
 
 
30
class RedirectingReverseProxyUriResource(ReverseProxyUriResource):
 
31
    def render(self, request):
 
32
        self.uri = self.follow_redirect(self.uri)
 
33
        self.resetUri(self.uri)
 
34
        return ReverseProxyUriResource.render(self, request)
 
35
 
 
36
    def follow_redirect(self, uri):
 
37
        netloc, path, query, fragment = urlsplit(uri)[1:]
 
38
        conn = httplib.HTTPConnection(netloc)
 
39
        conn.request('HEAD', '%s?%s#%s' % (path, query, fragment))
 
40
        res = conn.getresponse()
 
41
        if(res.status == 301 or res.status == 302):
 
42
            return self.follow_redirect(res.getheader('location'))
 
43
        else:
 
44
            return uri
 
45
 
 
46
 
 
47
class FeedStorageConfigurationException(Exception):
 
48
    pass
 
49
 
 
50
class FeedContainer(BackendItem):
 
51
    def __init__(self, parent_id, id, title):
 
52
        self.id = id
 
53
        self.parent_id = parent_id
 
54
        self.name = title
 
55
        self.mimetype = 'directory'
 
56
        self.item = DIDLLite.Container(self.id, self.parent_id, self.name)
 
57
 
 
58
        self.children = []
 
59
 
 
60
    def get_children(self, start=0, end=0):
 
61
        """returns all the chidlren of this container"""
 
62
        if end != 0:
 
63
            return self.children[start:end]
 
64
        return self.children[start:]
 
65
 
 
66
    def get_child_count(self):
 
67
        """returns the number of children in this container"""
 
68
        return len(self.children)
 
69
 
 
70
 
 
71
class FeedEnclosure(BackendItem):
 
72
    def __init__(self, store, parent, id, title, enclosure):
 
73
        self.store = store
 
74
        self.parent = parent
 
75
        self.external_id = id
 
76
        self.name = title
 
77
        self.location = RedirectingReverseProxyUriResource(enclosure.url.encode('latin-1'))
 
78
 
 
79
        # doing this because some (Fraunhofer Podcast) feeds say there mime type is audio/x-mpeg
 
80
        # which at least my XBOX doesn't like
 
81
        ext = enclosure.url.rsplit('.', 1)[0]
 
82
        if ext in MIME_TYPES_EXTENTION_MAPPING:
 
83
            mime_type = MIME_TYPES_EXTENTION_MAPPING[ext]
 
84
        else:
 
85
            mime_type = enclosure.type
 
86
        if(enclosure.type.startswith('audio')):
 
87
            self.item = DIDLLite.AudioItem(id, parent, self.name)
 
88
        elif(enclosure.type.startswith('video')):
 
89
            self.item = DIDLLite.VideoItem(id, parent, self.name)
 
90
        elif(enclosure.type.startswith('image')):
 
91
            self.item = DIDLLite.ImageItem(id, parent, self.name)
 
92
 
 
93
        res = DIDLLite.Resource("%s%d" % (store.urlbase, id), 'http-get:*:%s:*' % mime_type)
 
94
 
 
95
        self.item.res.append(res)
 
96
 
 
97
class FeedStore(BackendStore):
 
98
    """a general feed store"""
 
99
 
 
100
    logCategory = 'feed_store'
 
101
    implements = ['MediaServer']
 
102
 
 
103
    def __init__(self,server,**kwargs):
 
104
        BackendStore.__init__(self,server,**kwargs)
 
105
        self.name = kwargs.get('name', 'Feed Store')
 
106
        self.urlbase = kwargs.get('urlbase','')
 
107
        if( len(self.urlbase)>0 and
 
108
            self.urlbase[len(self.urlbase)-1] != '/'):
 
109
            self.urlbase += '/'
 
110
        self.feed_urls = kwargs.get('feed_urls')
 
111
        self.opml_url = kwargs.get('opml_url')
 
112
        if(not(self.feed_urls or self.opml_url)):
 
113
            raise FeedStorageConfigurationException("either feed_urls or opml_url has to be set")
 
114
        if(self.feed_urls and self.opml_url):
 
115
            raise FeedStorageConfigurationException("only feed_urls OR opml_url can be set")
 
116
 
 
117
        self.server = server
 
118
        self.refresh = int(kwargs.get('refresh', 1)) * (60 * 60) # TODO: not used yet
 
119
        self.store = {}
 
120
        self.wmc_mapping = {'4': str(AUDIO_ALL_CONTAINER_ID),    # all tracks
 
121
                            '7': str(AUDIO_ALBUM_CONTAINER_ID),    # all albums
 
122
                            '6': str(AUDIO_ARTIST_CONTAINER_ID),    # all artists
 
123
                            '15': str(VIDEO_FOLDER_CONTAINER_ID),    # all videos
 
124
                            }
 
125
 
 
126
        self.store[ROOT_CONTAINER_ID] = FeedContainer(-1, ROOT_CONTAINER_ID, self.name)
 
127
        self.store[AUDIO_ALL_CONTAINER_ID] = FeedContainer(-1, AUDIO_ALL_CONTAINER_ID, 'AUDIO_ALL_CONTAINER')
 
128
        self.store[AUDIO_ALBUM_CONTAINER_ID] = FeedContainer(-1, AUDIO_ALBUM_CONTAINER_ID, 'AUDIO_ALBUM_CONTAINER')
 
129
        self.store[VIDEO_FOLDER_CONTAINER_ID] = FeedContainer(-1, VIDEO_FOLDER_CONTAINER_ID, 'VIDEO_FOLDER_CONTAINER')
 
130
 
 
131
        try:
 
132
            self._update_data()
 
133
        except Exception, e:
 
134
            self.error('error while updateing the feed contant for %s: %s' % (self.name, str(e)))
 
135
        self.init_completed()
 
136
 
 
137
    def get_by_id(self,id):
 
138
        """returns the item according to the DIDLite id"""
 
139
        if isinstance(id, basestring):
 
140
            id = id.split('@',1)
 
141
            id = id[0]
 
142
        try:
 
143
            return self.store[int(id)]
 
144
        except (ValueError,KeyError):
 
145
            self.info("can't get item %d from %s feed storage" % (int(id), self.name))
 
146
        return None
 
147
 
 
148
    def _update_data(self):
 
149
        """get the feed xml, parse it, etc."""
 
150
        feed_urls = []
 
151
        if(self.opml_url):
 
152
            tree = ElementTree(file=urllib.urlopen(self.opml_url))
 
153
            body = tree.find('body')
 
154
            for outline in body.findall('outline'):
 
155
                feed_urls.append(outline.attrib['url'])
 
156
 
 
157
        if(self.feed_urls):
 
158
            feed_urls = self.feed_urls.split()
 
159
        container_id = 100
 
160
        item_id = 1001
 
161
        for feed_url in feed_urls:
 
162
            netloc, path, query, fragment = urlsplit(feed_url)[1:]
 
163
            conn = httplib.HTTPConnection(netloc)
 
164
            conn.request('HEAD', '%s?%s#%s' % (path, query, fragment))
 
165
            res = conn.getresponse()
 
166
            if res.status >= 400:
 
167
                self.warning('error getting %s status code: %d' % (feed_url, res.status))
 
168
                continue
 
169
            fp_dict = feedparser.parse(feed_url)
 
170
            name = fp_dict.feed.title
 
171
            self.store[container_id] = FeedContainer(ROOT_CONTAINER_ID, container_id, name)
 
172
            self.store[ROOT_CONTAINER_ID].children.append(self.store[container_id])
 
173
            self.store[VIDEO_FOLDER_CONTAINER_ID].children.append(self.store[container_id])
 
174
            self.store[AUDIO_ALBUM_CONTAINER_ID].children.append(self.store[container_id])
 
175
            for item in fp_dict.entries:
 
176
                for enclosure in item.enclosures:
 
177
                    self.store[item_id] = FeedEnclosure(self, container_id, item_id, '%04d - %s' % (item_id, item.title), enclosure)
 
178
                    self.store[container_id].children.append(self.store[item_id])
 
179
                    if enclosure.type.startswith('audio'):
 
180
                        self.store[AUDIO_ALL_CONTAINER_ID].children.append(self.store[item_id])
 
181
                        if not isinstance(self.store[container_id].item, DIDLLite.MusicAlbum):
 
182
                            self.store[container_id].item = DIDLLite.MusicAlbum(container_id, AUDIO_ALBUM_CONTAINER_ID, name)
 
183
 
 
184
                    item_id += 1
 
185
            if container_id <= 1000:
 
186
                container_id += 1
 
187
            else:
 
188
                raise Exception('to many containers')