10
.bumpversion.cfg
Normal file
|
@ -0,0 +1,10 @@
|
|||
[bumpversion]
|
||||
current_version = 0.3.12
|
||||
commit = True
|
||||
tag = True
|
||||
message = Bump version: {current_version} -> {new_version}
|
||||
|
||||
[bumpversion:file:lbrynet/__init__.py]
|
||||
|
||||
[bumpversion:file:packaging/ubuntu/lbry.desktop]
|
||||
|
11
.gitignore
vendored
|
@ -17,3 +17,14 @@ lbrynet.egg-info/PKG-INFO
|
|||
/build
|
||||
|
||||
/dist
|
||||
|
||||
*.so
|
||||
|
||||
*.pem
|
||||
|
||||
*.decTest
|
||||
|
||||
.coverage
|
||||
|
||||
# temporary files from the twisted.trial test runner
|
||||
_trial_temp/
|
||||
|
|
0
.gitmodules
vendored
Normal file
379
.pylintrc
Normal file
|
@ -0,0 +1,379 @@
|
|||
[MASTER]
|
||||
|
||||
# Specify a configuration file.
|
||||
#rcfile=
|
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as
|
||||
# pygtk.require().
|
||||
#init-hook=
|
||||
|
||||
# Add files or directories to the blacklist. They should be base names, not
|
||||
# paths.
|
||||
ignore=CVS
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=yes
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
# Use multiple processes to speed up Pylint.
|
||||
jobs=1
|
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||
# active Python interpreter and may run arbitrary code.
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
# run arbitrary code
|
||||
extension-pkg-whitelist=
|
||||
|
||||
# Allow optimization of some AST trees. This will activate a peephole AST
|
||||
# optimizer, which will apply various small optimizations. For instance, it can
|
||||
# be used to obtain the result of joining multiple strings with the addition
|
||||
# operator. Joining a lot of strings can lead to a maximum recursion error in
|
||||
# Pylint and this flag can prevent that. It has one side effect, the resulting
|
||||
# AST will be different than the one from reality.
|
||||
optimize-ast=no
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
|
||||
confidence=
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time (only on the command line, not in the configuration file where
|
||||
# it should appear only once). See also the "--disable" option for examples.
|
||||
#enable=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once).You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||
# (visual studio) and html. You can also give a reporter class, eg
|
||||
# mypackage.mymodule.MyReporterClass.
|
||||
output-format=text
|
||||
|
||||
# Put messages in a separate file for each module / package specified on the
|
||||
# command line instead of printing them on stdout. Reports (if any) will be
|
||||
# written in a file name "pylint_global.[txt|html]".
|
||||
files-output=no
|
||||
|
||||
# Tells whether to display a full report or only the messages
|
||||
reports=yes
|
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highest
|
||||
# note). You have access to the variables errors warning, statement which
|
||||
# respectively contain the number of errors / warnings messages and the total
|
||||
# number of statements analyzed. This is used by the global evaluation report
|
||||
# (RP0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Template used to display messages. This is a python new-style format string
|
||||
# used to format the message information. See doc for all details
|
||||
#msg-template=
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expectedly
|
||||
# not used).
|
||||
dummy-variables-rgx=_$|dummy
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
# List of strings which can identify a callback function by name. A callback
|
||||
# name must start or end with one of those strings.
|
||||
callbacks=cb_,_cb
|
||||
|
||||
|
||||
[LOGGING]
|
||||
|
||||
# Logging modules to check that the string format arguments are in logging
|
||||
# function parameter format
|
||||
logging-modules=logging
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# List of builtins function names that should not be used, separated by a comma
|
||||
bad-functions=map,filter,input
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names=i,j,k,ex,Run,_
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma
|
||||
bad-names=foo,bar,baz,toto,tutu,tata
|
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when
|
||||
# the name regexes allow several styles.
|
||||
name-group=
|
||||
|
||||
# Include a hint for the correct naming format with invalid-name
|
||||
include-naming-hint=no
|
||||
|
||||
# Regular expression matching correct function names
|
||||
function-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for function names
|
||||
function-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct variable names
|
||||
variable-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for variable names
|
||||
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct constant names
|
||||
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||
|
||||
# Naming hint for constant names
|
||||
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
|
||||
|
||||
# Regular expression matching correct attribute names
|
||||
attr-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for attribute names
|
||||
attr-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct argument names
|
||||
argument-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for argument names
|
||||
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression matching correct class attribute names
|
||||
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||
|
||||
# Naming hint for class attribute names
|
||||
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||
|
||||
# Regular expression matching correct inline iteration names
|
||||
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
|
||||
|
||||
# Naming hint for inline iteration names
|
||||
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct class names
|
||||
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Naming hint for class names
|
||||
class-name-hint=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Regular expression matching correct module names
|
||||
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||
|
||||
# Naming hint for module names
|
||||
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||
|
||||
# Regular expression matching correct method names
|
||||
method-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Naming hint for method names
|
||||
method-name-hint=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=^_
|
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter
|
||||
# ones are exempt.
|
||||
docstring-min-length=-1
|
||||
|
||||
|
||||
[ELIF]
|
||||
|
||||
# Maximum number of nested blocks for function / method body
|
||||
max-nested-blocks=5
|
||||
|
||||
|
||||
[SPELLING]
|
||||
|
||||
# Spelling dictionary name. Available dictionaries: none. To make it working
|
||||
# install python-enchant package.
|
||||
spelling-dict=
|
||||
|
||||
# List of comma separated words that should not be checked.
|
||||
spelling-ignore-words=
|
||||
|
||||
# A path to a file that contains private dictionary; one word per line.
|
||||
spelling-private-dict-file=
|
||||
|
||||
# Tells whether to store unknown words to indicated private dictionary in
|
||||
# --spelling-private-dict-file option instead of raising a message.
|
||||
spelling-store-unknown-words=no
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=100
|
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit.
|
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
|
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no
|
||||
# else.
|
||||
single-line-if-stmt=no
|
||||
|
||||
# List of optional constructs for which whitespace checking is disabled. `dict-
|
||||
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
|
||||
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
|
||||
# `empty-line` allows space-only lines.
|
||||
no-space-check=trailing-comma,dict-separator
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=1000
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
indent-string=' '
|
||||
|
||||
# Number of spaces of indent required inside a hanging or continued line.
|
||||
indent-after-paren=4
|
||||
|
||||
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
||||
expected-line-ending-format=
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=FIXME,XXX,TODO
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
# Ignore comments when computing similarities.
|
||||
ignore-comments=yes
|
||||
|
||||
# Ignore docstrings when computing similarities.
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Ignore imports when computing similarities.
|
||||
ignore-imports=no
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# List of module names for which member attributes should not be checked
|
||||
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||
# and thus existing member attributes cannot be deduced by static analysis. It
|
||||
# supports qualified module names, as well as Unix pattern matching.
|
||||
ignored-modules=twisted.internet.reactor,leveldb
|
||||
|
||||
# List of classes names for which member attributes should not be checked
|
||||
# (useful for classes with attributes dynamically set). This supports can work
|
||||
# with qualified names.
|
||||
ignored-classes=twisted.internet,RequestMessage
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
||||
# expressions are accepted.
|
||||
generated-members=lbrynet.lbrynet_daemon.LBRYDaemon.Parameters
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma
|
||||
deprecated-modules=regsub,TERMIOS,Bastion,rexec
|
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||
# given file (report RP0402 must not be disabled)
|
||||
import-graph=
|
||||
|
||||
# Create a graph of external dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
ext-import-graph=
|
||||
|
||||
# Create a graph of internal dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
int-import-graph=
|
||||
|
||||
|
||||
[DESIGN]
|
||||
|
||||
# Maximum number of arguments for function / method
|
||||
max-args=5
|
||||
|
||||
# Argument names that match this expression will be ignored. Default to name
|
||||
# with leading underscore
|
||||
ignored-argument-names=_.*
|
||||
|
||||
# Maximum number of locals for function / method body
|
||||
max-locals=15
|
||||
|
||||
# Maximum number of return / yield for function / method body
|
||||
max-returns=6
|
||||
|
||||
# Maximum number of branch for function / method body
|
||||
max-branches=12
|
||||
|
||||
# Maximum number of statements in function / method body
|
||||
max-statements=50
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=7
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=7
|
||||
|
||||
# Minimum number of public methods for a class (see R0903).
|
||||
min-public-methods=2
|
||||
|
||||
# Maximum number of public methods for a class (see R0904).
|
||||
max-public-methods=20
|
||||
|
||||
# Maximum number of boolean expressions in a if statement
|
||||
max-bool-expr=5
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,__new__,setUp
|
||||
|
||||
# List of valid names for the first argument in a class method.
|
||||
valid-classmethod-first-arg=cls
|
||||
|
||||
# List of valid names for the first argument in a metaclass class method.
|
||||
valid-metaclass-classmethod-first-arg=mcs
|
||||
|
||||
# List of member names, which should be excluded from the protected access
|
||||
# warning.
|
||||
exclude-protected=_asdict,_fields,_replace,_source,_make
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to
|
||||
# "Exception"
|
||||
overgeneral-exceptions=Exception
|
71
.travis.yml
Normal file
|
@ -0,0 +1,71 @@
|
|||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
sudo: required
|
||||
dist: trusty
|
||||
# dh-virtualenv requires that we use the same python interpreter
|
||||
# that comes with the system, so we don't want to use anything that
|
||||
# travis would try to set-up for us in python
|
||||
language: generic
|
||||
- os: osx
|
||||
# Use generic language for osx
|
||||
# python 2.7 is broken on osx on travis, so follow we have to specify the installation ourselves
|
||||
# https://github.com/travis-ci/travis-ci/issues/2312#issuecomment-195620855
|
||||
language: generic
|
||||
osx_image: xcode7.3
|
||||
|
||||
notifications:
|
||||
slack:
|
||||
secure: "Am13HPtpgCMljh0MDVuoFHvQXB8yhf4Kvf/qAeSp5N0vsHGL70CSF9Ahccw8dVPE6mbuak1OGtSUb6/UaErLHkpz3ztaRLkDa9x7CmBB3Kynnh8oO2VbB7b/2ROULqkhF4VZmAnNfwrQrbC3gs8Sybp261Nyc7y4ww15xDYBrk2fyq4ds2DCaJdRxfJUJFonrZ6KXr3fVaXosO6cjuyS8eRodcmrqsT4cCtinjNTD1hGWoH107E4ObSmpVelxQO193KhNJMRiLlEcVkvYUOqIWBtwdGHbNE/6Yeuq1TXgKJ0KeJWAmW3wTfUYNngGXNAsyCnrhul5TKNevNzfIAQZHvRsczYiWPJV6LtohHT0CcUiCXJtvEPOyahEBfwK3etY/xxFqny7N9OEmpdW2sgsEPNPX2LJynJti2rQA9SuAD1ogR3ZpDy/NXoaAZf8PTdPcuNUMULV9PGG7tVrLBecO/W1qO6hdFxwlLdgqGLxAENZgGp++v/DhPk/WvtmHj7iTbRq0nxaTWyX5uKOn2ADH+k/yfutjv6BsQU9xNyPeZEEtuEpc6X6waiYn/8G9vl9PecvKC5H0MgsZ6asAxmg7mZ3VSMFG7mo8ENeOhSZ0Oz6ZTBILL3wFccZA9uJIq7NWmqC9dRiGiuKXBB62No7sINoHg3114e2xYa9qvNmGg="
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
- $HOME/Library/Caches/pip
|
||||
- $TRAVIS_BUILD_DIR/cache/wheel
|
||||
|
||||
before_install:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./packaging/travis/setup_osx.sh; fi
|
||||
|
||||
install:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./packaging/travis/install_dependencies_and_run_tests.sh; fi
|
||||
|
||||
before_script:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in packaging/osx/certs/dist.cer.enc -d -a -out packaging/osx/certs/dist.cer; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in packaging/osx/certs/dist.p12.enc -d -a -out packaging/osx/certs/dist.p12; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./packaging/osx/add-key.sh; fi
|
||||
|
||||
script:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash packaging/ubuntu/ubuntu_package_setup.sh -t; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app && ./setup_app.sh && cd $TRAVIS_BUILD_DIR; fi
|
||||
# fail the build if this is a build for a tag and we don't have the versions matching; allow tags that start with 'test' to pass
|
||||
- if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "${TRAVIS_TAG}" == test* ]] || [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi
|
||||
|
||||
deploy:
|
||||
- provider: releases
|
||||
file: "${TRAVIS_BUILD_DIR}/`python setup.py --name`_`python setup.py -V`_amd64.deb"
|
||||
skip_cleanup: true
|
||||
prerelease: true
|
||||
on:
|
||||
tags: true
|
||||
condition: "$TRAVIS_OS_NAME = linux"
|
||||
# this is the oauth token for the lbry-ci user
|
||||
api_key:
|
||||
secure: nKdWGROnLNodx9k9nWvq2wezkPvSVL8zF63qjPXjhdOe9kCUbcLp7WnFDYpn9EJj4Pofq/ejeCHwjA+5x7JUP3Szk7SlJV61B4c/5hl64rl7oSKOoKskjdE2jaOG3CJuOUrh0yQ59U3vMMABcsnw/wJaCIuu/erdPIm8g8R+stu1YHOGtl5Y9WiW+zLJn2vc3GooV1TWtki9EnrmFfw0Vrqc4RMVMFB1ojE7ggrK1LIwcmGSbLIYzker1ZRz8SCy+84sGk4//V+2i2NNiz5AkPuG7BBGrU2twE9nD23IlruJAdVdi71P3ytAmi0kKyvxIU4VeNaqyTk9zeL5IB9J5IIgvekHgKcsKhFUZ6QcXT1Xfxl4ELftvWCTHWiewnXFdqLcG9GZiUaE6+7wdalwDAP3tqS2emiibetlBZERHR+RMR00ej+1MBYWGMlTse/0Tglndv0a2qqgAJYLKPRT02hTRYGxZ1MrJe+WGnChRmzwgLVTIgZuiDciFOahN0TYGSORk6OpnZBsxvpzSqDw5UDJx0BmbJ1xMNDFbOs8ubZ9yIpB9yNMGw66FPacOF61XNYnmA68ILC28UtOFKuuHLrUPbM5JmQkDVhtTfFbBnyHefyCLAL4MHvJJKGi1oaOXjYaJ/J095h636/kQ0cHHuVMgoWUQZOQ44xRAz7tMuc=
|
||||
- provider: releases
|
||||
file: "${TRAVIS_BUILD_DIR}/packaging/osx/lbry-osx-app/`python setup.py --name`.`python setup.py -V`.dmg"
|
||||
skip_cleanup: true
|
||||
prerelease: true
|
||||
on:
|
||||
tags: true
|
||||
condition: "$TRAVIS_OS_NAME = osx"
|
||||
# this is the oauth token for the lbry-ci user
|
||||
api_key:
|
||||
secure: nKdWGROnLNodx9k9nWvq2wezkPvSVL8zF63qjPXjhdOe9kCUbcLp7WnFDYpn9EJj4Pofq/ejeCHwjA+5x7JUP3Szk7SlJV61B4c/5hl64rl7oSKOoKskjdE2jaOG3CJuOUrh0yQ59U3vMMABcsnw/wJaCIuu/erdPIm8g8R+stu1YHOGtl5Y9WiW+zLJn2vc3GooV1TWtki9EnrmFfw0Vrqc4RMVMFB1ojE7ggrK1LIwcmGSbLIYzker1ZRz8SCy+84sGk4//V+2i2NNiz5AkPuG7BBGrU2twE9nD23IlruJAdVdi71P3ytAmi0kKyvxIU4VeNaqyTk9zeL5IB9J5IIgvekHgKcsKhFUZ6QcXT1Xfxl4ELftvWCTHWiewnXFdqLcG9GZiUaE6+7wdalwDAP3tqS2emiibetlBZERHR+RMR00ej+1MBYWGMlTse/0Tglndv0a2qqgAJYLKPRT02hTRYGxZ1MrJe+WGnChRmzwgLVTIgZuiDciFOahN0TYGSORk6OpnZBsxvpzSqDw5UDJx0BmbJ1xMNDFbOs8ubZ9yIpB9yNMGw66FPacOF61XNYnmA68ILC28UtOFKuuHLrUPbM5JmQkDVhtTfFbBnyHefyCLAL4MHvJJKGi1oaOXjYaJ/J095h636/kQ0cHHuVMgoWUQZOQ44xRAz7tMuc=
|
||||
|
||||
env:
|
||||
global:
|
||||
# needed to unlock the identity file when its on the keychain
|
||||
- secure: "aqgVNHfeh6JUqTTWG5+W+tTsIJkePS6HyLkcZlq6ODYrfdGKa90EeV4q4wdGfP5IkrBbf+WBFACDGVp574E7vfMLMNKUDuUtgnGVPwqOIhjG82ud8Xa7qF7lsw50QOnRYYVd2GLlCIzk8sffT5ncjSPN2ClNVM5iTwCkC8TNSAVnEJxu6bG6hcaT4uCWWjs0m+39O1xJwxJ4vTjpE/gy+j2FTSaUR5cNavOVCyJqpeKlga9aoBVsQHWvxlYurdWbwRLwVIV7bDE+sYPnwn1nMFQpx5RZ1AX1Z2UxNFKcYzLJgcWe85OjxLyT4udX+XZ9SLsdOjm1n201OLEKsmTxmHS8yqpbu6+pKQ1rLMEVpgPGfS8DdtqZyb3z0u6q4jztpm+uBe8hnFgwQFGXO63nOQsI0n1PMR3evAnVx7jYt7y/UALs2A9yjosMwDqgql1FMhyd0OJGo8Ky8YpDsr2J5zGVvIqt2/N+lP+SOe2D1J/5EhGlY0o4tqIAFskh3q8/GR6UGm70KT2l4LJqrU1WGrGxPJ+HoOEmvG6eqLesk03fAX3v5+DgXZWErnzXMIOGDPaFVpmx+G9VZTIlmf+3Wbu9TnZE5PRwFTdP1rqjGpjUhHeF0VTc2qgNq1OfL98CBO1wLgA195+em58cELalnwDMWUTmY1Jt1kUuAtCc11U="
|
||||
# used to decypt the aes decrypt the certificate and identity files
|
||||
- secure: "FzftiEBFMngQIci5KZ+tpKs/BubalhWXJ9f8yogGNVa3XplLheUXYgcsNU8sYT2MJaKAHN9fSjHWb67UtKT7yXddXxeOPnW6wJ3ua/FXnpynsmeGKTfh3stvgjpzdXous67uHmCWMMklfb6z7UuDohjUMAe5n5HKw5tq1RzTKpc2kJacOC6qUT5laUOvULyCaO9E9HmbHeR5ZeXAC6pnzX2ccsSrcXvPozHzBIZ9RyothKs+CZw8PEuJo07RRL8meboegqYOUrOYuz6A2gS2mZJoy59ivZKOFxS5shEuv2Jt80RyfyxBoUpKFq8OG3Am5nAEzDiTIGzmIoKDEGKeTagk/sEtSZXiMDkzDT4GX/j1rUNLCBU87bXEFS2zfRsrfg8c1XZPIzDYBT1PY2QtLBdddF5zzDoKdPLJ3sjN+fZFE5RlnwfwnMHriVRZZlzjcdk0Z06gKTBCxUg5BZamnOOK+K8qunMJXVS+Vmi5u4RoTZZiCosUlYKnSKJ8suO9C0+znxoViusPqP4ONprNHgDoZw+UKio84QW3PrZv9h4zH/D+msDgJRZ0ceqDD+6Wz1J8Mm5ptW1GOLh/IU12TPXjxteqh0Um2vv5eIPmjK9uEK666kK7PqtPDkYhAfWvF+nmOOyPMJfbP4MW/i9WHNF4ghsIMbPKfqNhgSmfrYw="
|
85
FAQ.md
Normal file
|
@ -0,0 +1,85 @@
|
|||
#### Getting LBRY for development
|
||||
|
||||
Q: How do I get lbry for command line?
|
||||
|
||||
A: In order to run lbry from command line, you need more than the packaged app/deb.
|
||||
|
||||
######On OS X
|
||||
|
||||
You can install LBRY command line by running `curl -sL https://rawgit.com/lbryio/lbry-setup/master/lbry_setup_osx.sh | sudo bash` in a terminal. This script will install lbrynet and its dependancies, as well as the app.
|
||||
|
||||
######On Linux
|
||||
|
||||
On Ubuntu or Mint you can install the prerequisites and lbrynet by running
|
||||
|
||||
sudo apt-get install libgmp3-dev build-essential python2.7 python2.7-dev python-pip git
|
||||
git clone https://github.com/lbryio/lbry.git
|
||||
cd lbry
|
||||
sudo python setup.py install
|
||||
|
||||
#### Using LBRY
|
||||
|
||||
Q: How do I run lbry from command line?
|
||||
|
||||
A: The command is `lbrynet-daemon`
|
||||
|
||||
***********
|
||||
|
||||
Q: How do I stop lbry from the command line?
|
||||
|
||||
A: You can ctrl-c or run `stop-lbrynet-daemon`
|
||||
|
||||
***********
|
||||
|
||||
Q: How do I run lbry with lbrycrdd (the blockchain node application)?
|
||||
|
||||
A: Start lbry with the --wallet flag set: `lbrynet-daemon --wallet=lbrycrd`
|
||||
|
||||
Note: when you change the wallet it is persistant until you specify you want to use another wallet - lbryum - with the --wallet flag again.
|
||||
|
||||
***********
|
||||
|
||||
Q: Where are all the behind the scenes files?
|
||||
|
||||
A: On linux, the relevant directories are `~/.lbrynet`, `~/.lbrycrd`, and `~/.lbryum`, depending on which wallets you've used. On OS X, the folders of interest are `~/Library/Application Support/LBRY`, `~/.lbrycrd` and `~/.lbryum`, also depending on which wallets you've used.
|
||||
|
||||
***********
|
||||
|
||||
Q: How can I see the log in the console?
|
||||
|
||||
A: Run lbry with the --log-to-console flag set: `lbrynet-daemon --log-to-console`
|
||||
|
||||
***********
|
||||
|
||||
Q: How do I specify a web-UI to use?
|
||||
|
||||
A: If the files for the UI you'd like to use are storred locally on your computer, start lbry with the --ui flag: `lbrynet-daemon --ui=/full/path/to/ui/files/root/folder`
|
||||
|
||||
Note, once set with the UI flag the given UI will be cached by lbry and used as the default going forward. Also, it will only successfully load a UI if it contains a conforming requirements.txt file to specify required lbrynet and lbryum versions. [Here](https://github.com/lbryio/lbry-web-ui/blob/master/dist/requirements.txt) is an example requirements.txt file.
|
||||
|
||||
To reset your ui to pull from lbryio, or to try a UI still in development, run lbry with the --branch flag: `lbrynet=daemon --branch=master`
|
||||
|
||||
***********
|
||||
|
||||
Q: How do I see the list of API functions I can call, and how do I call them?
|
||||
|
||||
A: Here is an example script to get the documentation for the various API calls. To use any of the functions displayed, just provide any specified arguments in a dictionary.
|
||||
|
||||
Note: the lbry api can only be used while either the app or lbrynet-daemon command line are running
|
||||
|
||||
import sys
|
||||
from jsonrpc.proxy import JSONRPCProxy
|
||||
|
||||
try:
|
||||
from lbrynet.conf import API_CONNECTION_STRING
|
||||
except:
|
||||
print "You don't have lbrynet installed!"
|
||||
sys.exit(0)
|
||||
|
||||
api = JSONRPCProxy.from_url(API_CONNECTION_STRING)
|
||||
if not api.is_running():
|
||||
print api.daemon_status()
|
||||
else:
|
||||
for func in api.help():
|
||||
print "%s:\n%s" % (func, api.help({'function': func}))
|
||||
|
60
INSTALL.md
|
@ -1,43 +1,35 @@
|
|||
Prerequisites
|
||||
-------------
|
||||
|
||||
To use the LBRYWallet, which enables spending and accepting LBRYcrds in exchange for data, the
|
||||
LBRYcrd application (insert link to LBRYcrd website here) must be installed and running. If
|
||||
this is not desired, the testing client can be used to simulate trading points, which is
|
||||
built into LBRYnet.
|
||||
|
||||
on Ubuntu:
|
||||
|
||||
```
|
||||
sudo apt-get install libgmp3-dev build-essential python-dev python-pip
|
||||
```
|
||||
|
||||
Getting the source
|
||||
------------------
|
||||
|
||||
Don't you already have it?
|
||||
|
||||
Setting up the environment
|
||||
#### Installing the LBRY app
|
||||
--------------------------
|
||||
|
||||
It's recommended that you use a virtualenv
|
||||
Installing LBRY is simple. You can get a dmg installer for OS X (Mavericks and up) or a .deb for linux [here](https://lbry.io/get).
|
||||
|
||||
##### OS X
|
||||
Just drag and drop LBRY.app into your applications folder (replacing any older versions). When it's running you'll have a LBRY icon in your status bar.
|
||||
|
||||
##### Linux
|
||||
Double click the .deb file and follow the prompts. The app can be started by searching "LBRY", and it can be turned off by clicking the red 'x' in the browser interface.
|
||||
|
||||
On both systems you can also open the UI while the app is running by going to lbry://lbry in Firefox or Safari, or localhost:5279 in Chrome.
|
||||
|
||||
|
||||
|
||||
#### Installing LBRY command line
|
||||
--------------------------
|
||||
|
||||
##### OS X
|
||||
You can install LBRY command line by running `curl -sL https://rawgit.com/lbryio/lbry-setup/master/lbry_setup_osx.sh | sudo bash` in a terminal. This script will install lbrynet and its dependancies, as well as the app. You can start LBRY by either starting the app or by running `lbrynet-daemon` from a terminal.
|
||||
|
||||
##### Linux
|
||||
On Ubuntu or Mint you can install the prerequisites and lbrynet by running
|
||||
|
||||
```
|
||||
sudo apt-get install python-virtualenv
|
||||
cd <source base directory>
|
||||
virtualenv .
|
||||
source bin/activate
|
||||
sudo apt-get install libgmp3-dev build-essential python2.7 python2.7-dev python-pip git
|
||||
git clone https://github.com/lbryio/lbry.git
|
||||
cd lbry
|
||||
sudo python setup.py install
|
||||
```
|
||||
|
||||
(to deactivate the virtualenv, enter 'deactivate')
|
||||
|
||||
```
|
||||
python setup.py install
|
||||
```
|
||||
|
||||
this will install all of the libraries and a few applications
|
||||
|
||||
For running the file sharing application, see [RUNNING](RUNNING.md)
|
||||
To start LBRY, run `lbrynet-daemon` in a terminal.
|
||||
|
||||
#### On windows:
|
||||
|
||||
|
|
2
MANIFEST.in
Normal file
|
@ -0,0 +1,2 @@
|
|||
include lbrynet/lbrynet_console/plugins/blindrepeater.yapsy-plugin
|
||||
recursive-include lbrynet/lbrynet_gui *.gif *.ico *.xbm *.conf
|
48
README.md
|
@ -1,9 +1,15 @@
|
|||
[![Build Status](https://travis-ci.org/lbryio/lbry.svg?branch=master)](https://travis-ci.org/lbryio/lbry)
|
||||
|
||||
# LBRYnet
|
||||
|
||||
LBRYnet is a fully decentralized network for distributing data. It consists of peers uploading
|
||||
and downloading data from other peers, possibly in exchange for payments, and a distributed hash
|
||||
table, used by peers to discover other peers.
|
||||
|
||||
## Installation
|
||||
|
||||
Download the [latest release](https://github.com/lbryio/lbry/releases/latest) or see [INSTALL.md](INSTALL.md) for manual installation.
|
||||
|
||||
## Overview
|
||||
|
||||
On LBRYnet, data is broken into chunks, and each chunk is specified by its sha384 hash sum. This
|
||||
|
@ -18,32 +24,48 @@ help peers find each other. For example, an application for which clients don't
|
|||
necessary chunks may use some identifier, chosen by the application, to find clients which do
|
||||
know all of the necessary chunks.
|
||||
|
||||
## Running
|
||||
## For Developers
|
||||
|
||||
LBRYnet comes with an file sharing application, called 'lbrynet-console', which breaks
|
||||
The bundled LBRY application uses the lbrynet JSONRPC api found in `lbrynet.lbrynet_daemon.LBRYDaemon`. This api allows for applications and web services like the lbry browser UI to interact with lbrynet. If you've installed lbrynet, you can run `lbrynet-daemon` without running the app. While the app or `lbrynet-daemon` is running, you can use the following to show the help for all the available commands:
|
||||
|
||||
```
|
||||
from jsonrpc.proxy import JSONRPCProxy
|
||||
|
||||
try:
|
||||
from lbrynet.conf import API_CONNECTION_STRING
|
||||
except:
|
||||
print "You don't have lbrynet installed!"
|
||||
API_CONNECTION_STRING = "http://localhost:5279/lbryapi"
|
||||
|
||||
api = JSONRPCProxy.from_url(API_CONNECTION_STRING)
|
||||
if not api.is_running():
|
||||
print api.daemon_status()
|
||||
else:
|
||||
for func in api.help():
|
||||
print "%s:\n%s" % (func, api.help({'function': func}))
|
||||
```
|
||||
|
||||
If you've installed lbrynet, it comes with a file sharing application, called `lbrynet-daemon`, which breaks
|
||||
files into chunks, encrypts them with a symmetric key, computes their sha384 hash sum, generates
|
||||
a special file called a 'stream descriptor' containing the hash sums and some other file metadata,
|
||||
and makes the chunks available for download by other peers. A peer wishing to download the file
|
||||
must first obtain the 'stream descriptor' and then may open it with his 'lbrynet-console' client,
|
||||
must first obtain the 'stream descriptor' and then may open it with his `lbrynet-daemon` client,
|
||||
download all of the chunks by locating peers with the chunks via the DHT, and then combine the
|
||||
chunks into the original file, according to the metadata included in the 'stream descriptor'.
|
||||
|
||||
To install and use this client, see [INSTALL](INSTALL.md) and [RUNNING](RUNNING.md)
|
||||
For detailed instructions, see [INSTALL.md](INSTALL.md) and [RUNNING.md](RUNNING.md).
|
||||
|
||||
## Installation
|
||||
Documentation: doc.lbry.io (may be out of date)
|
||||
|
||||
See [INSTALL](INSTALL.md)
|
||||
Source code: https://github.com/lbryio/lbry
|
||||
|
||||
## Developers
|
||||
|
||||
Documentation: doc.lbry.io
|
||||
Source code: trac.lbry.io/browser
|
||||
|
||||
To contribute to the development of LBRYnet or lbrynet-console, contact jimmy@lbry.io
|
||||
To contribute, [join us on Slack](https://lbry-slackin.herokuapp.com/) or contact josh@lbry.io. Pull requests are also welcome.
|
||||
|
||||
## Support
|
||||
|
||||
Send all support requests to jimmy@lbry.io
|
||||
Please open an issue and describe your situation in detail. We will respond as soon as we can.
|
||||
|
||||
For private issues, contact josh@lbry.io.
|
||||
|
||||
## License
|
||||
|
||||
|
|
79
RUNNING.md
|
@ -9,25 +9,43 @@ Download the file https://raw.githubusercontent.com/lbryio/lbry-setup/master/lbr
|
|||
Once it's done building, type:
|
||||
|
||||
```
|
||||
./lbrycrd/src/lbrycrdd -server -daemon
|
||||
lbrynet-gui
|
||||
lbrynet-console
|
||||
```
|
||||
|
||||
A window should show up with an entry box
|
||||
A console application will load, and after a moment you will be presented with a ">" signifying
|
||||
that the console is ready for commands.
|
||||
|
||||
Type wonderfullife into the box, hit go, and choose to stream or save
|
||||
If it's your first time running lbrynet-console, you should be given some credits to test the
|
||||
network. If they don't show up within a minute or two, let us know, and we'll send you some.
|
||||
|
||||
To stop lbrycrdd: `./lbrycrd/src/lbrycrd-cli stop`
|
||||
After your credits have shown up, type
|
||||
|
||||
```
|
||||
get wonderfullife
|
||||
```
|
||||
|
||||
into the prompt and hit enter.
|
||||
|
||||
You will be asked if you want to cancel, change options, save, and perhaps stream the file.
|
||||
|
||||
Enter 's' for save and then hit enter.
|
||||
|
||||
The file should start downloading. Enter the command 'status' to check on the status of files
|
||||
that are in the process of being downloaded.
|
||||
|
||||
To stop lbrynet-console, enter the command 'exit'.
|
||||
|
||||
|
||||
## Slightly longer install guide
|
||||
|
||||
### Installing lbrycrd from source
|
||||
### Installing lbrycrd, the full blockchain client
|
||||
|
||||
Note: this process takes upwards of an hour and is not necessary to use lbrynet.
|
||||
|
||||
```
|
||||
git clone --depth=1 https://github.com/lbryio/lbrycrd.git
|
||||
sudo apt-get install build-essential libtool autotools-dev autoconf pkg-config libssl-dev libboost-all-dev libdb-dev libdb++-dev libqt4-dev libprotobuf-dev protobuf-compiler git
|
||||
git clone --depth=1 -b alpha https://github.com/lbryio/lbrycrd.git
|
||||
cd lbrycrd
|
||||
sudo apt-get install build-essential libtool autotools-dev autoconf pkg-config libssl-dev libboost-all-dev libdb-dev libdb++-dev libqt4-dev libprotobuf-dev protobuf-compiler
|
||||
./autogen.sh
|
||||
./configure --with-incompatible-bdb --without-gui
|
||||
|
||||
|
@ -89,11 +107,21 @@ sudo python setup.py install
|
|||
|
||||
## Slightly longer running guide
|
||||
|
||||
###In order to use lbrynet-console or lbrynet-gui, lbyrcrd must be running.
|
||||
### lbrynet-console can be set to use lbrycrdd instead of the built in lightweight client.
|
||||
|
||||
### Running lbrycrd
|
||||
To run lbrynet-console with lbrycrdd:
|
||||
|
||||
If you ran the easy install script, the lbrycrd folder will be in the directory you ran lbry_setup.sh from. Otherwise it is the root of the cloned lbrycrd repository. Go to that directory.
|
||||
```
|
||||
lbrynet-console </path/to/lbrycrdd>
|
||||
```
|
||||
|
||||
If lbrycrdd is not already running, lbrynet will launch it at that path, and will shut it down
|
||||
when lbrynet exits. If lbrycrdd is already running, lbrynet will not launch it and will not
|
||||
shut it down, but it will connect to it and use it.
|
||||
|
||||
### Running lbrycrdd manually
|
||||
|
||||
From the lbrycrd directory, run:
|
||||
|
||||
```
|
||||
./src/lbrycrdd -server -daemon
|
||||
|
@ -105,37 +133,14 @@ If you want to mine LBC, also use the flag '-gen', so:
|
|||
./src/lbrycrdd -server -daemon -gen
|
||||
```
|
||||
|
||||
Warning: This will put a heavy load on your CPU
|
||||
|
||||
It will take a few minutes for your client to download the whole block chain.
|
||||
|
||||
lbrycrdd must be running in order for lbrynet to function.
|
||||
|
||||
To shut lbrycrdd down: from the lbrycrd directory, run
|
||||
To shut lbrycrdd down: from the lbrycrd directory, run:
|
||||
|
||||
```
|
||||
./src/lbrycrd-cli stop
|
||||
```
|
||||
|
||||
### Option 1) Running lbrynet-console
|
||||
|
||||
If you used the virtualenv instructions above, make sure the virtualenv is still active. If not, reactivate it according to the instructions above, under "Installing lbrynet from source"
|
||||
|
||||
In your terminal: `lbrynet-console`
|
||||
|
||||
You should be presented with a prompt.
|
||||
|
||||
Watch It's a Wonderful Life via LBRY
|
||||
|
||||
Type into the prompt: `get wonderfullife`
|
||||
|
||||
To shut it down, press ctrl-c at any time or enter `exit` into the prompt.
|
||||
|
||||
### Option 2) Running lbrynet-gui
|
||||
|
||||
If you used the virtualenv instructions above, make sure the virtualenv is still active. If not, reactivate it according to the instructions above, under "Installing lbrynet from source"
|
||||
|
||||
In your terminal: `lbrynet-gui`
|
||||
|
||||
A window should pop up with an entry box. Type `wonderfullife` into the box, hit go, and then choose to save it or stream it.
|
||||
Enjoy!
|
||||
|
||||
Any questions or problems, email jimmy@lbry.io
|
||||
|
|
BIN
app.icns
Normal file
|
@ -1,4 +1,8 @@
|
|||
import logging
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
log.setLevel(logging.INFO)
|
||||
|
||||
__version__ = "0.3.12"
|
||||
version = tuple(__version__.split('.'))
|
|
@ -3,9 +3,6 @@ Some network wide and also application specific parameters
|
|||
"""
|
||||
|
||||
|
||||
import os
|
||||
|
||||
|
||||
MAX_HANDSHAKE_SIZE = 2**16
|
||||
MAX_REQUEST_SIZE = 2**16
|
||||
MAX_BLOB_REQUEST_SIZE = 2**16
|
||||
|
@ -13,13 +10,50 @@ MAX_RESPONSE_INFO_SIZE = 2**16
|
|||
MAX_BLOB_INFOS_TO_REQUEST = 20
|
||||
BLOBFILES_DIR = ".blobfiles"
|
||||
BLOB_SIZE = 2**21
|
||||
MIN_BLOB_DATA_PAYMENT_RATE = .5 # points/megabyte
|
||||
MIN_BLOB_INFO_PAYMENT_RATE = 2.0 # points/1000 infos
|
||||
MIN_VALUABLE_BLOB_INFO_PAYMENT_RATE = 5.0 # points/1000 infos
|
||||
MIN_VALUABLE_BLOB_HASH_PAYMENT_RATE = 5.0 # points/1000 infos
|
||||
|
||||
MIN_BLOB_DATA_PAYMENT_RATE = .005 # points/megabyte
|
||||
MIN_BLOB_INFO_PAYMENT_RATE = .02 # points/1000 infos
|
||||
MIN_VALUABLE_BLOB_INFO_PAYMENT_RATE = .05 # points/1000 infos
|
||||
MIN_VALUABLE_BLOB_HASH_PAYMENT_RATE = .05 # points/1000 infos
|
||||
MAX_CONNECTIONS_PER_STREAM = 5
|
||||
|
||||
KNOWN_DHT_NODES = [('104.236.42.182', 4000),
|
||||
('lbrynet1.lbry.io', 4444),
|
||||
('lbrynet2.lbry.io', 4444),
|
||||
('lbrynet3.lbry.io', 4444)]
|
||||
|
||||
POINTTRADER_SERVER = 'http://ec2-54-187-192-68.us-west-2.compute.amazonaws.com:2424'
|
||||
#POINTTRADER_SERVER = 'http://127.0.0.1:2424'
|
||||
SEARCH_SERVERS = ["http://lighthouse1.lbry.io:50005",
|
||||
"http://lighthouse2.lbry.io:50005",
|
||||
"http://lighthouse3.lbry.io:50005"]
|
||||
|
||||
LOG_FILE_NAME = "lbrynet.log"
|
||||
LOG_POST_URL = "https://lbry.io/log-upload"
|
||||
|
||||
CRYPTSD_FILE_EXTENSION = ".cryptsd"
|
||||
|
||||
API_INTERFACE = "localhost"
|
||||
API_ADDRESS = "lbryapi"
|
||||
API_PORT = 5279
|
||||
ICON_PATH = "app.icns"
|
||||
APP_NAME = "LBRY"
|
||||
API_CONNECTION_STRING = "http://%s:%i/%s" % (API_INTERFACE, API_PORT, API_ADDRESS)
|
||||
UI_ADDRESS = "http://%s:%i" % (API_INTERFACE, API_PORT)
|
||||
PROTOCOL_PREFIX = "lbry"
|
||||
|
||||
DEFAULT_WALLET = "lbryum"
|
||||
WALLET_TYPES = ["lbryum", "lbrycrd"]
|
||||
DEFAULT_TIMEOUT = 30
|
||||
DEFAULT_MAX_SEARCH_RESULTS = 25
|
||||
DEFAULT_MAX_KEY_FEE = {'USD': {'amount': 25.0, 'address': ''}}
|
||||
DEFAULT_SEARCH_TIMEOUT = 3.0
|
||||
DEFAULT_CACHE_TIME = 3600
|
||||
DEFAULT_UI_BRANCH = "master"
|
||||
|
||||
SOURCE_TYPES = ['lbry_sd_hash', 'url', 'btih']
|
||||
CURRENCIES = {
|
||||
'BTC': {'type': 'crypto'},
|
||||
'LBC': {'type': 'crypto'},
|
||||
'USD': {'type': 'fiat'},
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@ class ConnectionClosedBeforeResponseError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class KeyFeeAboveMaxAllowed(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnknownNameError(Exception):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
@ -30,6 +34,14 @@ class UnknownNameError(Exception):
|
|||
return repr(self.name)
|
||||
|
||||
|
||||
class InvalidNameError(Exception):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.name)
|
||||
|
||||
|
||||
class UnknownStreamTypeError(Exception):
|
||||
def __init__(self, stream_type):
|
||||
self.stream_type = stream_type
|
||||
|
|
128
lbrynet/core/LBRYMetadata.py
Normal file
|
@ -0,0 +1,128 @@
|
|||
import requests
|
||||
import json
|
||||
import time
|
||||
|
||||
from copy import deepcopy
|
||||
from googlefinance import getQuotes
|
||||
from lbrynet.conf import CURRENCIES
|
||||
from lbrynet.core import utils
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
BITTREX_FEE = 0.0025
|
||||
|
||||
# Metadata version
|
||||
SOURCE_TYPES = ['lbry_sd_hash', 'url', 'btih']
|
||||
NAME_ALLOWED_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-'
|
||||
BASE_METADATA_FIELDS = ['title', 'description', 'author', 'language', 'license', 'content-type', 'sources']
|
||||
OPTIONAL_METADATA_FIELDS = ['thumbnail', 'preview', 'fee', 'contact', 'pubkey']
|
||||
|
||||
MV001 = "0.0.1"
|
||||
MV002 = "0.0.2"
|
||||
CURRENT_METADATA_VERSION = MV002
|
||||
|
||||
METADATA_REVISIONS = {}
|
||||
METADATA_REVISIONS[MV001] = {'required': BASE_METADATA_FIELDS, 'optional': OPTIONAL_METADATA_FIELDS}
|
||||
METADATA_REVISIONS[MV002] = {'required': ['nsfw', 'ver'], 'optional': ['license_url']}
|
||||
|
||||
# Fee version
|
||||
BASE_FEE_FIELDS = ['amount', 'address']
|
||||
|
||||
FV001 = "0.0.1"
|
||||
CURRENT_FEE_REVISION = FV001
|
||||
|
||||
FEE_REVISIONS = {}
|
||||
FEE_REVISIONS[FV001] = {'required': BASE_FEE_FIELDS, 'optional': []}
|
||||
|
||||
|
||||
def verify_name_characters(name):
|
||||
for c in name:
|
||||
assert c in NAME_ALLOWED_CHARSET, "Invalid character"
|
||||
return True
|
||||
|
||||
|
||||
class LBRYFeeValidator(dict):
|
||||
def __init__(self, fee_dict):
|
||||
dict.__init__(self)
|
||||
assert len(fee_dict) == 1
|
||||
self.fee_version = None
|
||||
self.currency_symbol = None
|
||||
|
||||
fee_to_load = deepcopy(fee_dict)
|
||||
|
||||
for currency in fee_dict:
|
||||
self._verify_fee(currency, fee_to_load)
|
||||
|
||||
self.amount = self._get_amount()
|
||||
self.address = self[self.currency_symbol]['address']
|
||||
|
||||
def _get_amount(self):
|
||||
amt = self[self.currency_symbol]['amount']
|
||||
if isinstance(amt, float):
|
||||
return amt
|
||||
else:
|
||||
try:
|
||||
return float(amt)
|
||||
except TypeError:
|
||||
log.error('Failed to convert %s to float', amt)
|
||||
raise
|
||||
|
||||
def _verify_fee(self, currency, f):
|
||||
# str in case someone made a claim with a wierd fee
|
||||
assert currency in CURRENCIES, "Unsupported currency: %s" % str(currency)
|
||||
self.currency_symbol = currency
|
||||
self.update({currency: {}})
|
||||
for version in FEE_REVISIONS:
|
||||
self._load_revision(version, f)
|
||||
if not f:
|
||||
self.fee_version = version
|
||||
break
|
||||
assert f[self.currency_symbol] == {}, "Unknown fee keys: %s" % json.dumps(f.keys())
|
||||
|
||||
def _load_revision(self, version, f):
|
||||
for k in FEE_REVISIONS[version]['required']:
|
||||
assert k in f[self.currency_symbol], "Missing required fee field: %s" % k
|
||||
self[self.currency_symbol].update({k: f[self.currency_symbol].pop(k)})
|
||||
for k in FEE_REVISIONS[version]['optional']:
|
||||
if k in f[self.currency_symbol]:
|
||||
self[self.currency_symbol].update({k: f[self.currency_symbol].pop(k)})
|
||||
|
||||
|
||||
class Metadata(dict):
|
||||
def __init__(self, metadata):
|
||||
dict.__init__(self)
|
||||
self.meta_version = None
|
||||
metadata_to_load = deepcopy(metadata)
|
||||
|
||||
self._verify_sources(metadata_to_load)
|
||||
self._verify_metadata(metadata_to_load)
|
||||
|
||||
def _load_revision(self, version, metadata):
|
||||
for k in METADATA_REVISIONS[version]['required']:
|
||||
assert k in metadata, "Missing required metadata field: %s" % k
|
||||
self.update({k: metadata.pop(k)})
|
||||
for k in METADATA_REVISIONS[version]['optional']:
|
||||
if k == 'fee':
|
||||
self._load_fee(metadata)
|
||||
elif k in metadata:
|
||||
self.update({k: metadata.pop(k)})
|
||||
|
||||
def _load_fee(self, metadata):
|
||||
if 'fee' in metadata:
|
||||
self['fee'] = LBRYFeeValidator(metadata.pop('fee'))
|
||||
|
||||
def _verify_sources(self, metadata):
|
||||
assert "sources" in metadata, "No sources given"
|
||||
for source in metadata['sources']:
|
||||
assert source in SOURCE_TYPES, "Unknown source type"
|
||||
|
||||
def _verify_metadata(self, metadata):
|
||||
for version in METADATA_REVISIONS:
|
||||
self._load_revision(version, metadata)
|
||||
if not metadata:
|
||||
self.meta_version = version
|
||||
if utils.version_is_greater_than(self.meta_version, "0.0.1"):
|
||||
assert self.meta_version == self['ver'], "version mismatch"
|
||||
break
|
||||
assert metadata == {}, "Unknown metadata keys: %s" % json.dumps(metadata.keys())
|
1284
lbrynet/core/LBRYWallet.py
Normal file
|
@ -1,764 +0,0 @@
|
|||
import sys
|
||||
from lbrynet.interfaces import IRequestCreator, IQueryHandlerFactory, IQueryHandler, ILBRYWallet
|
||||
from lbrynet.core.client.ClientRequest import ClientRequest
|
||||
from lbrynet.core.Error import UnknownNameError, InvalidStreamInfoError, RequestCanceledError
|
||||
from lbrynet.core.Error import InsufficientFundsError
|
||||
from lbrynet.core.sqlite_helpers import rerun_if_locked
|
||||
from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
|
||||
from twisted.internet import threads, reactor, defer, task
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.enterprise import adbapi
|
||||
from collections import defaultdict, deque
|
||||
from zope.interface import implements
|
||||
from decimal import Decimal
|
||||
import datetime
|
||||
import logging
|
||||
import json
|
||||
import subprocess
|
||||
import socket
|
||||
import time
|
||||
import os
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
alert = logging.getLogger("lbryalert." + __name__)
|
||||
|
||||
|
||||
class ReservedPoints(object):
|
||||
def __init__(self, identifier, amount):
|
||||
self.identifier = identifier
|
||||
self.amount = amount
|
||||
|
||||
|
||||
def _catch_connection_error(f):
|
||||
def w(*args):
|
||||
try:
|
||||
return f(*args)
|
||||
except socket.error:
|
||||
raise ValueError("Unable to connect to an lbrycrd server. Make sure an lbrycrd server " +
|
||||
"is running and that this application can connect to it.")
|
||||
return w
|
||||
|
||||
|
||||
class LBRYcrdWallet(object):
|
||||
"""This class implements the LBRYWallet interface for the LBRYcrd payment system"""
|
||||
implements(ILBRYWallet)
|
||||
|
||||
def __init__(self, db_dir, wallet_dir=None, wallet_conf=None, lbrycrdd_path=None):
|
||||
|
||||
self.db_dir = db_dir
|
||||
self.db = None
|
||||
self.next_manage_call = None
|
||||
self.wallet_balance = Decimal(0.0)
|
||||
self.total_reserved_points = Decimal(0.0)
|
||||
self.peer_addresses = {} # {Peer: string}
|
||||
self.queued_payments = defaultdict(Decimal) # {address(string): amount(Decimal)}
|
||||
self.expected_balances = defaultdict(Decimal) # {address(string): amount(Decimal)}
|
||||
self.current_address_given_to_peer = {} # {Peer: address(string)}
|
||||
self.expected_balance_at_time = deque() # (Peer, address(string), amount(Decimal), time(datetime), count(int),
|
||||
# incremental_amount(float))
|
||||
self.max_expected_payment_time = datetime.timedelta(minutes=3)
|
||||
self.stopped = True
|
||||
self.started_lbrycrdd = False
|
||||
self.wallet_dir = wallet_dir
|
||||
self.wallet_conf = wallet_conf
|
||||
self.lbrycrdd = None
|
||||
self.manage_running = False
|
||||
self.lbrycrdd_path = lbrycrdd_path
|
||||
|
||||
settings = self.get_rpc_conf()
|
||||
rpc_user = settings["username"]
|
||||
rpc_pass = settings["password"]
|
||||
rpc_port = settings["rpc_port"]
|
||||
rpc_url = "127.0.0.1"
|
||||
self.rpc_conn_string = "http://%s:%s@%s:%s" % (rpc_user, rpc_pass, rpc_url, str(rpc_port))
|
||||
|
||||
def start(self):
|
||||
|
||||
def make_connection():
|
||||
alert.info("Connecting to lbrycrdd...")
|
||||
if self.lbrycrdd_path is not None:
|
||||
self._start_daemon()
|
||||
self._get_info()
|
||||
log.info("Connected!")
|
||||
alert.info("Connected to lbrycrdd.")
|
||||
|
||||
def start_manage():
|
||||
self.stopped = False
|
||||
self.manage()
|
||||
return True
|
||||
|
||||
d = self._open_db()
|
||||
d.addCallback(lambda _: threads.deferToThread(make_connection))
|
||||
d.addCallback(lambda _: start_manage())
|
||||
return d
|
||||
|
||||
def stop(self):
|
||||
|
||||
def log_stop_error(err):
|
||||
log.error("An error occurred stopping the wallet. %s", err.getTraceback())
|
||||
|
||||
self.stopped = True
|
||||
# If self.next_manage_call is None, then manage is currently running or else
|
||||
# start has not been called, so set stopped and do nothing else.
|
||||
if self.next_manage_call is not None:
|
||||
self.next_manage_call.cancel()
|
||||
self.next_manage_call = None
|
||||
|
||||
d = self.manage()
|
||||
d.addErrback(log_stop_error)
|
||||
if self.lbrycrdd_path is not None:
|
||||
d.addCallback(lambda _: self._stop_daemon())
|
||||
d.addErrback(log_stop_error)
|
||||
return d
|
||||
|
||||
def manage(self):
|
||||
log.info("Doing manage")
|
||||
self.next_manage_call = None
|
||||
have_set_manage_running = [False]
|
||||
|
||||
def check_if_manage_running():
|
||||
|
||||
d = defer.Deferred()
|
||||
|
||||
def fire_if_not_running():
|
||||
if self.manage_running is False:
|
||||
self.manage_running = True
|
||||
have_set_manage_running[0] = True
|
||||
d.callback(True)
|
||||
else:
|
||||
task.deferLater(reactor, 1, fire_if_not_running)
|
||||
|
||||
fire_if_not_running()
|
||||
return d
|
||||
|
||||
d = check_if_manage_running()
|
||||
|
||||
d.addCallback(lambda _: self._check_expected_balances())
|
||||
|
||||
d.addCallback(lambda _: self._send_payments())
|
||||
|
||||
d.addCallback(lambda _: threads.deferToThread(self._get_wallet_balance))
|
||||
|
||||
def set_wallet_balance(balance):
|
||||
self.wallet_balance = balance
|
||||
|
||||
d.addCallback(set_wallet_balance)
|
||||
|
||||
def set_next_manage_call():
|
||||
if not self.stopped:
|
||||
self.next_manage_call = reactor.callLater(60, self.manage)
|
||||
|
||||
d.addCallback(lambda _: set_next_manage_call())
|
||||
|
||||
def log_error(err):
|
||||
log.error("Something went wrong during manage. Error message: %s", err.getErrorMessage())
|
||||
return err
|
||||
|
||||
d.addErrback(log_error)
|
||||
|
||||
def set_manage_not_running(arg):
|
||||
if have_set_manage_running[0] is True:
|
||||
self.manage_running = False
|
||||
return arg
|
||||
|
||||
d.addBoth(set_manage_not_running)
|
||||
return d
|
||||
|
||||
def get_info_exchanger(self):
|
||||
return LBRYcrdAddressRequester(self)
|
||||
|
||||
def get_wallet_info_query_handler_factory(self):
|
||||
return LBRYcrdAddressQueryHandlerFactory(self)
|
||||
|
||||
def get_balance(self):
|
||||
d = threads.deferToThread(self._get_wallet_balance)
|
||||
return d
|
||||
|
||||
def reserve_points(self, identifier, amount):
|
||||
"""
|
||||
Ensure a certain amount of points are available to be sent as payment, before the service is rendered
|
||||
|
||||
@param identifier: The peer to which the payment will ultimately be sent
|
||||
|
||||
@param amount: The amount of points to reserve
|
||||
|
||||
@return: A ReservedPoints object which is given to send_points once the service has been rendered
|
||||
"""
|
||||
rounded_amount = Decimal(str(round(amount, 8)))
|
||||
#if peer in self.peer_addresses:
|
||||
if self.wallet_balance >= self.total_reserved_points + rounded_amount:
|
||||
self.total_reserved_points += rounded_amount
|
||||
return ReservedPoints(identifier, rounded_amount)
|
||||
return None
|
||||
|
||||
def cancel_point_reservation(self, reserved_points):
|
||||
"""
|
||||
Return all of the points that were reserved previously for some ReservedPoints object
|
||||
|
||||
@param reserved_points: ReservedPoints previously returned by reserve_points
|
||||
|
||||
@return: None
|
||||
"""
|
||||
self.total_reserved_points -= reserved_points.amount
|
||||
|
||||
def send_points(self, reserved_points, amount):
|
||||
"""
|
||||
Schedule a payment to be sent to a peer
|
||||
|
||||
@param reserved_points: ReservedPoints object previously returned by reserve_points
|
||||
|
||||
@param amount: amount of points to actually send, must be less than or equal to the
|
||||
amount reserved in reserved_points
|
||||
|
||||
@return: Deferred which fires when the payment has been scheduled
|
||||
"""
|
||||
rounded_amount = Decimal(str(round(amount, 8)))
|
||||
peer = reserved_points.identifier
|
||||
assert(rounded_amount <= reserved_points.amount)
|
||||
assert(peer in self.peer_addresses)
|
||||
self.queued_payments[self.peer_addresses[peer]] += rounded_amount
|
||||
# make any unused points available
|
||||
self.total_reserved_points -= (reserved_points.amount - rounded_amount)
|
||||
log.info("ordering that %s points be sent to %s", str(rounded_amount),
|
||||
str(self.peer_addresses[peer]))
|
||||
peer.update_stats('points_sent', amount)
|
||||
return defer.succeed(True)
|
||||
|
||||
def send_points_to_address(self, reserved_points, amount):
|
||||
"""
|
||||
Schedule a payment to be sent to an address
|
||||
|
||||
@param reserved_points: ReservedPoints object previously returned by reserve_points
|
||||
|
||||
@param amount: amount of points to actually send. must be less than or equal to the
|
||||
amount reselved in reserved_points
|
||||
|
||||
@return: Deferred which fires when the payment has been scheduled
|
||||
"""
|
||||
rounded_amount = Decimal(str(round(amount, 8)))
|
||||
address = reserved_points.identifier
|
||||
assert(rounded_amount <= reserved_points.amount)
|
||||
self.queued_payments[address] += rounded_amount
|
||||
self.total_reserved_points -= (reserved_points.amount - rounded_amount)
|
||||
log.info("Ordering that %s points be sent to %s", str(rounded_amount),
|
||||
str(address))
|
||||
return defer.succeed(True)
|
||||
|
||||
def add_expected_payment(self, peer, amount):
|
||||
"""Increase the number of points expected to be paid by a peer"""
|
||||
rounded_amount = Decimal(str(round(amount, 8)))
|
||||
assert(peer in self.current_address_given_to_peer)
|
||||
address = self.current_address_given_to_peer[peer]
|
||||
log.info("expecting a payment at address %s in the amount of %s", str(address), str(rounded_amount))
|
||||
self.expected_balances[address] += rounded_amount
|
||||
expected_balance = self.expected_balances[address]
|
||||
expected_time = datetime.datetime.now() + self.max_expected_payment_time
|
||||
self.expected_balance_at_time.append((peer, address, expected_balance, expected_time, 0, amount))
|
||||
peer.update_stats('expected_points', amount)
|
||||
|
||||
def update_peer_address(self, peer, address):
|
||||
self.peer_addresses[peer] = address
|
||||
|
||||
def get_new_address_for_peer(self, peer):
|
||||
def set_address_for_peer(address):
|
||||
self.current_address_given_to_peer[peer] = address
|
||||
return address
|
||||
d = threads.deferToThread(self._get_new_address)
|
||||
d.addCallback(set_address_for_peer)
|
||||
return d
|
||||
|
||||
def get_stream_info_for_name(self, name):
|
||||
|
||||
def get_stream_info_from_value(result):
|
||||
r_dict = {}
|
||||
if 'value' in result:
|
||||
value = result['value']
|
||||
try:
|
||||
value_dict = json.loads(value)
|
||||
except ValueError:
|
||||
return Failure(InvalidStreamInfoError(name))
|
||||
known_fields = ['stream_hash', 'name', 'description', 'key_fee', 'key_fee_address', 'thumbnail']
|
||||
for field in known_fields:
|
||||
if field in value_dict:
|
||||
r_dict[field] = value_dict[field]
|
||||
if 'stream_hash' in r_dict and 'txid' in result:
|
||||
d = self._save_name_metadata(name, r_dict['stream_hash'], str(result['txid']))
|
||||
else:
|
||||
d = defer.succeed(True)
|
||||
d.addCallback(lambda _: r_dict)
|
||||
return d
|
||||
return Failure(UnknownNameError(name))
|
||||
|
||||
d = threads.deferToThread(self._get_value_for_name, name)
|
||||
d.addCallback(get_stream_info_from_value)
|
||||
return d
|
||||
|
||||
def claim_name(self, name, sd_hash, amount, description=None, key_fee=None,
|
||||
key_fee_address=None, thumbnail=None, content_license=None):
|
||||
value = {"stream_hash": sd_hash}
|
||||
if description is not None:
|
||||
value['description'] = description
|
||||
if key_fee is not None:
|
||||
value['key_fee'] = key_fee
|
||||
if key_fee_address is not None:
|
||||
value['key_fee_address'] = key_fee_address
|
||||
if thumbnail is not None:
|
||||
value['thumbnail'] = thumbnail
|
||||
if content_license is not None:
|
||||
value['content_license'] = content_license
|
||||
|
||||
d = threads.deferToThread(self._claim_name, name, json.dumps(value), amount)
|
||||
|
||||
def _save_metadata(txid):
|
||||
d = self._save_name_metadata(name, sd_hash, txid)
|
||||
d.addCallback(lambda _: txid)
|
||||
return d
|
||||
|
||||
d.addCallback(_save_metadata)
|
||||
return d
|
||||
|
||||
def abandon_name(self, txid):
|
||||
address = self._get_new_address()
|
||||
raw = self._get_raw_tx(txid)
|
||||
transaction = self._get_decoded_tx(raw)
|
||||
amount = float(transaction['vout'][1]['value'])
|
||||
return self._abandon_name(txid, address, amount)
|
||||
|
||||
def get_tx(self, txid):
|
||||
raw = self._get_raw_tx(txid)
|
||||
return self._get_decoded_tx(raw)
|
||||
|
||||
def get_name_claims(self):
|
||||
return threads.deferToThread(self._get_name_claims)
|
||||
|
||||
def start_miner(self):
|
||||
if not self._get_gen_status():
|
||||
return self._set_gen_status(True)
|
||||
else:
|
||||
return "Miner was already running"
|
||||
|
||||
def stop_miner(self):
|
||||
if self._get_gen_status():
|
||||
return self._set_gen_status(False)
|
||||
else:
|
||||
return "Miner wasn't running"
|
||||
|
||||
def get_miner_status(self):
|
||||
return self._get_gen_status()
|
||||
|
||||
def get_block(self, blockhash):
|
||||
return self._get_block(blockhash)
|
||||
|
||||
def get_blockchain_info(self):
|
||||
return self._get_blockchain_info()
|
||||
|
||||
def get_claims_for_tx(self, txid):
|
||||
return self._get_claims_for_tx(txid)
|
||||
|
||||
def get_nametrie(self):
|
||||
return self._get_nametrie()
|
||||
|
||||
def get_name_and_validity_for_sd_hash(self, sd_hash):
|
||||
d = self._get_claim_metadata_for_sd_hash(sd_hash)
|
||||
d.addCallback(lambda name_txid: threads.deferToThread(self._get_status_of_claim, name_txid[1], name_txid[0], sd_hash) if name_txid is not None else None)
|
||||
return d
|
||||
|
||||
def get_available_balance(self):
|
||||
return float(self.wallet_balance - self.total_reserved_points)
|
||||
|
||||
def get_new_address(self):
|
||||
return threads.deferToThread(self._get_new_address)
|
||||
|
||||
def check_first_run(self):
|
||||
d = threads.deferToThread(self._get_wallet_balance)
|
||||
d.addCallback(lambda bal: threads.deferToThread(self._get_num_addresses) if bal == 0 else 2)
|
||||
d.addCallback(lambda num_addresses: True if num_addresses <= 1 else False)
|
||||
return d
|
||||
|
||||
def get_most_recent_blocktime(self):
|
||||
return threads.deferToThread(self._get_best_block_time)
|
||||
|
||||
def get_rpc_conf(self):
|
||||
settings = {"username": "rpcuser",
|
||||
"password": "rpcpassword",
|
||||
"rpc_port": 8332}
|
||||
if os.path.exists(self.wallet_conf):
|
||||
conf = open(self.wallet_conf)
|
||||
for l in conf:
|
||||
if l.startswith("rpcuser="):
|
||||
settings["username"] = l[8:].rstrip('\n')
|
||||
if l.startswith("rpcpassword="):
|
||||
settings["password"] = l[12:].rstrip('\n')
|
||||
if l.startswith("rpcport="):
|
||||
settings["rpc_port"] = int(l[8:].rstrip('\n'))
|
||||
return settings
|
||||
|
||||
def _get_rpc_conn(self):
|
||||
return AuthServiceProxy(self.rpc_conn_string)
|
||||
|
||||
def _start_daemon(self):
|
||||
|
||||
tries = 0
|
||||
try:
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
rpc_conn.getinfo()
|
||||
log.info("lbrycrdd was already running when LBRYcrdWallet was started.")
|
||||
return
|
||||
except (socket.error, JSONRPCException):
|
||||
tries += 1
|
||||
log.info("lbrcyrdd was not running when LBRYcrdWallet was started. Attempting to start it.")
|
||||
|
||||
try:
|
||||
if os.name == "nt":
|
||||
si = subprocess.STARTUPINFO
|
||||
si.dwFlags = subprocess.STARTF_USESHOWWINDOW
|
||||
si.wShowWindow = subprocess.SW_HIDE
|
||||
self.lbrycrdd = subprocess.Popen([self.lbrycrdd_path, "-datadir=%s" % self.wallet_dir,
|
||||
"-conf=%s" % self.wallet_conf], startupinfo=si)
|
||||
else:
|
||||
if sys.platform == 'darwin':
|
||||
os.chdir("/Applications/LBRY.app/Contents/Resources")
|
||||
self.lbrycrdd = subprocess.Popen([self.lbrycrdd_path, "-datadir=%s" % self.wallet_dir,
|
||||
"-conf=%s" % self.wallet_conf])
|
||||
self.started_lbrycrdd = True
|
||||
except OSError:
|
||||
import traceback
|
||||
log.error("Couldn't launch lbrycrdd at path %s: %s", self.lbrycrdd_path, traceback.format_exc())
|
||||
raise ValueError("Couldn't launch lbrycrdd. Tried %s" % self.lbrycrdd_path)
|
||||
|
||||
while tries < 6:
|
||||
try:
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
rpc_conn.getinfo()
|
||||
break
|
||||
except (socket.error, JSONRPCException):
|
||||
tries += 1
|
||||
log.warning("Failed to connect to lbrycrdd.")
|
||||
if tries < 6:
|
||||
time.sleep(2 ** tries)
|
||||
log.warning("Trying again in %d seconds", 2 ** tries)
|
||||
else:
|
||||
log.warning("Giving up.")
|
||||
else:
|
||||
self.lbrycrdd.terminate()
|
||||
raise ValueError("Couldn't open lbrycrdd")
|
||||
|
||||
def _stop_daemon(self):
|
||||
if self.lbrycrdd is not None and self.started_lbrycrdd is True:
|
||||
alert.info("Stopping lbrycrdd...")
|
||||
d = threads.deferToThread(self._rpc_stop)
|
||||
d.addCallback(lambda _: alert.info("Stopped lbrycrdd."))
|
||||
return d
|
||||
return defer.succeed(True)
|
||||
|
||||
def _check_expected_balances(self):
|
||||
now = datetime.datetime.now()
|
||||
balances_to_check = []
|
||||
try:
|
||||
while self.expected_balance_at_time[0][3] < now:
|
||||
balances_to_check.append(self.expected_balance_at_time.popleft())
|
||||
except IndexError:
|
||||
pass
|
||||
ds = []
|
||||
for balance_to_check in balances_to_check:
|
||||
d = threads.deferToThread(self._check_expected_balance, balance_to_check)
|
||||
ds.append(d)
|
||||
dl = defer.DeferredList(ds)
|
||||
|
||||
def handle_checks(results):
|
||||
from future_builtins import zip
|
||||
for balance, (success, result) in zip(balances_to_check, results):
|
||||
peer = balance[0]
|
||||
if success is True:
|
||||
if result is False:
|
||||
if balance[4] <= 1: # first or second strike, give them another chance
|
||||
new_expected_balance = (balance[0],
|
||||
balance[1],
|
||||
balance[2],
|
||||
datetime.datetime.now() + self.max_expected_payment_time,
|
||||
balance[4] + 1,
|
||||
balance[5])
|
||||
self.expected_balance_at_time.append(new_expected_balance)
|
||||
peer.update_score(-5.0)
|
||||
else:
|
||||
peer.update_score(-50.0)
|
||||
else:
|
||||
if balance[4] == 0:
|
||||
peer.update_score(balance[5])
|
||||
peer.update_stats('points_received', balance[5])
|
||||
else:
|
||||
log.warning("Something went wrong checking a balance. Peer: %s, account: %s,"
|
||||
"expected balance: %s, expected time: %s, count: %s, error: %s",
|
||||
str(balance[0]), str(balance[1]), str(balance[2]), str(balance[3]),
|
||||
str(balance[4]), str(result.getErrorMessage()))
|
||||
|
||||
dl.addCallback(handle_checks)
|
||||
return dl
|
||||
|
||||
@_catch_connection_error
|
||||
def _check_expected_balance(self, expected_balance):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
log.info("Checking balance of address %s", str(expected_balance[1]))
|
||||
balance = rpc_conn.getreceivedbyaddress(expected_balance[1])
|
||||
log.debug("received balance: %s", str(balance))
|
||||
log.debug("expected balance: %s", str(expected_balance[2]))
|
||||
return balance >= expected_balance[2]
|
||||
|
||||
def _send_payments(self):
|
||||
log.info("Trying to send payments, if there are any to be sent")
|
||||
|
||||
def do_send(payments):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
rpc_conn.sendmany("", payments)
|
||||
|
||||
payments_to_send = {}
|
||||
for address, points in self.queued_payments.items():
|
||||
log.info("Should be sending %s points to %s", str(points), str(address))
|
||||
payments_to_send[address] = float(points)
|
||||
self.total_reserved_points -= points
|
||||
self.wallet_balance -= points
|
||||
del self.queued_payments[address]
|
||||
if payments_to_send:
|
||||
log.info("Creating a transaction with outputs %s", str(payments_to_send))
|
||||
return threads.deferToThread(do_send, payments_to_send)
|
||||
log.info("There were no payments to send")
|
||||
return defer.succeed(True)
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_info(self):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return rpc_conn.getinfo()
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_name_claims(self):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return rpc_conn.listnameclaims()
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_gen_status(self):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return rpc_conn.getgenerate()
|
||||
|
||||
@_catch_connection_error
|
||||
def _set_gen_status(self, b):
|
||||
if b:
|
||||
log.info("Starting miner")
|
||||
else:
|
||||
log.info("Stopping miner")
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return rpc_conn.setgenerate(b)
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_raw_tx(self, txid):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return rpc_conn.getrawtransaction(txid)
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_decoded_tx(self, raw):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return rpc_conn.decoderawtransaction(raw)
|
||||
|
||||
@_catch_connection_error
|
||||
def _abandon_name(self, txid, address, amount):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return rpc_conn.abandonname(txid, address, amount)
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_blockchain_info(self):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return rpc_conn.getblockchaininfo()
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_block(self, blockhash):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return rpc_conn.getblock(blockhash)
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_claims_for_tx(self, txid):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return rpc_conn.getclaimsfortx(txid)
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_nametrie(self):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return rpc_conn.getnametrie()
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_wallet_balance(self):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return rpc_conn.getbalance("")
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_new_address(self):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return rpc_conn.getnewaddress()
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_value_for_name(self, name):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return rpc_conn.getvalueforname(name)
|
||||
|
||||
@_catch_connection_error
|
||||
def _claim_name(self, name, value, amount):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
try:
|
||||
return str(rpc_conn.claimname(name, value, amount))
|
||||
except JSONRPCException as e:
|
||||
if 'message' in e.error and e.error['message'] == "Insufficient funds":
|
||||
raise InsufficientFundsError()
|
||||
elif 'message' in e.error:
|
||||
raise ValueError(e.error['message'])
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_status_of_claim(self, txhash, name, sd_hash):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
claims = rpc_conn.getclaimsfortx(txhash)
|
||||
if claims is None:
|
||||
claims = []
|
||||
for claim in claims:
|
||||
if 'in claim trie' in claim:
|
||||
if 'name' in claim and str(claim['name']) == name and 'value' in claim:
|
||||
try:
|
||||
value_dict = json.loads(claim['value'])
|
||||
except ValueError:
|
||||
return None
|
||||
if 'stream_hash' in value_dict and str(value_dict['stream_hash']) == sd_hash:
|
||||
if 'is controlling' in claim and claim['is controlling']:
|
||||
return name, "valid"
|
||||
if claim['in claim trie']:
|
||||
return name, "invalid"
|
||||
if 'in queue' in claim and claim['in queue']:
|
||||
return name, "pending"
|
||||
return name, "unconfirmed"
|
||||
return None
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_num_addresses(self):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
return len(rpc_conn.getaddressesbyaccount(""))
|
||||
|
||||
@_catch_connection_error
|
||||
def _get_best_block_time(self):
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
best_block_hash = rpc_conn.getbestblockhash()
|
||||
block = rpc_conn.getblock(best_block_hash)
|
||||
if 'time' in block:
|
||||
return block['time']
|
||||
raise ValueError("Could not get a block time")
|
||||
|
||||
|
||||
@_catch_connection_error
|
||||
def _rpc_stop(self):
|
||||
# check if our lbrycrdd is actually running, or if we connected to one that was already
|
||||
# running and ours failed to start
|
||||
if self.lbrycrdd.poll() is None:
|
||||
rpc_conn = self._get_rpc_conn()
|
||||
rpc_conn.stop()
|
||||
self.lbrycrdd.wait()
|
||||
|
||||
def _open_db(self):
|
||||
self.db = adbapi.ConnectionPool('sqlite3', os.path.join(self.db_dir, "blockchainname.db"),
|
||||
check_same_thread=False)
|
||||
return self.db.runQuery("create table if not exists name_metadata (" +
|
||||
" name text, " +
|
||||
" txid text, " +
|
||||
" sd_hash text)")
|
||||
|
||||
def _save_name_metadata(self, name, sd_hash, txid):
|
||||
d = self.db.runQuery("insert into name_metadata values (?, ?, ?)",
|
||||
(name, txid, sd_hash))
|
||||
return d
|
||||
|
||||
def _get_claim_metadata_for_sd_hash(self, sd_hash):
|
||||
d = self.db.runQuery("select name, txid from name_metadata where sd_hash=?", (sd_hash,))
|
||||
d.addCallback(lambda r: r[0] if len(r) else None)
|
||||
return d
|
||||
|
||||
|
||||
class LBRYcrdAddressRequester(object):
|
||||
implements([IRequestCreator])
|
||||
|
||||
def __init__(self, wallet):
|
||||
self.wallet = wallet
|
||||
self._protocols = []
|
||||
|
||||
######### IRequestCreator #########
|
||||
|
||||
def send_next_request(self, peer, protocol):
|
||||
|
||||
if not protocol in self._protocols:
|
||||
r = ClientRequest({'lbrycrd_address': True}, 'lbrycrd_address')
|
||||
d = protocol.add_request(r)
|
||||
d.addCallback(self._handle_address_response, peer, r, protocol)
|
||||
d.addErrback(self._request_failed, peer)
|
||||
self._protocols.append(protocol)
|
||||
return defer.succeed(True)
|
||||
else:
|
||||
return defer.succeed(False)
|
||||
|
||||
######### internal calls #########
|
||||
|
||||
def _handle_address_response(self, response_dict, peer, request, protocol):
|
||||
assert request.response_identifier in response_dict, \
|
||||
"Expected %s in dict but did not get it" % request.response_identifier
|
||||
assert protocol in self._protocols, "Responding protocol is not in our list of protocols"
|
||||
address = response_dict[request.response_identifier]
|
||||
self.wallet.update_peer_address(peer, address)
|
||||
|
||||
def _request_failed(self, err, peer):
|
||||
if not err.check(RequestCanceledError):
|
||||
log.warning("A peer failed to send a valid public key response. Error: %s, peer: %s",
|
||||
err.getErrorMessage(), str(peer))
|
||||
return err
|
||||
|
||||
|
||||
class LBRYcrdAddressQueryHandlerFactory(object):
|
||||
implements(IQueryHandlerFactory)
|
||||
|
||||
def __init__(self, wallet):
|
||||
self.wallet = wallet
|
||||
|
||||
######### IQueryHandlerFactory #########
|
||||
|
||||
def build_query_handler(self):
|
||||
q_h = LBRYcrdAddressQueryHandler(self.wallet)
|
||||
return q_h
|
||||
|
||||
def get_primary_query_identifier(self):
|
||||
return 'lbrycrd_address'
|
||||
|
||||
def get_description(self):
|
||||
return "LBRYcrd Address - an address for receiving payments via LBRYcrd"
|
||||
|
||||
|
||||
class LBRYcrdAddressQueryHandler(object):
|
||||
implements(IQueryHandler)
|
||||
|
||||
def __init__(self, wallet):
|
||||
self.wallet = wallet
|
||||
self.query_identifiers = ['lbrycrd_address']
|
||||
self.address = None
|
||||
self.peer = None
|
||||
|
||||
######### IQueryHandler #########
|
||||
|
||||
def register_with_request_handler(self, request_handler, peer):
|
||||
self.peer = peer
|
||||
request_handler.register_query_handler(self, self.query_identifiers)
|
||||
|
||||
def handle_queries(self, queries):
|
||||
|
||||
def create_response(address):
|
||||
self.address = address
|
||||
fields = {'lbrycrd_address': address}
|
||||
return fields
|
||||
|
||||
if self.query_identifiers[0] in queries:
|
||||
d = self.wallet.get_new_address_for_peer(self.peer)
|
||||
d.addCallback(create_response)
|
||||
return d
|
||||
if self.address is None:
|
||||
log.warning("Expected a request for an address, but did not receive one")
|
||||
return defer.fail(Failure(ValueError("Expected but did not receive an address request")))
|
||||
else:
|
||||
return defer.succeed({})
|
|
@ -12,7 +12,7 @@ from lbrynet.pointtraderclient import pointtraderclient
|
|||
from twisted.internet import defer, threads
|
||||
from zope.interface import implements
|
||||
from twisted.python.failure import Failure
|
||||
from lbrynet.core.LBRYcrdWallet import ReservedPoints
|
||||
from lbrynet.core.LBRYWallet import ReservedPoints
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
|
@ -28,7 +28,7 @@ class LBRYSession(object):
|
|||
def __init__(self, blob_data_payment_rate, db_dir=None, lbryid=None, peer_manager=None, dht_node_port=None,
|
||||
known_dht_nodes=None, peer_finder=None, hash_announcer=None,
|
||||
blob_dir=None, blob_manager=None, peer_port=None, use_upnp=True,
|
||||
rate_limiter=None, wallet=None):
|
||||
rate_limiter=None, wallet=None, dht_node_class=node.Node):
|
||||
"""
|
||||
@param blob_data_payment_rate: The default payment rate for blob data
|
||||
|
||||
|
@ -99,7 +99,7 @@ class LBRYSession(object):
|
|||
self.upnp_redirects = []
|
||||
|
||||
self.wallet = wallet
|
||||
|
||||
self.dht_node_class = dht_node_class
|
||||
self.dht_node = None
|
||||
|
||||
self.base_payment_rate_manager = BasePaymentRateManager(blob_data_payment_rate)
|
||||
|
@ -220,8 +220,11 @@ class LBRYSession(object):
|
|||
d.addCallback(match_port, port)
|
||||
ds.append(d)
|
||||
|
||||
self.dht_node = node.Node(udpPort=self.dht_node_port, lbryid=self.lbryid,
|
||||
externalIP=self.external_ip)
|
||||
self.dht_node = self.dht_node_class(
|
||||
udpPort=self.dht_node_port,
|
||||
lbryid=self.lbryid,
|
||||
externalIP=self.external_ip
|
||||
)
|
||||
self.peer_finder = DHTPeerFinder(self.dht_node, self.peer_manager)
|
||||
if self.hash_announcer is None:
|
||||
self.hash_announcer = DHTHashAnnouncer(self.dht_node, self.peer_port)
|
||||
|
|
|
@ -198,7 +198,7 @@ class StreamDescriptorIdentifier(object):
|
|||
return self._stream_downloader_factories[stream_type]
|
||||
|
||||
def _get_validator(self, stream_type):
|
||||
if not stream_type in self._stream_downloader_factories:
|
||||
if not stream_type in self._sd_info_validators:
|
||||
raise UnknownStreamTypeError(stream_type)
|
||||
return self._sd_info_validators[stream_type]
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ class DownloadManager(object):
|
|||
|
||||
def check_stop(result, manager):
|
||||
if isinstance(result, failure.Failure):
|
||||
log.error("Failed to stop the %s: %s", manager. result.getErrorMessage())
|
||||
log.error("Failed to stop the %s: %s", manager, result.getErrorMessage())
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -66,7 +66,7 @@ class DownloadManager(object):
|
|||
|
||||
def add_blobs_to_download(self, blob_infos):
|
||||
|
||||
log.debug("Adding %s to blobs", str(blob_infos))
|
||||
log.debug("Adding %s blobs to blobs", len(blob_infos))
|
||||
|
||||
def add_blob_to_list(blob, blob_num):
|
||||
self.blobs[blob_num] = blob
|
||||
|
|
24
lbrynet/core/log_support.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import logging
|
||||
import logging.handlers
|
||||
import sys
|
||||
|
||||
|
||||
DEFAULT_FORMAT = "%(asctime)s %(levelname)-8s %(name)s:%(lineno)d: %(message)s"
|
||||
DEFAULT_FORMATTER = logging.Formatter(DEFAULT_FORMAT)
|
||||
|
||||
|
||||
def configureConsole(log=None, level=logging.INFO):
|
||||
"""Convenience function to configure a logger that outputs to stdout"""
|
||||
log = log or logging.getLogger()
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
handler.setFormatter(DEFAULT_FORMATTER)
|
||||
log.addHandler(handler)
|
||||
log.setLevel(level=level)
|
||||
|
||||
|
||||
def configureFileHandler(file_name, log=None, level=logging.INFO):
|
||||
log = log or logging.getLogger()
|
||||
handler = logging.handlers.RotatingFileHandler(file_name, maxBytes=2097152, backupCount=5)
|
||||
handler.setFormatter(DEFAULT_FORMATTER)
|
||||
log.addHandler(handler)
|
||||
log.setLevel(level=level)
|
|
@ -140,6 +140,7 @@ class BlobRequestHandler(object):
|
|||
def set_expected_payment():
|
||||
log.info("Setting expected payment")
|
||||
if self.blob_bytes_uploaded != 0 and self.blob_data_payment_rate is not None:
|
||||
# TODO: explain why 2**20
|
||||
self.wallet.add_expected_payment(self.peer,
|
||||
self.currently_uploading.length * 1.0 *
|
||||
self.blob_data_payment_rate / 2**20)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
from lbrynet.core.cryptoutils import get_lbry_hash_obj
|
||||
import distutils.version
|
||||
import random
|
||||
|
||||
from lbrynet.core.cryptoutils import get_lbry_hash_obj
|
||||
|
||||
|
||||
|
||||
blobhash_length = get_lbry_hash_obj().digest_size * 2 # digest_size is in bytes, and blob hashes are hex encoded
|
||||
|
||||
|
@ -26,3 +29,11 @@ def is_valid_blobhash(blobhash):
|
|||
if l not in "0123456789abcdef":
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def version_is_greater_than(a, b):
|
||||
"""Returns True if version a is more recent than version b"""
|
||||
try:
|
||||
return distutils.version.StrictVersion(a) > distutils.version.StrictVersion(b)
|
||||
except ValueError:
|
||||
return distutils.version.LooseVersion(a) > distutils.version.LooseVersion(b)
|
||||
|
|
|
@ -96,8 +96,9 @@ class CryptStreamDownloader(object):
|
|||
self.starting = True
|
||||
self.completed = False
|
||||
self.finished_deferred = defer.Deferred()
|
||||
fd = self.finished_deferred
|
||||
d = self._start()
|
||||
d.addCallback(lambda _: self.finished_deferred)
|
||||
d.addCallback(lambda _: fd)
|
||||
return d
|
||||
|
||||
def stop(self, err=None):
|
||||
|
|
|
@ -99,7 +99,10 @@ class Bencode(Encoding):
|
|||
"""
|
||||
if len(data) == 0:
|
||||
raise DecodeError, 'Cannot decode empty string'
|
||||
try:
|
||||
return self._decodeRecursive(data)[0]
|
||||
except ValueError as e:
|
||||
raise DecodeError, e.message
|
||||
|
||||
@staticmethod
|
||||
def _decodeRecursive(data, startIndex=0):
|
||||
|
|
|
@ -241,7 +241,7 @@ class Node(object):
|
|||
|
||||
def log_error(err, n):
|
||||
log.error("error storing blob_hash %s at %s", binascii.hexlify(blob_hash), str(n))
|
||||
log.error(binascii.hexlify(err.getErrorMessage()))
|
||||
log.error(err.getErrorMessage())
|
||||
log.error(err.getTraceback())
|
||||
|
||||
def log_success(res):
|
||||
|
@ -516,7 +516,7 @@ class Node(object):
|
|||
else:
|
||||
raise TypeError, 'No NodeID given. Therefore we can\'t store this node'
|
||||
|
||||
if self_store is True and self.externalIP is not None:
|
||||
if self_store is True and self.externalIP:
|
||||
contact = Contact(self.id, self.externalIP, self.port, None, None)
|
||||
compact_ip = contact.compact_ip()
|
||||
elif '_rpcNodeContact' in kwargs:
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
# The docstrings in this module contain epytext markup; API documentation
|
||||
# may be created by processing this file with epydoc: http://epydoc.sf.net
|
||||
|
||||
import binascii
|
||||
import time
|
||||
|
||||
from twisted.internet import protocol, defer
|
||||
|
@ -23,6 +24,13 @@ reactor = twisted.internet.reactor
|
|||
|
||||
class TimeoutError(Exception):
|
||||
""" Raised when a RPC times out """
|
||||
def __init__(self, remote_contact_id):
|
||||
# remote_contact_id is a binary blob so we need to convert it
|
||||
# into something more readable
|
||||
msg = 'Timeout connecting to {}'.format(binascii.hexlify(remote_contact_id))
|
||||
Exception.__init__(self, msg)
|
||||
self.remote_contact_id = remote_contact_id
|
||||
|
||||
|
||||
class KademliaProtocol(protocol.DatagramProtocol):
|
||||
""" Implements all low-level network-related functions of a Kademlia node """
|
||||
|
|
|
@ -211,10 +211,20 @@ class LBRYFileSaver(LBRYFileDownloader):
|
|||
file_name = "_"
|
||||
if os.path.exists(os.path.join(self.download_directory, file_name)):
|
||||
ext_num = 1
|
||||
|
||||
def _get_file_name(ext):
|
||||
if len(file_name.split(".")):
|
||||
fn = ''.join(file_name.split(".")[:-1])
|
||||
file_ext = ''.join(file_name.split(".")[-1])
|
||||
return fn + "-" + str(ext) + "." + file_ext
|
||||
else:
|
||||
return file_name + "_" + str(ext)
|
||||
|
||||
while os.path.exists(os.path.join(self.download_directory,
|
||||
file_name + "_" + str(ext_num))):
|
||||
_get_file_name(ext_num))):
|
||||
ext_num += 1
|
||||
file_name = file_name + "_" + str(ext_num)
|
||||
|
||||
file_name = _get_file_name(ext_num)
|
||||
try:
|
||||
self.file_handle = open(os.path.join(self.download_directory, file_name), 'wb')
|
||||
self.file_written_to = os.path.join(self.download_directory, file_name)
|
||||
|
|
|
@ -131,7 +131,8 @@ def create_lbry_file(session, lbry_file_manager, file_name, file_handle, key=Non
|
|||
|
||||
def make_stream_desc_file(stream_hash):
|
||||
log.debug("creating the stream descriptor file")
|
||||
descriptor_writer = PlainStreamDescriptorWriter(file_name + conf.CRYPTSD_FILE_EXTENSION)
|
||||
descriptor_file_path = os.path.join(session.db_dir, file_name + conf.CRYPTSD_FILE_EXTENSION)
|
||||
descriptor_writer = PlainStreamDescriptorWriter(descriptor_file_path)
|
||||
|
||||
d = get_sd_info(lbry_file_manager.stream_info_manager, stream_hash, True)
|
||||
|
||||
|
|
|
@ -24,12 +24,33 @@ class ManagedLBRYFileDownloader(LBRYFileSaver):
|
|||
LBRYFileSaver.__init__(self, stream_hash, peer_finder, rate_limiter, blob_manager,
|
||||
stream_info_manager, payment_rate_manager, wallet, download_directory,
|
||||
upload_allowed, file_name)
|
||||
self.sd_hash = None
|
||||
self.txid = None
|
||||
self.uri = None
|
||||
self.rowid = rowid
|
||||
self.lbry_file_manager = lbry_file_manager
|
||||
self.saving_status = False
|
||||
|
||||
def restore(self):
|
||||
d = self.lbry_file_manager.get_lbry_file_status(self)
|
||||
d = self.stream_info_manager._get_sd_blob_hashes_for_stream(self.stream_hash)
|
||||
|
||||
def _save_sd_hash(sd_hash):
|
||||
if len(sd_hash):
|
||||
self.sd_hash = sd_hash[0]
|
||||
d = self.wallet._get_claim_metadata_for_sd_hash(self.sd_hash)
|
||||
else:
|
||||
d = defer.succeed(None)
|
||||
|
||||
return d
|
||||
|
||||
def _save_claim(name, txid):
|
||||
self.uri = name
|
||||
self.txid = txid
|
||||
return defer.succeed(None)
|
||||
|
||||
d.addCallback(_save_sd_hash)
|
||||
d.addCallback(lambda r: _save_claim(r[0], r[1]) if r else None)
|
||||
d.addCallback(lambda _: self.lbry_file_manager.get_lbry_file_status(self))
|
||||
|
||||
def restore_status(status):
|
||||
if status == ManagedLBRYFileDownloader.STATUS_RUNNING:
|
||||
|
@ -87,6 +108,24 @@ class ManagedLBRYFileDownloader(LBRYFileSaver):
|
|||
|
||||
d = LBRYFileSaver._start(self)
|
||||
|
||||
d.addCallback(lambda _: self.stream_info_manager._get_sd_blob_hashes_for_stream(self.stream_hash))
|
||||
|
||||
def _save_sd_hash(sd_hash):
|
||||
if len(sd_hash):
|
||||
self.sd_hash = sd_hash[0]
|
||||
d = self.wallet._get_claim_metadata_for_sd_hash(self.sd_hash)
|
||||
else:
|
||||
d = defer.succeed(None)
|
||||
|
||||
return d
|
||||
|
||||
def _save_claim(name, txid):
|
||||
self.uri = name
|
||||
self.txid = txid
|
||||
return defer.succeed(None)
|
||||
|
||||
d.addCallback(_save_sd_hash)
|
||||
d.addCallback(lambda r: _save_claim(r[0], r[1]) if r else None)
|
||||
d.addCallback(lambda _: self._save_status())
|
||||
|
||||
return d
|
||||
|
@ -119,7 +158,7 @@ class ManagedLBRYFileDownloaderFactory(object):
|
|||
def can_download(self, sd_validator):
|
||||
return True
|
||||
|
||||
def make_downloader(self, metadata, options, payment_rate_manager):
|
||||
def make_downloader(self, metadata, options, payment_rate_manager, download_directory=None, file_name=None):
|
||||
data_rate = options[0]
|
||||
upload_allowed = options[1]
|
||||
|
||||
|
@ -137,7 +176,9 @@ class ManagedLBRYFileDownloaderFactory(object):
|
|||
d.addCallback(lambda stream_hash: self.lbry_file_manager.add_lbry_file(stream_hash,
|
||||
payment_rate_manager,
|
||||
data_rate,
|
||||
upload_allowed))
|
||||
upload_allowed,
|
||||
download_directory=download_directory,
|
||||
file_name=file_name))
|
||||
return d
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -3,17 +3,16 @@ Keep track of which LBRY Files are downloading and store their LBRY File specifi
|
|||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from twisted.enterprise import adbapi
|
||||
from twisted.internet import defer, task, reactor
|
||||
from twisted.python.failure import Failure
|
||||
|
||||
import os
|
||||
import sys
|
||||
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader
|
||||
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory
|
||||
from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType
|
||||
from lbrynet.core.PaymentRateManager import PaymentRateManager
|
||||
from twisted.internet import defer, task, reactor
|
||||
from twisted.python.failure import Failure
|
||||
from lbrynet.cryptstream.client.CryptStreamDownloader import AlreadyStoppedError, CurrentlyStoppingError
|
||||
from lbrynet.core.sqlite_helpers import rerun_if_locked
|
||||
|
||||
|
@ -26,14 +25,14 @@ class LBRYFileManager(object):
|
|||
Keeps track of currently opened LBRY Files, their options, and their LBRY File specific metadata.
|
||||
"""
|
||||
|
||||
def __init__(self, session, stream_info_manager, sd_identifier):
|
||||
def __init__(self, session, stream_info_manager, sd_identifier, download_directory=None):
|
||||
self.session = session
|
||||
self.stream_info_manager = stream_info_manager
|
||||
self.sd_identifier = sd_identifier
|
||||
self.lbry_files = []
|
||||
self.sql_db = None
|
||||
if sys.platform.startswith("darwin"):
|
||||
self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads')
|
||||
if download_directory:
|
||||
self.download_directory = download_directory
|
||||
else:
|
||||
self.download_directory = os.getcwd()
|
||||
log.debug("Download directory for LBRYFileManager: %s", str(self.download_directory))
|
||||
|
@ -94,7 +93,10 @@ class LBRYFileManager(object):
|
|||
d.addCallback(start_lbry_files)
|
||||
return d
|
||||
|
||||
def start_lbry_file(self, rowid, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True):
|
||||
def start_lbry_file(self, rowid, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True,
|
||||
download_directory=None, file_name=None):
|
||||
if not download_directory:
|
||||
download_directory = self.download_directory
|
||||
payment_rate_manager.min_blob_data_payment_rate = blob_data_rate
|
||||
lbry_file_downloader = ManagedLBRYFileDownloader(rowid, stream_hash,
|
||||
self.session.peer_finder,
|
||||
|
@ -102,17 +104,19 @@ class LBRYFileManager(object):
|
|||
self.session.blob_manager,
|
||||
self.stream_info_manager, self,
|
||||
payment_rate_manager, self.session.wallet,
|
||||
self.download_directory,
|
||||
upload_allowed)
|
||||
download_directory,
|
||||
upload_allowed,
|
||||
file_name=file_name)
|
||||
self.lbry_files.append(lbry_file_downloader)
|
||||
d = lbry_file_downloader.set_stream_info()
|
||||
d.addCallback(lambda _: lbry_file_downloader)
|
||||
return d
|
||||
|
||||
def add_lbry_file(self, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True):
|
||||
def add_lbry_file(self, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True,
|
||||
download_directory=None, file_name=None):
|
||||
d = self._save_lbry_file(stream_hash, blob_data_rate)
|
||||
d.addCallback(lambda rowid: self.start_lbry_file(rowid, stream_hash, payment_rate_manager,
|
||||
blob_data_rate, upload_allowed))
|
||||
blob_data_rate, upload_allowed, download_directory, file_name))
|
||||
return d
|
||||
|
||||
def delete_lbry_file(self, lbry_file):
|
||||
|
@ -152,6 +156,7 @@ class LBRYFileManager(object):
|
|||
return defer.fail(Failure(ValueError("Could not find that LBRY file")))
|
||||
|
||||
def stop(self):
|
||||
|
||||
ds = []
|
||||
|
||||
def wait_for_finished(lbry_file, count=2):
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# pylint: skip-file
|
||||
# This file is not maintained, but might be used in the future
|
||||
#
|
||||
import logging
|
||||
import sys
|
||||
from lbrynet.lbrylive.LiveStreamCreator import StdOutLiveStreamCreator
|
||||
|
@ -15,7 +18,8 @@ from twisted.internet import defer, task
|
|||
|
||||
class LBRYStdinUploader():
|
||||
"""This class reads from standard in, creates a stream, and makes it available on the network."""
|
||||
def __init__(self, peer_port, dht_node_port, known_dht_nodes):
|
||||
def __init__(self, peer_port, dht_node_port, known_dht_nodes,
|
||||
stream_info_manager_class=DBLiveStreamMetadataManager, blob_manager_class=TempBlobManager):
|
||||
"""
|
||||
@param peer_port: the network port on which to listen for peers
|
||||
|
||||
|
@ -25,8 +29,8 @@ class LBRYStdinUploader():
|
|||
"""
|
||||
self.peer_port = peer_port
|
||||
self.lbry_server_port = None
|
||||
self.session = LBRYSession(blob_manager_class=TempBlobManager,
|
||||
stream_info_manager_class=DBLiveStreamMetadataManager,
|
||||
self.session = LBRYSession(blob_manager_class=blob_manager_class,
|
||||
stream_info_manager_class=stream_info_manager_class,
|
||||
dht_node_class=Node, dht_node_port=dht_node_port,
|
||||
known_dht_nodes=known_dht_nodes, peer_port=self.peer_port,
|
||||
use_upnp=False)
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
# pylint: skip-file
|
||||
# This file is not maintained, but might be used in the future
|
||||
#
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from lbrynet.lbrynet_console.plugins.LBRYLive.LBRYLiveStreamDownloader import LBRYLiveStreamDownloader
|
||||
from lbrynet.lbrylive.client.LiveStreamDownloader import LBRYLiveStreamDownloader
|
||||
from lbrynet.core.BlobManager import TempBlobManager
|
||||
from lbrynet.core.Session import LBRYSession
|
||||
from lbrynet.core.client.StandaloneBlobDownloader import StandaloneBlobDownloader
|
||||
|
@ -15,15 +18,17 @@ from twisted.internet import task
|
|||
|
||||
class LBRYStdoutDownloader():
|
||||
"""This class downloads a live stream from the network and outputs it to standard out."""
|
||||
def __init__(self, dht_node_port, known_dht_nodes):
|
||||
def __init__(self, dht_node_port, known_dht_nodes,
|
||||
stream_info_manager_class=DBLiveStreamMetadataManager, blob_manager_class=TempBlobManager):
|
||||
"""
|
||||
@param dht_node_port: the network port on which to listen for DHT node requests
|
||||
|
||||
@param known_dht_nodes: a list of (ip_address, dht_port) which will be used to join the DHT network
|
||||
|
||||
"""
|
||||
self.session = LBRYSession(blob_manager_class=TempBlobManager,
|
||||
stream_info_manager_class=DBLiveStreamMetadataManager,
|
||||
|
||||
self.session = LBRYSession(blob_manager_class=blob_manager_class,
|
||||
stream_info_manager_class=stream_info_manager_class,
|
||||
dht_node_class=Node, dht_node_port=dht_node_port, known_dht_nodes=known_dht_nodes,
|
||||
use_upnp=False)
|
||||
self.payment_rate_manager = BaseLiveStreamPaymentRateManager()
|
||||
|
|
|
@ -29,16 +29,10 @@ class ConsoleControl(basic.LineReceiver):
|
|||
|
||||
def send_initial_prompt(self):
|
||||
self.sendLine("")
|
||||
self.sendLine("In this early release of LBRY, some functions will not work\n"
|
||||
"until you have downloaded a full copy of our blockchain. To\n"
|
||||
"check whether you've caught up with the blockchain, use the\n"
|
||||
"command 'get-blockchain-status'.\n\n"
|
||||
"If, for example, you are unable to download some files or\n"
|
||||
"your balance is showing 0 when you know it shouldn't be, it\n"
|
||||
"is likely that the culprit is the blockchain.\n\n"
|
||||
"You should have received 1000 LBC the first time you ran\n"
|
||||
self.sendLine("You should have received 1000 LBC the first time you ran\n"
|
||||
"this program. If you did not, let us know! But first give\n"
|
||||
"them a couple of minutes to show up.\n\n"
|
||||
"them a moment to show up. The block time is currently 30\n"
|
||||
"seconds so they should show up within a couple of minutes.\n\n"
|
||||
"Welcome to lbrynet-console!")
|
||||
self.sendLine("")
|
||||
self.sendLine("Enter a command. Try 'get wonderfullife' or 'help' to see more options.")
|
||||
|
|
|
@ -120,7 +120,7 @@ class CommandHandlerFactory(object):
|
|||
return self.control_handler_class.prompt_description
|
||||
|
||||
def get_handler(self, console):
|
||||
return self.control_handler_class(console, *self.args)
|
||||
return self.control_handler_class(console, *self.args) # pylint: disable=not-callable
|
||||
|
||||
|
||||
class CommandHandler(object):
|
||||
|
@ -323,7 +323,7 @@ class GetWalletBalances(CommandHandler):
|
|||
def _show_time_behind_blockchain(self, rounded_time):
|
||||
if rounded_time.unit >= RoundedTime.HOUR:
|
||||
self.console.sendLine("\n\nYour balance may be out of date. This application\n"
|
||||
"is %s behind the LBC blockchain. It should take a few minutes to\n"
|
||||
"is %s behind the LBC blockchain. It may take a few minutes to\n"
|
||||
"catch up the first time you run this early version of LBRY.\n"
|
||||
"Please be patient =).\n\n" % str(rounded_time))
|
||||
else:
|
||||
|
@ -766,15 +766,15 @@ class AddStream(CommandHandler):
|
|||
|
||||
def do_download(stream_downloader):
|
||||
d = stream_downloader.start()
|
||||
d.addCallback(lambda _: self._download_succeeded(stream_downloader))
|
||||
d.addCallback(lambda result: self._download_succeeded(stream_downloader, result))
|
||||
return d
|
||||
|
||||
d.addCallback(do_download)
|
||||
d.addErrback(self._handle_download_error)
|
||||
return d
|
||||
|
||||
def _download_succeeded(self, stream_downloader):
|
||||
self.console.sendLine("%s has successfully downloaded." % str(stream_downloader))
|
||||
def _download_succeeded(self, stream_downloader, result):
|
||||
self.console.sendLine("%s: %s." % (str(stream_downloader), str(result)))
|
||||
|
||||
def _handle_download_error(self, err):
|
||||
if err.check(InsufficientFundsError):
|
||||
|
@ -783,6 +783,9 @@ class AddStream(CommandHandler):
|
|||
d.addCallback(get_time_behind_blockchain)
|
||||
d.addCallback(self._show_time_behind_blockchain_download)
|
||||
d.addErrback(self._log_recent_blockchain_time_error_download)
|
||||
d.addCallback(lambda _: self.wallet.is_first_run())
|
||||
d.addCallback(self._show_first_run_insufficient_funds)
|
||||
d.addErrback(self._log_first_run_check_error)
|
||||
else:
|
||||
log.error("An unexpected error has caused the download to stop: %s" % err.getTraceback())
|
||||
log_file = get_log_file()
|
||||
|
@ -797,12 +800,22 @@ class AddStream(CommandHandler):
|
|||
self.console.sendLine("\nThis application is %s behind the LBC blockchain, so some of your\n"
|
||||
"funds may not be available. Use 'get-blockchain-status' to check if\n"
|
||||
"your application is up to date with the blockchain.\n\n"
|
||||
"It should take a few minutes to catch up the first time you run this\n"
|
||||
"It may take a few minutes to catch up the first time you run this\n"
|
||||
"early version of LBRY. Please be patient =).\n\n" % str(rounded_time))
|
||||
|
||||
def _log_recent_blockchain_time_error_download(self, err):
|
||||
log.error("An error occurred trying to look up the most recent blocktime: %s", err.getTraceback())
|
||||
|
||||
def _show_first_run_insufficient_funds(self, is_first_run):
|
||||
if is_first_run:
|
||||
self.console.sendLine("\nThis appears to be the first time you have run LBRY. It can take\n"
|
||||
"a few minutes for your testing LBC to show up. If you haven't\n"
|
||||
"received them after a few minutes, please let us know.\n\n"
|
||||
"Thank you for your patience.\n\n")
|
||||
|
||||
def _log_first_run_check_error(self, err):
|
||||
log.error("An error occurred checking if this was the first run: %s", err.getTraceback())
|
||||
|
||||
|
||||
class AddStreamFromSD(AddStream):
|
||||
#prompt_description = "Add a stream from a stream descriptor file"
|
||||
|
@ -849,6 +862,9 @@ class AddStreamFromHash(AddStream):
|
|||
d.addCallback(get_time_behind_blockchain)
|
||||
d.addCallback(self._show_time_behind_blockchain_download)
|
||||
d.addErrback(self._log_recent_blockchain_time_error_download)
|
||||
d.addCallback(lambda _: self.wallet.is_first_run())
|
||||
d.addCallback(self._show_first_run_insufficient_funds)
|
||||
d.addErrback(self._log_first_run_check_error)
|
||||
d.addCallback(lambda _: self.console.sendLine("\n"))
|
||||
d.chainDeferred(self.finished_deferred)
|
||||
return
|
||||
|
@ -913,7 +929,7 @@ class AddStreamFromLBRYcrdName(AddStreamFromHash):
|
|||
self.console.sendLine("\nThis application is %s behind the LBC blockchain, which may be\n"
|
||||
"preventing this name from being resolved correctly. Use 'get-blockchain-status'\n"
|
||||
"to check if your application is up to date with the blockchain.\n\n"
|
||||
"It should take a few minutes to catch up the first time you run\n"
|
||||
"It may take a few minutes to catch up the first time you run\n"
|
||||
"this early version of LBRY. Please be patient =).\n\n" % str(rounded_time))
|
||||
else:
|
||||
self.console.sendLine("\n")
|
||||
|
@ -955,7 +971,7 @@ class AddStreamFromLBRYcrdName(AddStreamFromHash):
|
|||
def _get_info_to_show(self):
|
||||
i = AddStream._get_info_to_show(self)
|
||||
if self.description is not None:
|
||||
i.append(("description", self.description))
|
||||
i.append(("description", str(self.description)))
|
||||
if self.key_fee is None or self.key_fee_address is None:
|
||||
i.append(("decryption key fee", "Free"))
|
||||
else:
|
||||
|
@ -1850,13 +1866,23 @@ class Publish(CommandHandler):
|
|||
if rounded_time.unit >= RoundedTime.HOUR:
|
||||
self.console.sendLine("This application is %s behind the LBC blockchain\n"
|
||||
"and therefore may not have all of the funds you expect\n"
|
||||
"available at this time. It should take a few minutes to\n"
|
||||
"available at this time. It may take a few minutes to\n"
|
||||
"catch up the first time you run this early version of LBRY.\n"
|
||||
"Please be patient =).\n" % str(rounded_time))
|
||||
|
||||
def _log_best_blocktime_error(self, err):
|
||||
log.error("An error occurred checking the best time of the blockchain: %s", err.getTraceback())
|
||||
|
||||
def _show_first_run_insufficient_funds(self, is_first_run):
|
||||
if is_first_run:
|
||||
self.console.sendLine("\nThis appears to be the first time you have run LBRY. It can take\n"
|
||||
"a few minutes for your testing LBC to show up. If you haven't\n"
|
||||
"received them after a few minutes, please let us know.\n\n"
|
||||
"Thank you for your patience.\n\n")
|
||||
|
||||
def _log_first_run_check_error(self, err):
|
||||
log.error("An error occurred checking if this was the first run: %s", err.getTraceback())
|
||||
|
||||
def _show_publish_error(self, err):
|
||||
message = "An error occurred publishing %s to %s. Error: %s."
|
||||
if err.check(InsufficientFundsError):
|
||||
|
@ -1864,6 +1890,9 @@ class Publish(CommandHandler):
|
|||
d.addCallback(get_time_behind_blockchain)
|
||||
d.addCallback(self._show_time_behind_blockchain)
|
||||
d.addErrback(self._log_best_blocktime_error)
|
||||
d.addCallback(lambda _: self.wallet.is_first_run())
|
||||
d.addCallback(self._show_first_run_insufficient_funds)
|
||||
d.addErrback(self._log_first_run_check_error)
|
||||
error_message = "Insufficient funds"
|
||||
else:
|
||||
d = defer.succeed(True)
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
import logging
|
||||
from lbrynet.core.Session import LBRYSession
|
||||
import os.path
|
||||
import argparse
|
||||
import requests
|
||||
import locale
|
||||
import sys
|
||||
import webbrowser
|
||||
|
||||
if sys.platform == "darwin":
|
||||
from appdirs import user_data_dir
|
||||
from yapsy.PluginManager import PluginManager
|
||||
from twisted.internet import defer, threads, stdio, task, error
|
||||
# from lbrynet.core.client.AutoDownloader import AutoFetcher
|
||||
from jsonrpc.proxy import JSONRPCProxy
|
||||
|
||||
from lbrynet.core.Session import LBRYSession
|
||||
from lbrynet.lbrynet_console.ConsoleControl import ConsoleControl
|
||||
from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings
|
||||
from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager
|
||||
from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE # , MIN_BLOB_INFO_PAYMENT_RATE
|
||||
from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, API_CONNECTION_STRING # , MIN_BLOB_INFO_PAYMENT_RATE
|
||||
from lbrynet.core.utils import generate_id
|
||||
from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier
|
||||
from lbrynet.core.PaymentRateManager import PaymentRateManager
|
||||
|
@ -23,10 +28,8 @@ from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifi
|
|||
from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileOpenerFactory
|
||||
from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType
|
||||
from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager
|
||||
#from lbrynet.lbrylive.PaymentRateManager import LiveStreamPaymentRateManager
|
||||
from lbrynet.lbrynet_console.ControlHandlers import ApplicationStatusFactory, GetWalletBalancesFactory, ShutDownFactory
|
||||
#from lbrynet.lbrynet_console.ControlHandlers import AutoFetcherStartFactory, AutoFetcherStopFactory
|
||||
from lbrynet.lbrynet_console.ControlHandlers import ImmediateAnnounceAllBlobsFactory #, AutoFetcherStatusFactory
|
||||
from lbrynet.lbrynet_console.ControlHandlers import ImmediateAnnounceAllBlobsFactory
|
||||
from lbrynet.lbrynet_console.ControlHandlers import LBRYFileStatusFactory, DeleteLBRYFileChooserFactory
|
||||
from lbrynet.lbrynet_console.ControlHandlers import ToggleLBRYFileRunningChooserFactory
|
||||
from lbrynet.lbrynet_console.ControlHandlers import ModifyApplicationDefaultsFactory
|
||||
|
@ -40,7 +43,7 @@ from lbrynet.lbrynet_console.ControlHandlers import ShowServerStatusFactory, Mod
|
|||
from lbrynet.lbrynet_console.ControlHandlers import ModifyLBRYFileOptionsChooserFactory, StatusFactory
|
||||
from lbrynet.lbrynet_console.ControlHandlers import PeerStatsAndSettingsChooserFactory, PublishFactory
|
||||
from lbrynet.lbrynet_console.ControlHandlers import BlockchainStatusFactory
|
||||
from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet
|
||||
from lbrynet.core.LBRYWallet import LBRYcrdWallet, LBRYumWallet
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -49,9 +52,9 @@ alert = logging.getLogger("lbryalert." + __name__)
|
|||
|
||||
class LBRYConsole():
|
||||
"""A class which can upload and download file streams to and from the network"""
|
||||
def __init__(self, peer_port, dht_node_port, known_dht_nodes, wallet_type,
|
||||
def __init__(self, peer_port, dht_node_port, known_dht_nodes, fake_wallet,
|
||||
lbrycrd_conf, lbrycrd_dir, use_upnp, data_dir, created_data_dir,
|
||||
lbrycrdd_path, start_lbrycrdd):
|
||||
lbrycrdd_path):
|
||||
"""
|
||||
@param peer_port: the network port on which to listen for peers
|
||||
|
||||
|
@ -62,7 +65,7 @@ class LBRYConsole():
|
|||
self.peer_port = peer_port
|
||||
self.dht_node_port = dht_node_port
|
||||
self.known_dht_nodes = known_dht_nodes
|
||||
self.wallet_type = wallet_type
|
||||
self.fake_wallet = fake_wallet
|
||||
self.lbrycrd_conf = lbrycrd_conf
|
||||
self.lbrycrd_dir = lbrycrd_dir
|
||||
if not self.lbrycrd_dir:
|
||||
|
@ -72,10 +75,7 @@ class LBRYConsole():
|
|||
self.lbrycrd_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd")
|
||||
if not self.lbrycrd_conf:
|
||||
self.lbrycrd_conf = os.path.join(self.lbrycrd_dir, "lbrycrd.conf")
|
||||
# self.autofetcher_conf = os.path.join(self.lbrycrd_dir, "autofetcher.conf")
|
||||
self.lbrycrdd_path = lbrycrdd_path
|
||||
self.default_lbrycrdd_path = "./lbrycrdd"
|
||||
self.start_lbrycrdd = start_lbrycrdd
|
||||
self.use_upnp = use_upnp
|
||||
self.lbry_server_port = None
|
||||
self.session = None
|
||||
|
@ -99,7 +99,6 @@ class LBRYConsole():
|
|||
self.sd_identifier = StreamDescriptorIdentifier()
|
||||
self.plugin_objects = []
|
||||
self.db_migration_revisions = None
|
||||
# self.autofetcher = None
|
||||
|
||||
def start(self):
|
||||
"""Initialize the session and restore everything to its saved state"""
|
||||
|
@ -111,7 +110,6 @@ class LBRYConsole():
|
|||
d.addCallback(lambda _: add_lbry_file_to_sd_identifier(self.sd_identifier))
|
||||
d.addCallback(lambda _: self._setup_lbry_file_manager())
|
||||
d.addCallback(lambda _: self._setup_lbry_file_opener())
|
||||
#d.addCallback(lambda _: self._get_autofetcher())
|
||||
d.addCallback(lambda _: self._setup_control_handlers())
|
||||
d.addCallback(lambda _: self._setup_query_handlers())
|
||||
d.addCallback(lambda _: self._load_plugins())
|
||||
|
@ -120,10 +118,6 @@ class LBRYConsole():
|
|||
d.addErrback(self._show_start_error)
|
||||
return d
|
||||
|
||||
# def _get_autofetcher(self):
|
||||
# self.autofetcher = AutoFetcher(self.session, self.lbry_file_manager, self.lbry_file_metadata_manager,
|
||||
# self.session.wallet, self.sd_identifier, self.autofetcher_conf)
|
||||
|
||||
def _show_start_error(self, error):
|
||||
print error.getTraceback()
|
||||
log.error("An error occurred during start up: %s", error.getTraceback())
|
||||
|
@ -200,30 +194,6 @@ class LBRYConsole():
|
|||
d = self.settings.start()
|
||||
d.addCallback(lambda _: self.settings.get_lbryid())
|
||||
d.addCallback(self.set_lbryid)
|
||||
d.addCallback(lambda _: self.get_lbrycrdd_path())
|
||||
return d
|
||||
|
||||
def get_lbrycrdd_path(self):
|
||||
|
||||
if not self.start_lbrycrdd:
|
||||
return defer.succeed(None)
|
||||
|
||||
def get_lbrycrdd_path_conf_file():
|
||||
lbrycrdd_path_conf_path = os.path.join(os.path.expanduser("~"), ".lbrycrddpath.conf")
|
||||
if not os.path.exists(lbrycrdd_path_conf_path):
|
||||
return ""
|
||||
lbrycrdd_path_conf = open(lbrycrdd_path_conf_path)
|
||||
lines = lbrycrdd_path_conf.readlines()
|
||||
return lines
|
||||
|
||||
d = threads.deferToThread(get_lbrycrdd_path_conf_file)
|
||||
|
||||
def load_lbrycrdd_path(conf):
|
||||
for line in conf:
|
||||
if len(line.strip()) and line.strip()[0] != "#":
|
||||
self.lbrycrdd_path = line.strip()
|
||||
|
||||
d.addCallback(load_lbrycrdd_path)
|
||||
return d
|
||||
|
||||
def set_lbryid(self, lbryid):
|
||||
|
@ -244,17 +214,14 @@ class LBRYConsole():
|
|||
return d
|
||||
|
||||
def get_wallet():
|
||||
if self.wallet_type == "lbrycrd":
|
||||
lbrycrdd_path = None
|
||||
if self.start_lbrycrdd is True:
|
||||
lbrycrdd_path = self.lbrycrdd_path
|
||||
if not lbrycrdd_path:
|
||||
lbrycrdd_path = self.default_lbrycrdd_path
|
||||
if self.fake_wallet:
|
||||
d = defer.succeed(PTCWallet(self.db_dir))
|
||||
elif self.lbrycrdd_path is not None:
|
||||
d = defer.succeed(LBRYcrdWallet(self.db_dir, wallet_dir=self.lbrycrd_dir,
|
||||
wallet_conf=self.lbrycrd_conf,
|
||||
lbrycrdd_path=lbrycrdd_path))
|
||||
lbrycrdd_path=self.lbrycrdd_path))
|
||||
else:
|
||||
d = defer.succeed(PTCWallet(self.db_dir))
|
||||
d = defer.succeed(LBRYumWallet(self.db_dir))
|
||||
d.addCallback(lambda wallet: {"wallet": wallet})
|
||||
return d
|
||||
|
||||
|
@ -292,7 +259,7 @@ class LBRYConsole():
|
|||
return dl
|
||||
|
||||
def check_first_run(self):
|
||||
d = self.session.wallet.check_first_run()
|
||||
d = self.session.wallet.is_first_run()
|
||||
d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run else 0.0)
|
||||
return d
|
||||
|
||||
|
@ -372,14 +339,11 @@ class LBRYConsole():
|
|||
ModifyLBRYFileOptionsChooserFactory(self.lbry_file_manager),
|
||||
AddStreamFromHashFactory(self.sd_identifier, self.session, self.session.wallet),
|
||||
StatusFactory(self, self.session.rate_limiter, self.lbry_file_manager,
|
||||
self.session.blob_manager, self.session.wallet if self.wallet_type == 'lbrycrd' else None),
|
||||
# AutoFetcherStartFactory(self.autofetcher),
|
||||
# AutoFetcherStopFactory(self.autofetcher),
|
||||
# AutoFetcherStatusFactory(self.autofetcher),
|
||||
self.session.blob_manager, self.session.wallet if not self.fake_wallet else None),
|
||||
ImmediateAnnounceAllBlobsFactory(self.session.blob_manager)
|
||||
]
|
||||
self.add_control_handlers(handlers)
|
||||
if self.wallet_type == 'lbrycrd':
|
||||
if not self.fake_wallet:
|
||||
lbrycrd_handlers = [
|
||||
AddStreamFromLBRYcrdNameFactory(self.sd_identifier, self.session,
|
||||
self.session.wallet),
|
||||
|
@ -503,7 +467,6 @@ class LBRYConsole():
|
|||
|
||||
|
||||
def launch_lbry_console():
|
||||
|
||||
from twisted.internet import reactor
|
||||
|
||||
parser = argparse.ArgumentParser(description="Launch a lbrynet console")
|
||||
|
@ -519,15 +482,9 @@ def launch_lbry_console():
|
|||
parser.add_argument("--dht_node_port",
|
||||
help="The port on which the console will listen for DHT connections.",
|
||||
type=int, default=4444)
|
||||
parser.add_argument("--wallet_type",
|
||||
help="Either 'lbrycrd' or 'ptc'.",
|
||||
type=str, default="lbrycrd")
|
||||
parser.add_argument("--lbrycrd_wallet_dir",
|
||||
help="The directory in which lbrycrd data will stored. Used if lbrycrdd is "
|
||||
"launched by this application.")
|
||||
parser.add_argument("--lbrycrd_wallet_conf",
|
||||
help="The configuration file for the LBRYcrd wallet. Default: ~/.lbrycrd/lbrycrd.conf",
|
||||
type=str)
|
||||
parser.add_argument("--fake_wallet",
|
||||
help="Testing purposes only. Use a non-blockchain wallet.",
|
||||
action="store_true")
|
||||
parser.add_argument("--no_dht_bootstrap",
|
||||
help="Don't try to connect to the DHT",
|
||||
action="store_true")
|
||||
|
@ -544,15 +501,18 @@ def launch_lbry_console():
|
|||
action="store_true")
|
||||
parser.add_argument("--data_dir",
|
||||
help=("The full path to the directory in which lbrynet data and metadata will be stored. "
|
||||
"Default: ~/.lbrynet"),
|
||||
"Default: ~/.lbrynet on linux, ~/Library/Application Support/lbrynet on OS X"),
|
||||
type=str)
|
||||
parser.add_argument("--lbrycrdd_path",
|
||||
help="The path to lbrycrdd, which will be launched if it isn't running, unless "
|
||||
"launching lbrycrdd is disabled by --disable_launch_lbrycrdd. By default, "
|
||||
"the file ~/.lbrycrddpath.conf will be checked, and if no path is found "
|
||||
"there, it will be ./lbrycrdd")
|
||||
parser.add_argument("--disable_launch_lbrycrdd",
|
||||
help="Don't launch lbrycrdd even if it's not running.")
|
||||
help="The path to lbrycrdd, which will be launched if it isn't running. If"
|
||||
"this option is chosen, lbrycrdd will be used as the interface to the"
|
||||
"blockchain. By default, a lightweight interface is used.")
|
||||
parser.add_argument("--lbrycrd_wallet_dir",
|
||||
help="The directory in which lbrycrd data will stored. Used if lbrycrdd is "
|
||||
"launched by this application.")
|
||||
parser.add_argument("--lbrycrd_wallet_conf",
|
||||
help="The configuration file for the LBRYcrd wallet. Default: ~/.lbrycrd/lbrycrd.conf",
|
||||
type=str)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
@ -573,6 +533,9 @@ def launch_lbry_console():
|
|||
|
||||
created_data_dir = False
|
||||
if not args.data_dir:
|
||||
if sys.platform == "darwin":
|
||||
data_dir = user_data_dir("LBRY")
|
||||
else:
|
||||
data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet")
|
||||
else:
|
||||
data_dir = args.data_dir
|
||||
|
@ -580,7 +543,17 @@ def launch_lbry_console():
|
|||
os.mkdir(data_dir)
|
||||
created_data_dir = True
|
||||
|
||||
daemon = JSONRPCProxy.from_url(API_CONNECTION_STRING)
|
||||
try:
|
||||
daemon.is_running()
|
||||
log.info("Attempt to start lbrynet-console while lbrynet-daemon is running")
|
||||
print "lbrynet-daemon is running, you must turn it off before using lbrynet-console"
|
||||
print "If you're running the app, quit before starting lbrynet-console"
|
||||
print "If you're running lbrynet-daemon in a terminal, run 'stop-lbrynet-daemon' to turn it off"
|
||||
|
||||
webbrowser.open("http://localhost:5279")
|
||||
|
||||
except:
|
||||
log_format = "(%(asctime)s)[%(filename)s:%(lineno)s] %(funcName)s(): %(message)s"
|
||||
formatter = logging.Formatter(log_format)
|
||||
|
||||
|
@ -591,16 +564,14 @@ def launch_lbry_console():
|
|||
file_handler.addFilter(logging.Filter("lbrynet"))
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
console = LBRYConsole(peer_port, dht_node_port, bootstrap_nodes, wallet_type=args.wallet_type,
|
||||
|
||||
console = LBRYConsole(peer_port, dht_node_port, bootstrap_nodes, fake_wallet=args.fake_wallet,
|
||||
lbrycrd_conf=args.lbrycrd_wallet_conf, lbrycrd_dir=args.lbrycrd_wallet_dir,
|
||||
use_upnp=not args.disable_upnp, data_dir=data_dir,
|
||||
created_data_dir=created_data_dir, lbrycrdd_path=args.lbrycrdd_path,
|
||||
start_lbrycrdd=not args.disable_launch_lbrycrdd)
|
||||
created_data_dir=created_data_dir, lbrycrdd_path=args.lbrycrdd_path)
|
||||
|
||||
d = task.deferLater(reactor, 0, console.start)
|
||||
|
||||
d.addErrback(lambda _: reactor.stop())
|
||||
|
||||
reactor.addSystemEventTrigger('before', 'shutdown', console.shut_down)
|
||||
reactor.run()
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ class ValuableBlobHashQueryHandler(ValuableQueryHandler):
|
|||
for blob_hash, count in valuable_hashes:
|
||||
hashes_and_scores.append((blob_hash, 1.0 * count / 10.0))
|
||||
if len(hashes_and_scores) != 0:
|
||||
log.info("Responding to a valuable blob hashes request with %s blob hashes: %s",
|
||||
log.info("Responding to a valuable blob hashes request with %s blob hashes",
|
||||
str(len(hashes_and_scores)))
|
||||
expected_payment = 1.0 * len(hashes_and_scores) * self.valuable_blob_hash_payment_rate / 1000.0
|
||||
self.wallet.add_expected_payment(self.peer, expected_payment)
|
||||
|
@ -193,7 +193,7 @@ class ValuableBlobLengthQueryHandler(ValuableQueryHandler):
|
|||
if success is True:
|
||||
lengths.append(response_pair)
|
||||
if len(lengths) > 0:
|
||||
log.info("Responding with %s blob lengths: %s", str(len(lengths)))
|
||||
log.info("Responding with %s blob lengths", str(len(lengths)))
|
||||
expected_payment = 1.0 * len(lengths) * self.blob_length_payment_rate / 1000.0
|
||||
self.wallet.add_expected_payment(self.peer, expected_payment)
|
||||
self.peer.update_stats('uploaded_valuable_blob_infos', len(lengths))
|
||||
|
|
59
lbrynet/lbrynet_daemon/LBRYDaemonCLI.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
import sys
|
||||
import json
|
||||
|
||||
from lbrynet.conf import API_CONNECTION_STRING, LOG_FILE_NAME
|
||||
from jsonrpc.proxy import JSONRPCProxy
|
||||
|
||||
help_msg = "Useage: lbrynet-cli method json-args\n" \
|
||||
+ "Examples: " \
|
||||
+ "lbrynet-cli resolve_name '{\"name\": \"what\"}'\n" \
|
||||
+ "lbrynet-cli get_balance\n" \
|
||||
+ "lbrynet-cli help '{\"function\": \"resolve_name\"}'\n" \
|
||||
+ "\n******lbrynet-cli functions******\n"
|
||||
|
||||
|
||||
def main():
|
||||
api = JSONRPCProxy.from_url(API_CONNECTION_STRING)
|
||||
|
||||
try:
|
||||
s = api.is_running()
|
||||
except:
|
||||
print "lbrynet-daemon isn't running"
|
||||
sys.exit(1)
|
||||
|
||||
args = sys.argv[1:]
|
||||
meth = args[0]
|
||||
|
||||
msg = help_msg
|
||||
for f in api.help():
|
||||
msg += f + "\n"
|
||||
|
||||
if meth in ['--help', '-h', 'help']:
|
||||
print msg
|
||||
sys.exit(1)
|
||||
|
||||
if len(args) > 1:
|
||||
if isinstance(args[1], dict):
|
||||
params = args[1]
|
||||
elif isinstance(args[1], basestring):
|
||||
params = json.loads(args[1])
|
||||
else:
|
||||
params = None
|
||||
|
||||
if meth in api.help():
|
||||
try:
|
||||
if params:
|
||||
r = api.call(meth, params)
|
||||
else:
|
||||
r = api.call(meth)
|
||||
print r
|
||||
except:
|
||||
print "Something went wrong, here's the usage for %s:" % meth
|
||||
print api.help({'function': meth})
|
||||
else:
|
||||
print "Unknown function"
|
||||
print msg
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
127
lbrynet/lbrynet_daemon/LBRYDaemonControl.py
Normal file
|
@ -0,0 +1,127 @@
|
|||
import argparse
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import webbrowser
|
||||
import sys
|
||||
import socket
|
||||
import platform
|
||||
from appdirs import user_data_dir
|
||||
|
||||
from twisted.web import server
|
||||
from twisted.internet import reactor, defer
|
||||
from jsonrpc.proxy import JSONRPCProxy
|
||||
|
||||
from lbrynet.core import log_support
|
||||
from lbrynet.lbrynet_daemon.LBRYDaemonServer import LBRYDaemonServer, LBRYDaemonRequest
|
||||
from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, \
|
||||
DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH, LOG_FILE_NAME
|
||||
|
||||
# TODO: stop it!
|
||||
if sys.platform != "darwin":
|
||||
log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet")
|
||||
else:
|
||||
log_dir = user_data_dir("LBRY")
|
||||
|
||||
if not os.path.isdir(log_dir):
|
||||
os.mkdir(log_dir)
|
||||
|
||||
lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
REMOTE_SERVER = "www.google.com"
|
||||
|
||||
|
||||
def test_internet_connection():
|
||||
try:
|
||||
host = socket.gethostbyname(REMOTE_SERVER)
|
||||
s = socket.create_connection((host, 80), 2)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def stop():
|
||||
def _disp_shutdown():
|
||||
print "Shutting down lbrynet-daemon from command line"
|
||||
log.info("Shutting down lbrynet-daemon from command line")
|
||||
|
||||
def _disp_not_running():
|
||||
print "Attempt to shut down lbrynet-daemon from command line when daemon isn't running"
|
||||
log.info("Attempt to shut down lbrynet-daemon from command line when daemon isn't running")
|
||||
|
||||
d = defer.Deferred(None)
|
||||
d.addCallback(lambda _: JSONRPCProxy.from_url(API_CONNECTION_STRING).stop())
|
||||
d.addCallbacks(lambda _: _disp_shutdown(), lambda _: _disp_not_running())
|
||||
d.callback(None)
|
||||
|
||||
|
||||
def start():
|
||||
parser = argparse.ArgumentParser(description="Launch lbrynet-daemon")
|
||||
parser.add_argument("--wallet",
|
||||
help="lbrycrd or lbryum, default lbryum",
|
||||
type=str,
|
||||
default='')
|
||||
parser.add_argument("--ui",
|
||||
help="path to custom UI folder",
|
||||
default=None)
|
||||
parser.add_argument("--branch",
|
||||
help="Branch of lbry-web-ui repo to use, defaults on master")
|
||||
parser.add_argument('--no-launch', dest='launchui', action="store_false")
|
||||
parser.add_argument('--log-to-console', dest='logtoconsole', action="store_true")
|
||||
parser.add_argument('--quiet', dest='quiet', action="store_true")
|
||||
parser.set_defaults(branch=False, launchui=True, logtoconsole=False, quiet=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
log_support.configureFileHandler(lbrynet_log)
|
||||
if args.logtoconsole:
|
||||
log_support.configureConsole()
|
||||
|
||||
|
||||
try:
|
||||
JSONRPCProxy.from_url(API_CONNECTION_STRING).is_running()
|
||||
log.info("lbrynet-daemon is already running")
|
||||
if not args.logtoconsole:
|
||||
print "lbrynet-daemon is already running"
|
||||
if args.launchui:
|
||||
webbrowser.open(UI_ADDRESS)
|
||||
return
|
||||
except:
|
||||
pass
|
||||
|
||||
log.info("Starting lbrynet-daemon from command line")
|
||||
|
||||
if not args.logtoconsole and not args.quiet:
|
||||
print "Starting lbrynet-daemon from command line"
|
||||
print "To view activity, view the log file here: " + lbrynet_log
|
||||
print "Web UI is available at http://%s:%i" % (API_INTERFACE, API_PORT)
|
||||
print "JSONRPC API is available at " + API_CONNECTION_STRING
|
||||
print "To quit press ctrl-c or call 'stop' via the API"
|
||||
|
||||
if test_internet_connection():
|
||||
lbry = LBRYDaemonServer()
|
||||
|
||||
d = lbry.start(branch=args.branch if args.branch else DEFAULT_UI_BRANCH,
|
||||
user_specified=args.ui,
|
||||
wallet=args.wallet,
|
||||
branch_specified=True if args.branch else False)
|
||||
if args.launchui:
|
||||
d.addCallback(lambda _: webbrowser.open(UI_ADDRESS))
|
||||
|
||||
lbrynet_server = server.Site(lbry.root)
|
||||
lbrynet_server.requestFactory = LBRYDaemonRequest
|
||||
reactor.listenTCP(API_PORT, lbrynet_server, interface=API_INTERFACE)
|
||||
reactor.run()
|
||||
|
||||
if not args.logtoconsole and not args.quiet:
|
||||
print "\nClosing lbrynet-daemon"
|
||||
else:
|
||||
log.info("Not connected to internet, unable to start")
|
||||
if not args.logtoconsole:
|
||||
print "Not connected to internet, unable to start"
|
||||
return
|
||||
|
||||
if __name__ == "__main__":
|
||||
start()
|
397
lbrynet/lbrynet_daemon/LBRYDaemonServer.py
Normal file
|
@ -0,0 +1,397 @@
|
|||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import json
|
||||
import sys
|
||||
import mimetypes
|
||||
import mimetools
|
||||
import tempfile
|
||||
import time
|
||||
import cgi
|
||||
|
||||
from datetime import datetime
|
||||
from appdirs import user_data_dir
|
||||
from twisted.web import server, static, resource
|
||||
from twisted.internet import defer, interfaces, error, reactor, task, threads
|
||||
|
||||
from zope.interface import implements
|
||||
|
||||
from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon
|
||||
from lbrynet.conf import API_CONNECTION_STRING, API_ADDRESS, DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH, LOG_FILE_NAME
|
||||
|
||||
|
||||
# TODO: omg, this code is essentially duplicated in LBRYDaemon
|
||||
if sys.platform != "darwin":
|
||||
data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet")
|
||||
else:
|
||||
data_dir = user_data_dir("LBRY")
|
||||
if not os.path.isdir(data_dir):
|
||||
os.mkdir(data_dir)
|
||||
|
||||
lbrynet_log = os.path.join(data_dir, LOG_FILE_NAME)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LBRYDaemonRequest(server.Request):
|
||||
"""
|
||||
For LBRY specific request functionality. Currently just provides
|
||||
handling for large multipart POST requests, taken from here:
|
||||
http://sammitch.ca/2013/07/handling-large-requests-in-twisted/
|
||||
|
||||
For multipart POST requests, this populates self.args with temp
|
||||
file objects instead of strings. Note that these files don't auto-delete
|
||||
on close because we want to be able to move and rename them.
|
||||
|
||||
"""
|
||||
|
||||
# max amount of memory to allow any ~single~ request argument [ie: POSTed file]
|
||||
# note: this value seems to be taken with a grain of salt, memory usage may spike
|
||||
# FAR above this value in some cases.
|
||||
# eg: set the memory limit to 5 MB, write 2 blocks of 4MB, mem usage will
|
||||
# have spiked to 8MB before the data is rolled to disk after the
|
||||
# second write completes.
|
||||
memorylimit = 1024*1024*100
|
||||
|
||||
# enable/disable debug logging
|
||||
do_log = False
|
||||
|
||||
# re-defined only for debug/logging purposes
|
||||
def gotLength(self, length):
|
||||
if self.do_log:
|
||||
print '%f Headers received, Content-Length: %d' % (time.time(), length)
|
||||
server.Request.gotLength(self, length)
|
||||
|
||||
# re-definition of twisted.web.server.Request.requestreceived, the only difference
|
||||
# is that self.parse_multipart() is used rather than cgi.parse_multipart()
|
||||
def requestReceived(self, command, path, version):
|
||||
from twisted.web.http import parse_qs
|
||||
if self.do_log:
|
||||
print '%f Request Received' % time.time()
|
||||
|
||||
self.content.seek(0,0)
|
||||
self.args = {}
|
||||
self.stack = []
|
||||
|
||||
self.method, self.uri = command, path
|
||||
self.clientproto = version
|
||||
x = self.uri.split(b'?', 1)
|
||||
|
||||
if len(x) == 1:
|
||||
self.path = self.uri
|
||||
else:
|
||||
self.path, argstring = x
|
||||
self.args = parse_qs(argstring, 1)
|
||||
|
||||
# cache the client and server information, we'll need this later to be
|
||||
# serialized and sent with the request so CGIs will work remotely
|
||||
self.client = self.channel.transport.getPeer()
|
||||
self.host = self.channel.transport.getHost()
|
||||
|
||||
# Argument processing
|
||||
args = self.args
|
||||
ctype = self.requestHeaders.getRawHeaders(b'content-type')
|
||||
if ctype is not None:
|
||||
ctype = ctype[0]
|
||||
|
||||
if self.method == b"POST" and ctype:
|
||||
mfd = b'multipart/form-data'
|
||||
key, pdict = cgi.parse_header(ctype)
|
||||
if key == b'application/x-www-form-urlencoded':
|
||||
args.update(parse_qs(self.content.read(), 1))
|
||||
elif key == mfd:
|
||||
try:
|
||||
self.content.seek(0,0)
|
||||
args.update(self.parse_multipart(self.content, pdict))
|
||||
#args.update(cgi.parse_multipart(self.content, pdict))
|
||||
|
||||
except KeyError as e:
|
||||
if e.args[0] == b'content-disposition':
|
||||
# Parse_multipart can't cope with missing
|
||||
# content-dispostion headers in multipart/form-data
|
||||
# parts, so we catch the exception and tell the client
|
||||
# it was a bad request.
|
||||
self.channel.transport.write(
|
||||
b"HTTP/1.1 400 Bad Request\r\n\r\n")
|
||||
self.channel.transport.loseConnection()
|
||||
return
|
||||
raise
|
||||
|
||||
self.content.seek(0, 0)
|
||||
|
||||
self.process()
|
||||
|
||||
# re-definition of cgi.parse_multipart that uses a single temporary file to store
|
||||
# data rather than storing 2 to 3 copies in various lists.
|
||||
def parse_multipart(self, fp, pdict):
|
||||
if self.do_log:
|
||||
print '%f Parsing Multipart data: ' % time.time()
|
||||
rewind = fp.tell() #save cursor
|
||||
fp.seek(0,0) #reset cursor
|
||||
|
||||
boundary = ""
|
||||
if 'boundary' in pdict:
|
||||
boundary = pdict['boundary']
|
||||
if not cgi.valid_boundary(boundary):
|
||||
raise ValueError, ('Invalid boundary in multipart form: %r'
|
||||
% (boundary,))
|
||||
|
||||
nextpart = "--" + boundary
|
||||
lastpart = "--" + boundary + "--"
|
||||
partdict = {}
|
||||
terminator = ""
|
||||
|
||||
while terminator != lastpart:
|
||||
c_bytes = -1
|
||||
|
||||
data = tempfile.NamedTemporaryFile(delete=False)
|
||||
if terminator:
|
||||
# At start of next part. Read headers first.
|
||||
headers = mimetools.Message(fp)
|
||||
clength = headers.getheader('content-length')
|
||||
if clength:
|
||||
try:
|
||||
c_bytes = int(clength)
|
||||
except ValueError:
|
||||
pass
|
||||
if c_bytes > 0:
|
||||
data.write(fp.read(c_bytes))
|
||||
# Read lines until end of part.
|
||||
while 1:
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
terminator = lastpart # End outer loop
|
||||
break
|
||||
if line[:2] == "--":
|
||||
terminator = line.strip()
|
||||
if terminator in (nextpart, lastpart):
|
||||
break
|
||||
data.write(line)
|
||||
# Done with part.
|
||||
if data.tell() == 0:
|
||||
continue
|
||||
if c_bytes < 0:
|
||||
# if a Content-Length header was not supplied with the MIME part
|
||||
# then the trailing line break must be removed.
|
||||
# we have data, read the last 2 bytes
|
||||
rewind = min(2, data.tell())
|
||||
data.seek(-rewind, os.SEEK_END)
|
||||
line = data.read(2)
|
||||
if line[-2:] == "\r\n":
|
||||
data.seek(-2, os.SEEK_END)
|
||||
data.truncate()
|
||||
elif line[-1:] == "\n":
|
||||
data.seek(-1, os.SEEK_END)
|
||||
data.truncate()
|
||||
|
||||
line = headers['content-disposition']
|
||||
if not line:
|
||||
continue
|
||||
key, params = cgi.parse_header(line)
|
||||
if key != 'form-data':
|
||||
continue
|
||||
if 'name' in params:
|
||||
name = params['name']
|
||||
# kludge in the filename
|
||||
if 'filename' in params:
|
||||
fname_index = name + '_filename'
|
||||
if fname_index in partdict:
|
||||
partdict[fname_index].append(params['filename'])
|
||||
else:
|
||||
partdict[fname_index] = [params['filename']]
|
||||
else:
|
||||
# Unnamed parts are not returned at all.
|
||||
continue
|
||||
data.seek(0,0)
|
||||
if name in partdict:
|
||||
partdict[name].append(data)
|
||||
else:
|
||||
partdict[name] = [data]
|
||||
|
||||
fp.seek(rewind) # Restore cursor
|
||||
return partdict
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class LBRYFileStreamer(object):
|
||||
"""
|
||||
Writes downloaded LBRY file to request as the download comes in, pausing and resuming as requested
|
||||
used for Chrome
|
||||
"""
|
||||
|
||||
implements(interfaces.IPushProducer)
|
||||
|
||||
def __init__(self, request, path, start, stop, size):
|
||||
self._request = request
|
||||
self._fileObject = file(path)
|
||||
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-"
|
||||
self._cursor = self._start_pos = int(start)
|
||||
self._file_size = size
|
||||
self._depth = 0
|
||||
|
||||
self._paused = self._sent_bytes = self._stopped = False
|
||||
self._delay = 0.25
|
||||
self._deferred = defer.succeed(None)
|
||||
|
||||
self._request.setResponseCode(206)
|
||||
self._request.setHeader('accept-ranges', 'bytes')
|
||||
self._request.setHeader('content-type', self._content_type)
|
||||
|
||||
self.resumeProducing()
|
||||
|
||||
def pauseProducing(self):
|
||||
self._paused = True
|
||||
log.info("Pausing producer")
|
||||
return defer.succeed(None)
|
||||
|
||||
def resumeProducing(self):
|
||||
def _check_for_new_data():
|
||||
self._depth += 1
|
||||
self._fileObject.seek(self._start_pos, os.SEEK_END)
|
||||
readable_bytes = self._fileObject.tell()
|
||||
self._fileObject.seek(self._cursor)
|
||||
|
||||
self._sent_bytes = False
|
||||
|
||||
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:
|
||||
break
|
||||
else:
|
||||
data = self._fileObject.read(1)
|
||||
self._request.write(data)
|
||||
self._cursor += 1
|
||||
|
||||
log.info("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))
|
||||
self._sent_bytes = True
|
||||
|
||||
if self._cursor == self._stop_pos + 1:
|
||||
self.stopProducing()
|
||||
return defer.succeed(None)
|
||||
elif self._paused or self._stopped:
|
||||
return defer.succeed(None)
|
||||
else:
|
||||
self._deferred.addCallback(lambda _: threads.deferToThread(reactor.callLater, self._delay, _check_for_new_data))
|
||||
return defer.succeed(None)
|
||||
|
||||
log.info("Resuming producer")
|
||||
self._paused = False
|
||||
self._deferred.addCallback(lambda _: _check_for_new_data())
|
||||
|
||||
def stopProducing(self):
|
||||
log.info("Stopping producer")
|
||||
self._stopped = True
|
||||
# self._fileObject.close()
|
||||
self._deferred.addErrback(lambda err: err.trap(defer.CancelledError))
|
||||
self._deferred.addErrback(lambda err: err.trap(error.ConnectionDone))
|
||||
self._deferred.cancel()
|
||||
# self._request.finish()
|
||||
self._request.unregisterProducer()
|
||||
return defer.succeed(None)
|
||||
|
||||
|
||||
class HostedLBRYFile(resource.Resource):
|
||||
def __init__(self, api):
|
||||
self._api = api
|
||||
self._producer = None
|
||||
resource.Resource.__init__(self)
|
||||
|
||||
# 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("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
|
||||
|
||||
def render_GET(self, request):
|
||||
if 'name' in request.args.keys():
|
||||
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])
|
||||
# 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))
|
||||
|
||||
elif request.args['name'][0] in self._api.waiting_on.keys():
|
||||
request.redirect(UI_ADDRESS + "/?watch=" + request.args['name'][0])
|
||||
request.finish()
|
||||
else:
|
||||
request.redirect(UI_ADDRESS)
|
||||
request.finish()
|
||||
return server.NOT_DONE_YET
|
||||
|
||||
# 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()
|
||||
|
||||
class LBRYFileUpload(resource.Resource):
|
||||
"""
|
||||
Accepts a file sent via the file upload widget in the web UI, saves
|
||||
it into a temporary dir, and responds with a JSON string containing
|
||||
the path of the newly created file.
|
||||
"""
|
||||
|
||||
def __init__(self, api):
|
||||
self._api = api
|
||||
|
||||
def render_POST(self, request):
|
||||
origfilename = request.args['file_filename'][0]
|
||||
uploaded_file = request.args['file'][0] # Temp file created by request
|
||||
|
||||
# Move to a new temporary dir and restore the original file name
|
||||
newdirpath = tempfile.mkdtemp()
|
||||
newpath = os.path.join(newdirpath, origfilename)
|
||||
shutil.move(uploaded_file.name, newpath)
|
||||
self._api.uploaded_temp_files.append(newpath)
|
||||
|
||||
return json.dumps(newpath)
|
||||
|
||||
|
||||
class LBRYDaemonServer(object):
|
||||
def _setup_server(self, wallet):
|
||||
self.root = LBRYindex(os.path.join(os.path.join(data_dir, "lbry-ui"), "active"))
|
||||
self._api = LBRYDaemon(self.root, wallet_type=wallet)
|
||||
self.root.putChild("view", HostedLBRYFile(self._api))
|
||||
self.root.putChild("upload", LBRYFileUpload(self._api))
|
||||
self.root.putChild(API_ADDRESS, self._api)
|
||||
return defer.succeed(True)
|
||||
|
||||
def start(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False, wallet=None):
|
||||
d = self._setup_server(wallet)
|
||||
d.addCallback(lambda _: self._api.setup(branch, user_specified, branch_specified))
|
||||
return d
|
|
@ -1,21 +0,0 @@
|
|||
import xmlrpclib
|
||||
|
||||
|
||||
def main():
|
||||
daemon = xmlrpclib.ServerProxy("http://localhost:7080/")
|
||||
try:
|
||||
b = daemon.get_balance()
|
||||
is_running = True
|
||||
except:
|
||||
is_running = False
|
||||
|
||||
if is_running:
|
||||
try:
|
||||
daemon.stop()
|
||||
except:
|
||||
print "LBRYnet daemon stopped"
|
||||
else:
|
||||
print "LBRYnet daemon wasn't running"
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,27 +1,58 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
from copy import deepcopy
|
||||
from appdirs import user_data_dir
|
||||
from datetime import datetime
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.task import LoopingCall
|
||||
from lbrynet.core.Error import InvalidStreamInfoError, InsufficientFundsError
|
||||
|
||||
from lbrynet.core.Error import InvalidStreamInfoError, InsufficientFundsError, KeyFeeAboveMaxAllowed
|
||||
from lbrynet.core.PaymentRateManager import PaymentRateManager
|
||||
from lbrynet.core.StreamDescriptor import download_sd_blob
|
||||
from lbrynet.core.LBRYMetadata import Metadata, LBRYFeeValidator
|
||||
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory
|
||||
from lbrynet.conf import DEFAULT_TIMEOUT, LOG_FILE_NAME
|
||||
|
||||
INITIALIZING_CODE = 'initializing'
|
||||
DOWNLOAD_METADATA_CODE = 'downloading_metadata'
|
||||
DOWNLOAD_TIMEOUT_CODE = 'timeout'
|
||||
DOWNLOAD_RUNNING_CODE = 'running'
|
||||
DOWNLOAD_STOPPED_CODE = 'stopped'
|
||||
STREAM_STAGES = [
|
||||
(INITIALIZING_CODE, 'Initializing...'),
|
||||
(DOWNLOAD_METADATA_CODE, 'Downloading metadata'),
|
||||
(DOWNLOAD_RUNNING_CODE, 'Started stream'),
|
||||
(DOWNLOAD_STOPPED_CODE, 'Paused stream'),
|
||||
(DOWNLOAD_TIMEOUT_CODE, 'Stream timed out')
|
||||
]
|
||||
|
||||
if sys.platform != "darwin":
|
||||
log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet")
|
||||
else:
|
||||
log_dir = user_data_dir("LBRY")
|
||||
|
||||
if not os.path.isdir(log_dir):
|
||||
os.mkdir(log_dir)
|
||||
|
||||
lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GetStream(object):
|
||||
def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, pay_key=True, data_rate=0.5):
|
||||
def __init__(self, sd_identifier, session, wallet, lbry_file_manager, exchange_rate_manager,
|
||||
max_key_fee, data_rate=0.5, timeout=DEFAULT_TIMEOUT, download_directory=None, file_name=None):
|
||||
self.wallet = wallet
|
||||
self.resolved_name = None
|
||||
self.description = None
|
||||
self.key_fee = None
|
||||
self.key_fee_address = None
|
||||
self.fee = None
|
||||
self.data_rate = data_rate
|
||||
self.pay_key = pay_key
|
||||
self.name = None
|
||||
self.file_name = file_name
|
||||
self.session = session
|
||||
self.exchange_rate_manager = exchange_rate_manager
|
||||
self.payment_rate_manager = PaymentRateManager(self.session.base_payment_rate_manager)
|
||||
self.lbry_file_manager = lbry_file_manager
|
||||
self.sd_identifier = sd_identifier
|
||||
|
@ -29,167 +60,111 @@ class GetStream(object):
|
|||
self.max_key_fee = max_key_fee
|
||||
self.stream_info = None
|
||||
self.stream_info_manager = None
|
||||
self.d = defer.Deferred(None)
|
||||
self.timeout = timeout
|
||||
self.timeout_counter = 0
|
||||
self.download_directory = download_directory
|
||||
self.download_path = None
|
||||
self.downloader = None
|
||||
self.finished = defer.Deferred(None)
|
||||
self.checker = LoopingCall(self.check_status)
|
||||
self.code = STREAM_STAGES[0]
|
||||
|
||||
def check_status(self):
|
||||
self.timeout_counter += 1
|
||||
|
||||
def start(self, stream_info):
|
||||
self.stream_info = stream_info
|
||||
if 'stream_hash' in self.stream_info.keys():
|
||||
# TODO: Why is this the stopping condition for the finished callback?
|
||||
if self.download_path:
|
||||
self.checker.stop()
|
||||
self.finished.callback((self.stream_hash, self.download_path))
|
||||
|
||||
elif self.timeout_counter >= self.timeout:
|
||||
log.info("Timeout downloading lbry://%s" % self.resolved_name)
|
||||
self.checker.stop()
|
||||
self.d.cancel()
|
||||
self.code = STREAM_STAGES[4]
|
||||
self.finished.callback(False)
|
||||
|
||||
def _convert_max_fee(self):
|
||||
if isinstance(self.max_key_fee, dict):
|
||||
max_fee = LBRYFeeValidator(self.max_key_fee)
|
||||
if max_fee.currency_symbol == "LBC":
|
||||
return max_fee.amount
|
||||
return self.exchange_rate_manager.to_lbc(self.fee).amount
|
||||
elif isinstance(self.max_key_fee, float):
|
||||
return float(self.max_key_fee)
|
||||
|
||||
def start(self, stream_info, name):
|
||||
def _cause_timeout(err):
|
||||
log.error(err)
|
||||
log.debug('Forcing a timeout')
|
||||
self.timeout_counter = self.timeout * 2
|
||||
|
||||
def _set_status(x, status):
|
||||
log.info("Download lbry://%s status changed to %s" % (self.resolved_name, status))
|
||||
self.code = next(s for s in STREAM_STAGES if s[0] == status)
|
||||
return x
|
||||
|
||||
def get_downloader_factory(metadata):
|
||||
for factory in metadata.factories:
|
||||
if isinstance(factory, ManagedLBRYFileDownloaderFactory):
|
||||
return factory, metadata
|
||||
raise Exception('No suitable factory was found in {}'.format(metadata.factories))
|
||||
|
||||
def make_downloader(args):
|
||||
factory, metadata = args
|
||||
return factory.make_downloader(metadata,
|
||||
[self.data_rate, True],
|
||||
self.payment_rate_manager,
|
||||
download_directory=self.download_directory,
|
||||
file_name=self.file_name)
|
||||
|
||||
self.resolved_name = name
|
||||
self.stream_info = deepcopy(stream_info)
|
||||
self.description = self.stream_info['description']
|
||||
if 'key_fee' in self.stream_info.keys():
|
||||
self.key_fee = float(self.stream_info['key_fee'])
|
||||
if 'key_fee_address' in self.stream_info.keys():
|
||||
self.key_fee_address = self.stream_info['key_fee_address']
|
||||
else:
|
||||
self.key_fee_address = None
|
||||
else:
|
||||
self.key_fee = None
|
||||
self.key_fee_address = None
|
||||
self.stream_hash = self.stream_info['sources']['lbry_sd_hash']
|
||||
|
||||
self.stream_hash = self.stream_info['stream_hash']
|
||||
if 'fee' in self.stream_info:
|
||||
self.fee = LBRYFeeValidator(self.stream_info['fee'])
|
||||
max_key_fee = self._convert_max_fee()
|
||||
if self.exchange_rate_manager.to_lbc(self.fee).amount > max_key_fee:
|
||||
log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.fee.amount,
|
||||
self.max_key_fee,
|
||||
self.resolved_name))
|
||||
return defer.fail(KeyFeeAboveMaxAllowed())
|
||||
log.info("Key fee %s below limit of %f, downloading lbry://%s" % (json.dumps(self.fee),
|
||||
max_key_fee,
|
||||
self.resolved_name))
|
||||
|
||||
else:
|
||||
print 'InvalidStreamInfoError'
|
||||
raise InvalidStreamInfoError(self.stream_info)
|
||||
self.checker.start(1)
|
||||
|
||||
if self.key_fee > self.max_key_fee:
|
||||
if self.pay_key:
|
||||
print "Key fee (" + str(self.key_fee) + ") above limit of " + str(
|
||||
self.max_key_fee) + ", didn't download lbry://" + str(self.resolved_name)
|
||||
return defer.fail(None)
|
||||
else:
|
||||
pass
|
||||
self.d.addCallback(lambda _: _set_status(None, DOWNLOAD_METADATA_CODE))
|
||||
self.d.addCallback(lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager))
|
||||
self.d.addCallback(self.sd_identifier.get_metadata_for_sd_blob)
|
||||
self.d.addCallback(lambda r: _set_status(r, DOWNLOAD_RUNNING_CODE))
|
||||
self.d.addCallback(get_downloader_factory)
|
||||
self.d.addCallback(make_downloader)
|
||||
self.d.addCallbacks(self._start_download, _cause_timeout)
|
||||
self.d.callback(None)
|
||||
|
||||
d = defer.Deferred(None)
|
||||
d.addCallback(lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager))
|
||||
d.addCallback(self.sd_identifier.get_metadata_for_sd_blob)
|
||||
d.addCallback(lambda metadata:
|
||||
metadata.factories[1].make_downloader(metadata, [self.data_rate, True], self.payment_rate_manager))
|
||||
d.addErrback(lambda err: err.trap(defer.CancelledError))
|
||||
d.addErrback(lambda err: log.error("An exception occurred attempting to load the stream descriptor: %s", err.getTraceback()))
|
||||
d.addCallback(self._start_download)
|
||||
d.callback(None)
|
||||
|
||||
return d
|
||||
return self.finished
|
||||
|
||||
def _start_download(self, downloader):
|
||||
def _pay_key_fee():
|
||||
if self.key_fee is not None and self.key_fee_address is not None:
|
||||
reserved_points = self.wallet.reserve_points(self.key_fee_address, self.key_fee)
|
||||
if self.fee is not None:
|
||||
fee_lbc = self.exchange_rate_manager.to_lbc(self.fee).amount
|
||||
reserved_points = self.wallet.reserve_points(self.fee.address, fee_lbc)
|
||||
if reserved_points is None:
|
||||
return defer.fail(InsufficientFundsError())
|
||||
print 'Key fee: ' + str(self.key_fee) + ' | ' + str(self.key_fee_address)
|
||||
return self.wallet.send_points_to_address(reserved_points, self.key_fee)
|
||||
return self.wallet.send_points_to_address(reserved_points, fee_lbc)
|
||||
|
||||
return defer.succeed(None)
|
||||
|
||||
if self.pay_key:
|
||||
d = _pay_key_fee()
|
||||
else:
|
||||
d = defer.Deferred()
|
||||
|
||||
downloader.start()
|
||||
self.downloader = downloader
|
||||
self.download_path = os.path.join(downloader.download_directory, downloader.file_name)
|
||||
|
||||
print "Downloading", self.stream_hash, "-->", os.path.join(downloader.download_directory, downloader.file_name)
|
||||
d.addCallback(lambda _: log.info("Downloading %s --> %s", self.stream_hash, self.downloader.file_name))
|
||||
d.addCallback(lambda _: self.downloader.start())
|
||||
|
||||
return d
|
||||
|
||||
|
||||
class FetcherDaemon(object):
|
||||
def __init__(self, session, lbry_file_manager, lbry_file_metadata_manager, wallet, sd_identifier, autofetcher_conf):
|
||||
self.autofetcher_conf = autofetcher_conf
|
||||
self.max_key_fee = 0.0
|
||||
self.sd_identifier = sd_identifier
|
||||
self.wallet = wallet
|
||||
self.session = session
|
||||
self.lbry_file_manager = lbry_file_manager
|
||||
self.lbry_metadata_manager = lbry_file_metadata_manager
|
||||
self.seen = []
|
||||
self.lastbestblock = None
|
||||
self.search = None
|
||||
self.first_run = True
|
||||
self.is_running = False
|
||||
self._get_autofetcher_conf()
|
||||
|
||||
def start(self):
|
||||
if not self.is_running:
|
||||
self.is_running = True
|
||||
self.search = LoopingCall(self._looped_search)
|
||||
self.search.start(1)
|
||||
else:
|
||||
print "Autofetcher is already running"
|
||||
|
||||
def stop(self):
|
||||
if self.is_running:
|
||||
self.search.stop()
|
||||
self.is_running = False
|
||||
else:
|
||||
print "Autofetcher isn't running, there's nothing to stop"
|
||||
|
||||
def check_if_running(self):
|
||||
if self.is_running:
|
||||
msg = "Autofetcher is running\n"
|
||||
msg += "Last block hash: " + str(self.lastbestblock['bestblockhash'])
|
||||
else:
|
||||
msg = "Autofetcher is not running"
|
||||
return msg
|
||||
|
||||
def _get_names(self):
|
||||
c = self.wallet.get_blockchain_info()
|
||||
rtn = []
|
||||
if self.lastbestblock != c:
|
||||
block = self.wallet.get_block(c['bestblockhash'])
|
||||
txids = block['tx']
|
||||
transactions = [self.wallet.get_tx(t) for t in txids]
|
||||
for t in transactions:
|
||||
claims = self.wallet.get_claims_for_tx(t['txid'])
|
||||
# if self.first_run:
|
||||
# # claims = self.rpc_conn.getclaimsfortx("96aca2c60efded5806b7336430c5987b9092ffbea9c6ed444e3bf8e008993e11")
|
||||
# # claims = self.rpc_conn.getclaimsfortx("cc9c7f5225ecb38877e6ca7574d110b23214ac3556b9d65784065ad3a85b4f74")
|
||||
# self.first_run = False
|
||||
if claims:
|
||||
for claim in claims:
|
||||
if claim not in self.seen:
|
||||
msg = "[" + str(datetime.now()) + "] New claim | lbry://" + str(claim['name']) + \
|
||||
" | stream hash: " + str(json.loads(claim['value'])['stream_hash'])
|
||||
print msg
|
||||
log.debug(msg)
|
||||
rtn.append(claim)
|
||||
self.seen.append(claim)
|
||||
|
||||
self.lastbestblock = c
|
||||
|
||||
if len(rtn):
|
||||
return defer.succeed(rtn)
|
||||
|
||||
def _download_claims(self, claims):
|
||||
if claims:
|
||||
for claim in claims:
|
||||
download = defer.Deferred()
|
||||
stream = GetStream(self.sd_identifier, self.session, self.wallet, self.lbry_file_manager,
|
||||
self.max_key_fee, pay_key=False)
|
||||
download.addCallback(lambda _: stream.start(claim))
|
||||
download.callback(None)
|
||||
|
||||
return defer.succeed(None)
|
||||
|
||||
def _looped_search(self):
|
||||
d = defer.Deferred(None)
|
||||
d.addCallback(lambda _: self._get_names())
|
||||
d.addCallback(self._download_claims)
|
||||
d.callback(None)
|
||||
|
||||
def _get_autofetcher_conf(self):
|
||||
settings = {"maxkey": "0.0"}
|
||||
if os.path.exists(self.autofetcher_conf):
|
||||
conf = open(self.autofetcher_conf)
|
||||
for l in conf:
|
||||
if l.startswith("maxkey="):
|
||||
settings["maxkey"] = float(l[7:].rstrip('\n'))
|
||||
print "Autofetcher using max key price of", settings["maxkey"], ", to start call start_fetcher()"
|
||||
else:
|
||||
print "Autofetcher using default max key price of 0.0"
|
||||
print "To change this create the file:"
|
||||
print str(self.autofetcher_conf)
|
||||
print "Example contents of conf file:"
|
||||
print "maxkey=1.0"
|
||||
|
||||
self.max_key_fee = settings["maxkey"]
|
||||
|
|
215
lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py
Normal file
|
@ -0,0 +1,215 @@
|
|||
import time
|
||||
import requests
|
||||
import logging
|
||||
import json
|
||||
import googlefinance
|
||||
from twisted.internet import defer, reactor
|
||||
from twisted.internet.task import LoopingCall
|
||||
|
||||
from lbrynet.core.LBRYMetadata import LBRYFeeValidator
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
CURRENCY_PAIRS = ["USDBTC", "BTCLBC"]
|
||||
BITTREX_FEE = 0.0025
|
||||
COINBASE_FEE = 0.0 #add fee
|
||||
|
||||
|
||||
class ExchangeRate(object):
|
||||
def __init__(self, market, spot, ts):
|
||||
assert int(time.time()) - ts < 600
|
||||
self.currency_pair = (market[0:3], market[3:6])
|
||||
self.spot = spot
|
||||
self.ts = ts
|
||||
|
||||
def as_dict(self):
|
||||
return {'spot': self.spot, 'ts': self.ts}
|
||||
|
||||
|
||||
class MarketFeed(object):
|
||||
def __init__(self, market, name, url, params, fee):
|
||||
self.market = market
|
||||
self.name = name
|
||||
self.url = url
|
||||
self.params = params
|
||||
self.fee = fee
|
||||
self.rate = None
|
||||
self._updater = LoopingCall(self._update_price)
|
||||
|
||||
def _make_request(self):
|
||||
r = requests.get(self.url, self.params)
|
||||
return r.text
|
||||
|
||||
def _handle_response(self, response):
|
||||
return NotImplementedError
|
||||
|
||||
def _subtract_fee(self, from_amount):
|
||||
return defer.succeed(from_amount / (1.0 - self.fee))
|
||||
|
||||
def _save_price(self, price):
|
||||
log.info("Saving price update %f for %s" % (price, self.market))
|
||||
self.rate = ExchangeRate(self.market, price, int(time.time()))
|
||||
|
||||
def _update_price(self):
|
||||
d = defer.succeed(self._make_request())
|
||||
d.addCallback(self._handle_response)
|
||||
d.addCallback(self._subtract_fee)
|
||||
d.addCallback(self._save_price)
|
||||
|
||||
def start(self):
|
||||
if not self._updater.running:
|
||||
self._updater.start(300)
|
||||
|
||||
def stop(self):
|
||||
if self._updater.running:
|
||||
self._updater.stop()
|
||||
|
||||
|
||||
class BittrexFeed(MarketFeed):
|
||||
def __init__(self):
|
||||
MarketFeed.__init__(
|
||||
self,
|
||||
"BTCLBC",
|
||||
"Bittrex",
|
||||
"https://bittrex.com/api/v1.1/public/getmarkethistory",
|
||||
{'market': 'BTC-LBC', 'count': 50},
|
||||
BITTREX_FEE
|
||||
)
|
||||
|
||||
def _handle_response(self, response):
|
||||
trades = json.loads(response)['result']
|
||||
vwap = sum([i['Total'] for i in trades]) / sum([i['Quantity'] for i in trades])
|
||||
return defer.succeed(float(1.0 / vwap))
|
||||
|
||||
|
||||
class GoogleBTCFeed(MarketFeed):
|
||||
def __init__(self):
|
||||
MarketFeed.__init__(
|
||||
self,
|
||||
"USDBTC",
|
||||
"Coinbase via Google finance",
|
||||
None,
|
||||
None,
|
||||
COINBASE_FEE
|
||||
)
|
||||
|
||||
def _make_request(self):
|
||||
return googlefinance.getQuotes('CURRENCY:USDBTC')[0]
|
||||
|
||||
def _handle_response(self, response):
|
||||
return float(response['LastTradePrice'])
|
||||
|
||||
|
||||
def get_default_market_feed(currency_pair):
|
||||
currencies = None
|
||||
if isinstance(currency_pair, str):
|
||||
currencies = (currency_pair[0:3], currency_pair[3:6])
|
||||
elif isinstance(currency_pair, tuple):
|
||||
currencies = currency_pair
|
||||
assert currencies is not None
|
||||
|
||||
if currencies == ("USD", "BTC"):
|
||||
return GoogleBTCFeed()
|
||||
elif currencies == ("BTC", "LBC"):
|
||||
return BittrexFeed()
|
||||
|
||||
|
||||
class ExchangeRateManager(object):
|
||||
def __init__(self):
|
||||
reactor.addSystemEventTrigger('before', 'shutdown', self.stop)
|
||||
self.market_feeds = [get_default_market_feed(currency_pair) for currency_pair in CURRENCY_PAIRS]
|
||||
|
||||
def start(self):
|
||||
log.info("Starting exchange rate manager")
|
||||
for feed in self.market_feeds:
|
||||
feed.start()
|
||||
|
||||
def stop(self):
|
||||
log.info("Stopping exchange rate manager")
|
||||
for source in self.market_feeds:
|
||||
source.stop()
|
||||
|
||||
def convert_currency(self, from_currency, to_currency, amount):
|
||||
log.info("Converting %f %s to %s" % (amount, from_currency, to_currency))
|
||||
if from_currency == to_currency:
|
||||
return amount
|
||||
for market in self.market_feeds:
|
||||
if market.rate.currency_pair == (from_currency, to_currency):
|
||||
return amount * market.rate.spot
|
||||
for market in self.market_feeds:
|
||||
if market.rate.currency_pair[0] == from_currency:
|
||||
return self.convert_currency(market.rate.currency_pair[1], to_currency, amount * market.rate.spot)
|
||||
raise Exception('Unable to convert {} from {} to {}'.format(amount, from_currency, to_currency))
|
||||
|
||||
def fee_dict(self):
|
||||
return {market: market.rate.as_dict() for market in self.market_feeds}
|
||||
|
||||
def to_lbc(self, fee):
|
||||
if fee is None:
|
||||
return None
|
||||
if not isinstance(fee, LBRYFeeValidator):
|
||||
fee_in = LBRYFeeValidator(fee)
|
||||
else:
|
||||
fee_in = fee
|
||||
|
||||
return LBRYFeeValidator({fee_in.currency_symbol:
|
||||
{
|
||||
'amount': self.convert_currency(fee_in.currency_symbol, "LBC", fee_in.amount),
|
||||
'address': fee_in.address
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
class DummyBTCLBCFeed(MarketFeed):
|
||||
def __init__(self):
|
||||
MarketFeed.__init__(
|
||||
self,
|
||||
"BTCLBC",
|
||||
"market name",
|
||||
"derp.com",
|
||||
None,
|
||||
0.0
|
||||
)
|
||||
|
||||
|
||||
class DummyUSDBTCFeed(MarketFeed):
|
||||
def __init__(self):
|
||||
MarketFeed.__init__(
|
||||
self,
|
||||
"USDBTC",
|
||||
"market name",
|
||||
"derp.com",
|
||||
None,
|
||||
0.0
|
||||
)
|
||||
|
||||
|
||||
class DummyExchangeRateManager(object):
|
||||
def __init__(self, rates):
|
||||
self.market_feeds = [DummyBTCLBCFeed(), DummyUSDBTCFeed()]
|
||||
for feed in self.market_feeds:
|
||||
feed.rate = ExchangeRate(feed.market, rates[feed.market]['spot'], rates[feed.market]['ts'])
|
||||
|
||||
def convert_currency(self, from_currency, to_currency, amount):
|
||||
log.info("Converting %f %s to %s" % (amount, from_currency, to_currency))
|
||||
for market in self.market_feeds:
|
||||
if market.rate.currency_pair == (from_currency, to_currency):
|
||||
return amount * market.rate.spot
|
||||
for market in self.market_feeds:
|
||||
if market.rate.currency_pair[0] == from_currency:
|
||||
return self.convert_currency(market.rate.currency_pair[1], to_currency, amount * market.rate.spot)
|
||||
|
||||
def to_lbc(self, fee):
|
||||
if fee is None:
|
||||
return None
|
||||
if not isinstance(fee, LBRYFeeValidator):
|
||||
fee_in = LBRYFeeValidator(fee)
|
||||
else:
|
||||
fee_in = fee
|
||||
|
||||
return LBRYFeeValidator({fee_in.currency_symbol:
|
||||
{
|
||||
'amount': self.convert_currency(fee_in.currency_symbol, "LBC", fee_in.amount),
|
||||
'address': fee_in.address
|
||||
}
|
||||
})
|
|
@ -1,14 +1,29 @@
|
|||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import sys
|
||||
|
||||
from appdirs import user_data_dir
|
||||
from datetime import datetime
|
||||
|
||||
from lbrynet.core.Error import InsufficientFundsError
|
||||
from lbrynet.lbryfilemanager.LBRYFileCreator import create_lbry_file
|
||||
from lbrynet.lbryfile.StreamDescriptor import publish_sd_blob
|
||||
from lbrynet.core.PaymentRateManager import PaymentRateManager
|
||||
from lbrynet.core.LBRYMetadata import Metadata, CURRENT_METADATA_VERSION
|
||||
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader
|
||||
from lbrynet.conf import LOG_FILE_NAME
|
||||
from twisted.internet import threads, defer
|
||||
import os
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
if sys.platform != "darwin":
|
||||
log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet")
|
||||
else:
|
||||
log_dir = user_data_dir("LBRY")
|
||||
|
||||
if not os.path.isdir(log_dir):
|
||||
os.mkdir(log_dir)
|
||||
|
||||
lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -20,38 +35,25 @@ class Publisher(object):
|
|||
self.received_file_name = False
|
||||
self.file_path = None
|
||||
self.file_name = None
|
||||
self.thumbnail = None
|
||||
self.title = None
|
||||
self.publish_name = None
|
||||
self.bid_amount = None
|
||||
self.key_fee = None
|
||||
self.key_fee_address = None
|
||||
self.key_fee_address_chosen = False
|
||||
self.description = None
|
||||
self.verified = False
|
||||
self.lbry_file = None
|
||||
self.sd_hash = None
|
||||
self.tx_hash = None
|
||||
self.content_license = None
|
||||
self.txid = None
|
||||
self.stream_hash = None
|
||||
self.metadata = {}
|
||||
|
||||
def start(self, name, file_path, bid, title=None, description=None, thumbnail=None,
|
||||
key_fee=None, key_fee_address=None, content_license=None):
|
||||
def start(self, name, file_path, bid, metadata, old_txid):
|
||||
|
||||
def _show_result():
|
||||
message = "[" + str(datetime.now()) + " ] Published " + self.file_name + " --> lbry://" + \
|
||||
str(self.publish_name) + " with txid: " + str(self.tx_hash)
|
||||
print message
|
||||
return defer.succeed(message)
|
||||
log.info("Published %s --> lbry://%s txid: %s", self.file_name, self.publish_name, self.txid)
|
||||
return defer.succeed(self.txid)
|
||||
|
||||
self.publish_name = name
|
||||
self.file_path = file_path
|
||||
self.bid_amount = bid
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.thumbnail = thumbnail
|
||||
self.key_fee = key_fee
|
||||
self.key_fee_address = key_fee_address
|
||||
self.content_license = content_license
|
||||
self.metadata = metadata
|
||||
self.old_txid = old_txid
|
||||
|
||||
d = self._check_file_path(self.file_path)
|
||||
d.addCallback(lambda _: create_lbry_file(self.session, self.lbry_file_manager,
|
||||
|
@ -59,6 +61,7 @@ class Publisher(object):
|
|||
d.addCallback(self.add_to_lbry_files)
|
||||
d.addCallback(lambda _: self._create_sd_blob())
|
||||
d.addCallback(lambda _: self._claim_name())
|
||||
d.addCallback(lambda _: self.set_status())
|
||||
d.addCallbacks(lambda _: _show_result(), self._show_publish_error)
|
||||
|
||||
return d
|
||||
|
@ -71,26 +74,15 @@ class Publisher(object):
|
|||
return True
|
||||
return threads.deferToThread(check_file_threaded)
|
||||
|
||||
def _get_new_address(self):
|
||||
d = self.wallet.get_new_address()
|
||||
|
||||
def set_address(address):
|
||||
self.key_fee_address = address
|
||||
return True
|
||||
|
||||
d.addCallback(set_address)
|
||||
return d
|
||||
|
||||
def set_status(self, lbry_file_downloader):
|
||||
def set_lbry_file(self, lbry_file_downloader):
|
||||
self.lbry_file = lbry_file_downloader
|
||||
d = self.lbry_file_manager.change_lbry_file_status(self.lbry_file, ManagedLBRYFileDownloader.STATUS_FINISHED)
|
||||
d.addCallback(lambda _: lbry_file_downloader.restore())
|
||||
return d
|
||||
return defer.succeed(None)
|
||||
|
||||
def add_to_lbry_files(self, stream_hash):
|
||||
self.stream_hash = stream_hash
|
||||
prm = PaymentRateManager(self.session.base_payment_rate_manager)
|
||||
d = self.lbry_file_manager.add_lbry_file(stream_hash, prm)
|
||||
d.addCallback(self.set_status)
|
||||
d.addCallback(self.set_lbry_file)
|
||||
return d
|
||||
|
||||
def _create_sd_blob(self):
|
||||
|
@ -98,30 +90,49 @@ class Publisher(object):
|
|||
self.lbry_file.stream_hash)
|
||||
|
||||
def set_sd_hash(sd_hash):
|
||||
self.sd_hash = sd_hash
|
||||
if 'sources' not in self.metadata:
|
||||
self.metadata['sources'] = {}
|
||||
self.metadata['sources']['lbry_sd_hash'] = sd_hash
|
||||
|
||||
d.addCallback(set_sd_hash)
|
||||
return d
|
||||
|
||||
def _claim_name(self):
|
||||
d = self.wallet.claim_name(self.publish_name, self.sd_hash, self.bid_amount,
|
||||
description=self.description, key_fee=self.key_fee,
|
||||
key_fee_address=self.key_fee_address, thumbnail=self.thumbnail,
|
||||
content_license=self.content_license)
|
||||
def set_status(self):
|
||||
d = self.lbry_file_manager.change_lbry_file_status(self.lbry_file, ManagedLBRYFileDownloader.STATUS_FINISHED)
|
||||
d.addCallback(lambda _: self.lbry_file.restore())
|
||||
return d
|
||||
|
||||
def set_tx_hash(tx_hash):
|
||||
self.tx_hash = tx_hash
|
||||
def _claim_name(self):
|
||||
self.metadata['content-type'] = mimetypes.guess_type(os.path.join(self.lbry_file.download_directory,
|
||||
self.lbry_file.file_name))[0]
|
||||
self.metadata['ver'] = CURRENT_METADATA_VERSION
|
||||
|
||||
if self.old_txid:
|
||||
|
||||
d = self.wallet.abandon_name(self.old_txid)
|
||||
d.addCallback(lambda tx: log.info("Abandoned tx %s" % str(tx)))
|
||||
d.addCallback(lambda _: self.wallet.claim_name(self.publish_name,
|
||||
self.bid_amount,
|
||||
Metadata(self.metadata)))
|
||||
else:
|
||||
d = self.wallet.claim_name(self.publish_name,
|
||||
self.bid_amount,
|
||||
Metadata(self.metadata))
|
||||
def set_tx_hash(txid):
|
||||
self.txid = txid
|
||||
|
||||
d.addCallback(set_tx_hash)
|
||||
return d
|
||||
|
||||
def _show_publish_error(self, err):
|
||||
log.info(err.getTraceback())
|
||||
message = "An error occurred publishing %s to %s. Error: %s."
|
||||
if err.check(InsufficientFundsError):
|
||||
error_message = "Insufficient funds"
|
||||
else:
|
||||
d = defer.succeed(True)
|
||||
error_message = err.getErrorMessage()
|
||||
print message % (str(self.file_name), str(self.publish_name), error_message)
|
||||
|
||||
log.error(error_message)
|
||||
log.error(message, str(self.file_name), str(self.publish_name), err.getTraceback())
|
||||
return d
|
||||
|
||||
return defer.succeed(error_message)
|
||||
|
|
239
lbrynet/lbrynet_daemon/LBRYUIManager.py
Normal file
|
@ -0,0 +1,239 @@
|
|||
import os
|
||||
import logging
|
||||
import shutil
|
||||
import sys
|
||||
import json
|
||||
|
||||
from urllib2 import urlopen
|
||||
from StringIO import StringIO
|
||||
from twisted.web import static
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.task import LoopingCall
|
||||
from lbrynet.conf import DEFAULT_UI_BRANCH, LOG_FILE_NAME
|
||||
from lbrynet import __version__ as lbrynet_version
|
||||
from lbryum.version import LBRYUM_VERSION as lbryum_version
|
||||
from zipfile import ZipFile
|
||||
from appdirs import user_data_dir
|
||||
|
||||
if sys.platform != "darwin":
|
||||
log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet")
|
||||
else:
|
||||
log_dir = user_data_dir("LBRY")
|
||||
|
||||
if not os.path.isdir(log_dir):
|
||||
os.mkdir(log_dir)
|
||||
|
||||
lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LBRYUIManager(object):
|
||||
def __init__(self, root):
|
||||
if sys.platform != "darwin":
|
||||
self.data_dir = os.path.join(os.path.expanduser("~"), '.lbrynet')
|
||||
else:
|
||||
self.data_dir = user_data_dir("LBRY")
|
||||
|
||||
self.ui_root = os.path.join(self.data_dir, "lbry-ui")
|
||||
self.active_dir = os.path.join(self.ui_root, "active")
|
||||
self.update_dir = os.path.join(self.ui_root, "update")
|
||||
|
||||
if not os.path.isdir(self.data_dir):
|
||||
os.mkdir(self.data_dir)
|
||||
if not os.path.isdir(self.ui_root):
|
||||
os.mkdir(self.ui_root)
|
||||
if not os.path.isdir(self.active_dir):
|
||||
os.mkdir(self.active_dir)
|
||||
if not os.path.isdir(self.update_dir):
|
||||
os.mkdir(self.update_dir)
|
||||
|
||||
self.config = os.path.join(self.ui_root, "active.json")
|
||||
self.update_requires = os.path.join(self.update_dir, "requirements.txt")
|
||||
self.requirements = {}
|
||||
self.check_requirements = True
|
||||
self.ui_dir = self.active_dir
|
||||
self.git_version = None
|
||||
self.root = root
|
||||
self.branch = None
|
||||
self.update_checker = LoopingCall(self.setup)
|
||||
|
||||
if not os.path.isfile(os.path.join(self.config)):
|
||||
self.loaded_git_version = None
|
||||
self.loaded_branch = None
|
||||
self.loaded_requirements = None
|
||||
else:
|
||||
try:
|
||||
f = open(self.config, "r")
|
||||
loaded_ui = json.loads(f.read())
|
||||
f.close()
|
||||
self.loaded_git_version = loaded_ui['commit']
|
||||
self.loaded_branch = loaded_ui['branch']
|
||||
self.loaded_requirements = loaded_ui['requirements']
|
||||
except:
|
||||
self.loaded_git_version = None
|
||||
self.loaded_branch = None
|
||||
self.loaded_requirements = None
|
||||
|
||||
def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=None, branch_specified=False, check_requirements=None):
|
||||
if check_requirements is not None:
|
||||
self.check_requirements = check_requirements
|
||||
if self.branch is not None:
|
||||
self.branch = branch
|
||||
if user_specified:
|
||||
if os.path.isdir(user_specified):
|
||||
log.info("Checking user specified UI directory: " + str(user_specified))
|
||||
self.branch = "user-specified"
|
||||
self.loaded_git_version = "user-specified"
|
||||
d = self.migrate_ui(source=user_specified)
|
||||
d.addCallback(lambda _: self._load_ui())
|
||||
return d
|
||||
else:
|
||||
log.info("User specified UI directory doesn't exist, using " + self.branch)
|
||||
elif self.loaded_branch == "user-specified" and not branch_specified:
|
||||
log.info("Loading user provided UI")
|
||||
d = self._load_ui()
|
||||
return d
|
||||
else:
|
||||
log.info("Checking for updates for UI branch: " + branch)
|
||||
self._git_url = "https://s3.amazonaws.com/lbry-ui/{}/data.json".format(branch)
|
||||
self._dist_url = "https://s3.amazonaws.com/lbry-ui/{}/dist.zip".format(branch)
|
||||
|
||||
d = self._up_to_date()
|
||||
d.addCallback(lambda r: self._download_ui() if not r else self._load_ui())
|
||||
return d
|
||||
|
||||
def _up_to_date(self):
|
||||
def _get_git_info():
|
||||
response = urlopen(self._git_url)
|
||||
data = json.loads(response.read())
|
||||
return defer.succeed(data['sha'])
|
||||
|
||||
def _set_git(version):
|
||||
self.git_version = version.replace('\n', '')
|
||||
if self.git_version == self.loaded_git_version:
|
||||
log.info("UI is up to date")
|
||||
return defer.succeed(True)
|
||||
else:
|
||||
log.info("UI updates available, checking if installation meets requirements")
|
||||
return defer.succeed(False)
|
||||
|
||||
def _use_existing():
|
||||
log.info("Failed to check for new ui version, trying to use cached ui")
|
||||
return defer.succeed(True)
|
||||
|
||||
d = _get_git_info()
|
||||
d.addCallbacks(_set_git, lambda _: _use_existing)
|
||||
return d
|
||||
|
||||
def migrate_ui(self, source=None):
|
||||
if not source:
|
||||
requires_file = self.update_requires
|
||||
source_dir = self.update_dir
|
||||
delete_source = True
|
||||
else:
|
||||
requires_file = os.path.join(source, "requirements.txt")
|
||||
source_dir = source
|
||||
delete_source = False
|
||||
|
||||
def _skip_requirements():
|
||||
log.info("Skipping ui requirement check")
|
||||
return defer.succeed(True)
|
||||
|
||||
def _check_requirements():
|
||||
if not os.path.isfile(requires_file):
|
||||
log.info("No requirements.txt file, rejecting request to migrate this UI")
|
||||
return defer.succeed(False)
|
||||
|
||||
f = open(requires_file, "r")
|
||||
for requirement in [line for line in f.read().split('\n') if line]:
|
||||
t = requirement.split('=')
|
||||
if len(t) == 3:
|
||||
self.requirements[t[0]] = {'version': t[1], 'operator': '=='}
|
||||
elif t[0][-1] == ">":
|
||||
self.requirements[t[0][:-1]] = {'version': t[1], 'operator': '>='}
|
||||
elif t[0][-1] == "<":
|
||||
self.requirements[t[0][:-1]] = {'version': t[1], 'operator': '<='}
|
||||
f.close()
|
||||
passed_requirements = True
|
||||
for r in self.requirements:
|
||||
if r == 'lbrynet':
|
||||
c = lbrynet_version
|
||||
elif r == 'lbryum':
|
||||
c = lbryum_version
|
||||
else:
|
||||
c = None
|
||||
if c:
|
||||
if self.requirements[r]['operator'] == '==':
|
||||
if not self.requirements[r]['version'] == c:
|
||||
passed_requirements = False
|
||||
log.info("Local version %s of %s does not meet UI requirement for version %s" % (
|
||||
c, r, self.requirements[r]['version']))
|
||||
else:
|
||||
log.info("Local version of %s meets ui requirement" % r)
|
||||
if self.requirements[r]['operator'] == '>=':
|
||||
if not self.requirements[r]['version'] <= c:
|
||||
passed_requirements = False
|
||||
log.info("Local version %s of %s does not meet UI requirement for version %s" % (
|
||||
c, r, self.requirements[r]['version']))
|
||||
else:
|
||||
log.info("Local version of %s meets ui requirement" % r)
|
||||
if self.requirements[r]['operator'] == '<=':
|
||||
if not self.requirements[r]['version'] >= c:
|
||||
passed_requirements = False
|
||||
log.info("Local version %s of %s does not meet UI requirement for version %s" % (
|
||||
c, r, self.requirements[r]['version']))
|
||||
else:
|
||||
log.info("Local version of %s meets ui requirement" % r)
|
||||
return defer.succeed(passed_requirements)
|
||||
|
||||
def _disp_failure():
|
||||
log.info("Failed to satisfy requirements for branch '%s', update was not loaded" % self.branch)
|
||||
return defer.succeed(False)
|
||||
|
||||
def _do_migrate():
|
||||
if os.path.isdir(self.active_dir):
|
||||
shutil.rmtree(self.active_dir)
|
||||
shutil.copytree(source_dir, self.active_dir)
|
||||
if delete_source:
|
||||
shutil.rmtree(source_dir)
|
||||
|
||||
log.info("Loaded UI update")
|
||||
|
||||
f = open(self.config, "w")
|
||||
loaded_ui = {'commit': self.git_version, 'branch': self.branch, 'requirements': self.requirements}
|
||||
f.write(json.dumps(loaded_ui))
|
||||
f.close()
|
||||
|
||||
self.loaded_git_version = loaded_ui['commit']
|
||||
self.loaded_branch = loaded_ui['branch']
|
||||
self.loaded_requirements = loaded_ui['requirements']
|
||||
return defer.succeed(True)
|
||||
|
||||
d = _check_requirements() if self.check_requirements else _skip_requirements()
|
||||
d.addCallback(lambda r: _do_migrate() if r else _disp_failure())
|
||||
return d
|
||||
|
||||
def _download_ui(self):
|
||||
def _delete_update_dir():
|
||||
if os.path.isdir(self.update_dir):
|
||||
shutil.rmtree(self.update_dir)
|
||||
return defer.succeed(None)
|
||||
|
||||
def _dl_ui():
|
||||
url = urlopen(self._dist_url)
|
||||
z = ZipFile(StringIO(url.read()))
|
||||
names = [i for i in z.namelist() if '.DS_exStore' not in i and '__MACOSX' not in i]
|
||||
z.extractall(self.update_dir, members=names)
|
||||
log.info("Downloaded files for UI commit " + str(self.git_version).replace("\n", ""))
|
||||
return self.branch
|
||||
|
||||
d = _delete_update_dir()
|
||||
d.addCallback(lambda _: _dl_ui())
|
||||
d.addCallback(lambda _: self.migrate_ui())
|
||||
d.addCallback(lambda _: self._load_ui())
|
||||
return d
|
||||
|
||||
def _load_ui(self):
|
||||
for d in [i[0] for i in os.walk(self.active_dir) if os.path.dirname(i[0]) == self.active_dir]:
|
||||
self.root.putChild(os.path.basename(d), static.File(d))
|
||||
return defer.succeed(True)
|
|
@ -1,59 +0,0 @@
|
|||
import os
|
||||
import json
|
||||
import webbrowser
|
||||
import xmlrpclib, sys
|
||||
|
||||
|
||||
def render_video(path):
|
||||
r = r'<center><video src="' + path + r'" controls autoplay width="960" height="720"></center>'
|
||||
return r
|
||||
|
||||
|
||||
def main(args):
|
||||
if len(args) == 0:
|
||||
args.append('lbry://wonderfullife')
|
||||
|
||||
daemon = xmlrpclib.ServerProxy('http://localhost:7080/')
|
||||
|
||||
try:
|
||||
balance = daemon.get_balance()
|
||||
is_running = True
|
||||
if len(args) > 1:
|
||||
print 'Too many args', args
|
||||
|
||||
elif is_running:
|
||||
if args[0][7:] == 'lbry':
|
||||
daemon.render_gui()
|
||||
|
||||
elif args[0][7:] == 'settings':
|
||||
r = daemon.get_settings()
|
||||
html = "<body>" + json.dumps(r) + "</body>"
|
||||
r = daemon.render_html(html)
|
||||
else:
|
||||
if float(balance) > 0.0:
|
||||
r = daemon.get(args[0][7:])
|
||||
print r
|
||||
path = r['path']
|
||||
if path[0] != '/':
|
||||
path = '/' + path
|
||||
|
||||
print path
|
||||
filename = path.split('/')[len(path.split('/')) - 1]
|
||||
extension = path.split('.')[len(path.split('.')) - 1]
|
||||
|
||||
if extension in ['mp4', 'flv', 'mov']:
|
||||
html = render_video(path)
|
||||
daemon.render_html(html)
|
||||
|
||||
else:
|
||||
webbrowser.open('file://' + str(path))
|
||||
|
||||
except:
|
||||
webbrowser.open('http://lbry.io/get')
|
||||
is_running = False
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
|
@ -1,117 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>LBRYURIHandler</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>****</string>
|
||||
<string>fold</string>
|
||||
<string>disk</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>LBRYURIHandler</string>
|
||||
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>LBRYURIHandler</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>lbry</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSUIElement</key>
|
||||
<true/>
|
||||
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>PythonApplet.icns</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.pythonmac.unspecified.LBRYURIHandler</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>LBRYURIHandler</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.0.0</string>
|
||||
<key>LSHasLocalizedDisplayName</key>
|
||||
<false/>
|
||||
<key>NSAppleScriptEnabled</key>
|
||||
<false/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright not specified</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>PyMainFileNames</key>
|
||||
<array>
|
||||
<string>__boot__</string>
|
||||
</array>
|
||||
<key>PyOptions</key>
|
||||
<dict>
|
||||
<key>alias</key>
|
||||
<false/>
|
||||
<key>argv_emulation</key>
|
||||
<true/>
|
||||
<key>emulate_shell_environment</key>
|
||||
<false/>
|
||||
<key>no_chdir</key>
|
||||
<false/>
|
||||
<key>prefer_ppc</key>
|
||||
<false/>
|
||||
<key>site_packages</key>
|
||||
<false/>
|
||||
<key>use_faulthandler</key>
|
||||
<false/>
|
||||
<key>use_pythonpath</key>
|
||||
<false/>
|
||||
<key>verbose</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>PyResourcePackages</key>
|
||||
<array>
|
||||
</array>
|
||||
<key>PyRuntimeLocations</key>
|
||||
<array>
|
||||
<string>@executable_path/../Frameworks/Python.framework/Versions/2.7/Python</string>
|
||||
</array>
|
||||
<key>PythonInfoDict</key>
|
||||
<dict>
|
||||
<key>PythonExecutable</key>
|
||||
<string>/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python</string>
|
||||
<key>PythonLongVersion</key>
|
||||
<string>2.7.10 (v2.7.10:15c95b7d81dc, May 23 2015, 09:33:12)
|
||||
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)]</string>
|
||||
<key>PythonShortVersion</key>
|
||||
<string>2.7</string>
|
||||
<key>py2app</key>
|
||||
<dict>
|
||||
<key>alias</key>
|
||||
<false/>
|
||||
<key>template</key>
|
||||
<string>app</string>
|
||||
<key>version</key>
|
||||
<string>0.9</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -1,19 +0,0 @@
|
|||
"""
|
||||
This is a setup.py script generated by py2applet
|
||||
|
||||
Usage:
|
||||
python setup.py py2app
|
||||
"""
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
APP = ['LBRYURIHandler.py']
|
||||
DATA_FILES = []
|
||||
OPTIONS = {'argv_emulation': True}
|
||||
|
||||
setup(
|
||||
app=APP,
|
||||
data_files=DATA_FILES,
|
||||
options={'py2app': OPTIONS},
|
||||
setup_requires=['py2app'],
|
||||
)
|
68
lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
import json
|
||||
import logging.handlers
|
||||
import sys
|
||||
import os
|
||||
|
||||
from appdirs import user_data_dir
|
||||
from twisted.internet.task import LoopingCall
|
||||
from twisted.internet import reactor
|
||||
|
||||
|
||||
if sys.platform != "darwin":
|
||||
log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet")
|
||||
else:
|
||||
log_dir = user_data_dir("LBRY")
|
||||
|
||||
if not os.path.isdir(log_dir):
|
||||
os.mkdir(log_dir)
|
||||
|
||||
LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log')
|
||||
|
||||
if os.path.isfile(LOG_FILENAME):
|
||||
f = open(LOG_FILENAME, 'r')
|
||||
PREVIOUS_LOG = len(f.read())
|
||||
f.close()
|
||||
else:
|
||||
PREVIOUS_LOG = 0
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5)
|
||||
log.addHandler(handler)
|
||||
log.setLevel(logging.INFO)
|
||||
|
||||
|
||||
class Autofetcher(object):
|
||||
"""
|
||||
Download name claims as they occur
|
||||
"""
|
||||
|
||||
def __init__(self, api):
|
||||
self._api = api
|
||||
self._checker = LoopingCall(self._check_for_new_claims)
|
||||
self.best_block = None
|
||||
|
||||
def start(self):
|
||||
reactor.addSystemEventTrigger('before', 'shutdown', self.stop)
|
||||
self._checker.start(5)
|
||||
|
||||
def stop(self):
|
||||
log.info("Stopping autofetcher")
|
||||
self._checker.stop()
|
||||
|
||||
def _check_for_new_claims(self):
|
||||
block = self._api.get_best_blockhash()
|
||||
if block != self.best_block:
|
||||
log.info("Checking new block for name claims, block hash: %s" % block)
|
||||
self.best_block = block
|
||||
transactions = self._api.get_block({'blockhash': block})['tx']
|
||||
for t in transactions:
|
||||
c = self._api.get_claims_for_tx({'txid': t})
|
||||
if len(c):
|
||||
for i in c:
|
||||
log.info("Downloading stream for claim txid: %s" % t)
|
||||
self._api.get({'name': t, 'stream_info': json.loads(i['value'])})
|
||||
|
||||
|
||||
def run(api):
|
||||
fetcher = Autofetcher(api)
|
||||
fetcher.start()
|
0
lbrynet/lbrynet_daemon/daemon_scripts/__init__.py
Normal file
32
lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from twisted.internet import defer
|
||||
|
||||
|
||||
class migrator(object):
|
||||
"""
|
||||
Re-resolve lbry names to write missing data to blockchain.db and to cache the nametrie
|
||||
"""
|
||||
|
||||
def __init__(self, api):
|
||||
self._api = api
|
||||
|
||||
def start(self):
|
||||
def _resolve_claims(claimtrie):
|
||||
claims = [i for i in claimtrie if 'txid' in i.keys()]
|
||||
r = defer.DeferredList([self._api._resolve_name(claim['name'], force_refresh=True) for claim in claims], consumeErrors=True)
|
||||
return r
|
||||
|
||||
def _restart_lbry_files():
|
||||
def _restart_lbry_file(lbry_file):
|
||||
return lbry_file.restore()
|
||||
|
||||
r = defer.DeferredList([_restart_lbry_file(lbry_file) for lbry_file in self._api.lbry_file_manager.lbry_files if not lbry_file.txid], consumeErrors=True)
|
||||
return r
|
||||
|
||||
d = self._api.session.wallet.get_nametrie()
|
||||
d.addCallback(_resolve_claims)
|
||||
d.addCallback(lambda _: _restart_lbry_files())
|
||||
|
||||
|
||||
def run(api):
|
||||
refresher = migrator(api)
|
||||
refresher.start()
|
|
@ -1,352 +0,0 @@
|
|||
import Tkinter as tk
|
||||
import logging
|
||||
import sys
|
||||
import tkFont
|
||||
import tkMessageBox
|
||||
import ttk
|
||||
from lbrynet.lbrynet_gui.LBRYGui import LBRYDownloader
|
||||
from lbrynet.lbrynet_gui.StreamFrame import StreamFrame
|
||||
import locale
|
||||
import os
|
||||
from twisted.internet import defer, reactor, tksupport, task
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DownloaderApp(object):
|
||||
def __init__(self):
|
||||
self.master = None
|
||||
self.downloader = None
|
||||
self.wallet_balance_check = None
|
||||
self.streams_frame = None
|
||||
|
||||
def start(self):
|
||||
|
||||
d = defer.maybeDeferred(self._start_root)
|
||||
d.addCallback(lambda _: self._draw_main())
|
||||
d.addCallback(lambda _: self._start_downloader())
|
||||
d.addCallback(lambda _: self._start_checking_wallet_balance())
|
||||
d.addCallback(lambda _: self._enable_lookup())
|
||||
|
||||
def show_error_and_stop(err):
|
||||
log.error(err.getErrorMessage())
|
||||
tkMessageBox.showerror(title="Start Error", message=err.getErrorMessage())
|
||||
return self.stop()
|
||||
|
||||
d.addErrback(show_error_and_stop)
|
||||
return d
|
||||
|
||||
def stop(self):
|
||||
|
||||
def log_error(err):
|
||||
log.error(err.getErrorMessage())
|
||||
|
||||
if self.downloader is not None:
|
||||
d = self.downloader.stop()
|
||||
else:
|
||||
d = defer.succeed(True)
|
||||
d.addErrback(log_error)
|
||||
d.addCallback(lambda _: self._stop_checking_wallet_balance())
|
||||
d.addErrback(log_error)
|
||||
d.addCallback(lambda _: reactor.stop())
|
||||
d.addErrback(log_error)
|
||||
return d
|
||||
|
||||
def _start_root(self):
|
||||
if os.name == "nt":
|
||||
button_foreground = "#104639"
|
||||
lookup_button_padding = 10
|
||||
else:
|
||||
button_foreground = "#FFFFFF"
|
||||
lookup_button_padding = 11
|
||||
|
||||
root = tk.Tk()
|
||||
root.resizable(0, 0)
|
||||
root.wm_title("LBRY")
|
||||
|
||||
tksupport.install(root)
|
||||
|
||||
if os.name == "nt":
|
||||
root.iconbitmap(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),
|
||||
"lbry-dark-icon.ico"))
|
||||
else:
|
||||
root.wm_iconbitmap("@" + os.path.join(os.path.dirname(__file__), "lbry-dark-icon.xbm"))
|
||||
|
||||
root.button_font = tkFont.Font(size=9)
|
||||
|
||||
ttk.Style().configure(".", background="#FFFFFF")
|
||||
ttk.Style().configure("LBRY.TButton", background="#104639", foreground=button_foreground,
|
||||
borderwidth=1, relief="solid", font=root.button_font)
|
||||
ttk.Style().map("LBRY.TButton",
|
||||
background=[('pressed', "#104639"),
|
||||
('active', "#104639")])
|
||||
#ttk.Style().configure("LBRY.TButton.border", background="#808080")
|
||||
ttk.Style().configure("Lookup.LBRY.TButton", padding=lookup_button_padding)
|
||||
ttk.Style().configure("Stop.TButton", padding=1, background="#FFFFFF", relief="flat", borderwidth=0)
|
||||
ttk.Style().configure("TEntry", padding=11)
|
||||
ttk.Style().configure("Float.TEntry", padding=2)
|
||||
#ttk.Style().configure("A.TFrame", background="red")
|
||||
#ttk.Style().configure("B.TFrame", background="green")
|
||||
#ttk.Style().configure("B2.TFrame", background="#80FF80")
|
||||
#ttk.Style().configure("C.TFrame", background="orange")
|
||||
#ttk.Style().configure("D.TFrame", background="blue")
|
||||
#ttk.Style().configure("E.TFrame", background="yellow")
|
||||
#ttk.Style().configure("F.TFrame", background="#808080")
|
||||
#ttk.Style().configure("G.TFrame", background="#FF80FF")
|
||||
#ttk.Style().configure("H.TFrame", background="#0080FF")
|
||||
#ttk.Style().configure("LBRY.TProgressbar", background="#104639", orient="horizontal", thickness=5)
|
||||
#ttk.Style().configure("LBRY.TProgressbar")
|
||||
#ttk.Style().layout("Horizontal.LBRY.TProgressbar", ttk.Style().layout("Horizontal.TProgressbar"))
|
||||
|
||||
root.configure(background="#FFFFFF")
|
||||
|
||||
root.protocol("WM_DELETE_WINDOW", self.stop)
|
||||
|
||||
self.master = root
|
||||
|
||||
def _draw_main(self):
|
||||
self.frame = ttk.Frame(self.master, style="A.TFrame")
|
||||
self.frame.grid(padx=20, pady=20)
|
||||
|
||||
logo_file_name = "lbry-dark-242x80.gif"
|
||||
if os.name == "nt":
|
||||
logo_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), logo_file_name)
|
||||
else:
|
||||
logo_file = os.path.join(os.path.dirname(__file__), logo_file_name)
|
||||
|
||||
self.logo_picture = tk.PhotoImage(file=logo_file)
|
||||
|
||||
self.logo_frame = ttk.Frame(self.frame, style="B.TFrame")
|
||||
self.logo_frame.grid(pady=5, sticky=tk.W + tk.E)
|
||||
|
||||
self.dummy_frame = ttk.Frame(self.logo_frame, style="C.TFrame") # keeps the logo in the middle
|
||||
self.dummy_frame.grid(row=0, column=1, padx=5)
|
||||
|
||||
self.logo_label = ttk.Label(self.logo_frame, image=self.logo_picture)
|
||||
self.logo_label.grid(row=0, column=1, padx=5)
|
||||
|
||||
self.wallet_balance_frame = ttk.Frame(self.logo_frame, style="C.TFrame")
|
||||
self.wallet_balance_frame.grid(sticky=tk.E + tk.N, row=0, column=2)
|
||||
|
||||
self.logo_frame.grid_columnconfigure(0, weight=1, uniform="a")
|
||||
self.logo_frame.grid_columnconfigure(1, weight=2, uniform="b")
|
||||
self.logo_frame.grid_columnconfigure(2, weight=1, uniform="a")
|
||||
|
||||
self.wallet_balance = ttk.Label(
|
||||
self.wallet_balance_frame,
|
||||
text=" -- LBC"
|
||||
)
|
||||
self.wallet_balance.grid(row=0, column=0)
|
||||
|
||||
dropdown_file_name = "drop_down.gif"
|
||||
if os.name == "nt":
|
||||
dropdown_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),
|
||||
dropdown_file_name)
|
||||
else:
|
||||
dropdown_file = os.path.join(os.path.dirname(__file__), dropdown_file_name)
|
||||
|
||||
self.dropdown_picture = tk.PhotoImage(
|
||||
file=dropdown_file
|
||||
)
|
||||
|
||||
def get_new_address():
|
||||
def show_address(address):
|
||||
w = AddressWindow(self.master, address)
|
||||
w.show()
|
||||
d = defer.maybeDeferred(self.downloader.get_new_address)
|
||||
d.addCallback(show_address)
|
||||
|
||||
def show_error(err):
|
||||
tkMessageBox.showerror(title="Failed to get new address", message=err.getErrorMessage())
|
||||
|
||||
d.addErrback(show_error)
|
||||
|
||||
self.wallet_menu = tk.Menu(
|
||||
self.master, tearoff=0
|
||||
)
|
||||
self.wallet_menu.add_command(label="Get new LBRYcrd address", command=get_new_address)
|
||||
|
||||
if os.name == "nt":
|
||||
button_cursor = ""
|
||||
else:
|
||||
button_cursor = "hand1"
|
||||
|
||||
self.wallet_menu_button = ttk.Button(self.wallet_balance_frame, image=self.dropdown_picture,
|
||||
style="Stop.TButton", cursor=button_cursor)
|
||||
self.wallet_menu_button.grid(row=0, column=1, padx=(5, 0))
|
||||
|
||||
def popup(event):
|
||||
self.wallet_menu.tk_popup(event.x_root, event.y_root)
|
||||
|
||||
self.wallet_menu_button.bind("<Button-1>", popup)
|
||||
|
||||
self.uri_frame = ttk.Frame(self.frame, style="B.TFrame")
|
||||
self.uri_frame.grid()
|
||||
|
||||
self.uri_label = ttk.Label(
|
||||
self.uri_frame, text="lbry://"
|
||||
)
|
||||
self.uri_label.grid(row=0, column=0, sticky=tk.E, pady=2)
|
||||
|
||||
self.entry_font = tkFont.Font(size=11)
|
||||
|
||||
self.uri_entry = ttk.Entry(self.uri_frame, width=50, foreground="#222222", font=self.entry_font,
|
||||
state=tk.DISABLED)
|
||||
self.uri_entry.grid(row=0, column=1, padx=2, pady=2)
|
||||
|
||||
def copy_command():
|
||||
self.uri_entry.event_generate('<Control-c>')
|
||||
|
||||
def cut_command():
|
||||
self.uri_entry.event_generate('<Control-x>')
|
||||
|
||||
def paste_command():
|
||||
self.uri_entry.event_generate('<Control-v>')
|
||||
|
||||
def popup(event):
|
||||
selection_menu = tk.Menu(
|
||||
self.master, tearoff=0
|
||||
)
|
||||
if self.uri_entry.selection_present():
|
||||
selection_menu.add_command(label=" Cut ", command=cut_command)
|
||||
selection_menu.add_command(label=" Copy ", command=copy_command)
|
||||
selection_menu.add_command(label=" Paste ", command=paste_command)
|
||||
selection_menu.tk_popup(event.x_root, event.y_root)
|
||||
|
||||
self.uri_entry.bind("<Button-3>", popup)
|
||||
|
||||
self.uri_button = ttk.Button(
|
||||
self.uri_frame, text="Go", command=self._open_stream,
|
||||
style='Lookup.LBRY.TButton', cursor=button_cursor
|
||||
)
|
||||
self.uri_button.grid(row=0, column=2, pady=2, padx=0)
|
||||
|
||||
def _start_downloader(self):
|
||||
self.downloader = LBRYDownloader()
|
||||
d = self.downloader.start()
|
||||
d.addCallback(lambda _: self.downloader.check_first_run())
|
||||
d.addCallback(self._show_welcome_message)
|
||||
return d
|
||||
|
||||
def _show_welcome_message(self, points_sent):
|
||||
if points_sent != 0.0:
|
||||
w = WelcomeWindow(self.master, points_sent)
|
||||
w.show()
|
||||
|
||||
def stream_removed(self):
|
||||
if self.streams_frame is not None:
|
||||
if len(self.streams_frame.winfo_children()) == 0:
|
||||
self.streams_frame.destroy()
|
||||
self.streams_frame = None
|
||||
|
||||
def _start_checking_wallet_balance(self):
|
||||
|
||||
def set_balance(balance):
|
||||
self.wallet_balance.configure(text=locale.format_string("%.2f LBC", (round(balance, 2),),
|
||||
grouping=True))
|
||||
|
||||
def update_balance():
|
||||
balance = self.downloader.session.wallet.get_available_balance()
|
||||
set_balance(balance)
|
||||
|
||||
def start_looping_call():
|
||||
self.wallet_balance_check = task.LoopingCall(update_balance)
|
||||
self.wallet_balance_check.start(5)
|
||||
|
||||
d = self.downloader.session.wallet.get_balance()
|
||||
d.addCallback(set_balance)
|
||||
d.addCallback(lambda _: start_looping_call())
|
||||
|
||||
def _stop_checking_wallet_balance(self):
|
||||
if self.wallet_balance_check is not None:
|
||||
self.wallet_balance_check.stop()
|
||||
|
||||
def _enable_lookup(self):
|
||||
self.uri_entry.bind('<Return>', self._open_stream)
|
||||
self.uri_entry.config(state=tk.NORMAL)
|
||||
|
||||
def _open_stream(self, event=None):
|
||||
if self.streams_frame is None:
|
||||
self.streams_frame = ttk.Frame(self.frame, style="B2.TFrame")
|
||||
self.streams_frame.grid(sticky=tk.E + tk.W)
|
||||
uri = self.uri_entry.get()
|
||||
self.uri_entry.delete(0, tk.END)
|
||||
stream_frame = StreamFrame(self, "lbry://" + uri)
|
||||
|
||||
self.downloader.download_stream(stream_frame, uri)
|
||||
|
||||
|
||||
class WelcomeWindow(object):
|
||||
def __init__(self, root, points_sent):
|
||||
self.root = root
|
||||
self.points_sent = points_sent
|
||||
|
||||
def show(self):
|
||||
window = tk.Toplevel(self.root, background="#FFFFFF")
|
||||
window.transient(self.root)
|
||||
window.wm_title("Welcome to LBRY")
|
||||
window.protocol("WM_DELETE_WINDOW", window.destroy)
|
||||
window.resizable(0, 0)
|
||||
|
||||
text_box = tk.Text(window, width=45, height=3, relief=tk.FLAT, borderwidth=0,
|
||||
highlightthickness=0)
|
||||
|
||||
points_string = locale.format_string("%.2f LBC", (round(self.points_sent, 2),),
|
||||
grouping=True)
|
||||
|
||||
text_box.insert(tk.END, "Thank you for using LBRY! You have been\n"
|
||||
"given %s for free because we love\n"
|
||||
"you. Please give them 60 seconds to show up." % points_string)
|
||||
text_box.grid(row=0, padx=10, pady=5, columnspan=2)
|
||||
text_box.config(state='normal')
|
||||
|
||||
window.focus_set()
|
||||
|
||||
|
||||
class AddressWindow(object):
|
||||
def __init__(self, root, address):
|
||||
self.root = root
|
||||
self.address = address
|
||||
|
||||
def show(self):
|
||||
window = tk.Toplevel(self.root, background="#FFFFFF")
|
||||
window.transient(self.root)
|
||||
window.wm_title("New address")
|
||||
window.protocol("WM_DELETE_WINDOW", window.destroy)
|
||||
window.resizable(0, 0)
|
||||
|
||||
text_box = tk.Text(window, width=35, height=1, relief=tk.FLAT, borderwidth=0,
|
||||
highlightthickness=0)
|
||||
text_box.insert(tk.END, self.address)
|
||||
text_box.grid(row=0, padx=10, pady=5, columnspan=2)
|
||||
text_box.config(state='normal')
|
||||
|
||||
def copy_to_clipboard():
|
||||
self.root.clipboard_clear()
|
||||
self.root.clipboard_append(text_box.get('1.0', 'end-1c'))
|
||||
|
||||
def copy_command():
|
||||
text_box.event_generate("<Control-c>")
|
||||
|
||||
copy_menu = tk.Menu(
|
||||
self.root, tearoff=0
|
||||
)
|
||||
copy_menu.add_command(label=" Copy ", command=copy_command)
|
||||
|
||||
def popup(event):
|
||||
if text_box.tag_ranges("sel"):
|
||||
copy_menu.tk_popup(event.x_root, event.y_root)
|
||||
|
||||
text_box.bind("<Button-3>", popup)
|
||||
|
||||
copy_button = ttk.Button(
|
||||
window, text="Copy", command=copy_to_clipboard, style="LBRY.TButton"
|
||||
)
|
||||
copy_button.grid(row=1, column=0, pady=(0, 5), padx=5, sticky=tk.E)
|
||||
|
||||
done_button = ttk.Button(
|
||||
window, text="OK", command=window.destroy, style="LBRY.TButton"
|
||||
)
|
||||
done_button.grid(row=1, column=1, pady=(0, 5), padx=5, sticky=tk.W)
|
||||
window.focus_set()
|
|
@ -1,393 +0,0 @@
|
|||
Attribution 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More_considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution 4.0 International Public License ("Public License"). To the
|
||||
extent this Public License may be interpreted as a contract, You are
|
||||
granted the Licensed Rights in consideration of Your acceptance of
|
||||
these terms and conditions, and the Licensor grants You such rights in
|
||||
consideration of benefits the Licensor receives from making the
|
||||
Licensed Material available under these terms and conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
d. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
f. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
g. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
h. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
i. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
j. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
k. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's
|
||||
License You apply must not prevent recipients of the Adapted
|
||||
Material from complying with this Public License.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public licenses.
|
||||
Notwithstanding, Creative Commons may elect to apply one of its public
|
||||
licenses to material it publishes and in those instances will be
|
||||
considered the "Licensor." Except for the limited purpose of indicating
|
||||
that material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the public
|
||||
licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
|
@ -1,571 +0,0 @@
|
|||
import binascii
|
||||
import logging
|
||||
import tkMessageBox
|
||||
from Crypto import Random
|
||||
from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE
|
||||
from lbrynet.core import StreamDescriptor
|
||||
from lbrynet.core.Error import UnknownNameError, UnknownStreamTypeError, InvalidStreamDescriptorError
|
||||
from lbrynet.core.Error import InvalidStreamInfoError
|
||||
from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet
|
||||
from lbrynet.core.PaymentRateManager import PaymentRateManager
|
||||
from lbrynet.core.Session import LBRYSession
|
||||
from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier
|
||||
from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory
|
||||
from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory
|
||||
from lbrynet.core.server.ServerProtocol import ServerProtocolFactory
|
||||
from lbrynet.lbryfile.LBRYFileMetadataManager import TempLBRYFileMetadataManager
|
||||
from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType
|
||||
from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory
|
||||
from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier
|
||||
import os
|
||||
import requests
|
||||
import shutil
|
||||
import sys
|
||||
from twisted.internet import threads, defer, task
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LBRYDownloader(object):
|
||||
def __init__(self):
|
||||
self.session = None
|
||||
self.known_dht_nodes = [('104.236.42.182', 4000)]
|
||||
self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrydownloader")
|
||||
self.blobfile_dir = os.path.join(self.db_dir, "blobfiles")
|
||||
self.peer_port = 3333
|
||||
self.dht_node_port = 4444
|
||||
self.run_server = True
|
||||
self.first_run = False
|
||||
self.current_db_revision = 1
|
||||
if os.name == "nt":
|
||||
from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle
|
||||
self.download_directory = get_path(FOLDERID.Downloads, UserHandle.current)
|
||||
self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrycrd")
|
||||
else:
|
||||
if sys.platform == 'darwin':
|
||||
self.download_directory = os.path.join(os.path.expanduser("~"), "Downloads")
|
||||
self.wallet_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrycrd")
|
||||
else:
|
||||
self.download_directory = os.getcwd()
|
||||
self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd")
|
||||
self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf")
|
||||
self.wallet_user = None
|
||||
self.wallet_password = None
|
||||
self.sd_identifier = StreamDescriptorIdentifier()
|
||||
self.wallet_rpc_port = 8332
|
||||
self.download_deferreds = []
|
||||
self.stream_frames = []
|
||||
self.default_blob_data_payment_rate = MIN_BLOB_DATA_PAYMENT_RATE
|
||||
self.use_upnp = True
|
||||
self.start_lbrycrdd = True
|
||||
if os.name == "nt":
|
||||
self.lbrycrdd_path = "lbrycrdd.exe"
|
||||
else:
|
||||
self.lbrycrdd_path = None
|
||||
self.default_lbrycrdd_path = "./lbrycrdd"
|
||||
self.delete_blobs_on_remove = True
|
||||
self.blob_request_payment_rate_manager = None
|
||||
|
||||
def start(self):
|
||||
d = self._load_conf_options()
|
||||
d.addCallback(lambda _: threads.deferToThread(self._create_directory))
|
||||
d.addCallback(lambda _: self._check_db_migration())
|
||||
d.addCallback(lambda _: self._get_session())
|
||||
d.addCallback(lambda _: self._setup_stream_info_manager())
|
||||
d.addCallback(lambda _: self._setup_stream_identifier())
|
||||
d.addCallback(lambda _: self.start_server())
|
||||
return d
|
||||
|
||||
def stop(self):
|
||||
dl = defer.DeferredList(self.download_deferreds)
|
||||
for stream_frame in self.stream_frames:
|
||||
stream_frame.cancel_func()
|
||||
if self.session is not None:
|
||||
dl.addBoth(lambda _: self.stop_server())
|
||||
dl.addBoth(lambda _: self.session.shut_down())
|
||||
return dl
|
||||
|
||||
def get_new_address(self):
|
||||
return self.session.wallet.get_new_address()
|
||||
|
||||
def _check_db_migration(self):
|
||||
old_revision = 0
|
||||
db_revision_file = os.path.join(self.db_dir, "db_revision")
|
||||
if os.path.exists(db_revision_file):
|
||||
old_revision = int(open(db_revision_file).read().strip())
|
||||
if old_revision < self.current_db_revision:
|
||||
if os.name == "nt":
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def run_migrator():
|
||||
migrator_exe = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),
|
||||
"dbmigrator", "migrator.exe")
|
||||
print "trying to find the migrator at", migrator_exe
|
||||
si = subprocess.STARTUPINFO
|
||||
si.dwFlags = subprocess.STARTF_USESHOWWINDOW
|
||||
si.wShowWindow = subprocess.SW_HIDE
|
||||
print "trying to run the migrator"
|
||||
migrator_proc = subprocess.Popen([migrator_exe, self.db_dir, str(old_revision),
|
||||
str(self.current_db_revision)], startupinfo=si)
|
||||
print "started the migrator"
|
||||
migrator_proc.wait()
|
||||
print "migrator has returned"
|
||||
|
||||
return threads.deferToThread(run_migrator)
|
||||
else:
|
||||
from lbrynet.db_migrator import dbmigrator
|
||||
return threads.deferToThread(dbmigrator.migrate_db, self.db_dir, old_revision,
|
||||
self.current_db_revision)
|
||||
return defer.succeed(True)
|
||||
|
||||
def _load_conf_options(self):
|
||||
|
||||
def get_lbrycrdd_path_conf_file():
|
||||
if os.name == "nt":
|
||||
return ""
|
||||
lbrycrdd_path_conf_path = os.path.join(os.path.expanduser("~"), ".lbrycrddpath.conf")
|
||||
if not os.path.exists(lbrycrdd_path_conf_path):
|
||||
return ""
|
||||
lbrycrdd_path_conf = open(lbrycrdd_path_conf_path)
|
||||
lines = lbrycrdd_path_conf.readlines()
|
||||
return lines
|
||||
|
||||
d = threads.deferToThread(get_lbrycrdd_path_conf_file)
|
||||
|
||||
def load_lbrycrdd_path(conf):
|
||||
for line in conf:
|
||||
if len(line.strip()) and line.strip()[0] != "#":
|
||||
self.lbrycrdd_path = line.strip()
|
||||
|
||||
d.addCallback(load_lbrycrdd_path)
|
||||
|
||||
def get_configuration_file():
|
||||
if os.name == "nt":
|
||||
lbry_conf_path = "lbry.conf"
|
||||
if not os.path.exists(lbry_conf_path):
|
||||
log.debug("Could not read lbry.conf")
|
||||
return ""
|
||||
else:
|
||||
lbry_conf_path = os.path.join(os.path.expanduser("~"), ".lbrynetgui.conf")
|
||||
if not os.path.exists(lbry_conf_path):
|
||||
clean_conf_path = os.path.join(os.path.dirname(__file__), "lbry.conf")
|
||||
shutil.copy(clean_conf_path, lbry_conf_path)
|
||||
lbry_conf = open(lbry_conf_path)
|
||||
log.debug("Loading configuration options from %s", lbry_conf_path)
|
||||
lines = lbry_conf.readlines()
|
||||
log.debug("%s file contents:\n%s", lbry_conf_path, str(lines))
|
||||
return lines
|
||||
|
||||
d.addCallback(lambda _: threads.deferToThread(get_configuration_file))
|
||||
|
||||
def load_configuration_file(conf):
|
||||
for line in conf:
|
||||
if len(line.strip()) and line.strip()[0] != "#":
|
||||
try:
|
||||
field_name, field_value = map(lambda x: x.strip(), line.strip().split("=", 1))
|
||||
field_name = field_name.lower()
|
||||
except ValueError:
|
||||
raise ValueError("Invalid configuration line: %s" % line)
|
||||
if field_name == "known_dht_nodes":
|
||||
known_nodes = []
|
||||
nodes = field_value.split(",")
|
||||
for n in nodes:
|
||||
if n.strip():
|
||||
try:
|
||||
ip_address, port_string = map(lambda x: x.strip(), n.split(":"))
|
||||
ip_numbers = ip_address.split(".")
|
||||
assert len(ip_numbers) == 4
|
||||
for ip_num in ip_numbers:
|
||||
num = int(ip_num)
|
||||
assert 0 <= num <= 255
|
||||
known_nodes.append((ip_address, int(port_string)))
|
||||
except (ValueError, AssertionError):
|
||||
raise ValueError("Expected known nodes in format 192.168.1.1:4000,192.168.1.2:4001. Got %s" % str(field_value))
|
||||
log.debug("Setting known_dht_nodes to %s", str(known_nodes))
|
||||
self.known_dht_nodes = known_nodes
|
||||
elif field_name == "run_server":
|
||||
if field_value.lower() == "true":
|
||||
run_server = True
|
||||
elif field_value.lower() == "false":
|
||||
run_server = False
|
||||
else:
|
||||
raise ValueError("run_server must be set to True or False. Got %s" % field_value)
|
||||
log.debug("Setting run_server to %s", str(run_server))
|
||||
self.run_server = run_server
|
||||
elif field_name == "data_dir":
|
||||
log.debug("Setting data_dir to %s", str(field_value))
|
||||
self.db_dir = field_value
|
||||
self.blobfile_dir = os.path.join(self.db_dir, "blobfiles")
|
||||
elif field_name == "wallet_dir":
|
||||
log.debug("Setting wallet_dir to %s", str(field_value))
|
||||
self.wallet_dir = field_value
|
||||
elif field_name == "wallet_conf":
|
||||
log.debug("Setting wallet_conf to %s", str(field_value))
|
||||
self.wallet_conf = field_value
|
||||
elif field_name == "peer_port":
|
||||
try:
|
||||
peer_port = int(field_value)
|
||||
assert 0 <= peer_port <= 65535
|
||||
log.debug("Setting peer_port to %s", str(peer_port))
|
||||
self.peer_port = peer_port
|
||||
except (ValueError, AssertionError):
|
||||
raise ValueError("peer_port must be set to an integer between 1 and 65535. Got %s" % field_value)
|
||||
elif field_name == "dht_port":
|
||||
try:
|
||||
dht_port = int(field_value)
|
||||
assert 0 <= dht_port <= 65535
|
||||
log.debug("Setting dht_node_port to %s", str(dht_port))
|
||||
self.dht_node_port = dht_port
|
||||
except (ValueError, AssertionError):
|
||||
raise ValueError("dht_port must be set to an integer between 1 and 65535. Got %s" % field_value)
|
||||
elif field_name == "use_upnp":
|
||||
if field_value.lower() == "true":
|
||||
use_upnp = True
|
||||
elif field_value.lower() == "false":
|
||||
use_upnp = False
|
||||
else:
|
||||
raise ValueError("use_upnp must be set to True or False. Got %s" % str(field_value))
|
||||
log.debug("Setting use_upnp to %s", str(use_upnp))
|
||||
self.use_upnp = use_upnp
|
||||
elif field_name == "default_blob_data_payment_rate":
|
||||
try:
|
||||
rate = float(field_value)
|
||||
assert rate >= 0.0
|
||||
log.debug("Setting default_blob_data_payment_rate to %s", str(rate))
|
||||
self.default_blob_data_payment_rate = rate
|
||||
except (ValueError, AssertionError):
|
||||
raise ValueError("default_blob_data_payment_rate must be a positive floating point number, e.g. 0.5. Got %s" % str(field_value))
|
||||
elif field_name == "start_lbrycrdd":
|
||||
if field_value.lower() == "true":
|
||||
start_lbrycrdd = True
|
||||
elif field_value.lower() == "false":
|
||||
start_lbrycrdd = False
|
||||
else:
|
||||
raise ValueError("start_lbrycrdd must be set to True or False. Got %s" % field_value)
|
||||
log.debug("Setting start_lbrycrdd to %s", str(start_lbrycrdd))
|
||||
self.start_lbrycrdd = start_lbrycrdd
|
||||
elif field_name == "lbrycrdd_path":
|
||||
self.lbrycrdd_path = field_value
|
||||
elif field_name == "download_directory":
|
||||
log.debug("Setting download_directory to %s", str(field_value))
|
||||
self.download_directory = field_value
|
||||
elif field_name == "delete_blobs_on_stream_remove":
|
||||
if field_value.lower() == "true":
|
||||
self.delete_blobs_on_remove = True
|
||||
elif field_value.lower() == "false":
|
||||
self.delete_blobs_on_remove = False
|
||||
else:
|
||||
raise ValueError("delete_blobs_on_stream_remove must be set to True or False")
|
||||
else:
|
||||
log.warning("Got unknown configuration field: %s", field_name)
|
||||
|
||||
d.addCallback(load_configuration_file)
|
||||
return d
|
||||
|
||||
def _create_directory(self):
|
||||
if not os.path.exists(self.db_dir):
|
||||
os.makedirs(self.db_dir)
|
||||
db_revision = open(os.path.join(self.db_dir, "db_revision"), mode='w')
|
||||
db_revision.write(str(self.current_db_revision))
|
||||
db_revision.close()
|
||||
log.debug("Created the configuration directory: %s", str(self.db_dir))
|
||||
if not os.path.exists(self.blobfile_dir):
|
||||
os.makedirs(self.blobfile_dir)
|
||||
log.debug("Created the data directory: %s", str(self.blobfile_dir))
|
||||
if os.name == "nt":
|
||||
if not os.path.exists(self.wallet_dir):
|
||||
os.makedirs(self.wallet_dir)
|
||||
if not os.path.exists(self.wallet_conf):
|
||||
lbrycrd_conf = open(self.wallet_conf, mode='w')
|
||||
self.wallet_user = "rpcuser"
|
||||
lbrycrd_conf.write("rpcuser=%s\n" % self.wallet_user)
|
||||
self.wallet_password = binascii.hexlify(Random.new().read(20))
|
||||
lbrycrd_conf.write("rpcpassword=%s\n" % self.wallet_password)
|
||||
lbrycrd_conf.write("server=1\n")
|
||||
lbrycrd_conf.close()
|
||||
else:
|
||||
lbrycrd_conf = open(self.wallet_conf)
|
||||
for l in lbrycrd_conf:
|
||||
if l.startswith("rpcuser="):
|
||||
self.wallet_user = l[8:].rstrip('\n')
|
||||
if l.startswith("rpcpassword="):
|
||||
self.wallet_password = l[12:].rstrip('\n')
|
||||
if l.startswith("rpcport="):
|
||||
self.wallet_rpc_port = int(l[8:-1].rstrip('\n'))
|
||||
|
||||
def _get_session(self):
|
||||
lbrycrdd_path = None
|
||||
if self.start_lbrycrdd is True:
|
||||
lbrycrdd_path = self.lbrycrdd_path
|
||||
if not lbrycrdd_path:
|
||||
lbrycrdd_path = self.default_lbrycrdd_path
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
os.chdir("/Applications/LBRY.app/Contents/Resources")
|
||||
|
||||
wallet = LBRYcrdWallet(self.db_dir, wallet_dir=self.wallet_dir, wallet_conf=self.wallet_conf,
|
||||
lbrycrdd_path=lbrycrdd_path)
|
||||
|
||||
peer_port = None
|
||||
if self.run_server:
|
||||
peer_port = self.peer_port
|
||||
self.session = LBRYSession(self.default_blob_data_payment_rate, db_dir=self.db_dir,
|
||||
blob_dir=self.blobfile_dir, use_upnp=self.use_upnp, wallet=wallet,
|
||||
known_dht_nodes=self.known_dht_nodes, dht_node_port=self.dht_node_port,
|
||||
peer_port=peer_port)
|
||||
return self.session.setup()
|
||||
|
||||
def _setup_stream_info_manager(self):
|
||||
self.stream_info_manager = TempLBRYFileMetadataManager()
|
||||
return defer.succeed(True)
|
||||
|
||||
def start_server(self):
|
||||
|
||||
if self.run_server:
|
||||
self.blob_request_payment_rate_manager = PaymentRateManager(
|
||||
self.session.base_payment_rate_manager,
|
||||
self.default_blob_data_payment_rate
|
||||
)
|
||||
handlers = [
|
||||
BlobAvailabilityHandlerFactory(self.session.blob_manager),
|
||||
self.session.wallet.get_wallet_info_query_handler_factory(),
|
||||
BlobRequestHandlerFactory(self.session.blob_manager, self.session.wallet,
|
||||
self.blob_request_payment_rate_manager)
|
||||
]
|
||||
|
||||
server_factory = ServerProtocolFactory(self.session.rate_limiter,
|
||||
handlers,
|
||||
self.session.peer_manager)
|
||||
from twisted.internet import reactor
|
||||
self.lbry_server_port = reactor.listenTCP(self.peer_port, server_factory)
|
||||
|
||||
return defer.succeed(True)
|
||||
|
||||
def stop_server(self):
|
||||
if self.lbry_server_port is not None:
|
||||
self.lbry_server_port, p = None, self.lbry_server_port
|
||||
return defer.maybeDeferred(p.stopListening)
|
||||
else:
|
||||
return defer.succeed(True)
|
||||
|
||||
def _setup_stream_identifier(self):
|
||||
add_lbry_file_to_sd_identifier(self.sd_identifier)
|
||||
file_saver_factory = LBRYFileSaverFactory(self.session.peer_finder, self.session.rate_limiter,
|
||||
self.session.blob_manager, self.stream_info_manager,
|
||||
self.session.wallet, self.download_directory)
|
||||
self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, file_saver_factory)
|
||||
file_opener_factory = LBRYFileOpenerFactory(self.session.peer_finder, self.session.rate_limiter,
|
||||
self.session.blob_manager, self.stream_info_manager,
|
||||
self.session.wallet)
|
||||
self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, file_opener_factory)
|
||||
|
||||
def check_first_run(self):
|
||||
d = self.session.wallet.check_first_run()
|
||||
d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run else 0.0)
|
||||
return d
|
||||
|
||||
def _do_first_run(self):
|
||||
d = self.session.wallet.get_new_address()
|
||||
|
||||
def send_request(url, data):
|
||||
r = requests.post(url, json=data)
|
||||
if r.status_code == 200:
|
||||
return r.json()['credits_sent']
|
||||
return 0.0
|
||||
|
||||
def log_error(err):
|
||||
log.warning("unable to request free credits. %s", err.getErrorMessage())
|
||||
return 0.0
|
||||
|
||||
def request_credits(address):
|
||||
url = "http://credreq.lbry.io/requestcredits"
|
||||
data = {"address": address}
|
||||
d = threads.deferToThread(send_request, url, data)
|
||||
d.addErrback(log_error)
|
||||
return d
|
||||
|
||||
d.addCallback(request_credits)
|
||||
return d
|
||||
|
||||
def _resolve_name(self, uri):
|
||||
return self.session.wallet.get_stream_info_for_name(uri)
|
||||
|
||||
def download_stream(self, stream_frame, uri):
|
||||
resolve_d = self._resolve_name(uri)
|
||||
|
||||
stream_frame.show_metadata_status("resolving name...")
|
||||
|
||||
stream_frame.cancel_func = resolve_d.cancel
|
||||
payment_rate_manager = PaymentRateManager(self.session.base_payment_rate_manager)
|
||||
|
||||
def update_stream_name(value):
|
||||
if 'name' in value:
|
||||
stream_frame.show_name(value['name'])
|
||||
if 'description' in value:
|
||||
stream_frame.show_description(value['description'])
|
||||
if 'thumbnail' in value:
|
||||
stream_frame.show_thumbnail(value['thumbnail'])
|
||||
return value
|
||||
|
||||
def get_sd_hash(value):
|
||||
if 'stream_hash' in value:
|
||||
return value['stream_hash']
|
||||
raise UnknownNameError(uri)
|
||||
|
||||
def get_sd_blob(sd_hash):
|
||||
stream_frame.show_metadata_status("name resolved, fetching metadata...")
|
||||
get_sd_d = StreamDescriptor.download_sd_blob(self.session, sd_hash,
|
||||
payment_rate_manager)
|
||||
get_sd_d.addCallback(self.sd_identifier.get_metadata_for_sd_blob)
|
||||
get_sd_d.addCallbacks(choose_download_factory, bad_sd_blob)
|
||||
return get_sd_d
|
||||
|
||||
def get_info_from_validator(info_validator):
|
||||
stream_name = None
|
||||
stream_size = None
|
||||
for field, val in info_validator.info_to_show():
|
||||
if field == "suggested_file_name":
|
||||
stream_name = val
|
||||
elif field == "stream_name" and stream_name is None:
|
||||
stream_name = val
|
||||
elif field == "stream_size":
|
||||
stream_size = int(val)
|
||||
if stream_size is None:
|
||||
stream_size = "unknown"
|
||||
if stream_name is None:
|
||||
stream_name = "unknown"
|
||||
return stream_name, stream_size
|
||||
|
||||
def choose_download_factory(metadata):
|
||||
#info_validator, options, factories = info_and_factories
|
||||
stream_name, stream_size = get_info_from_validator(metadata.validator)
|
||||
if isinstance(stream_size, (int, long)):
|
||||
price = payment_rate_manager.get_effective_min_blob_data_payment_rate()
|
||||
estimated_cost = stream_size * 1.0 / 2**20 * price
|
||||
else:
|
||||
estimated_cost = "unknown"
|
||||
|
||||
stream_frame.show_stream_metadata(stream_name, stream_size, estimated_cost)
|
||||
|
||||
available_options = metadata.options.get_downloader_options(metadata.validator,
|
||||
payment_rate_manager)
|
||||
|
||||
stream_frame.show_download_options(available_options)
|
||||
|
||||
get_downloader_d = defer.Deferred()
|
||||
|
||||
def create_downloader(f, chosen_options):
|
||||
|
||||
def fire_get_downloader_d(downloader):
|
||||
if not get_downloader_d.called:
|
||||
get_downloader_d.callback(downloader)
|
||||
|
||||
stream_frame.disable_download_buttons()
|
||||
d = f.make_downloader(metadata, chosen_options,
|
||||
payment_rate_manager)
|
||||
d.addCallback(fire_get_downloader_d)
|
||||
|
||||
for factory in metadata.factories:
|
||||
|
||||
def choose_factory(f=factory):
|
||||
chosen_options = stream_frame.get_chosen_options()
|
||||
create_downloader(f, chosen_options)
|
||||
|
||||
stream_frame.add_download_factory(factory, choose_factory)
|
||||
|
||||
get_downloader_d.addCallback(start_download)
|
||||
|
||||
return get_downloader_d
|
||||
|
||||
def show_stream_status(downloader):
|
||||
total_bytes = downloader.get_total_bytes_cached()
|
||||
bytes_left_to_download = downloader.get_bytes_left_to_download()
|
||||
points_paid = payment_rate_manager.points_paid
|
||||
payment_rate = payment_rate_manager.get_effective_min_blob_data_payment_rate()
|
||||
points_remaining = 1.0 * bytes_left_to_download * payment_rate / 2**20
|
||||
stream_frame.show_progress(total_bytes, bytes_left_to_download,
|
||||
points_paid, points_remaining)
|
||||
|
||||
def show_finished(arg, downloader):
|
||||
show_stream_status(downloader)
|
||||
stream_frame.show_download_done(payment_rate_manager.points_paid)
|
||||
return arg
|
||||
|
||||
def start_download(downloader):
|
||||
stream_frame.stream_hash = downloader.stream_hash
|
||||
l = task.LoopingCall(show_stream_status, downloader)
|
||||
l.start(1)
|
||||
d = downloader.start()
|
||||
stream_frame.cancel_func = downloader.stop
|
||||
|
||||
def stop_looping_call(arg):
|
||||
l.stop()
|
||||
stream_frame.cancel_func = resolve_d.cancel
|
||||
return arg
|
||||
|
||||
d.addBoth(stop_looping_call)
|
||||
d.addCallback(show_finished, downloader)
|
||||
return d
|
||||
|
||||
def lookup_failed(err):
|
||||
stream_frame.show_metadata_status("name lookup failed")
|
||||
return err
|
||||
|
||||
def bad_sd_blob(err):
|
||||
stream_frame.show_metadata_status("Unknown type or badly formed metadata")
|
||||
return err
|
||||
|
||||
resolve_d.addCallback(update_stream_name)
|
||||
resolve_d.addCallback(get_sd_hash)
|
||||
resolve_d.addCallbacks(get_sd_blob, lookup_failed)
|
||||
|
||||
def show_err(err):
|
||||
tkMessageBox.showerror(title="Download Error", message=err.getErrorMessage())
|
||||
log.error(err.getErrorMessage())
|
||||
stream_frame.show_download_done(payment_rate_manager.points_paid)
|
||||
|
||||
resolve_d.addErrback(lambda err: err.trap(defer.CancelledError, UnknownNameError,
|
||||
UnknownStreamTypeError, InvalidStreamDescriptorError,
|
||||
InvalidStreamInfoError))
|
||||
resolve_d.addErrback(show_err)
|
||||
|
||||
def delete_associated_blobs():
|
||||
if stream_frame.stream_hash is None or self.delete_blobs_on_remove is False:
|
||||
return defer.succeed(True)
|
||||
d1 = self.stream_info_manager.get_blobs_for_stream(stream_frame.stream_hash)
|
||||
|
||||
def get_blob_hashes(blob_infos):
|
||||
return [b[0] for b in blob_infos if b[0] is not None]
|
||||
|
||||
d1.addCallback(get_blob_hashes)
|
||||
d2 = self.stream_info_manager.get_sd_blob_hashes_for_stream(stream_frame.stream_hash)
|
||||
|
||||
def combine_blob_hashes(results):
|
||||
blob_hashes = []
|
||||
for success, result in results:
|
||||
if success is True:
|
||||
blob_hashes.extend(result)
|
||||
return blob_hashes
|
||||
|
||||
def delete_blobs(blob_hashes):
|
||||
return self.session.blob_manager.delete_blobs(blob_hashes)
|
||||
|
||||
dl = defer.DeferredList([d1, d2], fireOnOneErrback=True)
|
||||
dl.addCallback(combine_blob_hashes)
|
||||
dl.addCallback(delete_blobs)
|
||||
return dl
|
||||
|
||||
resolve_d.addCallback(lambda _: delete_associated_blobs())
|
||||
self._add_download_deferred(resolve_d, stream_frame)
|
||||
|
||||
def _add_download_deferred(self, d, stream_frame):
|
||||
self.download_deferreds.append(d)
|
||||
self.stream_frames.append(stream_frame)
|
||||
|
||||
def remove_from_list():
|
||||
self.download_deferreds.remove(d)
|
||||
self.stream_frames.remove(stream_frame)
|
||||
|
||||
d.addBoth(lambda _: remove_from_list())
|
|
@ -1,464 +0,0 @@
|
|||
import Tkinter as tk
|
||||
import sys
|
||||
import tkFont
|
||||
import ttk
|
||||
import locale
|
||||
import os
|
||||
|
||||
|
||||
class StreamFrame(object):
|
||||
def __init__(self, app, uri):
|
||||
self.app = app
|
||||
self.uri = uri
|
||||
self.stream_hash = None
|
||||
self.cancel_func = None
|
||||
|
||||
self.stream_frame = ttk.Frame(self.app.streams_frame, style="B.TFrame")
|
||||
|
||||
self.stream_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=(30, 0))
|
||||
|
||||
self.stream_frame_header = ttk.Frame(self.stream_frame, style="C.TFrame")
|
||||
self.stream_frame_header.grid(sticky=tk.E + tk.W)
|
||||
|
||||
self.uri_font = tkFont.Font(size=8)
|
||||
self.uri_label = ttk.Label(
|
||||
self.stream_frame_header, text=self.uri, font=self.uri_font, foreground="#666666"
|
||||
)
|
||||
self.uri_label.grid(row=0, column=0, sticky=tk.W)
|
||||
|
||||
if os.name == "nt":
|
||||
self.button_cursor = ""
|
||||
else:
|
||||
self.button_cursor = "hand1"
|
||||
|
||||
close_file_name = "close2.gif"
|
||||
if os.name == "nt":
|
||||
close_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), "lbrynet",
|
||||
"lbrynet_downloader_gui", close_file_name)
|
||||
else:
|
||||
close_file = os.path.join(os.path.dirname(__file__), close_file_name)
|
||||
|
||||
self.close_picture = tk.PhotoImage(
|
||||
file=close_file
|
||||
)
|
||||
self.close_button = ttk.Button(
|
||||
self.stream_frame_header, command=self.cancel, style="Stop.TButton", cursor=self.button_cursor
|
||||
)
|
||||
self.close_button.config(image=self.close_picture)
|
||||
self.close_button.grid(row=0, column=1, sticky=tk.E + tk.N)
|
||||
|
||||
self.stream_frame_header.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.stream_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.stream_frame_body = ttk.Frame(self.stream_frame, style="C.TFrame")
|
||||
self.stream_frame_body.grid(row=1, column=0, sticky=tk.E + tk.W)
|
||||
|
||||
self.name_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame")
|
||||
self.name_frame.grid(sticky=tk.W + tk.E)
|
||||
self.name_frame.grid_columnconfigure(0, weight=16)
|
||||
self.name_frame.grid_columnconfigure(1, weight=1)
|
||||
|
||||
self.stream_frame_body.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.metadata_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame")
|
||||
self.metadata_frame.grid(sticky=tk.W + tk.E, row=1)
|
||||
self.metadata_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.options_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame")
|
||||
|
||||
self.outer_button_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame")
|
||||
self.outer_button_frame.grid(sticky=tk.W + tk.E, row=4)
|
||||
|
||||
#show_options_picture_file_name = "show_options.gif"
|
||||
#if os.name == "nt":
|
||||
# show_options_picture_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),
|
||||
# "lbrynet", "lbrynet_downloader_gui",
|
||||
# show_options_picture_file_name)
|
||||
#else:
|
||||
# show_options_picture_file = os.path.join(os.path.dirname(__file__),
|
||||
# show_options_picture_file_name)
|
||||
|
||||
#self.show_options_picture = tk.PhotoImage(
|
||||
# file=show_options_picture_file
|
||||
#)
|
||||
|
||||
#hide_options_picture_file_name = "hide_options.gif"
|
||||
#if os.name == "nt":
|
||||
# hide_options_picture_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),
|
||||
# "lbrynet", "lbrynet_downloader_gui",
|
||||
# hide_options_picture_file_name)
|
||||
#else:
|
||||
# hide_options_picture_file = os.path.join(os.path.dirname(__file__),
|
||||
# hide_options_picture_file_name)
|
||||
|
||||
#self.hide_options_picture = tk.PhotoImage(
|
||||
# file=hide_options_picture_file
|
||||
#)
|
||||
|
||||
#self.show_options_button = None
|
||||
|
||||
self.status_label = None
|
||||
#self.name_label = None
|
||||
self.name_text = None
|
||||
self.estimated_cost_frame = None
|
||||
self.bytes_downloaded_label = None
|
||||
self.button_frame = None
|
||||
self.download_buttons = []
|
||||
self.option_frames = []
|
||||
self.name_font = None
|
||||
self.description_text = None
|
||||
#self.description_label = None
|
||||
self.file_name_frame = None
|
||||
self.cost_frame = None
|
||||
self.cost_description = None
|
||||
self.remaining_cost_description = None
|
||||
self.cost_label = None
|
||||
self.remaining_cost_label = None
|
||||
self.progress_frame = None
|
||||
|
||||
def cancel(self):
|
||||
if self.cancel_func is not None:
|
||||
self.cancel_func()
|
||||
self.stream_frame.destroy()
|
||||
self.app.stream_removed()
|
||||
|
||||
def _resize_text(self, text_widget):
|
||||
actual_height = text_widget.tk.call(text_widget._w, "count", "-displaylines", "0.0", "end")
|
||||
text_widget.config(height=int(actual_height))
|
||||
|
||||
def show_name(self, name):
|
||||
self.name_font = tkFont.Font(size=16)
|
||||
#self.name_label = ttk.Label(
|
||||
# self.name_frame, text=name, font=self.name_font
|
||||
#)
|
||||
#self.name_label.grid(row=0, column=0, sticky=tk.W)
|
||||
self.name_text = tk.Text(
|
||||
self.name_frame, font=self.name_font, wrap=tk.WORD, relief=tk.FLAT, borderwidth=0,
|
||||
highlightthickness=0, width=1, height=1
|
||||
)
|
||||
self.name_text.insert(tk.INSERT, name)
|
||||
self.name_text.config(state=tk.DISABLED)
|
||||
self.name_text.grid(row=0, column=0, sticky=tk.W+tk.E+tk.N+tk.S)
|
||||
self.name_text.update()
|
||||
self._resize_text(self.name_text)
|
||||
|
||||
def show_description(self, description):
|
||||
#if os.name == "nt":
|
||||
# wraplength = 580
|
||||
#else:
|
||||
# wraplength = 600
|
||||
#self.description_label = ttk.Label(
|
||||
# self.name_frame, text=description, wraplength=wraplength
|
||||
#)
|
||||
#self.description_label.grid(row=1, column=0, sticky=tk.W)
|
||||
self.description_text = tk.Text(
|
||||
self.name_frame, wrap=tk.WORD, relief=tk.FLAT, borderwidth=0,
|
||||
highlightthickness=0, width=1, height=1
|
||||
)
|
||||
self.description_text.insert(tk.INSERT, description)
|
||||
self.description_text.config(state=tk.DISABLED)
|
||||
self.description_text.grid(row=1, column=0, sticky=tk.W+tk.E+tk.N+tk.S)
|
||||
self.description_text.update()
|
||||
self._resize_text(self.description_text)
|
||||
|
||||
def show_thumbnail(self, url=None):
|
||||
thumbnail = None
|
||||
if url is not None:
|
||||
import urllib2
|
||||
import base64
|
||||
response = urllib2.urlopen(url)
|
||||
thumbnail_data = base64.b64encode(response.read())
|
||||
thumbnail = tk.PhotoImage(data=thumbnail_data)
|
||||
current_width = thumbnail.width()
|
||||
current_height = thumbnail.height()
|
||||
max_width = 130
|
||||
max_height = 90
|
||||
scale_ratio = max(1.0 * current_width / max_width, 1.0 * current_height / max_height)
|
||||
if scale_ratio < 1:
|
||||
scale_ratio = 1
|
||||
else:
|
||||
scale_ratio = int(scale_ratio + 1)
|
||||
thumbnail = thumbnail.subsample(scale_ratio)
|
||||
if thumbnail is not None:
|
||||
label = ttk.Label(self.name_frame, image=thumbnail)
|
||||
label.safekeeping = thumbnail
|
||||
label.grid(row=0, column=1, rowspan=2, sticky=tk.E+tk.N+tk.W+tk.S)
|
||||
label.update()
|
||||
self.description_text.update()
|
||||
self.name_text.update()
|
||||
self._resize_text(self.description_text)
|
||||
self._resize_text(self.name_text)
|
||||
|
||||
def show_metadata_status(self, value):
|
||||
if self.status_label is None:
|
||||
self.status_label = ttk.Label(
|
||||
self.metadata_frame, text=value
|
||||
)
|
||||
self.status_label.grid()
|
||||
self.metadata_frame.grid_columnconfigure(0, weight=1)
|
||||
else:
|
||||
self.status_label.config(text=value)
|
||||
|
||||
@staticmethod
|
||||
def get_formatted_stream_size(stream_size):
|
||||
if isinstance(stream_size, (int, long)):
|
||||
if stream_size >= 2**40:
|
||||
units = "TB"
|
||||
factor = 2**40
|
||||
elif stream_size >= 2**30:
|
||||
units = "GB"
|
||||
factor = 2**30
|
||||
elif stream_size >= 2**20:
|
||||
units = "MB"
|
||||
factor = 2**20
|
||||
elif stream_size >= 2**10:
|
||||
units = "KB"
|
||||
factor = 2**10
|
||||
else:
|
||||
return str(stream_size) + " B"
|
||||
return "%.1f %s" % (round((stream_size * 1.0 / factor), 1), units)
|
||||
return stream_size
|
||||
|
||||
def show_stream_metadata(self, stream_name, stream_size, stream_cost):
|
||||
if self.status_label is not None:
|
||||
self.status_label.destroy()
|
||||
|
||||
self.file_name_frame = ttk.Frame(self.metadata_frame, style="F.TFrame")
|
||||
self.file_name_frame.grid(row=0, column=0, sticky=tk.W)
|
||||
self.metadata_frame.grid_columnconfigure(0, weight=1, uniform="metadata")
|
||||
|
||||
file_size_label = ttk.Label(
|
||||
self.file_name_frame,
|
||||
text=self.get_formatted_stream_size(stream_size)
|
||||
)
|
||||
file_size_label.grid(row=0, column=2)
|
||||
|
||||
file_name_label = ttk.Label(
|
||||
self.file_name_frame,
|
||||
text=" - " + stream_name,
|
||||
)
|
||||
file_name_label.grid(row=0, column=3)
|
||||
|
||||
self.estimated_cost_frame = ttk.Frame(self.metadata_frame, style="F.TFrame")
|
||||
self.estimated_cost_frame.grid(row=1, column=0, sticky=tk.W)
|
||||
|
||||
estimated_cost_label = ttk.Label(
|
||||
self.estimated_cost_frame,
|
||||
text=locale.format_string("%.2f LBC",
|
||||
(round(stream_cost, 2)), grouping=True),
|
||||
foreground="red"
|
||||
)
|
||||
estimated_cost_label.grid(row=1, column=2)
|
||||
|
||||
self.button_frame = ttk.Frame(self.outer_button_frame, style="E.TFrame")
|
||||
self.button_frame.grid(row=0, column=1)
|
||||
|
||||
self.outer_button_frame.grid_columnconfigure(0, weight=1, uniform="buttons")
|
||||
self.outer_button_frame.grid_columnconfigure(1, weight=2, uniform="buttons1")
|
||||
self.outer_button_frame.grid_columnconfigure(2, weight=1, uniform="buttons")
|
||||
|
||||
def add_download_factory(self, factory, download_func):
|
||||
download_button = ttk.Button(
|
||||
self.button_frame, text=factory.get_description(), command=download_func,
|
||||
style='LBRY.TButton', cursor=self.button_cursor
|
||||
)
|
||||
self.download_buttons.append(download_button)
|
||||
download_button.grid(row=0, column=len(self.download_buttons) - 1, padx=5, pady=(1, 2))
|
||||
|
||||
def disable_download_buttons(self):
|
||||
for download_button in self.download_buttons:
|
||||
download_button.config(state=tk.DISABLED)
|
||||
|
||||
def remove_download_buttons(self):
|
||||
for download_button in self.download_buttons:
|
||||
download_button.destroy()
|
||||
self.download_buttons = []
|
||||
|
||||
def get_option_widget(self, option_type, option_frame):
|
||||
if option_type.value == float:
|
||||
entry_frame = ttk.Frame(
|
||||
option_frame,
|
||||
style="H.TFrame"
|
||||
)
|
||||
entry_frame.grid()
|
||||
col = 0
|
||||
if option_type.short_description is not None:
|
||||
entry_label = ttk.Label(
|
||||
entry_frame,
|
||||
#text=option_type.short_description
|
||||
text=""
|
||||
)
|
||||
entry_label.grid(row=0, column=0, sticky=tk.W)
|
||||
col = 1
|
||||
entry = ttk.Entry(
|
||||
entry_frame,
|
||||
width=10,
|
||||
style="Float.TEntry"
|
||||
)
|
||||
entry_frame.entry = entry
|
||||
entry.grid(row=0, column=col, sticky=tk.W)
|
||||
return entry_frame
|
||||
if option_type.value == bool:
|
||||
bool_frame = ttk.Frame(
|
||||
option_frame,
|
||||
style="H.TFrame"
|
||||
)
|
||||
bool_frame.chosen_value = tk.BooleanVar()
|
||||
true_text = "True"
|
||||
false_text = "False"
|
||||
if option_type.bool_options_description is not None:
|
||||
true_text, false_text = option_type.bool_options_description
|
||||
true_radio_button = ttk.Radiobutton(
|
||||
bool_frame, text=true_text, variable=bool_frame.chosen_value, value=True
|
||||
)
|
||||
true_radio_button.grid(row=0, sticky=tk.W)
|
||||
false_radio_button = ttk.Radiobutton(
|
||||
bool_frame, text=false_text, variable=bool_frame.chosen_value, value=False
|
||||
)
|
||||
false_radio_button.grid(row=1, sticky=tk.W)
|
||||
return bool_frame
|
||||
label = ttk.Label(
|
||||
option_frame,
|
||||
text=""
|
||||
)
|
||||
return label
|
||||
|
||||
def show_download_options(self, options):
|
||||
left_padding = 20
|
||||
for option in options:
|
||||
f = ttk.Frame(
|
||||
self.options_frame,
|
||||
style="E.TFrame"
|
||||
)
|
||||
f.grid(sticky=tk.W + tk.E, padx=left_padding)
|
||||
self.option_frames.append((option, f))
|
||||
description_label = ttk.Label(
|
||||
f,
|
||||
text=option.long_description
|
||||
)
|
||||
description_label.grid(row=0, sticky=tk.W)
|
||||
if len(option.option_types) > 1:
|
||||
f.chosen_type = tk.IntVar()
|
||||
choices_frame = ttk.Frame(
|
||||
f,
|
||||
style="F.TFrame"
|
||||
)
|
||||
f.choices_frame = choices_frame
|
||||
choices_frame.grid(row=1, sticky=tk.W, padx=left_padding)
|
||||
choices_frame.choices = []
|
||||
for i, option_type in enumerate(option.option_types):
|
||||
choice_frame = ttk.Frame(
|
||||
choices_frame,
|
||||
style="G.TFrame"
|
||||
)
|
||||
choice_frame.grid(sticky=tk.W)
|
||||
option_text = ""
|
||||
if option_type.short_description is not None:
|
||||
option_text = option_type.short_description
|
||||
option_radio_button = ttk.Radiobutton(
|
||||
choice_frame, text=option_text, variable=f.chosen_type, value=i
|
||||
)
|
||||
option_radio_button.grid(row=0, column=0, sticky=tk.W)
|
||||
option_widget = self.get_option_widget(option_type, choice_frame)
|
||||
option_widget.grid(row=0, column=1, sticky=tk.W)
|
||||
choices_frame.choices.append(option_widget)
|
||||
if i == 0:
|
||||
option_radio_button.invoke()
|
||||
else:
|
||||
choice_frame = ttk.Frame(
|
||||
f,
|
||||
style="F.TFrame"
|
||||
)
|
||||
choice_frame.grid(sticky=tk.W, padx=left_padding)
|
||||
option_widget = self.get_option_widget(option.option_types[0], choice_frame)
|
||||
option_widget.grid(row=0, column=0, sticky=tk.W)
|
||||
f.option_widget = option_widget
|
||||
#self.show_options_button = ttk.Button(
|
||||
# self.stream_frame_body, command=self._toggle_show_options, style="Stop.TButton",
|
||||
# cursor=self.button_cursor
|
||||
#)
|
||||
#self.show_options_button.config(image=self.show_options_picture)
|
||||
#self.show_options_button.grid(sticky=tk.W, row=2, column=0)
|
||||
|
||||
def _get_chosen_option(self, option_type, option_widget):
|
||||
if option_type.value == float:
|
||||
return float(option_widget.entry.get())
|
||||
if option_type.value == bool:
|
||||
return option_widget.chosen_value.get()
|
||||
return option_type.value
|
||||
|
||||
def get_chosen_options(self):
|
||||
chosen_options = []
|
||||
for o, f in self.option_frames:
|
||||
if len(o.option_types) > 1:
|
||||
chosen_index = f.chosen_type.get()
|
||||
option_type = o.option_types[chosen_index]
|
||||
option_widget = f.choices_frame.choices[chosen_index]
|
||||
chosen_options.append(self._get_chosen_option(option_type, option_widget))
|
||||
else:
|
||||
option_type = o.option_types[0]
|
||||
option_widget = f.option_widget
|
||||
chosen_options.append(self._get_chosen_option(option_type, option_widget))
|
||||
return chosen_options
|
||||
|
||||
#def _toggle_show_options(self):
|
||||
# if self.options_frame.winfo_ismapped():
|
||||
# self.show_options_button.config(image=self.show_options_picture)
|
||||
# self.options_frame.grid_forget()
|
||||
# else:
|
||||
# self.show_options_button.config(image=self.hide_options_picture)
|
||||
# self.options_frame.grid(sticky=tk.W + tk.E, row=3)
|
||||
|
||||
def show_progress(self, total_bytes, bytes_left_to_download, points_paid,
|
||||
points_remaining):
|
||||
if self.bytes_downloaded_label is None:
|
||||
self.remove_download_buttons()
|
||||
self.button_frame.destroy()
|
||||
self.estimated_cost_frame.destroy()
|
||||
for option, frame in self.option_frames:
|
||||
frame.destroy()
|
||||
self.options_frame.destroy()
|
||||
#self.show_options_button.destroy()
|
||||
|
||||
self.progress_frame = ttk.Frame(self.outer_button_frame, style="F.TFrame")
|
||||
self.progress_frame.grid(row=0, column=0, sticky=tk.W, pady=(0, 8))
|
||||
|
||||
self.bytes_downloaded_label = ttk.Label(
|
||||
self.progress_frame,
|
||||
text=""
|
||||
)
|
||||
self.bytes_downloaded_label.grid(row=0, column=0)
|
||||
|
||||
self.cost_frame = ttk.Frame(self.outer_button_frame, style="F.TFrame")
|
||||
self.cost_frame.grid(row=1, column=0, sticky=tk.W, pady=(0, 4))
|
||||
|
||||
self.cost_label = ttk.Label(
|
||||
self.cost_frame,
|
||||
text="",
|
||||
foreground="red"
|
||||
)
|
||||
self.cost_label.grid(row=0, column=1, padx=(1, 0))
|
||||
self.outer_button_frame.grid_columnconfigure(2, weight=0, uniform="")
|
||||
|
||||
if self.bytes_downloaded_label.winfo_exists():
|
||||
percent_done = 0
|
||||
if total_bytes > 0:
|
||||
percent_done = 100.0 * (total_bytes - bytes_left_to_download) / total_bytes
|
||||
percent_done_string = locale.format_string(" (%.2f%%)", percent_done)
|
||||
self.bytes_downloaded_label.config(
|
||||
text=self.get_formatted_stream_size(total_bytes - bytes_left_to_download) + percent_done_string
|
||||
)
|
||||
if self.cost_label.winfo_exists():
|
||||
total_points = points_remaining + points_paid
|
||||
self.cost_label.config(text=locale.format_string("%.2f/%.2f LBC",
|
||||
(round(points_paid, 2), round(total_points, 2)),
|
||||
grouping=True))
|
||||
|
||||
def show_download_done(self, total_points_paid):
|
||||
if self.bytes_downloaded_label is not None and self.bytes_downloaded_label.winfo_exists():
|
||||
self.bytes_downloaded_label.destroy()
|
||||
if self.cost_label is not None and self.cost_label.winfo_exists():
|
||||
self.cost_label.config(text=locale.format_string("%.2f LBC",
|
||||
(round(total_points_paid, 2),),
|
||||
grouping=True))
|
|
@ -1 +0,0 @@
|
|||
"""A gui application for downloading LBRY files from LBRYnet"""
|
Before Width: | Height: | Size: 188 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 195 B |
Before Width: | Height: | Size: 72 B |
|
@ -1,33 +0,0 @@
|
|||
import logging
|
||||
import sys
|
||||
|
||||
from lbrynet.lbrynet_gui.GuiApp import DownloaderApp
|
||||
from twisted.internet import reactor, task
|
||||
import locale
|
||||
|
||||
|
||||
def start_gui():
|
||||
|
||||
log_format = "(%(asctime)s)[%(filename)s:%(lineno)s] %(funcName)s(): %(message)s"
|
||||
formatter = logging.Formatter(log_format)
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.DEBUG)
|
||||
file_handler = logging.FileHandler("gui.log")
|
||||
file_handler.setFormatter(formatter)
|
||||
file_handler.addFilter(logging.Filter("lbrynet"))
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
sys.stdout = open("gui.out.log", 'w')
|
||||
sys.stderr = open("gui.err.log", 'w')
|
||||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
|
||||
app = DownloaderApp()
|
||||
|
||||
d = task.deferLater(reactor, 0, app.start)
|
||||
|
||||
reactor.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_gui()
|
Before Width: | Height: | Size: 174 B |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 361 KiB |
|
@ -1,14 +0,0 @@
|
|||
#define lbry_dark_icon2_width 32
|
||||
#define lbry_dark_icon2_height 32
|
||||
static unsigned char lbry_dark_icon2_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00,
|
||||
0x00, 0xc0, 0x07, 0x00, 0x00, 0x70, 0x1c, 0x00, 0x00, 0x1c, 0xf0, 0x00,
|
||||
0x00, 0x0e, 0xc0, 0x03, 0x80, 0x03, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x3c,
|
||||
0x70, 0x00, 0x00, 0x70, 0x1c, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x38,
|
||||
0x03, 0x00, 0x00, 0x0e, 0x13, 0x00, 0x00, 0x07, 0x71, 0x00, 0xc0, 0xf1,
|
||||
0xe3, 0x01, 0x60, 0x70, 0x03, 0x07, 0x38, 0x7c, 0x07, 0x1e, 0x0e, 0x0e,
|
||||
0x3c, 0x70, 0x87, 0x03, 0xf0, 0xe0, 0xc1, 0x00, 0xc0, 0x03, 0x70, 0x00,
|
||||
0x00, 0x0f, 0x1c, 0x00, 0x00, 0x38, 0x0e, 0x00, 0x00, 0xf0, 0x03, 0x00,
|
||||
0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
@ -1,108 +0,0 @@
|
|||
# ===== known_dht_nodes =====
|
||||
# A comma-separated list of dht nodes to use to bootstrap into the
|
||||
# DHT network. The nodes must be specified in dotted quad format
|
||||
# followed by a colon and the port number. For example:
|
||||
# 192.168.1.1:4000,10.0.0.1:4000,172.16.1.1:5000
|
||||
#
|
||||
# known_dht_nodes = 104.236.42.182:4000
|
||||
|
||||
|
||||
# ===== download_directory =====
|
||||
# On Windows, the default location for "download_directory" is the
|
||||
# directory the user has registered with the OS as the default
|
||||
# download directory, usually the 'Downloads' folder in the user's
|
||||
# home directory. On Linux, it is the current directory.
|
||||
#
|
||||
# download_directory = /path/to/example_directory
|
||||
|
||||
|
||||
# ===== data_dir =====
|
||||
# The data directory is the directory in which the application
|
||||
# stores information about the encrypted chunks stored on disk
|
||||
# and the streams they belong to, the encrypted chunks themselves,
|
||||
# and any other metadata. By default, set to '.lbrydownloader'
|
||||
# in the user's home directory.
|
||||
#
|
||||
# data_dir = /path/to/home/.lbrydownloader
|
||||
|
||||
|
||||
# ===== run_server =====
|
||||
# Turn on or off the server, which uploads encrypted chunks of data
|
||||
# to other clients on the network.
|
||||
#
|
||||
# run_server = True
|
||||
|
||||
|
||||
# ===== start_lbrycrdd =====
|
||||
# Whether to launch an lbyrcrdd server to use to send and receive
|
||||
# LBC and to look up names regisered on the LBRYcrd network.
|
||||
# Defaults to True on Windows and False on Linux.
|
||||
#
|
||||
# start_lbrycrdd = True/False
|
||||
|
||||
# ===== lbrycrdd_path =====
|
||||
# If start_lbrycrdd is set to True, the path the application will
|
||||
# use to try to run lbrycrdd. On Windows, the default path will
|
||||
# be "lbrycrdd.exe". On Linux, the default path will be "./lbrycrdd".
|
||||
#
|
||||
# lbrycrdd_path = /path/to/lbrycrdd
|
||||
|
||||
# ===== wallet_dir =====
|
||||
# The directory which the lbrycrdd instance will be given as the
|
||||
# base directory for the instance of lbrycrdd, if it is launched
|
||||
# by the application. By default, it is set to the .lbrycrd
|
||||
# directory in the user's home directory.
|
||||
#
|
||||
# wallet_dir = /path/to/home/.lbrycrd
|
||||
|
||||
|
||||
# ===== wallet_conf =====
|
||||
# The configuration file used by the lbrycrd server which will be
|
||||
# used by the application to send and receive LBC and to look up
|
||||
# registered names on the LBRYcrd network. By default, this is
|
||||
# set to the file lbrycrd.conf in the wallet_dir directory.
|
||||
#
|
||||
# wallet_conf = /path/to/home/.lbrycrd/lbrycrd.conf
|
||||
|
||||
|
||||
# ===== use_upnp =====
|
||||
# Whether to try to use UPnP to open ports in a firewall so that
|
||||
# other applications on the network can connect to the application
|
||||
#
|
||||
# use_upnp = True
|
||||
|
||||
|
||||
# ===== default_blob_data_payment_rate =====
|
||||
# The amount of LBC per megabyte to send to uploaders of data, by
|
||||
# default. Must be a positive floating point number.
|
||||
#
|
||||
# default_blob_data_payment_rate = 0.5
|
||||
|
||||
|
||||
# ===== dht_port =====
|
||||
# The UPD port which other applications will connect to in order
|
||||
# to announce their association with sha384 hashsums and to
|
||||
# find other applications which are associated with sha384
|
||||
# hashsums.
|
||||
#
|
||||
# dht_port = 4444
|
||||
|
||||
|
||||
# ===== peer_port =====
|
||||
# The TCP port which other applications will connect to in order
|
||||
# to download encrypted chunks of data from this application,
|
||||
# if this application is acting as a server.
|
||||
#
|
||||
# peer_port = 3333
|
||||
|
||||
|
||||
# ===== delete_blobs_on_stream_remove =====
|
||||
# If this is set to True, all blobs associated with the stream
|
||||
# will be deleted when the stream is removed from the GUI,
|
||||
# whether by clicking the 'x' or by closing the application.
|
||||
# If this is set to False, they will not be deleted then.
|
||||
# However, they may be deleted otherwise. For example if the
|
||||
# option to allow reuploading is set to False, they will be
|
||||
# deleted as soon as they're outputted by the application.
|
||||
#
|
||||
# delete_blobs_on_stream_remove = True
|
Before Width: | Height: | Size: 155 B |
25
packaging/osx/add-key.sh
Executable file
|
@ -0,0 +1,25 @@
|
|||
#!/bin/sh
|
||||
# http://stackoverflow.com/a/246128
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
# adapted from https://www.objc.io/issues/6-build-tools/travis-ci/#add-scripts
|
||||
|
||||
KEYCHAIN_PASSWORD=travis
|
||||
|
||||
# Create a custom keychain
|
||||
security create-keychain -p ${KEYCHAIN_PASSWORD} osx-build.keychain
|
||||
|
||||
# Make the custom keychain default, so xcodebuild will use it for signing
|
||||
security default-keychain -s osx-build.keychain
|
||||
|
||||
# Unlock the keychain
|
||||
security unlock-keychain -p ${KEYCHAIN_PASSWORD} osx-build.keychain
|
||||
|
||||
# Set keychain timeout to 1 hour for long builds
|
||||
# see http://www.egeek.me/2013/02/23/jenkins-and-xcode-user-interaction-is-not-allowed/
|
||||
security set-keychain-settings -t 3600 -l ~/Library/Keychains/osx-build.keychain
|
||||
|
||||
# Add certificates to keychain and allow codesign to access them
|
||||
security import ${DIR}/certs/dist.cer -k ~/Library/Keychains/osx-build.keychain -T /usr/bin/codesign
|
||||
security import ${DIR}/certs/dist.p12 -k ~/Library/Keychains/osx-build.keychain -P $KEY_PASSWORD -T /usr/bin/codesign
|
||||
|
2
packaging/osx/certs/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
dist.cer
|
||||
dist.p12
|
30
packaging/osx/certs/dist.cer.enc
Normal file
|
@ -0,0 +1,30 @@
|
|||
U2FsdGVkX184Qhj+Znx23me5PxRw3d8AgHu/h2uingV6T0lAb9/xDlxOU7E0HEsE
|
||||
NIVvS0r5kqK6FXhUODny567FR+OGihl/XiKMjMoJSxNIAjYcuo91hVZ2mN+AbIDl
|
||||
OaaSRSXdwg948eNYhLsjfjyxU4fpZ5P+fSvcMZ4y4xSm7gwOCPrTFhRXmiCxFVsY
|
||||
x8td9OtmnGwRMnkTz7les3ZW7lHFbsmiHwct+L3QCWcLZ+xklbsLLVkXOuYpws7J
|
||||
pWKc8YgmyySH9uXnzuxWuRrqvw4coq1pO51WB/6ZaSbiE5FzIq32usnQocl8hjY2
|
||||
0rveOAR5nLSNA4YQY6O2gbnN6Fq0TDGOIJ1Lvl8XkHKrMqSu9ifFXAmebHH5xfFS
|
||||
HFZ9mije0lNSxg5a6b2EJkCmbIE5GHzqzzWccAlmgCrOtd6ZpytpW1oTJZEvboo5
|
||||
G4TdZ3te31ltn+d/2Jr7Z3q2ByueTOVj01fx/mJcDCK+q5ytWOTvqkoGzrHIDbxK
|
||||
eV/XfhcQ1+dCFIYu89++/bt19NZ7KrxBQ5D2W6G3+71BGIxXYlyGfyTy7dbyl/EY
|
||||
f9ddk+BxDQgGpj+fRLAOIboKp94bUcneG79H5Fw+w+aTHQM5T/Ilmjq60sUft+2u
|
||||
gcs0H8Slb3Gnf/QTwSLoxd/GJofAIhIcTD/HSWD8NH9YsK7lvLuLTamnLkprtdvB
|
||||
NfhsLHENg0Ha/s/eEtU2GAG/RBFT0XwZKR0O19YNSWjEvop7w/cSlwv+be7gT09O
|
||||
0/vO6xouqG16bSWEg7nxTYs/jMPPfrdn6fhNWEUo2p7FYDbq5BerN/1Eh1xjHwq3
|
||||
a1pcnFkRumpjMH32aBMS79Ute1ij5xPfFKT/Bh+J4wCTlnKp0EsyhTY9DHtVaw9G
|
||||
+IfLiFTkN2MQSCGGTcGx6KDAWkjXui/8WLM/adtcLPUBrAHd4S4DoJ8v9sxACRDb
|
||||
iX950xj0IRqdzb8xF6EPCvb8t02ldzKjQw69FvFFlW4P+La+qvTSgIPo1SJ/uPGm
|
||||
Asutx5EL51b1zCQk/YrH93pAK2RIqMn40I7sB9t5kcN/rhzcVcgW3ENb4wLynK5C
|
||||
+gyr65cBgwHIZK7Lpq4rUaWh9TliDpkJqspDJb81IaQjvEKKD3weAg28H4969mju
|
||||
7Q+Cg1X4ciHZo9aydD0le3PC//lOZ6huPEW51azFKII2QQEG4JKFT6Q57F1tXNqw
|
||||
sXi0HaW9MW3doHh589NNFFU3/7zrZfMHsh5l9cA/TY7oUZFj+lWSPhZsuoy+J7e2
|
||||
7r5NfmuV35Z9v1suuEbGZ4Un0ZvVWWhW4/fVhFjEr9hjVb20kd1//EJKQoK5WMFC
|
||||
MkFNpi5hIaCXiLEh7B3e95XFXddZKf/IBgeCeYSnUOHwq6TFezifah9J9polovB2
|
||||
bwf+2HUh8buPUN+Zo2mxh3J/eJjvoY75dSuqk6wPRvGSkTmk8w2zToqUwFXBEoi0
|
||||
on3rxJB/dpFrC/zYz8c6IuIM3Zi5FAAgOrBD4gr9M9NEnt13rwsx+YxpSgPsB/LK
|
||||
3j6XMrClj1faFLEpqsrSUUMRT27m9tro353JQJhTITg9oQywi9nKixNbCM72n262
|
||||
FSucD8L07p+Q+tiw+ShwjJ8CW/t97lk5b9gfbQgvVThfQrarBYml8Fj4/lK+uO9q
|
||||
wjnOHzjEAN6MAxy8Nbfp3xz7LB18aShMuLLwWayKBGlECkbaGj0eH1+ZfvF6QPOq
|
||||
CsUnzFFR4TyeITNJyj8S1LrMUxMzPyHgTVHShECDrjILJJnSt4yzGZXMweoWV62n
|
||||
AwHqiP+sEEOu7ihOySsoW/3kqpxKhAoNxbW4Kh1Lk2KgebLjcdfDIQQLK0N0VXu5
|
||||
wHO80TEZVEqyfOeJTST/jA==
|
67
packaging/osx/certs/dist.p12.enc
Normal file
|
@ -0,0 +1,67 @@
|
|||
U2FsdGVkX1/nZdeV0RBXBMg3aUrBekilENXXcvQ1sR5cLfA+TLOecPR+TtkXvRPk
|
||||
ZsRUDMAyE53eOuam2DMZgRx65V9lBYNrzWoUS0AQr+TX2s/NItjj/owiJyOb1tcP
|
||||
FPcw0K7oEA5BCD+iqN66YIbPOuQ1AohPl0A8Ee1mP8OrwlzIiu3nSf/kGGlORZX1
|
||||
lA4Hhmc1PMdO7DHWxg78+QVPw1t7oI4bIublY0byl6b1dU0Zo8ALD/mCPwI5iusF
|
||||
fmWRAjO7l+DIDDud6S0jXujtC7Ppq1KO4no9E85QYCC1eO6HdigyptAVNcSnVsIC
|
||||
NYicQ2C8fkplncoF+2ECH7hGa9Ne+/TogVzsOaOgcdpfdSq/hsF7uUwdZVngmH2+
|
||||
VNJZZxPRQU7zZ5nsuUqeGF/9cDnTEEza8Al98zmDeGE2UjFcejHEKXU+PAr+AZ87
|
||||
CTFVyZn0nIiTEyT7Fnct9IlePtKl8dkR3brXTuzfAZlmeVKiDTNdR+ULLZ0ewvim
|
||||
wW/2wIi3nrIs0uB6YWUnbGkDnR1XT5TLsQ+hfpMW5uo48jgxQvu6U83uIZjaT+O9
|
||||
yvXNRuqn23JNtDSp3E+wp9/5G3STnJxAlKKKG+WXXRCOUwD4C5jzFfZfy0WIvp+5
|
||||
gVvBsp9kz+XszCU5xlFCRUT+CsAyPhCZqgQrLJ6DEFt+9M/3/njudSEjuXcMxm0h
|
||||
F2pAz6Llox7YS7IHlTywnAl04l4UhoHcFzTupE9NFM3NASSlMwN6BwGn9Rd0N6Sr
|
||||
sr7JPWdYWBFr2+HSf9FHfM75GycYx9l+Kt2Igz1qidgYZfzepyuLJ7Ffib0+in5f
|
||||
s9nL3GfPGTJAsSK5OcDaOWE3ae2bmZL6P2ztpZP4yec1DBS3+YA1L+gh5P3m4xrE
|
||||
EphmtfJPozGCrk9cbtW9xT5z2Npj1p6UhtQ/DPEbbqggnwzYsoLGL5k3LXJdnj3u
|
||||
BVokDuq2Cz+ChXWLFvVVf3XGHLfdSDveXXyWuMquVrurTYxIgiiOi9Lskl5m/GS7
|
||||
Ngz0mbqf5aQ+LclMoc5T9r3Ah1CC1Rso4mu88WL4PfIkMK8Q83OFtax766j571gG
|
||||
Xs/Zd44uO6/w4Ewh9r7qGu4hW92lwn7SgshiXfmrp8+eca8hbCT33icioGUm5lFB
|
||||
z5gaPE77YI3ZVnNrGfIgd9NEH3w6JU6V/wMnOTPwP6Jkg6oB0VcynEaBBOwLleWc
|
||||
Rzrp+NRKMNQzx+OKgr7kk0NV8fNyp5c7kI7k64vPdbQP5qIqZh9KC9TddnqkZrnP
|
||||
aJCPTwdRV9fd2kxaaUbrtK7TYpeXEYNDotCglAS56ty05CCR9tmwVfptTxr7izye
|
||||
FCzrNMtHzZzxwqvfI/eXdTZgz/TCZpVb/K/G4USMAA56iBs5ccuBAoYfS/ZLfVby
|
||||
0pcNlliDKhb9hEsfFt2pAQt6BZ0JfMIh6uWTHHEEpLVzwUDY00MGIIf9+APKDYaS
|
||||
lMS8v6xh/NxMDwcLWDSpdTyQ9bUMUe2+aym/y6bsHVHQnB8Wo+FWq90OqDrT+kQB
|
||||
qrKbHE2DQfCUPahAzmsLS+yv71KOhMpzFntZ86G3qqO61+pQrpKpzaKaOUdXq8xl
|
||||
QdkabkGGPUXPHwWrkBUA/dq3V8yV8kvidHX19ufrg5IuuswkVbg42GdCWjexAaft
|
||||
TNqW29+l8PLnGFHHE9sfnyQjnCDqHkIRgNyc1LM5fHOsWNUtKRcVTBKGRpiCvdb8
|
||||
C+HR3ip+wQ5rrLUVIgYoLIkqgXB2oHZIvHs4Fyphpg9nAwuuc0/JdSUS6Q1Mj3uI
|
||||
gCmS0nJ4WDNUgvqhag1CisgLmgyrXYjF0R5h0Gv2WVqVvW6SvS01/GX27wKj3Qzt
|
||||
UCskL8oaA8AiLlATN8rWwOvB9AJSlfV2L20QOhKZYzMms2ekwURLNO/payO4ML9h
|
||||
1pWUR9uzXOkMUYyS8NPkeK/FABZDOIpppcJ3/pPgVgFNJ1iljb3863FIrg/AecSY
|
||||
ftzsrEYT0Wr92Ef7Mm6H1hBNaH5q6J4JGLhk7d+EkVKcenTxz+v9n161gxpa0V6t
|
||||
ehKSGkLjh/Nth06lfT5pd/qmbwPPJVyaOJLVW+9uETBen+2Ezkf6WEFKYPb88CK5
|
||||
FqSivs5ZLwvLUucLwgOKbovnysXtl6zklJTMjOm1V9JhPDMlvm8nD9j6NwaUs1bW
|
||||
1/2Z5+Ve/Q0KZE5VG+Hm2FKK1WC779GzCmGj5PQ6kUy/dixsGDOd8sO7BqqoesbN
|
||||
i4TZOzSd1QB/RyoezIgoHDllpM/7YRz4z8bs2nuJtD5pa7OS4ceO3om9DvBHcyx6
|
||||
yBL0MS2ow0JKJ58Pa5rSlkLLDThG+i2Y0wjwljiXxfIh1TWmJUOdW2J+adXAi2ID
|
||||
VN9GnbumxpNKLXFfLkRR0MvHARbf//nZNt9vgZhfsn2iZBNemwEOlIPkkZBRg1hK
|
||||
LpZmDr6GHy7kaS1mAvlNKyFjPt9hHffm5nHhduFZxv8ceynIad6iHqJHGtZSrZeD
|
||||
x9Ecn4QTRjZ5T1ff6uW/DGeT8G/Uh+2sAgkK4xZuAS78Rn+dhk77Q8USSJw/SyXH
|
||||
Rh6zMybljzk5KAgoqipsrrD5n0gJizGrxFw9Jv4YMYDmNvWKsKvORIKqf/Z8Kaj0
|
||||
37y8ClRa69OetzSJwXCL7h+6CXnmw3ghHG7IhbBljKKTOBovby0cJb4nV+p5O7/n
|
||||
vGTHFCeqILowMtai0BvRVj6kos/y5WhUPhZ2eprL+psTgnQZ3Cshy2VdcXQu66+J
|
||||
qJM3vBlQxpeKA2ODougKzFeaM1MmywBZ20oLVCC/K5C0m0ylsKnSLvPjtcxmMtyl
|
||||
yE75aLFUtcRpM7uQXkkBry2oXqp+kbyNwmOqTB95XMhIh6lzWB76ndnWjJ6S7v6C
|
||||
f7Wu38+ztlye+tuFnPUA41we7cC/ZMeomzaucoZJkicN6vh/cWuLAmkEExHtf62W
|
||||
HGhhjZJ05gRAgNdXQGVx6gur4XWRNQQT7VO+02C90GyzVcuhnD0FKfv4nnbZMCbl
|
||||
86r4cIOlx5tSbhHS3RdTqf2en3vjuSeJdBDHbAU6qbBUkEA6v/3tZwOh+HwTrdEu
|
||||
67Qpz4T+YGS0jBBxZL7THihgbvcllgEZkc/DYe8qDegLfVbNk50d1DUoy7e87c+N
|
||||
r40Eir79N+3OoxjtbRel0DKcKM7O2RIGjPJhgCo29Fyf32MLSeUVBTeOifXjWJDl
|
||||
ktqF6t/VffjM5Ha7OwWF9KI/dSy6ZE2cOmj2DRUCKHyFSofe6pyTLj84Dyimt4uF
|
||||
Yjfjxo1l8qvGyJ2bAUVEDAUT4TMnuyToZUFHSVid3IxJZtTT6P8UIgWiafhznagc
|
||||
DT29oRhmF7+Z6NHcWg3S8FOiFsNj84LhWm7FBmi2TMnRfP0a3/DfelnKD0Nzztn5
|
||||
dBXkRJna8IqGd84NYp4cquSQ/0EoZ4yxF31mHYkgctZ4DSUt9rkObfb29B7GpU3I
|
||||
7h1pJRUa/5I6Y/0qYYKVb/CKUVWd5GtYQsFarW4RsdO4nGgjMWXds8so+4AB65lx
|
||||
weYvHd6eLtOQvMyM+IpkVhfTUVHIyDvVi0SaRDj1307AMBR/yfg9HajW8K7e46Mt
|
||||
yh+IBfucXgm/QiLlAszh4XtCeneXdMKyTruGXyIgcTjEyO41cPfW4/3QK7t9Gm/0
|
||||
u0sbOsdejITkXDRArMmYyoslVCYHBD0PIgJjuOvMSTm/ZduF18Efy5hjnVCUBFeo
|
||||
9stOl6zBm4Wf3D65xXmVM069XA+ww1z6gmR7ecJgoOc3sRTXC4oYVEQ/IVklmN+b
|
||||
Wr1uoO0SM9yviIc7MRmKqvntQ0/ZXAC1yJmT5GJ1i2UHjY1+qTsxexp4YJe4p1aT
|
||||
Vf1e43bT4lXZtSQPJfC0dMTWv+GVN9TLWl35hLyiJHSwd43DFC6H9Qz7/CJM4uGc
|
||||
dVrx0QA/ru3/HXPUbg5oVyM3Rf0eFN9zjEZT60aXeKqdXcc6aYc9CX64wzWn+DKY
|
||||
n9qoy/5x5SzDmmwphbx8hbAk4yZIJex7dTKaqjr78Sz7KCUg/J2Y39mZD/NtwniN
|
||||
ssL57nYjjBu2HFfuqSIfe1aYG0bnRJAAwGLZr9Dbt7hwLDBGN/3Y9CfFoVjicwcr
|
||||
B+Tq01wVKOPftNskMCmKlz0Z2bO95NDceZKIUmHHp0jSS5ZckRVtwAIDSkYfrFD2
|
||||
zxMU+8O8rxbJRYT/PjdnGLjmp6Mw88SWSUy2tzje2f1Ay5vshtZLCYfxEI4nXRVq
|
||||
EjIMJeXgdGFW7PEdY/kROQ==
|
12
packaging/osx/lbry-osx-app/.gitignore
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
|
||||
*.pyc
|
||||
|
||||
*.pyo
|
||||
|
||||
*.so
|
||||
|
||||
*.xml
|
||||
|
||||
*.iml
|
||||
|
||||
id.conf
|
BIN
packaging/osx/lbry-osx-app/app.icns
Normal file
|
@ -0,0 +1,60 @@
|
|||
import os
|
||||
import json
|
||||
import webbrowser
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from time import sleep
|
||||
from jsonrpc.proxy import JSONRPCProxy
|
||||
|
||||
API_CONNECTION_STRING = "http://localhost:5279/lbryapi"
|
||||
UI_ADDRESS = "http://localhost:5279"
|
||||
|
||||
|
||||
class LBRYURIHandler(object):
|
||||
def __init__(self):
|
||||
self.started_daemon = False
|
||||
self.daemon = JSONRPCProxy.from_url(API_CONNECTION_STRING)
|
||||
|
||||
def handle_osx(self, lbry_name):
|
||||
try:
|
||||
status = self.daemon.is_running()
|
||||
except:
|
||||
os.system("open /Applications/LBRY.app")
|
||||
sleep(3)
|
||||
|
||||
if lbry_name == "lbry" or lbry_name == "":
|
||||
webbrowser.open(UI_ADDRESS)
|
||||
else:
|
||||
webbrowser.open(UI_ADDRESS + "/?watch=" + lbry_name)
|
||||
|
||||
def handle_linux(self, lbry_name):
|
||||
try:
|
||||
status = self.daemon.is_running()
|
||||
except:
|
||||
cmd = r'DIR = "$( cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )"' \
|
||||
r'if [-z "$(pgrep lbrynet-daemon)"]; then' \
|
||||
r'echo "running lbrynet-daemon..."' \
|
||||
r'$DIR / lbrynet - daemon &' \
|
||||
r'sleep 3 # let the daemon load before connecting' \
|
||||
r'fi'
|
||||
subprocess.Popen(cmd, shell=True)
|
||||
|
||||
if lbry_name == "lbry" or lbry_name == "":
|
||||
webbrowser.open(UI_ADDRESS)
|
||||
else:
|
||||
webbrowser.open(UI_ADDRESS + "/?watch=" + lbry_name)
|
||||
|
||||
|
||||
def main(args):
|
||||
if len(args) != 1:
|
||||
args = ['lbry://lbry']
|
||||
|
||||
name = args[0][7:]
|
||||
if sys.platform == "darwin":
|
||||
LBRYURIHandler().handle_osx(lbry_name=name)
|
||||
else:
|
||||
LBRYURIHandler().handle_linux(lbry_name=name)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
94
packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
import AppKit
|
||||
import webbrowser
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import socket
|
||||
import platform
|
||||
import shutil
|
||||
from appdirs import user_data_dir
|
||||
|
||||
from PyObjCTools import AppHelper
|
||||
|
||||
from twisted.internet import reactor
|
||||
from twisted.web import server
|
||||
|
||||
import Foundation
|
||||
bundle = Foundation.NSBundle.mainBundle()
|
||||
lbrycrdd_path = bundle.pathForResource_ofType_('lbrycrdd', None)
|
||||
lbrycrdd_path_conf = os.path.join(os.path.expanduser("~"), ".lbrycrddpath.conf")
|
||||
wallet_dir = user_data_dir("lbrycrd")
|
||||
|
||||
if not os.path.isdir(wallet_dir):
|
||||
shutil.os.mkdir(wallet_dir)
|
||||
|
||||
if not os.path.isfile(lbrycrdd_path_conf):
|
||||
f = open(lbrycrdd_path_conf, "w")
|
||||
f.write(lbrycrdd_path)
|
||||
f.close()
|
||||
|
||||
from lbrynet.lbrynet_daemon.LBRYDaemonServer import LBRYDaemonServer
|
||||
from lbrynet.conf import API_PORT, API_INTERFACE, ICON_PATH, APP_NAME
|
||||
from lbrynet.conf import UI_ADDRESS
|
||||
|
||||
if platform.mac_ver()[0] >= "10.10":
|
||||
from LBRYNotify import LBRYNotify
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
REMOTE_SERVER = "www.google.com"
|
||||
|
||||
|
||||
def test_internet_connection():
|
||||
try:
|
||||
host = socket.gethostbyname(REMOTE_SERVER)
|
||||
s = socket.create_connection((host, 80), 2)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
class LBRYDaemonApp(AppKit.NSApplication):
|
||||
def finishLaunching(self):
|
||||
self.connection = False
|
||||
statusbar = AppKit.NSStatusBar.systemStatusBar()
|
||||
self.statusitem = statusbar.statusItemWithLength_(AppKit.NSVariableStatusItemLength)
|
||||
self.icon = AppKit.NSImage.alloc().initByReferencingFile_(ICON_PATH)
|
||||
self.icon.setScalesWhenResized_(True)
|
||||
self.icon.setSize_((20, 20))
|
||||
self.statusitem.setImage_(self.icon)
|
||||
self.menubarMenu = AppKit.NSMenu.alloc().init()
|
||||
self.open = AppKit.NSMenuItem.alloc().initWithTitle_action_keyEquivalent_("Open", "openui:", "")
|
||||
self.menubarMenu.addItem_(self.open)
|
||||
self.quit = AppKit.NSMenuItem.alloc().initWithTitle_action_keyEquivalent_("Quit", "replyToApplicationShouldTerminate:", "")
|
||||
self.menubarMenu.addItem_(self.quit)
|
||||
self.statusitem.setMenu_(self.menubarMenu)
|
||||
self.statusitem.setToolTip_(APP_NAME)
|
||||
|
||||
|
||||
if test_internet_connection():
|
||||
if platform.mac_ver()[0] >= "10.10":
|
||||
LBRYNotify("Starting LBRY")
|
||||
else:
|
||||
if platform.mac_ver()[0] >= "10.10":
|
||||
LBRYNotify("LBRY needs an internet connection to start, try again when one is available")
|
||||
sys.exit(0)
|
||||
|
||||
# if not subprocess.check_output("git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep HEAD | cut -f 1",
|
||||
# shell=True):
|
||||
# LBRYNotify(
|
||||
# "You should have been prompted to install xcode command line tools, please do so and then start LBRY")
|
||||
# sys.exit(0)
|
||||
|
||||
lbry = LBRYDaemonServer()
|
||||
d = lbry.start()
|
||||
d.addCallback(lambda _: webbrowser.open(UI_ADDRESS))
|
||||
reactor.listenTCP(API_PORT, server.Site(lbry.root), interface=API_INTERFACE)
|
||||
|
||||
def openui_(self, sender):
|
||||
webbrowser.open(UI_ADDRESS)
|
||||
|
||||
def replyToApplicationShouldTerminate_(self, shouldTerminate):
|
||||
if platform.mac_ver()[0] >= "10.10":
|
||||
LBRYNotify("Goodbye!")
|
||||
reactor.stop()
|
27
packaging/osx/lbry-osx-app/lbrygui/LBRYNotify.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
import Foundation
|
||||
import objc
|
||||
import AppKit
|
||||
|
||||
NSUserNotification = objc.lookUpClass('NSUserNotification')
|
||||
NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter')
|
||||
|
||||
def LBRYNotify(message):
|
||||
notification = NSUserNotification.alloc().init()
|
||||
notification.setTitle_("LBRY")
|
||||
notification.setSubtitle_("")
|
||||
notification.setInformativeText_(message)
|
||||
notification.setUserInfo_({})
|
||||
notification.setSoundName_("NSUserNotificationDefaultSoundName")
|
||||
notification.setDeliveryDate_(Foundation.NSDate.dateWithTimeInterval_sinceDate_(0, Foundation.NSDate.date()))
|
||||
NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notification)
|
||||
|
||||
def notify(title, subtitle, info_text, delay=0, sound=False, userInfo={}):
|
||||
notification = NSUserNotification.alloc().init()
|
||||
notification.setTitle_(title)
|
||||
notification.setSubtitle_(subtitle)
|
||||
notification.setInformativeText_(info_text)
|
||||
notification.setUserInfo_(userInfo)
|
||||
if sound:
|
||||
notification.setSoundName_("NSUserNotificationDefaultSoundName")
|
||||
notification.setDeliveryDate_(Foundation.NSDate.dateWithTimeInterval_sinceDate_(delay, Foundation.NSDate.date()))
|
||||
NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notification)
|
0
packaging/osx/lbry-osx-app/lbrygui/__init__.py
Normal file
BIN
packaging/osx/lbry-osx-app/lbrygui/app.icns
Normal file
35
packaging/osx/lbry-osx-app/lbrygui/main.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
from PyObjCTools import AppHelper
|
||||
from twisted.internet.cfreactor import install
|
||||
install(runner=AppHelper.runEventLoop)
|
||||
from twisted.internet import reactor
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
from appdirs import user_data_dir
|
||||
|
||||
from LBRYApp import LBRYDaemonApp
|
||||
|
||||
if sys.platform != "darwin":
|
||||
log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet")
|
||||
else:
|
||||
log_dir = user_data_dir("LBRY")
|
||||
|
||||
if not os.path.isdir(log_dir):
|
||||
os.mkdir(log_dir)
|
||||
|
||||
LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log')
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5)
|
||||
log.addHandler(handler)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def main():
|
||||
app = LBRYDaemonApp.sharedApplication()
|
||||
reactor.addSystemEventTrigger("after", "shutdown", AppHelper.stopEventLoop)
|
||||
reactor.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
BIN
packaging/osx/lbry-osx-app/libgmp.10.dylib
Executable file
30
packaging/osx/lbry-osx-app/setup_app.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
from setuptools import setup
|
||||
from lbrynet.conf import PROTOCOL_PREFIX, APP_NAME, ICON_PATH
|
||||
import sys
|
||||
|
||||
APP = [os.path.join('lbrygui', 'main.py')]
|
||||
DATA_FILES = []
|
||||
DATA_FILES.append('app.icns')
|
||||
|
||||
OPTIONS = {
|
||||
'iconfile': ICON_PATH,
|
||||
'plist': {
|
||||
'CFBundleIdentifier': 'io.lbry.LBRY',
|
||||
'LSUIElement': True,
|
||||
},
|
||||
'packages': [
|
||||
'lbrynet', 'lbryum', 'requests', 'unqlite', 'certifi',
|
||||
'pkg_resources', 'json', 'jsonrpc', 'seccure',
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
setup(
|
||||
name=APP_NAME,
|
||||
app=APP,
|
||||
options={'py2app': OPTIONS},
|
||||
data_files=DATA_FILES,
|
||||
)
|
106
packaging/osx/lbry-osx-app/setup_app.sh
Executable file
|
@ -0,0 +1,106 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -o errexit
|
||||
set -o xtrace
|
||||
|
||||
DEST=`pwd`
|
||||
tmp="${DEST}/build"
|
||||
ON_TRAVIS=false
|
||||
|
||||
rm -rf build dist LBRY.app
|
||||
|
||||
pip install wheel
|
||||
# the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app
|
||||
pip install git+https://github.com/metachris/py2app
|
||||
pip install jsonrpc
|
||||
|
||||
mkdir -p $tmp
|
||||
cd $tmp
|
||||
|
||||
echo "Updating lbrynet"
|
||||
if [ -z ${TRAVIS_BUILD_DIR+x} ]; then
|
||||
# building locally
|
||||
git clone --depth 1 http://github.com/lbryio/lbry.git
|
||||
cd lbry
|
||||
LBRY="${tmp}/lbry"
|
||||
else
|
||||
# building on travis
|
||||
ON_TRAVIS=true
|
||||
cd ${TRAVIS_BUILD_DIR}
|
||||
LBRY=${TRAVIS_BUILD_DIR}
|
||||
fi
|
||||
NAME=`python setup.py --name`
|
||||
VERSION=`python setup.py -V`
|
||||
pip install -r requirements.txt
|
||||
# not totally sure if pyOpenSSl is needed (JIE)
|
||||
pip install pyOpenSSL
|
||||
python setup.py install
|
||||
|
||||
echo "Building URI Handler"
|
||||
cd "${DEST}"
|
||||
rm -rf build dist
|
||||
python setup_uri_handler.py py2app
|
||||
|
||||
echo "Signing URI Handler"
|
||||
codesign -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRYURIHandler.app/Contents/Frameworks/Python.framework/Versions/2.7"
|
||||
codesign -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRYURIHandler.app/Contents/MacOS/python"
|
||||
# not sure if --deep is appropriate here, but need to get LBRYURIHandler.app/Contents/Frameworks/libcrypto.1.0.0.dylib signed
|
||||
codesign --deep -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRYURIHandler.app/Contents/MacOS/LBRYURIHandler"
|
||||
codesign -vvvv "${DEST}/dist/LBRYURIHandler.app"
|
||||
|
||||
pip install certifi
|
||||
MODULES="pyobjc-core pyobjc-framework-Cocoa pyobjc-framework-CFNetwork"
|
||||
if [ ${ON_TRAVIS} = true ]; then
|
||||
WHEEL_DIR="${TRAVIS_BUILD_DIR}/cache/wheel"
|
||||
mkdir -p "${WHEEL_DIR}"
|
||||
# mapping from the package name to the
|
||||
# actual built wheel file is surprisingly
|
||||
# hard so instead of checking for the existance
|
||||
# of each wheel, we mark with a file when they've all been
|
||||
# built and skip when that file exists
|
||||
if [ ! -f "${WHEEL_DIR}"/finished ]; then
|
||||
pip wheel -w "${WHEEL_DIR}" ${MODULES}
|
||||
touch "${WHEEL_DIR}"/finished
|
||||
fi
|
||||
pip install "${WHEEL_DIR}"/*.whl
|
||||
else
|
||||
pip install $MODULES
|
||||
fi
|
||||
|
||||
|
||||
# add lbrycrdd as a resource. Following
|
||||
# http://stackoverflow.com/questions/11370012/can-executables-made-with-py2app-include-other-terminal-scripts-and-run-them
|
||||
wget https://github.com/lbryio/lbrycrd/releases/download/v0.3-osx/lbrycrdd
|
||||
python setup_app.py py2app --resources lbrycrdd
|
||||
|
||||
chmod +x "${DEST}/dist/LBRY.app/Contents/Resources/lbrycrdd"
|
||||
|
||||
echo "Removing i386 libraries"
|
||||
|
||||
remove_arch () {
|
||||
if [[ `lipo "$2" -verify_arch "$1"` ]]; then
|
||||
lipo -output build/lipo.tmp -remove "$1" "$2" && mv build/lipo.tmp "$2"
|
||||
fi
|
||||
}
|
||||
|
||||
for i in `find dist/LBRY.app/Contents/Resources/lib/python2.7/lib-dynload/ -name "*.so"`; do
|
||||
remove_arch i386 $i
|
||||
done
|
||||
|
||||
|
||||
echo "Moving LBRYURIHandler.app into LBRY.app"
|
||||
mv "${DEST}/dist/LBRYURIHandler.app" "${DEST}/dist/LBRY.app/Contents/Resources"
|
||||
|
||||
echo "Signing LBRY.app"
|
||||
codesign -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRY.app/Contents/Frameworks/Python.framework/Versions/2.7"
|
||||
codesign -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRY.app/Contents/Frameworks/libgmp.10.dylib"
|
||||
codesign -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRY.app/Contents/MacOS/python"
|
||||
# adding deep here as well because of subcomponent issues
|
||||
codesign --deep -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRY.app/Contents/MacOS/LBRY"
|
||||
codesign -vvvv "${DEST}/dist/LBRY.app"
|
||||
|
||||
rm -rf $tmp
|
||||
mv dist/LBRY.app LBRY.app
|
||||
rm -rf dist "${NAME}.${VERSION}.dmg"
|
||||
# TODO: make this pretty!
|
||||
hdiutil create "${NAME}.${VERSION}.dmg" -volname lbry -srcfolder LBRY.app
|
25
packaging/osx/lbry-osx-app/setup_uri_handler.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from setuptools import setup
|
||||
import os
|
||||
|
||||
APP = [os.path.join('lbry_uri_handler', 'LBRYURIHandler.py')]
|
||||
DATA_FILES = []
|
||||
OPTIONS = {'argv_emulation': True,
|
||||
'packages': ['jsonrpc'],
|
||||
'plist': {
|
||||
'LSUIElement': True,
|
||||
'CFBundleIdentifier': 'io.lbry.LBRYURIHandler',
|
||||
'CFBundleURLTypes': [
|
||||
{
|
||||
'CFBundleURLTypes': 'LBRYURIHandler',
|
||||
'CFBundleURLSchemes': ['lbry']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
setup(
|
||||
app=APP,
|
||||
data_files=DATA_FILES,
|
||||
options={'py2app': OPTIONS},
|
||||
setup_requires=['py2app'],
|
||||
)
|
47
packaging/travis/install_dependencies_and_run_tests.sh
Executable file
|
@ -0,0 +1,47 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# This script is used by travis to install lbry from source
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
set -o xtrace
|
||||
|
||||
SUDO=''
|
||||
if (( $EUID != 0 )); then
|
||||
SUDO='sudo'
|
||||
fi
|
||||
|
||||
if [ -z ${TRAVIS+x} ]; then
|
||||
# if not on travis, its nice to see progress
|
||||
QUIET=""
|
||||
else
|
||||
QUIET="-qq"
|
||||
fi
|
||||
|
||||
# get the required OS packages
|
||||
$SUDO apt-get ${QUIET} update
|
||||
$SUDO apt-get ${QUIET} install -y --no-install-recommends \
|
||||
build-essential python-dev libffi-dev libssl-dev git \
|
||||
libgmp3-dev wget ca-certificates python-virtualenv
|
||||
|
||||
# create a virtualenv so we don't muck with anything on the system
|
||||
virtualenv venv
|
||||
# need to unset these or else we can't activate
|
||||
set +eu
|
||||
source venv/bin/activate
|
||||
set -eu
|
||||
|
||||
# need a modern version of pip (more modern than ubuntu default)
|
||||
wget https://bootstrap.pypa.io/get-pip.py
|
||||
python get-pip.py
|
||||
rm get-pip.py
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
pip install mock pylint
|
||||
trial tests
|
||||
# TODO: submit coverage report to coveralls
|
||||
|
||||
# Ignoring distutils because: https://github.com/PyCQA/pylint/issues/73
|
||||
# TODO: as code quality improves, make pylint be more strict
|
||||
pylint -E --disable=inherit-non-class --disable=no-member --ignored-modules=distutils lbrynet
|
10
packaging/travis/setup_osx.sh
Executable file
|
@ -0,0 +1,10 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -euo pipefail
|
||||
set -o xtrace
|
||||
|
||||
wget https://www.python.org/ftp/python/2.7.11/python-2.7.11-macosx10.6.pkg
|
||||
sudo installer -pkg python-2.7.11-macosx10.6.pkg -target /
|
||||
pip install -U pip
|
||||
brew install gmp
|
||||
|
5
packaging/ubuntu/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# package scripts
|
||||
|
||||
How to build LBRY packages.
|
||||
|
||||
For best results, run on a fresh image.
|
BIN
packaging/ubuntu/icons/lbry128.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
packaging/ubuntu/icons/lbry256.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
packaging/ubuntu/icons/lbry32.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
packaging/ubuntu/icons/lbry48.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
packaging/ubuntu/icons/lbry96.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
62
packaging/ubuntu/lbry
Executable file
|
@ -0,0 +1,62 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
LBRYCRDDPATHCONF="$HOME/.lbrycrddpath.conf"
|
||||
LBRYCRDDIR="$HOME/.lbrycrd"
|
||||
LBRYCRDCONF="$LBRYCRDDIR/lbrycrd.conf"
|
||||
|
||||
if [ ! -f "$LBRYCRDDPATHCONF" ]; then
|
||||
echo "/usr/bin/lbrycrdd" > "$LBRYCRDDPATHCONF"
|
||||
fi
|
||||
|
||||
if [ ! -f "$LBRYCRDCONF" ]; then
|
||||
mkdir -p "$LBRYCRDDIR"
|
||||
echo -e "rpcuser=lbryrpc\nrpcpassword=$(env LC_CTYPE=C LC_ALL=C tr -dc A-Za-z0-9 < /dev/urandom | head -c 16 | xargs)" > "$LBRYCRDCONF"
|
||||
fi
|
||||
|
||||
WEB_UI_BRANCH='master'
|
||||
|
||||
urlencode() {
|
||||
local LANG=C
|
||||
local length="${#1}"
|
||||
for (( i = 0; i < length; i++ )); do
|
||||
local c="${1:i:1}"
|
||||
case $c in
|
||||
[a-zA-Z0-9.~_-]) printf "$c" ;;
|
||||
*) printf '%%%02X' "'$c" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
# find true dir of executable
|
||||
SOURCE="${BASH_SOURCE[0]}"
|
||||
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
SOURCE="$(readlink "$SOURCE")"
|
||||
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
|
||||
done
|
||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||
|
||||
|
||||
if [ -z "$(pgrep lbrynet-daemon)" ]; then
|
||||
echo "running lbrynet-daemon..."
|
||||
$DIR/lbrynet-daemon --branch="$WEB_UI_BRANCH" &
|
||||
sleep 3 # let the daemon load before connecting
|
||||
fi
|
||||
|
||||
ARG=${1:-}
|
||||
|
||||
if [ -z "$ARG" ]; then
|
||||
URL=""
|
||||
else
|
||||
NAME=$(echo "$ARG" | cut -c 8-)
|
||||
if [ -z "$NAME" -o "$NAME" == "lbry" ]; then
|
||||
URL=""
|
||||
else
|
||||
URL="/?watch=$(urlencode "$NAME")"
|
||||
fi
|
||||
fi
|
||||
|
||||
/usr/bin/xdg-open "http://localhost:5279$URL"
|
11
packaging/ubuntu/lbry-init.conf
Normal file
|
@ -0,0 +1,11 @@
|
|||
description "LBRY Daemon"
|
||||
|
||||
#start on (local-filesystems and net-device-up IFACE=eth0)
|
||||
stop on runlevel [016]
|
||||
|
||||
#expect fork
|
||||
|
||||
respawn
|
||||
respawn limit 5 20
|
||||
|
||||
exec /usr/share/python/lbrynet/bin/lbrynet-daemon
|
19
packaging/ubuntu/lbry.desktop
Normal file
|
@ -0,0 +1,19 @@
|
|||
[Desktop Entry]
|
||||
Version=0.3.12
|
||||
Name=LBRY
|
||||
Comment=The world's first user-owned content marketplace
|
||||
Icon=lbry
|
||||
GenericName=Content Marketplace
|
||||
Categories=Network;Internet;Filesharing
|
||||
Terminal=false
|
||||
Type=Application
|
||||
|
||||
MimeType=x-scheme-handler/lbry;
|
||||
|
||||
Exec=/usr/share/python/lbrynet/bin/lbry %U
|
||||
|
||||
Actions=StopDaemon;
|
||||
|
||||
[Desktop Action StopDaemon]
|
||||
Name=Stop Daemon
|
||||
Exec=/usr/share/python/lbrynet/bin/stop-lbrynet-daemon
|
15
packaging/ubuntu/postinst_append
Executable file
|
@ -0,0 +1,15 @@
|
|||
|
||||
(
|
||||
|
||||
if hash zenity 2>/dev/null; then
|
||||
sleep 3
|
||||
|
||||
zenity --info --icon-name="system-software-install" \
|
||||
--text="\
|
||||
<span size=\"xx-large\">LBRY Installed</span>\n\nLBRY has been installed.\n\n\
|
||||
Please start LBRY by running <b><tt>lbry</tt></b> from the command line or selecting <b>LBRY</b> from the application menu.\n\n\
|
||||
If you need help or have any questions, join us on Slack (<tt>https://slack.lbry.io</tt>) or email <tt>hello@lbry.io</tt>.\
|
||||
"
|
||||
|
||||
fi
|
||||
) &
|
201
packaging/ubuntu/ubuntu_package_setup.sh
Executable file
|
@ -0,0 +1,201 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
function HELP {
|
||||
echo "Build a debian package for lbry"
|
||||
echo "-----"
|
||||
echo "When run without any arguments, this script expects the current directory"
|
||||
echo "to be the main lbry repo and it builds what is in that directory"
|
||||
echo
|
||||
echo "Optional arguments:"
|
||||
echo
|
||||
echo "-c: clone a fresh copy of the repo"
|
||||
echo "-b <branch>: use the specified branch of the lbry repo"
|
||||
echo "-w <web-ui-branch>: set the webui branch"
|
||||
echo "-d <build-dir>: specifiy the build directory"
|
||||
echo "-h: show help"
|
||||
echo "-t: turn trace on"
|
||||
exit 1
|
||||
}
|
||||
|
||||
CLONE=false
|
||||
BUILD_DIR=""
|
||||
BRANCH=""
|
||||
WEB_UI_BRANCH="master"
|
||||
|
||||
while getopts :hctb:w:d: FLAG; do
|
||||
case $FLAG in
|
||||
c)
|
||||
CLONE=true
|
||||
;;
|
||||
b)
|
||||
BRANCH=${OPTARG}
|
||||
;;
|
||||
w)
|
||||
WEB_UI_BRANCH=${OPTARG}
|
||||
;;
|
||||
d)
|
||||
BUILD_DIR=${OPTARG}
|
||||
;;
|
||||
t)
|
||||
set -o xtrace
|
||||
;;
|
||||
h)
|
||||
HELP
|
||||
;;
|
||||
\?) #unrecognized option - show help
|
||||
echo "Option -$OPTARG not allowed."
|
||||
HELP
|
||||
;;
|
||||
:)
|
||||
echo "Option -$OPTARG requires an argument."
|
||||
HELP
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
shift $((OPTIND-1))
|
||||
|
||||
|
||||
SUDO=''
|
||||
if (( $EUID != 0 )); then
|
||||
SUDO='sudo'
|
||||
fi
|
||||
|
||||
if [ "$CLONE" = false ]; then
|
||||
if [ `basename $PWD` != "lbry" ]; then
|
||||
echo "Not currently in the lbry directory. Cowardly refusing to go forward"
|
||||
exit 1
|
||||
fi
|
||||
SOURCE_DIR=$PWD
|
||||
fi
|
||||
|
||||
if [ -z "${BUILD_DIR}" ]; then
|
||||
if [ "$CLONE" = true ]; then
|
||||
# build in the current directory
|
||||
BUILD_DIR="lbry-build-$(date +%Y%m%d-%H%M%S)"
|
||||
else
|
||||
BUILD_DIR="../lbry-build-$(date +%Y%m%d-%H%M%S)"
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
cd "$BUILD_DIR"
|
||||
|
||||
if [ -z ${TRAVIS+x} ]; then
|
||||
# if not on travis, its nice to see progress
|
||||
QUIET=""
|
||||
else
|
||||
QUIET="-qq"
|
||||
fi
|
||||
|
||||
# get the required OS packages
|
||||
$SUDO apt-get ${QUIET} update
|
||||
$SUDO apt-get ${QUIET} install -y --no-install-recommends software-properties-common
|
||||
$SUDO add-apt-repository -y ppa:spotify-jyrki/dh-virtualenv
|
||||
$SUDO apt-get ${QUIET} update
|
||||
$SUDO apt-get ${QUIET} install -y --no-install-recommends \
|
||||
build-essential git python-dev libffi-dev libssl-dev \
|
||||
libgmp3-dev dh-virtualenv debhelper wget python-pip fakeroot
|
||||
|
||||
# need a modern version of pip (more modern than ubuntu default)
|
||||
$SUDO pip install --upgrade pip
|
||||
$SUDO pip install git+https://github.com/jobevers/make-deb
|
||||
|
||||
# build packages
|
||||
#
|
||||
# dpkg-buildpackage outputs its results into '..' so
|
||||
# we need to move/clone lbry into the build directory
|
||||
if [ "$CLONE" == true ]; then
|
||||
git clone https://github.com/lbryio/lbry.git
|
||||
else
|
||||
cp -a $SOURCE_DIR lbry
|
||||
fi
|
||||
(
|
||||
cd lbry
|
||||
if [ -n "${BRANCH}" ]; then
|
||||
git checkout "${BRANCH}"
|
||||
fi
|
||||
make-deb
|
||||
dpkg-buildpackage -us -uc
|
||||
)
|
||||
|
||||
|
||||
### insert our extra files
|
||||
|
||||
# extract .deb
|
||||
PACKAGE="$(ls | grep '.deb')"
|
||||
ar vx "$PACKAGE"
|
||||
mkdir control data
|
||||
tar -xzf control.tar.gz --directory control
|
||||
|
||||
# The output of the travis build is a
|
||||
# tar.gz and the output locally is tar.xz.
|
||||
# Instead of having tar detect the compression used, we
|
||||
# could update the config to output the same in either spot.
|
||||
# Unfortunately, doing so requires editting some auto-generated
|
||||
# files: http://linux.spiney.org/forcing_gzip_compression_when_building_debian_packages
|
||||
tar -xf data.tar.?z --directory data
|
||||
|
||||
PACKAGING_DIR='lbry/packaging/ubuntu'
|
||||
|
||||
# set web ui branch
|
||||
sed -i "s/^WEB_UI_BRANCH='[^']\+'/WEB_UI_BRANCH='$WEB_UI_BRANCH'/" "$PACKAGING_DIR/lbry"
|
||||
|
||||
# add files
|
||||
function addfile() {
|
||||
FILE="$1"
|
||||
TARGET="$2"
|
||||
mkdir -p "$(dirname "data/$TARGET")"
|
||||
cp -d "$FILE" "data/$TARGET"
|
||||
echo "$(md5sum "data/$TARGET" | cut -d' ' -f1) $TARGET" >> control/md5sums
|
||||
}
|
||||
|
||||
# add icons
|
||||
addfile "$PACKAGING_DIR/icons/lbry32.png" usr/share/icons/hicolor/32x32/apps/lbry.png
|
||||
addfile "$PACKAGING_DIR/icons/lbry48.png" usr/share/icons/hicolor/48x48/apps/lbry.png
|
||||
addfile "$PACKAGING_DIR/icons/lbry96.png" usr/share/icons/hicolor/96x96/apps/lbry.png
|
||||
addfile "$PACKAGING_DIR/icons/lbry128.png" usr/share/icons/hicolor/128x128/apps/lbry.png
|
||||
addfile "$PACKAGING_DIR/icons/lbry256.png" usr/share/icons/hicolor/256x256/apps/lbry.png
|
||||
addfile "$PACKAGING_DIR/lbry.desktop" usr/share/applications/lbry.desktop
|
||||
|
||||
# add lbry executable script
|
||||
BINPATH=usr/share/python/lbrynet/bin
|
||||
addfile "$PACKAGING_DIR/lbry" "$BINPATH/lbry"
|
||||
|
||||
# symlink script into /usr/bin
|
||||
ln -s "/$BINPATH/lbry" "$PACKAGING_DIR/lbry-temp-symlink"
|
||||
addfile "$PACKAGING_DIR/lbry-temp-symlink" usr/bin/lbry
|
||||
|
||||
# add lbrycrdd and lbrycrd-cli
|
||||
mkdir -p "$PACKAGING_DIR/bins"
|
||||
wget http://s3.amazonaws.com/files.lbry.io/bins.zip --output-document "$PACKAGING_DIR/bins.zip"
|
||||
unzip "$PACKAGING_DIR/bins.zip" -d "$PACKAGING_DIR/bins/"
|
||||
addfile "$PACKAGING_DIR/bins/lbrycrdd" usr/bin/lbrycrdd
|
||||
addfile "$PACKAGING_DIR/bins/lbrycrd-cli" usr/bin/lbrycrd-cli
|
||||
|
||||
# add postinstall script
|
||||
cat "$PACKAGING_DIR/postinst_append" >> control/postinst
|
||||
|
||||
# change package name from lbrynet to lbry
|
||||
sed -i 's/^Package: lbrynet/Package: lbry/' control/control
|
||||
echo "Conflicts: lbrynet (<< 0.3.5)" >> control/control
|
||||
echo "Replaces: lbrynet (<< 0.3.5)" >> control/control
|
||||
|
||||
# repackage .deb
|
||||
$SUDO chown -R root:root control data
|
||||
tar -czf control.tar.gz -C control .
|
||||
tar -cJf data.tar.xz -C data .
|
||||
$SUDO chown root:root debian-binary control.tar.gz data.tar.xz
|
||||
ar r "$PACKAGE" debian-binary control.tar.gz data.tar.xz
|
||||
|
||||
# TODO: we can append to data.tar instead of extracting it all and recompressing
|
||||
|
||||
if [[ ! -z "${TRAVIS_BUILD_DIR+x}" ]]; then
|
||||
# move it to a consistent place so that later it can be uploaded
|
||||
# to the github releases page
|
||||
mv "${PACKAGE}" "${TRAVIS_BUILD_DIR}/${PACKAGE}"
|
||||
# want to be able to check the size of the result in the log
|
||||
ls -l "${TRAVIS_BUILD_DIR}/${PACKAGE}"
|
||||
fi
|
29
requirements.txt
Normal file
|
@ -0,0 +1,29 @@
|
|||
Twisted==16.0.0
|
||||
Yapsy==1.11.223
|
||||
appdirs==1.4.0
|
||||
argparse==1.2.1
|
||||
colorama==0.3.7
|
||||
dnspython==1.12.0
|
||||
ecdsa==0.13
|
||||
gmpy==1.17
|
||||
jsonrpc==1.2
|
||||
jsonrpclib==0.1.7
|
||||
https://github.com/lbryio/lbryum/tarball/master/#egg=lbryum
|
||||
leveldb==0.193
|
||||
miniupnpc==1.9
|
||||
pbkdf2==1.3
|
||||
protobuf==3.0.0b3
|
||||
pycrypto==2.6.1
|
||||
python-bitcoinrpc==0.1
|
||||
qrcode==5.2.2
|
||||
requests==2.9.1
|
||||
seccure==0.3.1.3
|
||||
simplejson==3.8.2
|
||||
six==1.9.0
|
||||
slowaes==0.1a1
|
||||
txJSON-RPC==0.3.1
|
||||
unqlite==0.2.0
|
||||
wsgiref==0.1.2
|
||||
zope.interface==4.1.3
|
||||
base58==0.2.2
|
||||
googlefinance==0.7
|
110
scripts/migrate_lbryum_to_lbrycrd.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import base58
|
||||
|
||||
from lbryum import SimpleConfig, Network
|
||||
from lbryum.wallet import WalletStorage, Wallet
|
||||
from lbryum.commands import known_commands, Commands
|
||||
from lbryum import lbrycrd
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--wallet', help='path to lbryum wallet')
|
||||
args = parser.parse_args()
|
||||
|
||||
ensureCliIsOnPathAndServerIsRunning()
|
||||
|
||||
wallet = getWallet(args.wallet)
|
||||
addresses = wallet.addresses(True)
|
||||
for addr in addresses[:-1]:
|
||||
printBalance(wallet, addr)
|
||||
saveAddr(wallet, addr)
|
||||
# on the last one, rescan. Don't rescan early for sake of efficiency
|
||||
addr = addresses[-1]
|
||||
printBalance(wallet, addr)
|
||||
saveAddr(wallet, addr, "true")
|
||||
|
||||
|
||||
def ensureCliIsOnPathAndServerIsRunning():
|
||||
try:
|
||||
output = subprocess.check_output(['lbrycrd-cli', 'getinfo'])
|
||||
except OSError:
|
||||
print 'Failed to run: lbrycrd-cli needs to be on the PATH'
|
||||
sys.exit(1)
|
||||
except subprocess.CalledProcessError:
|
||||
print 'Failed to run: could not connect to the lbrycrd server.'
|
||||
print 'Make sure it is running and able to be connected to.'
|
||||
print 'One way to do this is to run:'
|
||||
print ' lbrycrdd -server -printtoconsole'
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def validateAddress(addr):
|
||||
raw_output = subprocess.check_output(
|
||||
['lbrycrd-cli', 'validateaddress', addr])
|
||||
output = json.loads(raw_output)
|
||||
if not output['isvalid']:
|
||||
raise Exception('Address {} is not valid'.format(addr))
|
||||
if not output['ismine']:
|
||||
raise Exception('Address {} is not yours'.format(addr))
|
||||
|
||||
|
||||
def printBalance(wallet, addr):
|
||||
balance = getBalance(wallet, addr)
|
||||
print 'Importing private key for %s with balance %s' % (addr, balance)
|
||||
|
||||
|
||||
def getBalance(wallet, addr):
|
||||
return sum(wallet.get_addr_balance(addr))
|
||||
|
||||
|
||||
def getWallet(path=None):
|
||||
if not path:
|
||||
config = SimpleConfig()
|
||||
path = config.get_wallet_path()
|
||||
storage = WalletStorage(path)
|
||||
if not storage.file_exists:
|
||||
print "Failed to run: No wallet to migrate"
|
||||
sys.exit(1)
|
||||
return Wallet(storage)
|
||||
|
||||
|
||||
def saveAddr(wallet, addr, rescan="false"):
|
||||
keys = wallet.get_private_key(addr, None)
|
||||
assert len(keys) == 1, 'Address {} has {} keys. Expected 1'.format(addr, len(keys))
|
||||
key = keys[0]
|
||||
# copied from lbrycrd.regenerate_key
|
||||
b = lbrycrd.ASecretToSecret(key)
|
||||
pkey = b[0:32]
|
||||
is_compressed = lbrycrd.is_compressed(key)
|
||||
wif = pkeyToWif(pkey, is_compressed)
|
||||
subprocess.check_call(
|
||||
['lbrycrd-cli', 'importprivkey', wif, "", rescan])
|
||||
validateAddress(addr)
|
||||
|
||||
|
||||
def pkeyToWif(pkey, compressed):
|
||||
# Follow https://en.bitcoin.it/wiki/Wallet_import_format
|
||||
# to convert from a private key to the wallet import format
|
||||
prefix = '\x1c'
|
||||
wif = prefix + pkey
|
||||
if compressed:
|
||||
wif += '\x01'
|
||||
intermediate_checksum = hashlib.sha256(wif).digest()
|
||||
checksum = hashlib.sha256(intermediate_checksum).digest()
|
||||
wif = wif + checksum[:4]
|
||||
return base58.b58encode(wif)
|
||||
|
||||
|
||||
def wifToPkey(wif):
|
||||
pkey = base58.b58decode(wif)
|
||||
return pkey[1:-4]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|