2016-04-21 04:02:52 +02:00
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import shutil
|
|
|
|
import json
|
2016-05-01 11:17:59 +02:00
|
|
|
import sys
|
2016-05-02 10:10:50 +02:00
|
|
|
import mimetypes
|
2016-04-21 04:02:52 +02:00
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
from appdirs import user_data_dir
|
|
|
|
from twisted.web import server, static, resource
|
2016-05-02 21:58:40 +02:00
|
|
|
from twisted.internet import defer, interfaces, error, reactor, task, threads
|
2016-05-01 11:17:59 +02:00
|
|
|
|
|
|
|
from zope.interface import implements
|
2016-04-21 04:02:52 +02:00
|
|
|
|
|
|
|
from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon
|
2016-06-07 10:19:51 +02:00
|
|
|
from lbrynet.conf import API_CONNECTION_STRING, API_ADDRESS, DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH, LOG_FILE_NAME
|
2016-04-21 04:02:52 +02:00
|
|
|
|
|
|
|
|
2016-05-01 11:17:59 +02:00
|
|
|
if sys.platform != "darwin":
|
|
|
|
data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet")
|
|
|
|
else:
|
|
|
|
data_dir = user_data_dir("LBRY")
|
2016-04-21 04:02:52 +02:00
|
|
|
if not os.path.isdir(data_dir):
|
|
|
|
os.mkdir(data_dir)
|
|
|
|
|
2016-06-07 10:30:22 +02:00
|
|
|
lbrynet_log = os.path.join(data_dir, LOG_FILE_NAME)
|
2016-05-01 11:17:59 +02:00
|
|
|
log = logging.getLogger(__name__)
|
2016-06-07 10:30:22 +02:00
|
|
|
handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5)
|
2016-06-07 10:19:51 +02:00
|
|
|
log.addHandler(handler)
|
|
|
|
log.setLevel(logging.INFO)
|
2016-04-21 04:02:52 +02:00
|
|
|
|
|
|
|
|
|
|
|
class LBRYindex(resource.Resource):
|
|
|
|
def __init__(self, ui_dir):
|
|
|
|
resource.Resource.__init__(self)
|
|
|
|
self.ui_dir = ui_dir
|
|
|
|
|
|
|
|
isLeaf = False
|
|
|
|
|
|
|
|
def _delayed_render(self, request, results):
|
|
|
|
request.write(str(results))
|
|
|
|
request.finish()
|
|
|
|
|
|
|
|
def getChild(self, name, request):
|
|
|
|
if name == '':
|
|
|
|
return self
|
|
|
|
return resource.Resource.getChild(self, name, request)
|
|
|
|
|
|
|
|
def render_GET(self, request):
|
|
|
|
return static.File(os.path.join(self.ui_dir, "index.html")).render_GET(request)
|
|
|
|
|
|
|
|
|
2016-05-01 11:17:59 +02:00
|
|
|
class LBRYFileStreamer(object):
|
|
|
|
"""
|
|
|
|
Writes downloaded LBRY file to request as the download comes in, pausing and resuming as requested
|
|
|
|
used for Chrome
|
|
|
|
"""
|
2016-04-24 10:42:42 +02:00
|
|
|
|
2016-05-01 11:17:59 +02:00
|
|
|
implements(interfaces.IPushProducer)
|
2016-04-24 10:42:42 +02:00
|
|
|
|
2016-05-01 11:17:59 +02:00
|
|
|
def __init__(self, request, path, start, stop, size):
|
|
|
|
self._request = request
|
|
|
|
self._fileObject = file(path)
|
2016-05-02 10:10:50 +02:00
|
|
|
self._content_type = mimetypes.guess_type(path)[0]
|
|
|
|
self._stop_pos = size - 1 if stop == '' else int(stop) #chrome and firefox send range requests for "0-"
|
2016-05-01 11:17:59 +02:00
|
|
|
self._cursor = self._start_pos = int(start)
|
|
|
|
self._file_size = size
|
2016-05-02 21:58:40 +02:00
|
|
|
self._depth = 0
|
2016-04-24 23:51:24 +02:00
|
|
|
|
2016-05-02 10:10:50 +02:00
|
|
|
self._paused = self._sent_bytes = self._stopped = False
|
2016-05-02 21:58:40 +02:00
|
|
|
self._delay = 0.25
|
2016-05-02 10:10:50 +02:00
|
|
|
self._deferred = defer.succeed(None)
|
2016-04-24 10:42:42 +02:00
|
|
|
|
2016-05-01 11:17:59 +02:00
|
|
|
self._request.setResponseCode(206)
|
|
|
|
self._request.setHeader('accept-ranges', 'bytes')
|
2016-05-02 10:10:50 +02:00
|
|
|
self._request.setHeader('content-type', self._content_type)
|
2016-04-26 04:35:21 +02:00
|
|
|
|
2016-05-01 11:17:59 +02:00
|
|
|
self.resumeProducing()
|
2016-04-24 10:42:42 +02:00
|
|
|
|
2016-05-01 11:17:59 +02:00
|
|
|
def pauseProducing(self):
|
|
|
|
self._paused = True
|
|
|
|
log.info("[" + str(datetime.now()) + "] Pausing producer")
|
|
|
|
return defer.succeed(None)
|
2016-04-24 10:42:42 +02:00
|
|
|
|
|
|
|
def resumeProducing(self):
|
2016-05-01 11:17:59 +02:00
|
|
|
def _check_for_new_data():
|
2016-05-02 21:58:40 +02:00
|
|
|
self._depth += 1
|
2016-05-01 11:17:59 +02:00
|
|
|
self._fileObject.seek(self._start_pos, os.SEEK_END)
|
|
|
|
readable_bytes = self._fileObject.tell()
|
|
|
|
self._fileObject.seek(self._cursor)
|
|
|
|
|
|
|
|
self._sent_bytes = False
|
|
|
|
|
2016-05-02 10:10:50 +02:00
|
|
|
if (readable_bytes > self._cursor) and not (self._stopped or self._paused):
|
|
|
|
read_length = min(readable_bytes, self._stop_pos) - self._cursor + 1
|
|
|
|
self._request.setHeader('content-range', 'bytes %s-%s/%s' % (self._cursor, self._cursor + read_length - 1, self._file_size))
|
|
|
|
self._request.setHeader('content-length', str(read_length))
|
|
|
|
start_cur = self._cursor
|
|
|
|
for i in range(read_length):
|
|
|
|
if self._paused or self._stopped:
|
2016-05-01 11:17:59 +02:00
|
|
|
break
|
|
|
|
else:
|
|
|
|
data = self._fileObject.read(1)
|
|
|
|
self._request.write(data)
|
|
|
|
self._cursor += 1
|
2016-05-02 10:10:50 +02:00
|
|
|
|
2016-05-02 21:58:40 +02:00
|
|
|
log.info("[" + str(datetime.now()) + "] Wrote range %s-%s/%s, length: %s, readable: %s, depth: %s" %
|
|
|
|
(start_cur, self._cursor, self._file_size, self._cursor - start_cur, readable_bytes, self._depth))
|
2016-05-01 11:17:59 +02:00
|
|
|
self._sent_bytes = True
|
|
|
|
|
|
|
|
if self._cursor == self._stop_pos + 1:
|
|
|
|
self.stopProducing()
|
|
|
|
return defer.succeed(None)
|
2016-05-02 10:10:50 +02:00
|
|
|
elif self._paused or self._stopped:
|
2016-05-01 11:17:59 +02:00
|
|
|
return defer.succeed(None)
|
|
|
|
else:
|
2016-05-02 21:58:40 +02:00
|
|
|
self._deferred.addCallback(lambda _: threads.deferToThread(reactor.callLater, self._delay, _check_for_new_data))
|
2016-05-02 10:10:50 +02:00
|
|
|
return defer.succeed(None)
|
2016-05-01 11:17:59 +02:00
|
|
|
|
|
|
|
log.info("[" + str(datetime.now()) + "] Resuming producer")
|
|
|
|
self._paused = False
|
2016-05-02 10:10:50 +02:00
|
|
|
self._deferred.addCallback(lambda _: _check_for_new_data())
|
2016-04-24 10:42:42 +02:00
|
|
|
|
|
|
|
def stopProducing(self):
|
2016-05-02 10:10:50 +02:00
|
|
|
log.info("[" + str(datetime.now()) + "] Stopping producer")
|
|
|
|
self._stopped = True
|
2016-05-01 11:17:59 +02:00
|
|
|
# self._fileObject.close()
|
2016-05-02 10:10:50 +02:00
|
|
|
self._deferred.addErrback(lambda err: err.trap(defer.CancelledError))
|
|
|
|
self._deferred.addErrback(lambda err: err.trap(error.ConnectionDone))
|
|
|
|
self._deferred.cancel()
|
|
|
|
# self._request.finish()
|
2016-05-01 11:17:59 +02:00
|
|
|
self._request.unregisterProducer()
|
2016-05-02 21:58:40 +02:00
|
|
|
return defer.succeed(None)
|
2016-04-24 10:42:42 +02:00
|
|
|
|
|
|
|
|
|
|
|
class HostedLBRYFile(resource.Resource):
|
|
|
|
def __init__(self, api):
|
|
|
|
self._api = api
|
2016-05-02 10:10:50 +02:00
|
|
|
self._producer = None
|
2016-04-24 10:42:42 +02:00
|
|
|
resource.Resource.__init__(self)
|
|
|
|
|
2016-05-30 21:49:25 +02:00
|
|
|
# todo: fix LBRYFileStreamer and use it instead of static.File
|
|
|
|
# def makeProducer(self, request, stream):
|
|
|
|
# def _save_producer(producer):
|
|
|
|
# self._producer = producer
|
|
|
|
# return defer.succeed(None)
|
|
|
|
#
|
|
|
|
# range_header = request.getAllHeaders()['range'].replace('bytes=', '').split('-')
|
|
|
|
# start, stop = int(range_header[0]), range_header[1]
|
|
|
|
# log.info("[" + str(datetime.now()) + "] GET range %s-%s" % (start, stop))
|
|
|
|
# path = os.path.join(self._api.download_directory, stream.file_name)
|
|
|
|
#
|
|
|
|
# d = stream.get_total_bytes()
|
|
|
|
# d.addCallback(lambda size: _save_producer(LBRYFileStreamer(request, path, start, stop, size)))
|
|
|
|
# d.addCallback(lambda _: request.registerProducer(self._producer, streaming=True))
|
|
|
|
# # request.notifyFinish().addCallback(lambda _: self._producer.stopProducing())
|
|
|
|
# request.notifyFinish().addErrback(self._responseFailed, d)
|
|
|
|
# return d
|
2016-04-24 10:42:42 +02:00
|
|
|
|
|
|
|
def render_GET(self, request):
|
|
|
|
if 'name' in request.args.keys():
|
2016-04-24 23:51:24 +02:00
|
|
|
if request.args['name'][0] != 'lbry' and request.args['name'][0] not in self._api.waiting_on.keys():
|
|
|
|
d = self._api._download_name(request.args['name'][0])
|
2016-05-02 21:58:40 +02:00
|
|
|
# d.addCallback(lambda stream: self.makeProducer(request, stream))
|
|
|
|
d.addCallback(lambda stream: static.File(os.path.join(self._api.download_directory,
|
|
|
|
stream.file_name)).render_GET(request))
|
2016-05-02 10:10:50 +02:00
|
|
|
|
2016-04-24 23:51:24 +02:00
|
|
|
elif request.args['name'][0] in self._api.waiting_on.keys():
|
|
|
|
request.redirect(UI_ADDRESS + "/?watch=" + request.args['name'][0])
|
|
|
|
request.finish()
|
2016-04-24 10:42:42 +02:00
|
|
|
else:
|
|
|
|
request.redirect(UI_ADDRESS)
|
|
|
|
request.finish()
|
|
|
|
return server.NOT_DONE_YET
|
2016-04-23 00:18:17 +02:00
|
|
|
|
2016-05-30 21:49:25 +02:00
|
|
|
# def _responseFailed(self, err, call):
|
|
|
|
# call.addErrback(lambda err: err.trap(error.ConnectionDone))
|
|
|
|
# call.addErrback(lambda err: err.trap(defer.CancelledError))
|
|
|
|
# call.addErrback(lambda err: log.info("Error: " + str(err)))
|
|
|
|
# call.cancel()
|
2016-04-21 04:02:52 +02:00
|
|
|
|
|
|
|
|
|
|
|
class LBRYDaemonServer(object):
|
2016-05-30 21:49:25 +02:00
|
|
|
def _setup_server(self, wallet):
|
|
|
|
self.root = LBRYindex(os.path.join(os.path.join(data_dir, "lbry-ui"), "active"))
|
2016-07-04 11:43:26 +02:00
|
|
|
self._api = LBRYDaemon(self.root, wallet_type="lbrycrd")
|
2016-04-24 10:42:42 +02:00
|
|
|
self.root.putChild("view", HostedLBRYFile(self._api))
|
2016-04-21 04:02:52 +02:00
|
|
|
self.root.putChild(API_ADDRESS, self._api)
|
|
|
|
return defer.succeed(True)
|
|
|
|
|
2016-06-02 02:52:15 +02:00
|
|
|
def start(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False, wallet=None):
|
|
|
|
d = self._setup_server(wallet)
|
2016-05-30 21:49:25 +02:00
|
|
|
d.addCallback(lambda _: self._api.setup(branch, user_specified, branch_specified))
|
2016-04-21 04:02:52 +02:00
|
|
|
return d
|