~ubuntu-branches/ubuntu/lucid/exaile/lucid

« back to all changes in this revision

Viewing changes to plugins/daapserver/spydaap/server.py

  • Committer: Bazaar Package Importer
  • Author(s): Andrew Starr-Bochicchio
  • Date: 2010-02-12 19:51:01 UTC
  • mfrom: (1.1.11 upstream)
  • Revision ID: james.westby@ubuntu.com-20100212195101-8jt3tculxcl92e6v
Tags: 0.3.1~b1-0ubuntu1
* New upstream release.
* Adjust exaile.install for new plugins.
* debian/control:
 - Drop unneeded python-dev Build-Dep.
 - Bump Standards-Version to 3.8.4 
* debian/rules: No empty po files to delete.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#Copyright (C) 2009 Erik Hetzner
 
2
 
 
3
#This file is part of Spydaap. Spydaap is free software: you can
 
4
#redistribute it and/or modify it under the terms of the GNU General
 
5
#Public License as published by the Free Software Foundation, either
 
6
#version 3 of the License, or (at your option) any later version.
 
7
 
 
8
#Spydaap is distributed in the hope that it will be useful, but
 
9
#WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 
11
#General Public License for more details.
 
12
 
 
13
#You should have received a copy of the GNU General Public License
 
14
#along with Spydaap. If not, see <http://www.gnu.org/licenses/>.
 
15
 
 
16
import BaseHTTPServer, errno, logging, os, re, urlparse, socket, spydaap, sys, traceback
 
17
from spydaap.daap import do
 
18
 
 
19
def makeDAAPHandlerClass(server_name, cache, md_cache, container_cache):
 
20
    session_id = 1
 
21
    log = logging.getLogger('spydaap.server')
 
22
    daap_server_revision = 1
 
23
 
 
24
    class DAAPHandler(BaseHTTPServer.BaseHTTPRequestHandler):
 
25
        protocol_version = "HTTP/1.1"
 
26
 
 
27
        def h(self, data, **kwargs):
 
28
            self.send_response(kwargs.get('status', 200))
 
29
            self.send_header('Content-Type', kwargs.get('type', 'application/x-dmap-tagged'))
 
30
            self.send_header('DAAP-Server', 'Simple')
 
31
            self.send_header('Expires', '-1')
 
32
            self.send_header('Cache-Control', 'no-cache')
 
33
            self.send_header('Accept-Ranges', 'bytes')
 
34
            self.send_header('Content-Language', 'en_us')
 
35
            if kwargs.has_key('extra_headers'):
 
36
                for k, v in kwargs['extra_headers'].iteritems():
 
37
                    self.send_header(k, v)
 
38
            try:
 
39
                if type(data) == file:
 
40
                    self.send_header("Content-Length", str(os.stat(data.name).st_size))
 
41
                else:
 
42
                    self.send_header("Content-Length", len(data))                   
 
43
            except:
 
44
                pass
 
45
            self.end_headers()
 
46
            if hasattr(self, 'isHEAD') and self.isHEAD:
 
47
                pass
 
48
            else:
 
49
                try:
 
50
                    if (hasattr(data, 'next')):
 
51
                        for d in data:
 
52
                            self.wfile.write(d)
 
53
                    else:
 
54
                        self.wfile.write(data)
 
55
                except socket.error, ex:
 
56
                    if ex.errno in [errno.ECONNRESET]: pass
 
57
                    else: raise
 
58
            if (hasattr(data, 'close')):
 
59
                data.close()
 
60
 
 
61
        #itunes sends request for:
 
62
        #GET daap://192.168.1.4:3689/databases/1/items/626.mp3?seesion-id=1
 
63
        #so we must hack the urls; annoying.
 
64
        itunes_re = '^(?://[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}:[0-9]+)?'
 
65
        drop_q = '(?:\\?.*)?$'
 
66
 
 
67
        def do_GET(self):
 
68
            parsed_path = urlparse.urlparse(self.path).path
 
69
            if re.match(self.itunes_re + "/$", parsed_path):
 
70
                self.do_GET_login()
 
71
            elif re.match(self.itunes_re + '/server-info$', parsed_path):
 
72
                self.do_GET_server_info()
 
73
            elif re.match(self.itunes_re + '/content-codes$', parsed_path):
 
74
                self.do_GET_content_codes()
 
75
            elif re.match(self.itunes_re + '/databases$', parsed_path):
 
76
                self.do_GET_database_list()
 
77
            elif re.match(self.itunes_re + '/databases/([0-9]+)/items$', parsed_path):
 
78
                md = re.match(self.itunes_re + '/databases/([0-9]+)/items$', parsed_path)
 
79
                self.do_GET_item_list(md.group(1))
 
80
            elif re.match(self.itunes_re + '/databases/([0-9]+)/items/([0-9]+)\\.([0-9a-z]+)' + self.drop_q, parsed_path):
 
81
                md = re.match(self.itunes_re + '/databases/([0-9]+)/items/([0-9]+)\\.([0-9a-z]+)' + self.drop_q, parsed_path)
 
82
                self.do_GET_item(md.group(1), md.group(2), md.group(3))
 
83
            elif re.match(self.itunes_re + '/databases/([0-9]+)/containers$', parsed_path):
 
84
                md = re.match(self.itunes_re + '/databases/([0-9]+)/containers$', parsed_path)
 
85
                self.do_GET_container_list(md.group(1))
 
86
            elif re.match(self.itunes_re + '/databases/([0-9]+)/containers/([0-9]+)/items$', parsed_path):
 
87
                md = re.match(self.itunes_re + '/databases/([0-9]+)/containers/([0-9]+)/items$', parsed_path)
 
88
                self.do_GET_container_item_list(md.group(1), md.group(2))
 
89
            elif re.match('^/login$', parsed_path):
 
90
                self.do_GET_login()
 
91
            elif re.match('^/logout$', parsed_path):
 
92
                self.do_GET_logout()
 
93
            elif re.match('^/update$', parsed_path):
 
94
                self.do_GET_update()
 
95
            else:
 
96
                self.send_error(404)
 
97
            return
 
98
 
 
99
        def do_HEAD(self):
 
100
            self.isHEAD = True
 
101
            self.do_GET()
 
102
 
 
103
        def do_GET_login(self):
 
104
            mlog = do('dmap.loginresponse',
 
105
                      [ do('dmap.status', 200),
 
106
                        do('dmap.sessionid', session_id) ])
 
107
            self.h(mlog.encode())
 
108
 
 
109
        def do_GET_logout(self):
 
110
            self.send_response(204)
 
111
            self.end_headers()
 
112
 
 
113
        def do_GET_server_info(self):
 
114
            msrv = do('dmap.serverinforesponse',
 
115
                      [ do('dmap.status', 200),
 
116
                        do('dmap.protocolversion', '2.0'),
 
117
                        do('daap.protocolversion', '3.0'),
 
118
                        do('dmap.timeoutinterval', 1800),
 
119
                        do('dmap.itemname', server_name),
 
120
                        do('dmap.loginrequired', 0),
 
121
                        do('dmap.authenticationmethod', 0),
 
122
                        do('dmap.supportsextensions', 0),
 
123
                        do('dmap.supportsindex', 0),
 
124
                        do('dmap.supportsbrowse', 0),
 
125
                        do('dmap.supportsquery', 0),
 
126
                        do('dmap.supportspersistentids', 0),
 
127
                        do('dmap.databasescount', 1),                
 
128
                        #do('dmap.supportsautologout', 0),
 
129
                        #do('dmap.supportsupdate', 0),
 
130
                        #do('dmap.supportsresolve', 0),
 
131
                       ])
 
132
            self.h(msrv.encode())
 
133
 
 
134
        def do_GET_content_codes(self):
 
135
            children = [ do('dmap.status', 200) ]
 
136
            for code in spydaap.daap.dmapCodeTypes.keys():
 
137
                (name, dtype) = spydaap.daap.dmapCodeTypes[code]
 
138
                d = do('dmap.dictionary',
 
139
                       [ do('dmap.contentcodesnumber', code),
 
140
                         do('dmap.contentcodesname', name),
 
141
                         do('dmap.contentcodestype',
 
142
                            spydaap.daap.dmapReverseDataTypes[dtype])
 
143
                         ])
 
144
                children.append(d)
 
145
            mccr = do('dmap.contentcodesresponse',
 
146
                      children)
 
147
            self.h(mccr.encode())
 
148
 
 
149
        def do_GET_database_list(self):
 
150
            d = do('daap.serverdatabases',
 
151
                   [ do('dmap.status', 200),
 
152
                     do('dmap.updatetype', 0),
 
153
                     do('dmap.specifiedtotalcount', 1),
 
154
                     do('dmap.returnedcount', 1),
 
155
                     do('dmap.listing',
 
156
                        [ do('dmap.listingitem',
 
157
                             [ do('dmap.itemid', 1),
 
158
                               do('dmap.persistentid', 1),
 
159
                               do('dmap.itemname', server_name),
 
160
                               do('dmap.itemcount', 
 
161
                                  len(md_cache)),
 
162
                               do('dmap.containercount', len(container_cache))])
 
163
                          ])
 
164
                     ])
 
165
            self.h(d.encode())
 
166
 
 
167
        def do_GET_item_list(self, database_id):
 
168
            def build_item(md):
 
169
                return do('dmap.listingitem', 
 
170
                          [ do('dmap.itemkind', 2),
 
171
                            do('dmap.containeritemid', md.id),
 
172
                            do('dmap.itemid', md.id),
 
173
                            md.get_dmap_raw()
 
174
                            ])
 
175
 
 
176
            def build():
 
177
                children = [ build_item (md) for md in md_cache ]
 
178
                file_count = len(children)
 
179
                d = do('daap.databasesongs',
 
180
                       [ do('dmap.status', 200),
 
181
                         do('dmap.updatetype', 0),
 
182
                         do('dmap.specifiedtotalcount', file_count),
 
183
                         do('dmap.returnedcount', file_count),
 
184
                         do('dmap.listing',
 
185
                            children) ])
 
186
                return d.encode()
 
187
 
 
188
            data = build()
 
189
#            data = cache.get('item_list', build)
 
190
            self.h(data)
 
191
 
 
192
        def do_GET_update(self):
 
193
            mupd = do('dmap.updateresponse',
 
194
                      [ do('dmap.status', 200),
 
195
                        do('dmap.serverrevision', daap_server_revision),
 
196
                        ])
 
197
            self.h(mupd.encode())
 
198
 
 
199
        def do_GET_item(self, database, item, format):
 
200
            try:
 
201
                fn = md_cache.get_item_by_id(item).get_original_filename()
 
202
            except IndexError:          # if the track isn't in the DB, we get an exception
 
203
                self.send_error(404)    # this can be caused by left overs from previous sessions
 
204
                return
 
205
 
 
206
            if (self.headers.has_key('Range')):
 
207
                rs = self.headers['Range']
 
208
                m = re.compile('bytes=([0-9]+)-([0-9]+)?').match(rs)
 
209
                (start, end) = m.groups()
 
210
                if end != None: end = int(end)
 
211
                else: end = os.stat(fn).st_size
 
212
                start = int(start)
 
213
                f = spydaap.ContentRangeFile(fn, open(fn), start, end)
 
214
                extra_headers={"Content-Range": "bytes %s-%s/%s"%(str(start), str(end), str(os.stat(fn).st_size))}
 
215
                status=206
 
216
            else: 
 
217
                f = open(fn)
 
218
                extra_headers={}
 
219
                status=200
 
220
            # this is ugly, very wrong.
 
221
            type = "audio/%s"%(os.path.splitext(fn)[1])
 
222
            self.h(f, type=type, status=status, extra_headers=extra_headers)
 
223
 
 
224
        def do_GET_container_list(self, database):
 
225
            container_do = []
 
226
            for i, c in enumerate(container_cache):
 
227
                d = [ do('dmap.itemid', i + 1 ),
 
228
                      do('dmap.itemcount', len(c)),
 
229
                      do('dmap.containeritemid', i + 1),
 
230
                      do('dmap.itemname', c.get_name()) ]
 
231
                if c.get_name() == 'Library': # this should be better
 
232
                    d.append(do('daap.baseplaylist', 1))
 
233
                else:
 
234
                    d.append(do('com.apple.itunes.smart-playlist', 1))
 
235
                container_do.append(do('dmap.listingitem', d))
 
236
            d = do('daap.databaseplaylists',
 
237
                   [ do('dmap.status', 200),
 
238
                     do('dmap.updatetype', 0),
 
239
                     do('dmap.specifiedtotalcount', len(container_do)),
 
240
                     do('dmap.returnedcount', len(container_do)),
 
241
                     do('dmap.listing',
 
242
                        container_do)
 
243
                     ])
 
244
            self.h(d.encode())
 
245
 
 
246
        def do_GET_container_item_list(self, database_id, container_id):
 
247
            container = container_cache.get_item_by_id(container_id)
 
248
            self.h(container.get_daap_raw())
 
249
 
 
250
    return DAAPHandler