mirror of
https://github.com/JohnDoee/deluge-streaming/
synced 2026-07-01 07:31:17 -07:00
150 lines
5.4 KiB
Python
150 lines
5.4 KiB
Python
from twisted.internet import defer
|
|
from twisted.python import log
|
|
from twisted.web import http, resource, server, static
|
|
|
|
# NOTICE!
|
|
# All these producers are taken directly from the Twisted Project.
|
|
# This is because i needed to make them accept defers.
|
|
# /NOTICE!
|
|
|
|
class NoRangeStaticProducer(static.NoRangeStaticProducer):
|
|
@defer.inlineCallbacks
|
|
def resumeProducing(self):
|
|
if not self.request:
|
|
return
|
|
data = yield defer.maybeDeferred(self.fileObject.read, self.bufferSize)
|
|
if data:
|
|
# this .write will spin the reactor, calling .doWrite and then
|
|
# .resumeProducing again, so be prepared for a re-entrant call
|
|
self.request.write(data)
|
|
else:
|
|
self.request.unregisterProducer()
|
|
self.request.finish()
|
|
self.stopProducing()
|
|
|
|
class SingleRangeStaticProducer(static.SingleRangeStaticProducer):
|
|
@defer.inlineCallbacks
|
|
def resumeProducing(self):
|
|
if not self.request:
|
|
return
|
|
data = yield defer.maybeDeferred(self.fileObject.read,
|
|
min(self.bufferSize, self.size - self.bytesWritten))
|
|
if data:
|
|
self.bytesWritten += len(data)
|
|
# this .write will spin the reactor, calling .doWrite and then
|
|
# .resumeProducing again, so be prepared for a re-entrant call
|
|
self.request.write(data)
|
|
if self.request and self.bytesWritten == self.size:
|
|
self.request.unregisterProducer()
|
|
self.request.finish()
|
|
self.stopProducing()
|
|
|
|
class MultipleRangeStaticProducer(static.MultipleRangeStaticProducer):
|
|
@defer.inlineCallbacks
|
|
def resumeProducing(self):
|
|
if not self.request:
|
|
return
|
|
data = []
|
|
dataLength = 0
|
|
done = False
|
|
while dataLength < self.bufferSize:
|
|
if self.partBoundary:
|
|
dataLength += len(self.partBoundary)
|
|
data.append(self.partBoundary)
|
|
self.partBoundary = None
|
|
p = yield defer.maybeDeferred(self.fileObject.read,
|
|
min(self.bufferSize - dataLength,
|
|
self._partSize - self._partBytesWritten))
|
|
self._partBytesWritten += len(p)
|
|
dataLength += len(p)
|
|
data.append(p)
|
|
if self.request and self._partBytesWritten == self._partSize:
|
|
try:
|
|
self._nextRange()
|
|
except StopIteration:
|
|
done = True
|
|
break
|
|
self.request.write(''.join(data))
|
|
if done:
|
|
self.request.unregisterProducer()
|
|
self.request.finish()
|
|
self.request = None
|
|
|
|
class FilelikeObjectResource(static.File):
|
|
isLeaf = True
|
|
contentType = None
|
|
fileObject = None
|
|
encoding = 'bytes'
|
|
|
|
def __init__(self, fileObject, size, contentType='bytes'):
|
|
self.contentType = contentType
|
|
self.fileObject = fileObject
|
|
self.fileSize = size
|
|
resource.Resource.__init__(self)
|
|
|
|
def _setContentHeaders(self, request, size=None):
|
|
if size is None:
|
|
size = self.getFileSize()
|
|
|
|
if size:
|
|
request.setHeader('content-length', str(size))
|
|
if self.contentType:
|
|
request.setHeader('content-type', self.contentType)
|
|
if self.encoding:
|
|
request.setHeader('content-encoding', self.encoding)
|
|
|
|
def makeProducer(self, request, fileForReading):
|
|
"""
|
|
Make a L{StaticProducer} that will produce the body of this response.
|
|
|
|
This method will also set the response code and Content-* headers.
|
|
|
|
@param request: The L{Request} object.
|
|
@param fileForReading: The file object containing the resource.
|
|
@return: A L{StaticProducer}. Calling C{.start()} on this will begin
|
|
producing the response.
|
|
"""
|
|
byteRange = request.getHeader('range')
|
|
if byteRange is None or not self.getFileSize():
|
|
self._setContentHeaders(request)
|
|
request.setResponseCode(http.OK)
|
|
return NoRangeStaticProducer(request, fileForReading)
|
|
try:
|
|
parsedRanges = self._parseRangeHeader(byteRange)
|
|
except ValueError:
|
|
log.msg("Ignoring malformed Range header %r" % (byteRange,))
|
|
self._setContentHeaders(request)
|
|
request.setResponseCode(http.OK)
|
|
return NoRangeStaticProducer(request, fileForReading)
|
|
|
|
if len(parsedRanges) == 1:
|
|
offset, size = self._doSingleRangeRequest(
|
|
request, parsedRanges[0])
|
|
self._setContentHeaders(request, size)
|
|
return SingleRangeStaticProducer(
|
|
request, fileForReading, offset, size)
|
|
else:
|
|
rangeInfo = self._doMultipleRangeRequest(request, parsedRanges)
|
|
return MultipleRangeStaticProducer(
|
|
request, fileForReading, rangeInfo)
|
|
|
|
def getFileSize(self):
|
|
return self.fileSize
|
|
|
|
def render_GET(self, request):
|
|
"""
|
|
Begin sending the contents of this L{File} (or a subset of the
|
|
contents, based on the 'range' header) to the given request.
|
|
"""
|
|
request.setHeader('accept-ranges', 'bytes')
|
|
|
|
producer = self.makeProducer(request, self.fileObject)
|
|
|
|
if request.method == 'HEAD':
|
|
return ''
|
|
|
|
producer.start()
|
|
# and make sure the connection doesn't get closed
|
|
return server.NOT_DONE_YET
|
|
render_HEAD = render_GET
|