Package flumotion :: Package component :: Package misc :: Package httpfile :: Module file
[hide private]

Source Code for Module flumotion.component.misc.httpfile.file

  1  # -*- Mode: Python -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3  # 
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 21  import string 
 22  import os 
 23   
 24  from flumotion.component import component 
 25  from flumotion.common import log, messages, errors, netutils 
 26  from flumotion.component.component import moods 
 27  from flumotion.component.misc.porter import porterclient 
 28  from flumotion.component.base import http as httpbase 
 29  from twisted.web import resource, server, http 
 30  from twisted.web import error as weberror 
 31  from twisted.internet import defer, reactor, error, abstract 
 32  from twisted.python import filepath 
 33  from flumotion.twisted import fdserver 
 34  from twisted.cred import credentials 
 35   
 36  from twisted.web.static import loadMimeTypes, getTypeAndEncoding 
 37   
38 -class File(resource.Resource, filepath.FilePath, log.Loggable):
39 __pychecker__ = 'no-objattrs' 40 41 contentTypes = loadMimeTypes() 42 43 defaultType = "application/octet-stream" 44 45 childNotFound = weberror.NoResource("File not found.") 46
47 - def __init__(self, path, component):
48 resource.Resource.__init__(self) 49 filepath.FilePath.__init__(self, path) 50 51 self.component = component
52
53 - def getChild(self, path, request):
54 self.restat() 55 56 if not self.isdir(): 57 return self.childNotFound 58 59 if path: 60 fpath = self.child(path) 61 else: 62 return self.childNotFound 63 64 if not fpath.exists(): 65 return self.childNotFound 66 67 return self.createSimilarFile(fpath.path)
68
69 - def openForReading(self):
70 """Open a file and return it.""" 71 f = self.open() 72 self.debug("Reading file from FD: %d", f.fileno()) 73 return f
74
75 - def getFileSize(self):
76 """Return file size.""" 77 return self.getsize()
78
79 - def render(self, request):
80 def terminateSimpleRequest(res, request): 81 if res != server.NOT_DONE_YET: 82 request.finish()
83 84 d = self.component.startAuthentication(request) 85 d.addCallback(self.renderAuthenticated, request) 86 d.addCallback(terminateSimpleRequest, request) 87 88 return server.NOT_DONE_YET
89
90 - def renderAuthenticated(self, res, request):
91 """ 92 Now that we're authenticated (or authentication wasn't requested), 93 write the file (or appropriate other response) to the client. 94 We override static.File to implement Range requests, and to get access 95 to the transfer object to abort it later; the bulk of this is a direct 96 copy, though. 97 """ 98 self.restat() 99 100 ext = os.path.splitext(self.basename())[1].lower() 101 type = self.contentTypes.get(ext, self.defaultType) 102 103 if not self.exists(): 104 self.debug("Couldn't find resource %s", self.basename()) 105 return self.childNotFound.render(request) 106 107 if self.isdir(): 108 return self.childNotFound.render(request) 109 110 # Different headers not normally set in static.File... 111 # Specify that we will close the connection after this request, and 112 # that the client must not issue further requests. 113 # We do this because future requests on this server might actually need 114 # to go to a different process (because of the porter) 115 request.setHeader('Connection', 'close') 116 # We can do range requests, in bytes. 117 request.setHeader('Accept-Ranges', 'bytes') 118 119 if type: 120 request.setHeader('content-type', type) 121 122 try: 123 f = self.openForReading() 124 except IOError, e: 125 import errno 126 if e[0] == errno.EACCES: 127 return weberror.ForbiddenResource().render(request) 128 else: 129 raise 130 131 if request.setLastModified(self.getmtime()) is http.CACHED: 132 return '' 133 134 tsize = fsize = size = self.getFileSize() 135 range = request.getHeader('range') 136 start = 0 137 if range is not None: 138 # TODO: Add a unit test - or several - for this stuff. 139 # We have a partial-data request... 140 # Some variables... we start at byte offset 'start', end at byte 141 # offset 'end'. fsize is the number of bytes we're sending. tsize 142 # is the total size of the file. 'size' is the byte offset we will 143 # stop at, plus 1. 144 bytesrange = string.split(range, '=') 145 if len(bytesrange) != 2: 146 request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE) 147 return '' 148 149 start, end = string.split(bytesrange[1], '-', 1) 150 if start: 151 start = int(start) 152 f.seek(start) 153 if end: 154 end = int(end) 155 else: 156 end = size - 1 157 fsize = end - start + 1 158 elif end: 159 lastbytes = int(end) 160 if size < lastbytes: 161 lastbytes = size 162 start = size - lastbytes 163 f.seek(start) 164 fsize = lastbytes 165 end = size - 1 166 else: 167 request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE) 168 return '' 169 size = end + 1 170 171 request.setResponseCode(http.PARTIAL_CONTENT) 172 request.setHeader('Content-Range', "bytes %d-%d/%d" % 173 (start, end, tsize)) 174 175 request.setHeader("Content-Length", str(fsize)) 176 177 if request.method == 'HEAD': 178 return '' 179 180 request._transfer = FileTransfer(f, size, request) 181 182 return server.NOT_DONE_YET
183
184 - def createSimilarFile(self, path):
185 self.debug("createSimilarFile at %r", path) 186 f = self.__class__(path, self.component) 187 return f
188
189 -class FileTransfer:
190 """ 191 A class to represent the transfer of a file over the network. 192 """ 193 request = None 194
195 - def __init__(self, file, size, request):
196 self.file = file 197 self.size = size 198 self.request = request 199 self.written = self.file.tell() 200 self.bytesWritten = 0 201 request.registerProducer(self, 0)
202
203 - def resumeProducing(self):
204 if not self.request: 205 return 206 data = self.file.read(min(abstract.FileDescriptor.bufferSize, 207 self.size - self.written)) 208 if data: 209 self.written += len(data) 210 self.bytesWritten += len(data) 211 # this .write will spin the reactor, calling .doWrite and then 212 # .resumeProducing again, so be prepared for a re-entrant call 213 self.request.write(data) 214 if self.request and self.file.tell() == self.size: 215 self.request.unregisterProducer() 216 self.request.finish() 217 self.request = None
218
219 - def pauseProducing(self):
220 pass
221
222 - def stopProducing(self):
223 self.file.close() 224 self.request = None
225