diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 000000000..a7e5c071f --- /dev/null +++ b/.bumpversion.cfg @@ -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] + diff --git a/.gitignore b/.gitignore index 23ba8568c..249826fac 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..e69de29bb diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 000000000..e49732469 --- /dev/null +++ b/.pylintrc @@ -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*(# )??$ + +# 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 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..2ccba1e61 --- /dev/null +++ b/.travis.yml @@ -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=" diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 000000000..091acd711 --- /dev/null +++ b/FAQ.md @@ -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})) + diff --git a/INSTALL.md b/INSTALL.md index a964c8a8f..8bf9f8917 100644 --- a/INSTALL.md +++ b/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). -``` -sudo apt-get install python-virtualenv -cd -virtualenv . -source bin/activate -``` +##### 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. -(to deactivate the virtualenv, enter 'deactivate') +##### 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. -``` -python setup.py install -``` +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. -this will install all of the libraries and a few applications -For running the file sharing application, see [RUNNING](RUNNING.md) + +#### 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 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 start LBRY, run `lbrynet-daemon` in a terminal. #### On windows: diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..97304ecca --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include lbrynet/lbrynet_console/plugins/blindrepeater.yapsy-plugin +recursive-include lbrynet/lbrynet_gui *.gif *.ico *.xbm *.conf diff --git a/README.md b/README.md index d9f7cd98e..646b333f7 100644 --- a/README.md +++ b/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 diff --git a/RUNNING.md b/RUNNING.md index 1d839bc9e..d2f1842b4 100644 --- a/RUNNING.md +++ b/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 +``` + +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 diff --git a/app.icns b/app.icns new file mode 100644 index 000000000..b4d00d2f2 Binary files /dev/null and b/app.icns differ diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index c53c52efe..b501b627e 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -1,4 +1,8 @@ import logging +log = logging.getLogger(__name__) +logging.getLogger(__name__).addHandler(logging.NullHandler()) +log.setLevel(logging.INFO) -logging.getLogger(__name__).addHandler(logging.NullHandler()) \ No newline at end of file +__version__ = "0.3.12" +version = tuple(__version__.split('.')) \ No newline at end of file diff --git a/lbrynet/conf.py b/lbrynet/conf.py index b4d91b5b8..96e8b18dc 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -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"] -CRYPTSD_FILE_EXTENSION = ".cryptsd" \ No newline at end of file +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'}, + } diff --git a/lbrynet/core/Error.py b/lbrynet/core/Error.py index b2c4c3360..8146dc169 100644 --- a/lbrynet/core/Error.py +++ b/lbrynet/core/Error.py @@ -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 diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py new file mode 100644 index 000000000..656b55c99 --- /dev/null +++ b/lbrynet/core/LBRYMetadata.py @@ -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()) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py new file mode 100644 index 000000000..60696624b --- /dev/null +++ b/lbrynet/core/LBRYWallet.py @@ -0,0 +1,1284 @@ +import sys +import datetime +import logging +import json +import subprocess +import socket +import time +import os +import requests + +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 +from googlefinance import getQuotes + +from lbryum import SimpleConfig, Network +from lbryum.lbrycrd import COIN, TYPE_ADDRESS +from lbryum.wallet import WalletStorage, Wallet +from lbryum.commands import known_commands, Commands +from lbryum.transaction import Transaction + +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 lbrynet.conf import SOURCE_TYPES +from lbrynet.core.LBRYMetadata import Metadata + +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 LBRYWallet(object): + """This class implements the LBRYWallet interface for the LBRYcrd payment system""" + implements(ILBRYWallet) + + _FIRST_RUN_UNKNOWN = 0 + _FIRST_RUN_YES = 1 + _FIRST_RUN_NO = 2 + + def __init__(self, db_dir): + 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.is_lagging = None + + self.manage_running = False + self._manage_count = 0 + self._balance_refresh_time = 3 + self._batch_count = 20 + self._first_run = self._FIRST_RUN_UNKNOWN + + def start(self): + + def start_manage(): + self.stopped = False + self.manage() + return True + + d = self._open_db() + d.addCallback(lambda _: self._start()) + d.addCallback(lambda _: start_manage()) + return d + + @staticmethod + def log_stop_error(err): + log.error("An error occurred stopping the wallet: %s", err.getTraceback()) + + def stop(self): + + 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(do_full=True) + d.addErrback(self.log_stop_error) + d.addCallback(lambda _: self._stop()) + d.addErrback(self.log_stop_error) + return d + + def manage(self, do_full=False): + self.next_manage_call = None + have_set_manage_running = [False] + self._manage_count += 1 + if self._manage_count % self._batch_count == 0: + self._manage_count = 0 + do_full = True + + 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) + elif do_full is False: + d.callback(False) + else: + task.deferLater(reactor, 1, fire_if_not_running) + + fire_if_not_running() + return d + + d = check_if_manage_running() + + def do_manage(): + if do_full: + d = self._check_expected_balances() + d.addCallback(lambda _: self._send_payments()) + else: + d = defer.succeed(True) + + d.addCallback(lambda _: self.get_balance()) + + def set_wallet_balance(balance): + if self.wallet_balance != balance: + log.info("Got a new balance: %s", str(balance)) + self.wallet_balance = balance + + d.addCallback(set_wallet_balance) + return d + + d.addCallback(lambda should_run: do_manage() if should_run else None) + + def set_next_manage_call(): + if not self.stopped: + self.next_manage_call = reactor.callLater(self._balance_refresh_time, 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 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 = self.get_new_address() + d.addCallback(set_address_for_peer) + return d + + def _send_payments(self): + 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] = 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)) + d = self._do_send_many(payments_to_send) + d.addCallback(lambda txid: log.debug("Sent transaction %s", txid)) + return d + log.info("There were no payments to send") + return defer.succeed(True) + + def get_stream_info_for_name(self, name): + d = self._get_value_for_name(name) + d.addCallback(self._get_stream_info_from_value, name) + return d + + def get_txid_for_name(self, name): + d = self._get_value_for_name(name) + d.addCallback(lambda r: None if 'txid' not in r else r['txid']) + return d + + def get_stream_info_from_txid(self, name, txid): + d = self.get_claims_from_tx(txid) + + def get_claim_for_name(claims): + for claim in claims: + if claim['name'] == name: + claim['txid'] = txid + return claim + return Failure(UnknownNameError(name)) + + d.addCallback(get_claim_for_name) + d.addCallback(self._get_stream_info_from_value, name) + return d + + def _get_stream_info_from_value(self, result, name): + def _check_result_fields(r): + for k in ['value', 'txid', 'n', 'height', 'amount']: + assert k in r, "getvalueforname response missing field %s" % k + + if 'error' in result: + log.warning("Got an error looking up a name: %s", result['error']) + return Failure(UnknownNameError(name)) + + _check_result_fields(result) + + try: + metadata = Metadata(json.loads(result['value'])) + except (ValueError, TypeError): + return Failure(InvalidStreamInfoError(name)) + + d = self._save_name_metadata(name, str(result['txid']), metadata['sources']['lbry_sd_hash']) + d.addCallback(lambda _: log.info("lbry://%s complies with %s" % (name, metadata.meta_version))) + d.addCallback(lambda _: metadata) + return d + + def _get_claim_info(self, result, name): + def _check_result_fields(r): + for k in ['value', 'txid', 'n', 'height', 'amount']: + assert k in r, "getvalueforname response missing field %s" % k + + def _build_response(m, result): + result['value'] = m + return result + + if 'error' in result: + log.warning("Got an error looking up a name: %s", result['error']) + return Failure(UnknownNameError(name)) + + _check_result_fields(result) + + try: + metadata = Metadata(json.loads(result['value'])) + except (ValueError, TypeError): + return Failure(InvalidStreamInfoError(name)) + + d = self._save_name_metadata(name, str(result['txid']), metadata['sources']['lbry_sd_hash']) + d.addCallback(lambda _: log.info("lbry://%s complies with %s" % (name, metadata.meta_version))) + d.addCallback(lambda _: _build_response(metadata, result)) + return d + + def get_claim_info(self, name): + d = self._get_value_for_name(name) + d.addCallback(lambda r: self._get_claim_info(r, name)) + return d + + + def claim_name(self, name, bid, m): + + metadata = Metadata(m) + + d = self._send_name_claim(name, json.dumps(metadata), bid) + + def _save_metadata(txid): + log.info("Saving metadata for claim %s" % txid) + d = self._save_name_metadata(name, txid, metadata['sources']['lbry_sd_hash']) + d.addCallback(lambda _: txid) + return d + + d.addCallback(_save_metadata) + return d + + def abandon_name(self, txid): + d1 = self.get_new_address() + d2 = self.get_claims_from_tx(txid) + + def get_txout_of_claim(claims): + for claim in claims: + if 'name' in claim and 'nOut' in claim: + return claim['nOut'] + return defer.fail(ValueError("No claims in tx")) + + def get_value_of_txout(nOut): + d = self._get_raw_tx(txid) + d.addCallback(self._get_decoded_tx) + d.addCallback(lambda tx: tx['vout'][nOut]['value']) + return d + + d2.addCallback(get_txout_of_claim) + d2.addCallback(get_value_of_txout) + dl = defer.DeferredList([d1, d2], consumeErrors=True) + + def abandon(results): + if results[0][0] and results[1][0]: + address = results[0][1] + amount = float(results[1][1]) + return self._send_abandon(txid, address, amount) + elif results[0][0] is False: + return defer.fail(Failure(ValueError("Couldn't get a new address"))) + else: + return results[1][1] + + dl.addCallback(abandon) + return dl + + def get_tx(self, txid): + d = self._get_raw_tx(txid) + d.addCallback(self._get_decoded_tx) + return d + + def update_name(self, name, bid, value, old_txid): + d = self._get_value_for_name(name) + d.addCallback(lambda r: self.abandon_name(r['txid'] if not old_txid else old_txid)) + d.addCallback(lambda r: log.info("Abandon claim tx %s" % str(r))) + d.addCallback(lambda _: self.claim_name(name, bid, value)) + + return d + + 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: 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 is_first_run(self): + if self._first_run == self._FIRST_RUN_UNKNOWN: + d = self._check_first_run() + + def set_first_run(is_first): + self._first_run = self._FIRST_RUN_YES if is_first else self._FIRST_RUN_NO + + d.addCallback(set_first_run) + else: + d = defer.succeed(self._FIRST_RUN_YES if self._first_run else self._FIRST_RUN_NO) + + d.addCallback(lambda _: self._first_run == self._FIRST_RUN_YES) + return d + + def _get_status_of_claim(self, txid, name, sd_hash): + d = self.get_claims_from_tx(txid) + + def get_status(claims): + 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, TypeError): + return None + claim_sd_hash = None + if 'stream_hash' in value_dict: + claim_sd_hash = str(value_dict['stream_hash']) + if 'sources' in value_dict and 'lbrynet_sd_hash' in value_dict['sources']: + claim_sd_hash = str(value_dict['sources']['lbry_sd_hash']) + if claim_sd_hash is not None and claim_sd_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 + + d.addCallback(get_status) + return d + + 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: + log.info("Checking balance of address %s", str(balance_to_check[1])) + d = self._get_balance_for_address(balance_to_check[1]) + d.addCallback(lambda bal: bal >= balance_to_check[2]) + 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 + + 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, txid, sd_hash): + d = self.db.runQuery("select * from name_metadata where name=? and txid=? and sd_hash=?", (name, txid, sd_hash)) + d.addCallback(lambda r: self.db.runQuery("insert into name_metadata values (?, ?, ?)", (name, txid, sd_hash)) + if not len(r) else None) + + 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 + + ######### Must be overridden ######### + + def get_balance(self): + return defer.fail(NotImplementedError()) + + def get_new_address(self): + return defer.fail(NotImplementedError()) + + def get_block(self, blockhash): + return defer.fail(NotImplementedError()) + + def get_most_recent_blocktime(self): + return defer.fail(NotImplementedError()) + + def get_best_blockhash(self): + return defer.fail(NotImplementedError()) + + def get_name_claims(self): + return defer.fail(NotImplementedError()) + + def _check_first_run(self): + return defer.fail(NotImplementedError()) + + def _get_raw_tx(self, txid): + return defer.fail(NotImplementedError()) + + def _send_name_claim(self, name, val, amount): + return defer.fail(NotImplementedError()) + + def _get_decoded_tx(self, raw_tx): + return defer.fail(NotImplementedError()) + + def _send_abandon(self, txid, address, amount): + return defer.fail(NotImplementedError()) + + def _update_name(self, txid, value, amount): + return defer.fail(NotImplementedError()) + + def _do_send_many(self, payments_to_send): + return defer.fail(NotImplementedError()) + + def _get_value_for_name(self, name): + return defer.fail(NotImplementedError()) + + def get_claims_from_tx(self, txid): + return defer.fail(NotImplementedError()) + + def _get_balance_for_address(self, address): + return defer.fail(NotImplementedError()) + + def _start(self): + pass + + def _stop(self): + pass + + +class LBRYcrdWallet(LBRYWallet): + def __init__(self, db_dir, wallet_dir=None, wallet_conf=None, lbrycrdd_path=None): + LBRYWallet.__init__(self, db_dir) + self.started_lbrycrdd = False + self.wallet_dir = wallet_dir + self.wallet_conf = wallet_conf + self.lbrycrdd = None + 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): + return threads.deferToThread(self._make_connection) + + def _stop(self): + if self.lbrycrdd_path is not None: + return self._stop_daemon() + + def _make_connection(self): + alert.info("Connecting to lbrycrdd...") + if self.lbrycrdd_path is not None: + self._start_daemon() + self._get_info_rpc() + log.info("Connected!") + alert.info("Connected to lbrycrdd.") + + def _get_rpc_conf(self): + settings = {"username": "rpcuser", + "password": "rpcpassword", + "rpc_port": 9245} + if self.wallet_conf and 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 _check_first_run(self): + d = self.get_balance() + d.addCallback(lambda bal: threads.deferToThread(self._get_num_addresses_rpc) if bal == 0 else 2) + d.addCallback(lambda num_addresses: True if num_addresses <= 1 else False) + return d + + def get_new_address(self): + return threads.deferToThread(self._get_new_address_rpc) + + def get_balance(self): + return threads.deferToThread(self._get_wallet_balance_rpc) + + def get_most_recent_blocktime(self): + d = threads.deferToThread(self._get_best_blockhash_rpc) + d.addCallback(lambda blockhash: threads.deferToThread(self._get_block_rpc, blockhash)) + d.addCallback( + lambda block: block['time'] if 'time' in block else Failure(ValueError("Could not get a block time"))) + return d + + def get_name_claims(self): + return threads.deferToThread(self._get_name_claims_rpc) + + def get_block(self, blockhash): + return threads.deferToThread(self._get_block_rpc, blockhash) + + def get_best_blockhash(self): + d = threads.deferToThread(self._get_blockchain_info_rpc) + d.addCallback(lambda blockchain_info: blockchain_info['bestblockhash']) + return d + + def get_nametrie(self): + return threads.deferToThread(self._get_nametrie_rpc) + + def start_miner(self): + d = threads.deferToThread(self._get_gen_status_rpc) + d.addCallback(lambda status: threads.deferToThread(self._set_gen_status_rpc, True) if not status + else "Miner was already running") + return d + + def stop_miner(self): + d = threads.deferToThread(self._get_gen_status_rpc) + d.addCallback(lambda status: threads.deferToThread(self._set_gen_status_rpc, False) if status + else "Miner wasn't running") + return d + + def get_miner_status(self): + return threads.deferToThread(self._get_gen_status_rpc) + + def _get_balance_for_address(self, address): + return threads.deferToThread(self._get_balance_for_address_rpc, address) + + def _do_send_many(self, payments_to_send): + outputs = {address: float(points) for address, points in payments_to_send.iteritems()} + return threads.deferToThread(self._do_send_many_rpc, outputs) + + def _send_name_claim(self, name, value, amount): + return threads.deferToThread(self._send_name_claim_rpc, name, value, amount) + + def _get_raw_tx(self, txid): + return threads.deferToThread(self._get_raw_tx_rpc, txid) + + def _get_decoded_tx(self, raw_tx): + return threads.deferToThread(self._get_decoded_tx_rpc, raw_tx) + + def _send_abandon(self, txid, address, amount): + return threads.deferToThread(self._send_abandon_rpc, txid, address, amount) + + def _update_name(self, txid, value, amount): + return threads.deferToThread(self._update_name_rpc, txid, value, amount) + + def get_claims_from_tx(self, txid): + return threads.deferToThread(self._get_claims_from_tx_rpc, txid) + + def _get_value_for_name(self, name): + return threads.deferToThread(self._get_value_for_name_rpc, name) + + def _get_rpc_conn(self): + return AuthServiceProxy(self.rpc_conn_string) + + def _start_daemon(self): + + tries = 0 + try: + rpc_conn = self._get_rpc_conn() + try: + rpc_conn.getinfo() + except ValueError: + log.exception('Failed to get rpc info. Rethrowing with a hopefully more useful error message') + raise Exception('Failed to get rpc info from lbrycrdd. Try restarting lbrycrdd') + 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._stop_rpc) + d.addCallback(lambda _: alert.info("Stopped lbrycrdd.")) + return d + return defer.succeed(True) + + @_catch_connection_error + def _get_balance_for_address_rpc(self, address): + rpc_conn = self._get_rpc_conn() + balance = rpc_conn.getreceivedbyaddress(address) + log.debug("received balance for %s: %s", str(address), str(balance)) + return balance + + @_catch_connection_error + def _do_send_many_rpc(self, payments): + rpc_conn = self._get_rpc_conn() + return rpc_conn.sendmany("", payments) + + @_catch_connection_error + def _get_info_rpc(self): + rpc_conn = self._get_rpc_conn() + return rpc_conn.getinfo() + + @_catch_connection_error + def _get_name_claims_rpc(self): + rpc_conn = self._get_rpc_conn() + return rpc_conn.listnameclaims() + + @_catch_connection_error + def _get_gen_status_rpc(self): + rpc_conn = self._get_rpc_conn() + return rpc_conn.getgenerate() + + @_catch_connection_error + def _set_gen_status_rpc(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_rpc(self, txid): + rpc_conn = self._get_rpc_conn() + return rpc_conn.getrawtransaction(txid) + + @_catch_connection_error + def _get_decoded_tx_rpc(self, raw): + rpc_conn = self._get_rpc_conn() + return rpc_conn.decoderawtransaction(raw) + + @_catch_connection_error + def _send_abandon_rpc(self, txid, address, amount): + rpc_conn = self._get_rpc_conn() + return rpc_conn.abandonclaim(txid, address, amount) + + @_catch_connection_error + def _get_blockchain_info_rpc(self): + rpc_conn = self._get_rpc_conn() + return rpc_conn.getblockchaininfo() + + @_catch_connection_error + def _get_block_rpc(self, blockhash): + rpc_conn = self._get_rpc_conn() + return rpc_conn.getblock(blockhash) + + @_catch_connection_error + def _get_claims_from_tx_rpc(self, txid): + rpc_conn = self._get_rpc_conn() + return rpc_conn.getclaimsfortx(txid) + + @_catch_connection_error + def _get_nametrie_rpc(self): + rpc_conn = self._get_rpc_conn() + return rpc_conn.getclaimtrie() + + @_catch_connection_error + def _get_wallet_balance_rpc(self): + rpc_conn = self._get_rpc_conn() + return rpc_conn.getbalance("") + + @_catch_connection_error + def _get_new_address_rpc(self): + rpc_conn = self._get_rpc_conn() + return rpc_conn.getnewaddress() + + @_catch_connection_error + def _get_value_for_name_rpc(self, name): + rpc_conn = self._get_rpc_conn() + return rpc_conn.getvalueforname(name) + + def _update_name_rpc(self, txid, value, amount): + rpc_conn = self._get_rpc_conn() + return rpc_conn.updateclaim(txid, value, amount) + + @_catch_connection_error + def _send_name_claim_rpc(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_num_addresses_rpc(self): + rpc_conn = self._get_rpc_conn() + return len(rpc_conn.getaddressesbyaccount("")) + + @_catch_connection_error + def _get_best_blockhash_rpc(self): + rpc_conn = self._get_rpc_conn() + return rpc_conn.getbestblockhash() + + @_catch_connection_error + def _stop_rpc(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() + + +class LBRYumWallet(LBRYWallet): + + def __init__(self, db_dir): + LBRYWallet.__init__(self, db_dir) + self.config = None + self.network = None + self.wallet = None + self.cmd_runner = None + self.first_run = False + self.printed_retrieving_headers = False + self._start_check = None + self._catch_up_check = None + self._caught_up_counter = 0 + self._lag_counter = 0 + self.blocks_behind_alert = 0 + self.catchup_progress = 0 + self.max_behind = 0 + + def _start(self): + + network_start_d = defer.Deferred() + + def setup_network(): + self.config = SimpleConfig({'auto_connect': True}) + self.network = Network(self.config) + alert.info("Loading the wallet...") + return defer.succeed(self.network.start()) + + d = setup_network() + + def check_started(): + if self.network.is_connecting(): + if not self.printed_retrieving_headers and self.network.blockchain.retrieving_headers: + alert.info("Running the wallet for the first time...this may take a moment.") + self.printed_retrieving_headers = True + return False + self._start_check.stop() + self._start_check = None + if self.network.is_connected(): + network_start_d.callback(True) + else: + network_start_d.errback(ValueError("Failed to connect to network.")) + + self._start_check = task.LoopingCall(check_started) + + d.addCallback(lambda _: self._start_check.start(.1)) + d.addCallback(lambda _: network_start_d) + d.addCallback(lambda _: self._load_wallet()) + d.addCallback(lambda _: self._get_cmd_runner()) + return d + + def _stop(self): + if self._start_check is not None: + self._start_check.stop() + self._start_check = None + + if self._catch_up_check is not None: + self._catch_up_check.stop() + self._catch_up_check = None + + d = defer.Deferred() + + def check_stopped(): + if self.network: + if self.network.is_connected(): + return False + stop_check.stop() + self.network = None + d.callback(True) + + if self.network: + self.network.stop() + + stop_check = task.LoopingCall(check_stopped) + stop_check.start(.1) + return d + + def _load_wallet(self): + + def get_wallet(): + path = self.config.get_wallet_path() + storage = WalletStorage(path) + wallet = Wallet(storage) + if not storage.file_exists: + self.first_run = True + seed = wallet.make_seed() + wallet.add_seed(seed, None) + wallet.create_master_keys(None) + wallet.create_main_account() + wallet.synchronize() + self.wallet = wallet + + blockchain_caught_d = defer.Deferred() + + def check_caught_up(): + local_height = self.network.get_catchup_progress() + remote_height = self.network.get_server_height() + + if remote_height != 0 and remote_height - local_height <= 5: + msg = "" + if self._caught_up_counter != 0: + msg += "All caught up. " + msg += "Wallet loaded." + alert.info(msg) + self._catch_up_check.stop() + self._catch_up_check = None + blockchain_caught_d.callback(True) + + elif remote_height != 0: + past_blocks_behind = self.blocks_behind_alert + self.blocks_behind_alert = remote_height - local_height + if self.blocks_behind_alert < past_blocks_behind: + self._lag_counter = 0 + self.is_lagging = False + else: + self._lag_counter += 1 + if self._lag_counter >= 900: + self.is_lagging = True + + if self.blocks_behind_alert > self.max_behind: + self.max_behind = self.blocks_behind_alert + self.catchup_progress = int(100 * (self.blocks_behind_alert / (5 + self.max_behind))) + if self._caught_up_counter == 0: + alert.info('Catching up with the blockchain...showing blocks left...') + if self._caught_up_counter % 30 == 0: + alert.info('%d...', (remote_height - local_height)) + + self._caught_up_counter += 1 + + + self._catch_up_check = task.LoopingCall(check_caught_up) + + d = threads.deferToThread(get_wallet) + d.addCallback(self._save_wallet) + d.addCallback(lambda _: self.wallet.start_threads(self.network)) + d.addCallback(lambda _: self._catch_up_check.start(.1)) + d.addCallback(lambda _: blockchain_caught_d) + return d + + def _get_cmd_runner(self): + self.cmd_runner = Commands(self.config, self.wallet, self.network) + + def get_balance(self): + cmd = known_commands['getbalance'] + func = getattr(self.cmd_runner, cmd.name) + d = threads.deferToThread(func) + d.addCallback(lambda result: result['unmatured'] if 'unmatured' in result else result['confirmed']) + d.addCallback(Decimal) + return d + + def get_new_address(self): + d = threads.deferToThread(self.wallet.create_new_address) + d.addCallback(self._save_wallet) + return d + + def get_block(self, blockhash): + cmd = known_commands['getblock'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, blockhash) + + def get_most_recent_blocktime(self): + header = self.network.get_header(self.network.get_local_height()) + return defer.succeed(header['timestamp']) + + def get_best_blockhash(self): + height = self.network.get_local_height() + d = threads.deferToThread(self.network.blockchain.read_header, height) + d.addCallback(lambda header: self.network.blockchain.hash_header(header)) + return d + + def get_name_claims(self): + cmd = known_commands['getnameclaims'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func) + + def _check_first_run(self): + return defer.succeed(self.first_run) + + def _get_raw_tx(self, txid): + cmd = known_commands['gettransaction'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, txid) + + def _send_name_claim(self, name, val, amount): + def send_claim(address): + cmd = known_commands['claimname'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, address, amount, name, val) + d = self.get_new_address() + d.addCallback(send_claim) + d.addCallback(self._broadcast_transaction) + return d + + def _get_decoded_tx(self, raw_tx): + tx = Transaction(raw_tx) + decoded_tx = {} + decoded_tx['vout'] = [] + for output in tx.outputs(): + out = {} + out['value'] = Decimal(output[2]) / Decimal(COIN) + decoded_tx['vout'].append(out) + return decoded_tx + + def _send_abandon(self, txid, address, amount): + log.info("Abandon " + str(txid) + " " + str(address) + " " + str(amount)) + cmd = known_commands['abandonclaim'] + func = getattr(self.cmd_runner, cmd.name) + d = threads.deferToThread(func, txid, address, amount) + d.addCallback(self._broadcast_transaction) + return d + + def _broadcast_transaction(self, raw_tx): + log.info("Broadcast: " + str(raw_tx)) + cmd = known_commands['broadcast'] + func = getattr(self.cmd_runner, cmd.name) + d = threads.deferToThread(func, raw_tx) + d.addCallback(self._save_wallet) + return d + + def _do_send_many(self, payments_to_send): + log.warning("Doing send many. payments to send: %s", str(payments_to_send)) + cmd = known_commands['paytomanyandsend'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, payments_to_send.iteritems()) + + def _get_value_for_name(self, name): + cmd = known_commands['getvalueforname'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, name) + + def get_claims_from_tx(self, txid): + cmd = known_commands['getclaimsfromtx'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, txid) + + def _get_balance_for_address(self, address): + return defer.succeed(Decimal(self.wallet.get_addr_received(address))/COIN) + + def get_nametrie(self): + cmd = known_commands['getclaimtrie'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func) + + def get_history(self): + cmd = known_commands['history'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func) + + def get_tx_json(self, txid): + def _decode(raw_tx): + tx = Transaction(raw_tx).deserialize() + decoded_tx = {} + for txkey in tx.keys(): + if isinstance(tx[txkey], list): + decoded_tx[txkey] = [] + for i in tx[txkey]: + tmp = {} + for k in i.keys(): + if isinstance(i[k], Decimal): + tmp[k] = float(i[k] / 1e8) + else: + tmp[k] = i[k] + decoded_tx[txkey].append(tmp) + else: + decoded_tx[txkey] = tx[txkey] + return decoded_tx + + d = self._get_raw_tx(txid) + d.addCallback(_decode) + return d + + def get_pub_keys(self, wallet): + cmd = known_commands['getpubkeys'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, wallet) + + def _save_wallet(self, val): + d = threads.deferToThread(self.wallet.storage.write) + d.addCallback(lambda _: val) + 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({}) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py deleted file mode 100644 index 3db07f1b3..000000000 --- a/lbrynet/core/LBRYcrdWallet.py +++ /dev/null @@ -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({}) \ No newline at end of file diff --git a/lbrynet/core/PTCWallet.py b/lbrynet/core/PTCWallet.py index bf02a3b7e..38f187034 100644 --- a/lbrynet/core/PTCWallet.py +++ b/lbrynet/core/PTCWallet.py @@ -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__) diff --git a/lbrynet/core/Session.py b/lbrynet/core/Session.py index d919e33b3..c8ff3f290 100644 --- a/lbrynet/core/Session.py +++ b/lbrynet/core/Session.py @@ -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) @@ -269,4 +272,4 @@ class LBRYSession(object): d = threads.deferToThread(threaded_unset_upnp) d.addErrback(lambda err: str(err)) - return d \ No newline at end of file + return d diff --git a/lbrynet/core/StreamDescriptor.py b/lbrynet/core/StreamDescriptor.py index a965969e8..fd21a4b87 100644 --- a/lbrynet/core/StreamDescriptor.py +++ b/lbrynet/core/StreamDescriptor.py @@ -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] @@ -238,4 +238,4 @@ def download_sd_blob(session, blob_hash, payment_rate_manager): """ downloader = StandaloneBlobDownloader(blob_hash, session.blob_manager, session.peer_finder, session.rate_limiter, payment_rate_manager, session.wallet) - return downloader.download() \ No newline at end of file + return downloader.download() diff --git a/lbrynet/core/client/DownloadManager.py b/lbrynet/core/client/DownloadManager.py index fced77969..265e090eb 100644 --- a/lbrynet/core/client/DownloadManager.py +++ b/lbrynet/core/client/DownloadManager.py @@ -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 @@ -115,4 +115,4 @@ class DownloadManager(object): if not self.blobs: return self.calculate_total_bytes() else: - return sum([b.length for b in self.needed_blobs() if b.length is not None]) \ No newline at end of file + return sum([b.length for b in self.needed_blobs() if b.length is not None]) diff --git a/lbrynet/core/log_support.py b/lbrynet/core/log_support.py new file mode 100644 index 000000000..208157d3d --- /dev/null +++ b/lbrynet/core/log_support.py @@ -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) diff --git a/lbrynet/core/server/BlobRequestHandler.py b/lbrynet/core/server/BlobRequestHandler.py index 2ce3688b4..36f67d797 100644 --- a/lbrynet/core/server/BlobRequestHandler.py +++ b/lbrynet/core/server/BlobRequestHandler.py @@ -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) @@ -156,4 +157,4 @@ class BlobRequestHandler(object): if reason is not None and isinstance(reason, Failure): log.info("Upload has failed. Reason: %s", reason.getErrorMessage()) - return _send_file() \ No newline at end of file + return _send_file() diff --git a/lbrynet/core/utils.py b/lbrynet/core/utils.py index 89b57fb0c..34ea99ba3 100644 --- a/lbrynet/core/utils.py +++ b/lbrynet/core/utils.py @@ -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 @@ -25,4 +28,12 @@ def is_valid_blobhash(blobhash): for l in blobhash: if l not in "0123456789abcdef": return False - return True \ No newline at end of file + 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) diff --git a/lbrynet/cryptstream/client/CryptStreamDownloader.py b/lbrynet/cryptstream/client/CryptStreamDownloader.py index 19a565f1f..e0091598f 100644 --- a/lbrynet/cryptstream/client/CryptStreamDownloader.py +++ b/lbrynet/cryptstream/client/CryptStreamDownloader.py @@ -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): diff --git a/lbrynet/dht/encoding.py b/lbrynet/dht/encoding.py index a57747628..e0b42ee4f 100644 --- a/lbrynet/dht/encoding.py +++ b/lbrynet/dht/encoding.py @@ -99,7 +99,10 @@ class Bencode(Encoding): """ if len(data) == 0: raise DecodeError, 'Cannot decode empty string' - return self._decodeRecursive(data)[0] + try: + return self._decodeRecursive(data)[0] + except ValueError as e: + raise DecodeError, e.message @staticmethod def _decodeRecursive(data, startIndex=0): diff --git a/lbrynet/dht/node.py b/lbrynet/dht/node.py index 1fae2e889..ae9000582 100644 --- a/lbrynet/dht/node.py +++ b/lbrynet/dht/node.py @@ -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: diff --git a/lbrynet/dht/protocol.py b/lbrynet/dht/protocol.py index 1a9f2c262..a7d53bdcb 100644 --- a/lbrynet/dht/protocol.py +++ b/lbrynet/dht/protocol.py @@ -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 """ diff --git a/lbrynet/lbryfile/client/LBRYFileDownloader.py b/lbrynet/lbryfile/client/LBRYFileDownloader.py index a65f49484..16ee9425d 100644 --- a/lbrynet/lbryfile/client/LBRYFileDownloader.py +++ b/lbrynet/lbryfile/client/LBRYFileDownloader.py @@ -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) diff --git a/lbrynet/lbryfilemanager/LBRYFileCreator.py b/lbrynet/lbryfilemanager/LBRYFileCreator.py index 1b0fcb5b0..770d1ba6b 100644 --- a/lbrynet/lbryfilemanager/LBRYFileCreator.py +++ b/lbrynet/lbryfilemanager/LBRYFileCreator.py @@ -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) diff --git a/lbrynet/lbryfilemanager/LBRYFileDownloader.py b/lbrynet/lbryfilemanager/LBRYFileDownloader.py index fed0824e5..b01a84708 100644 --- a/lbrynet/lbryfilemanager/LBRYFileDownloader.py +++ b/lbrynet/lbryfilemanager/LBRYFileDownloader.py @@ -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 diff --git a/lbrynet/lbryfilemanager/LBRYFileManager.py b/lbrynet/lbryfilemanager/LBRYFileManager.py index ed4c75a79..d805fb38d 100644 --- a/lbrynet/lbryfilemanager/LBRYFileManager.py +++ b/lbrynet/lbryfilemanager/LBRYFileManager.py @@ -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): diff --git a/lbrynet/lbrylive/LBRYStdinUploader.py b/lbrynet/lbrylive/LBRYStdinUploader.py index 03570a975..086cb52a0 100644 --- a/lbrynet/lbrylive/LBRYStdinUploader.py +++ b/lbrynet/lbrylive/LBRYStdinUploader.py @@ -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) @@ -114,4 +118,4 @@ def launch_stdin_uploader(): d.addCallback(lambda _: start_stdin_uploader()) d.addCallback(lambda _: shut_down()) reactor.addSystemEventTrigger('before', 'shutdown', uploader.shut_down) - reactor.run() \ No newline at end of file + reactor.run() diff --git a/lbrynet/lbrylive/LBRYStdoutDownloader.py b/lbrynet/lbrylive/LBRYStdoutDownloader.py index 0c1fe8c60..317cfefa2 100644 --- a/lbrynet/lbrylive/LBRYStdoutDownloader.py +++ b/lbrynet/lbrylive/LBRYStdoutDownloader.py @@ -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() @@ -93,4 +98,4 @@ def launch_stdout_downloader(): d.addErrback(print_error) d.addCallback(lambda _: shut_down()) reactor.addSystemEventTrigger('before', 'shutdown', downloader.shut_down) - reactor.run() \ No newline at end of file + reactor.run() diff --git a/lbrynet/lbrynet_console/ConsoleControl.py b/lbrynet/lbrynet_console/ConsoleControl.py index 8b6552e6e..be52da843 100644 --- a/lbrynet/lbrynet_console/ConsoleControl.py +++ b/lbrynet/lbrynet_console/ConsoleControl.py @@ -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.") diff --git a/lbrynet/lbrynet_console/ControlHandlers.py b/lbrynet/lbrynet_console/ControlHandlers.py index 926b96f39..d19d55c88 100644 --- a/lbrynet/lbrynet_console/ControlHandlers.py +++ b/lbrynet/lbrynet_console/ControlHandlers.py @@ -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) diff --git a/lbrynet/lbrynet_console/LBRYConsole.py b/lbrynet/lbrynet_console/LBRYConsole.py index 0005e30a7..72e40d467 100644 --- a/lbrynet/lbrynet_console/LBRYConsole.py +++ b/lbrynet/lbrynet_console/LBRYConsole.py @@ -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,36 +533,47 @@ def launch_lbry_console(): created_data_dir = False if not args.data_dir: - data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") + 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 if not os.path.exists(data_dir): 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" - log_format = "(%(asctime)s)[%(filename)s:%(lineno)s] %(funcName)s(): %(message)s" - formatter = logging.Formatter(log_format) + webbrowser.open("http://localhost:5279") - logger = logging.getLogger() - logger.setLevel(logging.DEBUG) - file_handler = logging.FileHandler(os.path.join(data_dir, "console.log")) - file_handler.setFormatter(formatter) - file_handler.addFilter(logging.Filter("lbrynet")) - logger.addHandler(file_handler) + except: + log_format = "(%(asctime)s)[%(filename)s:%(lineno)s] %(funcName)s(): %(message)s" + formatter = logging.Formatter(log_format) - console = LBRYConsole(peer_port, dht_node_port, bootstrap_nodes, wallet_type=args.wallet_type, - 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) + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + file_handler = logging.FileHandler(os.path.join(data_dir, "console.log")) + file_handler.setFormatter(formatter) + file_handler.addFilter(logging.Filter("lbrynet")) + logger.addHandler(file_handler) - d = task.deferLater(reactor, 0, console.start) - d.addErrback(lambda _: reactor.stop()) + 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) - reactor.addSystemEventTrigger('before', 'shutdown', console.shut_down) - reactor.run() + d = task.deferLater(reactor, 0, console.start) + d.addErrback(lambda _: reactor.stop()) + reactor.addSystemEventTrigger('before', 'shutdown', console.shut_down) + reactor.run() if __name__ == "__main__": - launch_lbry_console() \ No newline at end of file + launch_lbry_console() diff --git a/lbrynet/lbrynet_console/plugins/BlindRepeater/ValuableBlobQueryHandler.py b/lbrynet/lbrynet_console/plugins/BlindRepeater/ValuableBlobQueryHandler.py index 08d5a57a0..d8dd0009e 100644 --- a/lbrynet/lbrynet_console/plugins/BlindRepeater/ValuableBlobQueryHandler.py +++ b/lbrynet/lbrynet_console/plugins/BlindRepeater/ValuableBlobQueryHandler.py @@ -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,10 +193,10 @@ 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)) return {'blob_length': {'blob_lengths': lengths}} - dl.addCallback(make_response) \ No newline at end of file + dl.addCallback(make_response) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 3c4d81ad9..ea9124d57 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1,120 +1,510 @@ +import binascii +import distutils.version +import locale +import logging.handlers +import mimetypes +import os +import platform +import random +import re +import socket +import string +import subprocess +import sys +import base58 +import requests +import simplejson as json +import pkg_resources + +from urllib2 import urlopen +from appdirs import user_data_dir +from datetime import datetime +from decimal import Decimal +from twisted.web import server +from twisted.internet import defer, threads, error, reactor +from twisted.internet.task import LoopingCall +from txjsonrpc import jsonrpclib +from txjsonrpc.web import jsonrpc +from txjsonrpc.web.jsonrpc import Handler, Proxy + +from lbrynet import __version__ as lbrynet_version +from lbryum.version import LBRYUM_VERSION as lbryum_version from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory from lbrynet.core.server.ServerProtocol import ServerProtocolFactory -from lbrynet.lbrynet_console.ControlHandlers import get_time_behind_blockchain -from lbrynet.core.Error import UnknownNameError +from lbrynet.core.Error import UnknownNameError, InsufficientFundsError, InvalidNameError 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 -from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream, FetcherDaemon +from lbrynet.lbrynet_daemon.LBRYUIManager import LBRYUIManager +from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher +from lbrynet.lbrynet_daemon.LBRYExchangeRateManager import ExchangeRateManager +from lbrynet.core import utils +from lbrynet.core.LBRYMetadata import verify_name_characters from lbrynet.core.utils import generate_id from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings -from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE +from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, DEFAULT_MAX_SEARCH_RESULTS, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ + DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, LOG_POST_URL, LOG_FILE_NAME, SOURCE_TYPES +from lbrynet.conf import SEARCH_SERVERS +from lbrynet.conf import DEFAULT_TIMEOUT, WALLET_TYPES from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession from lbrynet.core.PTCWallet import PTCWallet -from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet +from lbrynet.core.LBRYWallet import LBRYcrdWallet, LBRYumWallet from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager -from twisted.web import xmlrpc, server -from twisted.internet import defer, threads, reactor, error -from datetime import datetime -import logging -import os -import sys -import json -import binascii -import webbrowser -from decimal import Decimal +# from lbryum import LOG_PATH as lbryum_log + + +# TODO: this code snippet is everywhere. Make it go away +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__) -#TODO add login credentials in a conf file +if os.path.isfile(lbrynet_log): + with open(lbrynet_log, 'r') as f: + PREVIOUS_LBRYNET_LOG = len(f.read()) +else: + PREVIOUS_LBRYNET_LOG = 0 -#issues with delete: -#TODO when stream is stopped the generated file is deleted +INITIALIZING_CODE = 'initializing' +LOADING_DB_CODE = 'loading_db' +LOADING_WALLET_CODE = 'loading_wallet' +LOADING_FILE_MANAGER_CODE = 'loading_file_manager' +LOADING_SERVER_CODE = 'loading_server' +STARTED_CODE = 'started' +WAITING_FOR_FIRST_RUN_CREDITS = 'waiting_for_credits' +STARTUP_STAGES = [ + (INITIALIZING_CODE, 'Initializing...'), + (LOADING_DB_CODE, 'Loading databases...'), + (LOADING_WALLET_CODE, 'Catching up with the blockchain... %s'), + (LOADING_FILE_MANAGER_CODE, 'Setting up file manager'), + (LOADING_SERVER_CODE, 'Starting lbrynet'), + (STARTED_CODE, 'Started lbrynet'), + (WAITING_FOR_FIRST_RUN_CREDITS, 'Waiting for first run credits...') + ] -#functions to add: -#TODO send credits to address -#TODO alert if your copy of a lbry file is out of date with the name record +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 %s, got %s/%s blobs, stream status: %s'), + (DOWNLOAD_STOPPED_CODE, 'Paused stream'), + (DOWNLOAD_TIMEOUT_CODE, 'Stream timed out') + ] + +CONNECT_CODE_VERSION_CHECK = 'version_check' +CONNECT_CODE_NETWORK = 'network_connection' +CONNECT_CODE_WALLET = 'wallet_catchup_lag' +CONNECTION_PROBLEM_CODES = [ + (CONNECT_CODE_VERSION_CHECK, "There was a problem checking for updates on github"), + (CONNECT_CODE_NETWORK, "Your internet connection appears to have been interrupted"), + (CONNECT_CODE_WALLET, "Synchronization with the blockchain is lagging... if this continues try restarting LBRY") + ] + +ALLOWED_DURING_STARTUP = ['is_running', 'is_first_run', + 'get_time_behind_blockchain', 'stop', + 'daemon_status', 'get_start_notice', + 'version'] + +BAD_REQUEST = 400 +NOT_FOUND = 404 +OK_CODE = 200 + +# TODO add login credentials in a conf file +# TODO alert if your copy of a lbry file is out of date with the name record -class LBRYDaemon(xmlrpc.XMLRPC): +REMOTE_SERVER = "www.google.com" + + +class LBRYDaemon(jsonrpc.JSONRPC): """ - LBRYnet daemon + LBRYnet daemon, a jsonrpc interface to lbry functions """ - def setup(self): - def _set_vars(): - self.fetcher = None - self.current_db_revision = 1 - self.run_server = True - self.session = None - self.known_dht_nodes = [('104.236.42.182', 4000)] + isLeaf = True + + def __init__(self, root, wallet_type=None): + jsonrpc.JSONRPC.__init__(self) + reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) + + self.startup_status = STARTUP_STAGES[0] + self.startup_message = None + self.announced_startup = False + self.connected_to_internet = True + self.connection_problem = None + self.query_handlers = {} + self.git_lbrynet_version = None + self.git_lbryum_version = None + self.ui_version = None + self.ip = None + # TODO: this is confusing to set here, and then to be reset below. + self.wallet_type = wallet_type + self.first_run = None + self.log_file = lbrynet_log + self.current_db_revision = 1 + self.run_server = True + self.session = None + self.exchange_rate_manager = ExchangeRateManager() + self.waiting_on = {} + self.streams = {} + self.pending_claims = {} + self.known_dht_nodes = KNOWN_DHT_NODES + self.first_run_after_update = False + self.uploaded_temp_files = [] + + if os.name == "nt": + from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle + default_download_directory = get_path(FOLDERID.Downloads, UserHandle.current) + self.db_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrynet") + elif sys.platform == "darwin": + default_download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') + self.db_dir = user_data_dir("LBRY") + else: + default_download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") - self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") - self.peer_port = 3333 - self.dht_node_port = 4444 - self.first_run = False - if os.name == "nt": - from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle - self.download_directory = get_path(FOLDERID.Downloads, UserHandle.current) + try: + if not os.path.isdir(default_download_directory): + os.mkdir(default_download_directory) + except: + log.info("Couldn't make download directory, using home") + default_download_directory = os.path.expanduser("~") + + self.daemon_conf = os.path.join(self.db_dir, 'daemon_settings.json') + + self.default_settings = { + 'run_on_startup': False, + 'data_rate': MIN_BLOB_DATA_PAYMENT_RATE, + 'max_key_fee': DEFAULT_MAX_KEY_FEE, + 'download_directory': default_download_directory, + 'max_upload': 0.0, + 'max_download': 0.0, + 'upload_log': True, + 'search_timeout': DEFAULT_SEARCH_TIMEOUT, + 'download_timeout': DEFAULT_TIMEOUT, + 'max_search_results': DEFAULT_MAX_SEARCH_RESULTS, + 'wallet_type': DEFAULT_WALLET, + 'delete_blobs_on_remove': True, + 'peer_port': 3333, + 'dht_node_port': 4444, + 'use_upnp': True, + 'start_lbrycrdd': True, + 'requested_first_run_credits': False, + 'cache_time': DEFAULT_CACHE_TIME, + 'startup_scripts': [], + 'last_version': {'lbrynet': lbrynet_version, 'lbryum': lbryum_version} + } + + if os.path.isfile(self.daemon_conf): + f = open(self.daemon_conf, "r") + loaded_settings = json.loads(f.read()) + f.close() + missing_settings = {} + removed_settings = {} + for k in self.default_settings.keys(): + if k not in loaded_settings.keys(): + missing_settings[k] = self.default_settings[k] + for k in loaded_settings.keys(): + if not k in self.default_settings.keys(): + log.info("Removing unused setting: " + k + " with value: " + str(loaded_settings[k])) + removed_settings[k] = loaded_settings[k] + del loaded_settings[k] + for k in missing_settings.keys(): + log.info("Adding missing setting: " + k + " with default value: " + str(missing_settings[k])) + loaded_settings[k] = missing_settings[k] + if loaded_settings['wallet_type'] != self.wallet_type and self.wallet_type: + loaded_settings['wallet_type'] = self.wallet_type + + if missing_settings or removed_settings: + log.info("Updated and loaded lbrynet-daemon configuration") + else: + log.info("Loaded lbrynet-daemon configuration") + self.session_settings = loaded_settings + else: + missing_settings = self.default_settings + log.info("Writing default settings : " + json.dumps(self.default_settings) + " --> " + str(self.daemon_conf)) + self.session_settings = self.default_settings + + if 'last_version' in missing_settings.keys(): + self.session_settings['last_version'] = None + + if self.session_settings['last_version'] != self.default_settings['last_version']: + self.session_settings['last_version'] = self.default_settings['last_version'] + self.first_run_after_update = True + log.info("First run after update") + log.info("lbrynet %s --> %s" % (self.session_settings['last_version']['lbrynet'], self.default_settings['last_version']['lbrynet'])) + log.info("lbryum %s --> %s" % (self.session_settings['last_version']['lbryum'], self.default_settings['last_version']['lbryum'])) + + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.session_settings)) + f.close() + + self.run_on_startup = self.session_settings['run_on_startup'] + self.data_rate = self.session_settings['data_rate'] + self.max_key_fee = self.session_settings['max_key_fee'] + self.download_directory = self.session_settings['download_directory'] + self.max_upload = self.session_settings['max_upload'] + self.max_download = self.session_settings['max_download'] + self.upload_log = self.session_settings['upload_log'] + self.search_timeout = self.session_settings['search_timeout'] + self.download_timeout = self.session_settings['download_timeout'] + self.max_search_results = self.session_settings['max_search_results'] + #### + # + # Ignore the saved wallet type. Some users will have their wallet type + # saved as lbrycrd and we want wallets to be lbryum unless explicitly + # set on the command line to be lbrycrd. + # + # if self.session_settings['wallet_type'] in WALLET_TYPES and not wallet_type: + # self.wallet_type = self.session_settings['wallet_type'] + # log.info("Using wallet type %s from config" % self.wallet_type) + # else: + # self.wallet_type = wallet_type + # self.session_settings['wallet_type'] = wallet_type + # log.info("Using wallet type %s specified from command line" % self.wallet_type) + # + # Instead, if wallet is not set on the command line, default to the default wallet + # + if wallet_type: + log.info("Using wallet type %s specified from command line", wallet_type) + self.wallet_type = wallet_type + else: + log.info("Using the default wallet type %s", DEFAULT_WALLET) + self.wallet_type = DEFAULT_WALLET + # + #### + self.delete_blobs_on_remove = self.session_settings['delete_blobs_on_remove'] + self.peer_port = self.session_settings['peer_port'] + self.dht_node_port = self.session_settings['dht_node_port'] + self.use_upnp = self.session_settings['use_upnp'] + self.start_lbrycrdd = self.session_settings['start_lbrycrdd'] + self.requested_first_run_credits = self.session_settings['requested_first_run_credits'] + self.cache_time = self.session_settings['cache_time'] + self.startup_scripts = self.session_settings['startup_scripts'] + + if os.path.isfile(os.path.join(self.db_dir, "stream_info_cache.json")): + f = open(os.path.join(self.db_dir, "stream_info_cache.json"), "r") + self.name_cache = json.loads(f.read()) + f.close() + log.info("Loaded claim info cache") + else: + self.name_cache = {} + + if os.name == "nt": + from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle + self.lbrycrdd_path = "lbrycrdd.exe" + if self.wallet_type == "lbrycrd": self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrycrd") - elif 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.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") - self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') - - self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") - self.wallet_user = None - self.wallet_password = None - self.sd_identifier = StreamDescriptorIdentifier() - self.stream_info_manager = TempLBRYFileMetadataManager() - self.wallet_rpc_port = 8332 - self.downloads = [] - 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.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbryum") + elif sys.platform == "darwin": + # use the path from the bundle if its available. + try: + import Foundation + bundle = Foundation.NSBundle.mainBundle() + self.lbrycrdd_path = bundle.pathForResource_ofType_('lbrycrdd', None) + except Exception: + log.exception('Failed to get path from bundle, falling back to default') self.lbrycrdd_path = "./lbrycrdd" - self.delete_blobs_on_remove = True - self.blob_request_payment_rate_manager = None - self.lbry_file_metadata_manager = None - self.lbry_file_manager = None - self.settings = LBRYSettings(self.db_dir) - self.wallet_type = "lbrycrd" - self.lbrycrd_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") - self.autofetcher_conf = os.path.join(self.wallet_dir, "autofetcher.conf") - self.files = [] - self.created_data_dir = False - if not os.path.exists(self.db_dir): - os.mkdir(self.db_dir) - self.created_data_dir = True - self.session_settings = None - self.data_rate = 0.5 - self.max_key_fee = 100.0 - self.query_handlers = {} + if self.wallet_type == "lbrycrd": + self.wallet_dir = user_data_dir("lbrycrd") + else: + self.wallet_dir = user_data_dir("LBRY") + else: + self.lbrycrdd_path = "lbrycrdd" + if self.wallet_type == "lbrycrd": + self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") + else: + self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbryum") + if os.name != 'nt': + lbrycrdd_path_conf = os.path.join(os.path.expanduser("~"), ".lbrycrddpath.conf") + if not os.path.isfile(lbrycrdd_path_conf): + f = open(lbrycrdd_path_conf, "w") + f.write(str(self.lbrycrdd_path)) + f.close() + + self.created_data_dir = False + if not os.path.exists(self.db_dir): + os.mkdir(self.db_dir) + self.created_data_dir = True + + self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") + self.lbrycrd_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") + self.autofetcher_conf = os.path.join(self.wallet_dir, "autofetcher.conf") + self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") + self.wallet_user = None + self.wallet_password = None + + self.internet_connection_checker = LoopingCall(self._check_network_connection) + self.version_checker = LoopingCall(self._check_remote_versions) + self.connection_problem_checker = LoopingCall(self._check_connection_problems) + self.pending_claim_checker = LoopingCall(self._check_pending_claims) + # self.lbrynet_connection_checker = LoopingCall(self._check_lbrynet_connection) + + self.sd_identifier = StreamDescriptorIdentifier() + self.stream_info_manager = TempLBRYFileMetadataManager() + self.settings = LBRYSettings(self.db_dir) + self.lbry_ui_manager = LBRYUIManager(root) + self.blob_request_payment_rate_manager = None + self.lbry_file_metadata_manager = None + self.lbry_file_manager = None + + if self.wallet_type == "lbrycrd": + if os.path.isfile(self.lbrycrd_conf): + log.info("Using lbrycrd.conf found at " + self.lbrycrd_conf) + else: + log.info("No lbrycrd.conf found at " + self.lbrycrd_conf + ". Generating now...") + password = "".join(random.SystemRandom().choice(string.ascii_letters + string.digits + "_") for i in range(20)) + with open(self.lbrycrd_conf, 'w') as f: + f.write("rpcuser=rpcuser\n") + f.write("rpcpassword=" + password) + log.info("Done writing lbrycrd.conf") + + def render(self, request): + request.content.seek(0, 0) + # Unmarshal the JSON-RPC data. + content = request.content.read() + parsed = jsonrpclib.loads(content) + functionPath = parsed.get("method") + args = parsed.get('params') + + #TODO convert args to correct types if possible + + id = parsed.get('id') + version = parsed.get('jsonrpc') + if version: + version = int(float(version)) + elif id and not version: + version = jsonrpclib.VERSION_1 + else: + version = jsonrpclib.VERSION_PRE1 + # XXX this all needs to be re-worked to support logic for multiple + # versions... + + if not self.announced_startup: + if functionPath not in ALLOWED_DURING_STARTUP: + return server.failure + + if self.wallet_type == "lbryum" and functionPath in ['set_miner', 'get_miner_status']: + return server.failure + + try: + function = self._getFunction(functionPath) + except jsonrpclib.Fault, f: + self._cbRender(f, request, id, version) + else: + request.setHeader("Access-Control-Allow-Origin", "*") + request.setHeader("content-type", "text/json") + if args == [{}]: + d = defer.maybeDeferred(function) + else: + d = defer.maybeDeferred(function, *args) + d.addErrback(self._ebRender, id) + d.addCallback(self._cbRender, request, id, version) + return server.NOT_DONE_YET + + def _cbRender(self, result, request, id, version): + def default_decimal(obj): + if isinstance(obj, Decimal): + return float(obj) + + if isinstance(result, Handler): + result = result.result + + if isinstance(result, dict): + result = result['result'] + + if version == jsonrpclib.VERSION_PRE1: + if not isinstance(result, jsonrpclib.Fault): + result = (result,) + # Convert the result (python) to JSON-RPC + try: + s = jsonrpclib.dumps(result, version=version, default=default_decimal) + except: + f = jsonrpclib.Fault(self.FAILURE, "can't serialize output") + s = jsonrpclib.dumps(f, version=version) + request.setHeader("content-length", str(len(s))) + request.write(s) + request.finish() + + def _ebRender(self, failure, id): + if isinstance(failure.value, jsonrpclib.Fault): + return failure.value + log.error(failure) + return jsonrpclib.Fault(self.FAILURE, "error") + + def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False, host_ui=True): + def _log_starting_vals(): + log.info("Starting balance: " + str(self.session.wallet.wallet_balance)) return defer.succeed(None) - def _disp_startup(): - print "Started LBRYnet daemon" - print "The daemon can be shut down by running 'stop-lbrynet-daemon' in a terminal" - return defer.succeed(None) + def _announce_startup(): + def _wait_for_credits(): + if float(self.session.wallet.wallet_balance) == 0.0: + self.startup_status = STARTUP_STAGES[6] + return reactor.callLater(1, _wait_for_credits) + else: + return _announce() + + def _announce(): + self.announced_startup = True + self.startup_status = STARTUP_STAGES[5] + log.info("Started lbrynet-daemon") + if len(self.startup_scripts): + log.info("Scheduling scripts") + reactor.callLater(3, self._run_scripts) + + # self.lbrynet_connection_checker.start(3600) + + if self.first_run: + d = self._upload_log(log_type="first_run") + elif self.upload_log: + d = self._upload_log(exclude_previous=True, log_type="start") + else: + d = defer.succeed(None) + + # if float(self.session.wallet.wallet_balance) == 0.0: + # d.addCallback(lambda _: self._check_first_run()) + # d.addCallback(self._show_first_run_result) + + # d.addCallback(lambda _: _wait_for_credits() if self.requested_first_run_credits else _announce()) + d.addCallback(lambda _: _announce()) + return d + + log.info("Starting lbrynet-daemon") + + self.internet_connection_checker.start(3600) + self.version_checker.start(3600 * 12) + self.connection_problem_checker.start(1) + self.exchange_rate_manager.start() + + if host_ui: + self.lbry_ui_manager.update_checker.start(1800, now=False) d = defer.Deferred() - d.addCallback(lambda _: _set_vars()) + if host_ui: + d.addCallback(lambda _: self.lbry_ui_manager.setup(branch=branch, + user_specified=user_specified, + branch_specified=branch_specified)) + d.addCallback(lambda _: self._initial_setup()) d.addCallback(lambda _: threads.deferToThread(self._setup_data_directory)) d.addCallback(lambda _: self._check_db_migration()) d.addCallback(lambda _: self._get_settings()) - d.addCallback(lambda _: self._get_lbrycrdd_path()) d.addCallback(lambda _: self._get_session()) d.addCallback(lambda _: add_lbry_file_to_sd_identifier(self.sd_identifier)) d.addCallback(lambda _: self._setup_stream_identifier()) @@ -122,14 +512,152 @@ class LBRYDaemon(xmlrpc.XMLRPC): d.addCallback(lambda _: self._setup_lbry_file_opener()) d.addCallback(lambda _: self._setup_query_handlers()) d.addCallback(lambda _: self._setup_server()) - d.addCallback(lambda _: self._setup_fetcher()) - d.addCallback(lambda _: _disp_startup()) + d.addCallback(lambda _: _log_starting_vals()) + d.addCallback(lambda _: _announce_startup()) d.callback(None) return defer.succeed(None) - def _start_server(self): + def _get_platform(self): + r = { + "processor": platform.processor(), + "python_version: ": platform.python_version(), + "platform": platform.platform(), + "os_release": platform.release(), + "os_system": platform.system(), + "lbrynet_version: ": lbrynet_version, + "lbryum_version: ": lbryum_version, + "ui_version": self.lbry_ui_manager.loaded_git_version, + } + if not self.ip: + try: + r['ip'] = json.load(urlopen('http://jsonip.com'))['ip'] + self.ip = r['ip'] + except: + r['ip'] = "Could not determine" + return r + + def _initial_setup(self): + def _log_platform(): + log.info("Platform: " + json.dumps(self._get_platform())) + return defer.succeed(None) + + d = _log_platform() + + return d + + def _check_network_connection(self): + try: + host = socket.gethostbyname(REMOTE_SERVER) + s = socket.create_connection((host, 80), 2) + self.connected_to_internet = True + except: + log.info("Internet connection not working") + self.connected_to_internet = False + + def _check_lbrynet_connection(self): + def _log_success(): + log.info("lbrynet connectivity test passed") + def _log_failure(): + log.info("lbrynet connectivity test failed") + + wonderfullife_sh = "6f3af0fa3924be98a54766aa2715d22c6c1509c3f7fa32566df4899a41f3530a9f97b2ecb817fa1dcbf1b30553aefaa7" + d = download_sd_blob(self.session, wonderfullife_sh, self.session.base_payment_rate_manager) + d.addCallbacks(lambda _: _log_success, lambda _: _log_failure) + + def _check_remote_versions(self): + def _get_lbryum_version(): + try: + r = urlopen("https://raw.githubusercontent.com/lbryio/lbryum/master/lib/version.py").read().split('\n') + version = next(line.split("=")[1].split("#")[0].replace(" ", "") + for line in r if "LBRYUM_VERSION" in line) + version = version.replace("'", "") + log.info( + "remote lbryum %s > local lbryum %s = %s", + version, lbryum_version, + utils.version_is_greater_than(version, lbryum_version) + ) + self.git_lbryum_version = version + return defer.succeed(None) + except: + log.info("Failed to get lbryum version from git") + self.git_lbryum_version = None + return defer.fail(None) + + def _get_lbrynet_version(): + try: + version = get_lbrynet_version_from_github() + log.info( + "remote lbrynet %s > local lbrynet %s = %s", + version, lbrynet_version, + utils.version_is_greater_than(version, lbrynet_version) + ) + self.git_lbrynet_version = version + return defer.succeed(None) + except: + log.info("Failed to get lbrynet version from git") + self.git_lbrynet_version = None + return defer.fail(None) + + d = _get_lbrynet_version() + d.addCallback(lambda _: _get_lbryum_version()) + + def _check_connection_problems(self): + if not self.git_lbrynet_version or not self.git_lbryum_version: + self.connection_problem = CONNECTION_PROBLEM_CODES[0] + + elif self.startup_status[0] == 'loading_wallet': + if self.session.wallet.is_lagging: + self.connection_problem = CONNECTION_PROBLEM_CODES[2] + else: + self.connection_problem = None + + if not self.connected_to_internet: + self.connection_problem = CONNECTION_PROBLEM_CODES[1] + + def _add_to_pending_claims(self, name, txid): + log.info("Adding lbry://%s to pending claims, txid %s" % (name, txid)) + self.pending_claims[name] = txid + return txid + + def _check_pending_claims(self): + # TODO: this was blatantly copied from jsonrpc_start_lbry_file. Be DRY. + def _start_file(f): + d = self.lbry_file_manager.toggle_lbry_file_running(f) + return defer.succeed("Started LBRY file") + + def _get_and_start_file(name): + d = defer.succeed(self.pending_claims.pop(name)) + d.addCallback(lambda _: self._get_lbry_file("name", name, return_json=False)) + d.addCallback(lambda l: _start_file(l) if l.stopped else "LBRY file was already running") + + def re_add_to_pending_claims(name): + txid = self.pending_claims.pop(name) + self._add_to_pending_claims(name, txid) + + def _process_lbry_file(name, lbry_file): + # lbry_file is an instance of ManagedLBRYFileDownloader or None + # TODO: check for sd_hash in addition to txid + ready_to_start = ( + lbry_file and + self.pending_claims[name] == lbry_file.txid + ) + if ready_to_start: + _get_and_start_file(name) + else: + re_add_to_pending_claims(name) + + for name in self.pending_claims: + log.info("Checking if new claim for lbry://%s is confirmed" % name) + d = self._resolve_name(name, force_refresh=True) + d.addCallback(lambda _: self._get_lbry_file_by_uri(name)) + d.addCallbacks( + lambda lbry_file: _process_lbry_file(name, lbry_file), + lambda _: re_add_to_pending_claims(name) + ) + + def _start_server(self): if self.peer_port is not None: server_factory = ServerProtocolFactory(self.session.rate_limiter, @@ -144,29 +672,33 @@ class LBRYDaemon(xmlrpc.XMLRPC): 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: + try: + 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) + except AttributeError: return defer.succeed(True) def _setup_server(self): - def restore_running_status(running): if running is True: return self._start_server() return defer.succeed(True) + self.startup_status = STARTUP_STAGES[4] + dl = self.settings.get_server_running_status() dl.addCallback(restore_running_status) return dl def _setup_query_handlers(self): handlers = [ - #CryptBlobInfoQueryHandlerFactory(self.lbry_file_metadata_manager, self.session.wallet, + # CryptBlobInfoQueryHandlerFactory(self.lbry_file_metadata_manager, self.session.wallet, # self._server_payment_rate_manager), BlobAvailabilityHandlerFactory(self.session.blob_manager), - #BlobRequestHandlerFactory(self.session.blob_manager, self.session.wallet, + # BlobRequestHandlerFactory(self.session.blob_manager, self.session.wallet, # self._server_payment_rate_manager), self.session.wallet.get_wallet_info_query_handler_factory(), ] @@ -186,7 +718,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): return dl def _add_query_handlers(self, query_handlers): - def _set_query_handlers(statuses): from future_builtins import zip for handler, (success, status) in zip(query_handlers, statuses): @@ -200,24 +731,154 @@ class LBRYDaemon(xmlrpc.XMLRPC): dl.addCallback(_set_query_handlers) return dl + def _upload_log(self, log_type=None, exclude_previous=False, force=False): + if self.upload_log or force: + for lm, lp in [('lbrynet', lbrynet_log)]: #, ('lbryum', lbryum_log)]: + if os.path.isfile(lp): + if exclude_previous: + f = open(lp, "r") + f.seek(PREVIOUS_LBRYNET_LOG) # if lm == 'lbrynet' else PREVIOUS_LBRYUM_LOG) + log_contents = f.read() + f.close() + else: + f = open(lp, "r") + log_contents = f.read() + f.close() + params = { + 'date': datetime.utcnow().strftime('%Y%m%d-%H%M%S'), + 'hash': base58.b58encode(self.lbryid)[:20], + 'sys': platform.system(), + 'type': "%s-%s" % (lm, log_type) if log_type else lm, + 'log': log_contents + } + requests.post(LOG_POST_URL, params) + + return defer.succeed(None) + else: + return defer.succeed(None) + + def _clean_up_temp_files(self): + for path in self.uploaded_temp_files: + try: + os.remove(path) + except OSError: + pass + def _shutdown(self): - print 'Closing lbrynet session' - d = self._stop_server() + log.info("Closing lbrynet session") + log.info("Status at time of shutdown: " + self.startup_status[0]) + if self.internet_connection_checker.running: + self.internet_connection_checker.stop() + if self.version_checker.running: + self.version_checker.stop() + if self.connection_problem_checker.running: + self.connection_problem_checker.stop() + if self.lbry_ui_manager.update_checker.running: + self.lbry_ui_manager.update_checker.stop() + if self.pending_claim_checker.running: + self.pending_claim_checker.stop() + + self._clean_up_temp_files() + + d = self._upload_log(log_type="close", exclude_previous=False if self.first_run else True) + d.addCallback(lambda _: self._stop_server()) + d.addErrback(lambda err: True) + d.addCallback(lambda _: self.lbry_file_manager.stop()) + d.addErrback(lambda err: True) if self.session is not None: d.addCallback(lambda _: self.session.shut_down()) + d.addErrback(lambda err: True) return d - def _update_settings(self): + def _update_settings(self, settings): + for k in settings.keys(): + if k == 'run_on_startup': + if type(settings['run_on_startup']) is bool: + self.session_settings['run_on_startup'] = settings['run_on_startup'] + else: + return defer.fail() + elif k == 'data_rate': + if type(settings['data_rate']) is float: + self.session_settings['data_rate'] = settings['data_rate'] + elif type(settings['data_rate']) is int: + self.session_settings['data_rate'] = float(settings['data_rate']) + else: + return defer.fail() + elif k == 'max_key_fee': + if type(settings['max_key_fee']) is float: + self.session_settings['max_key_fee'] = settings['max_key_fee'] + elif type(settings['max_key_fee']) is int: + self.session_settings['max_key_fee'] = float(settings['max_key_fee']) + else: + return defer.fail() + elif k == 'download_directory': + if type(settings['download_directory']) is unicode: + if os.path.isdir(settings['download_directory']): + self.session_settings['download_directory'] = settings['download_directory'] + else: + pass + else: + return defer.fail() + elif k == 'max_upload': + if type(settings['max_upload']) is float: + self.session_settings['max_upload'] = settings['max_upload'] + elif type(settings['max_upload']) is int: + self.session_settings['max_upload'] = float(settings['max_upload']) + else: + return defer.fail() + elif k == 'max_download': + if type(settings['max_download']) is float: + self.session_settings['max_download'] = settings['max_download'] + if type(settings['max_download']) is int: + self.session_settings['max_download'] = float(settings['max_download']) + else: + return defer.fail() + elif k == 'upload_log': + if type(settings['upload_log']) is bool: + self.session_settings['upload_log'] = settings['upload_log'] + else: + return defer.fail() + elif k == 'download_timeout': + if type(settings['download_timeout']) is int: + self.session_settings['download_timeout'] = settings['download_timeout'] + elif type(settings['download_timeout']) is float: + self.session_settings['download_timeout'] = int(settings['download_timeout']) + else: + return defer.fail() + elif k == 'search_timeout': + if type(settings['search_timeout']) is float: + self.session_settings['search_timeout'] = settings['search_timeout'] + elif type(settings['search_timeout']) is int: + self.session_settings['search_timeout'] = float(settings['search_timeout']) + else: + return defer.fail() + elif k == 'cache_time': + if type(settings['cache_time']) is int: + self.session_settings['cache_time'] = settings['cache_time'] + elif type(settings['cache_time']) is float: + self.session_settings['cache_time'] = int(settings['cache_time']) + else: + return defer.fail() + self.run_on_startup = self.session_settings['run_on_startup'] self.data_rate = self.session_settings['data_rate'] self.max_key_fee = self.session_settings['max_key_fee'] + self.download_directory = self.session_settings['download_directory'] + self.max_upload = self.session_settings['max_upload'] + self.max_download = self.session_settings['max_download'] + self.upload_log = self.session_settings['upload_log'] + self.download_timeout = self.session_settings['download_timeout'] + self.search_timeout = self.session_settings['search_timeout'] + self.cache_time = self.session_settings['cache_time'] - def _setup_fetcher(self): - self.fetcher = FetcherDaemon(self.session, self.lbry_file_manager, self.lbry_file_metadata_manager, - self.session.wallet, self.sd_identifier, self.autofetcher_conf) - return defer.succeed(None) + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.session_settings)) + f.close() + + return defer.succeed(True) def _setup_data_directory(self): - print "Loading databases..." + self.startup_status = STARTUP_STAGES[1] + log.info("Loading databases...") if self.created_data_dir: db_revision = open(os.path.join(self.db_dir, "db_revision"), mode='w') db_revision.write(str(self.current_db_revision)) @@ -234,7 +895,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): old_revision = int(open(db_revision_file).read().strip()) if old_revision < self.current_db_revision: from lbrynet.db_migrator import dbmigrator - print "Upgrading your databases..." + log.info("Upgrading your databases...") d = threads.deferToThread(dbmigrator.migrate_db, self.db_dir, old_revision, self.current_db_revision) def print_success(old_dirs): @@ -245,7 +906,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): success_string += old_dir if i + 1 < len(old_dir): success_string += ", " - print success_string + log.info(success_string) d.addCallback(print_success) return d @@ -255,26 +916,31 @@ class LBRYDaemon(xmlrpc.XMLRPC): 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 _set_lbryid(self, lbryid): if lbryid is None: return self._make_lbryid() else: + log.info("LBRY ID: " + base58.b58encode(lbryid)) self.lbryid = lbryid def _make_lbryid(self): self.lbryid = generate_id() + log.info("Generated new LBRY ID: " + base58.b58encode(self.lbryid)) d = self.settings.save_lbryid(self.lbryid) return d def _setup_lbry_file_manager(self): + self.startup_status = STARTUP_STAGES[3] self.lbry_file_metadata_manager = DBLBRYFileMetadataManager(self.db_dir) d = self.lbry_file_metadata_manager.setup() def set_lbry_file_manager(): - self.lbry_file_manager = LBRYFileManager(self.session, self.lbry_file_metadata_manager, self.sd_identifier) + self.lbry_file_manager = LBRYFileManager(self.session, + self.lbry_file_metadata_manager, + self.sd_identifier, + download_directory=self.download_directory) return self.lbry_file_manager.setup() d.addCallback(lambda _: set_lbry_file_manager()) @@ -284,21 +950,26 @@ class LBRYDaemon(xmlrpc.XMLRPC): def _get_session(self): def get_default_data_rate(): d = self.settings.get_default_data_payment_rate() - d.addCallback(lambda rate: {"default_data_payment_rate": - rate if rate is not None else MIN_BLOB_DATA_PAYMENT_RATE}) + d.addCallback(lambda rate: {"default_data_payment_rate": rate if rate is not None else + MIN_BLOB_DATA_PAYMENT_RATE}) 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 + log.info("Using lbrycrd wallet") d = defer.succeed(LBRYcrdWallet(self.db_dir, wallet_dir=self.wallet_dir, wallet_conf=self.lbrycrd_conf, - lbrycrdd_path=lbrycrdd_path)) - else: + lbrycrdd_path=self.lbrycrdd_path)) + elif self.wallet_type == "lbryum": + log.info("Using lbryum wallet") + d = defer.succeed(LBRYumWallet(self.db_dir)) + elif self.wallet_type == "ptc": + log.info("Using PTC wallet") d = defer.succeed(PTCWallet(self.db_dir)) + else: + # TODO: should fail here. Can't switch to lbrycrd because the wallet_dir, conf and path won't be set + log.info("Requested unknown wallet '%s', using default lbryum", self.wallet_type) + d = defer.succeed(LBRYumWallet(self.db_dir)) + d.addCallback(lambda wallet: {"wallet": wallet}) return d @@ -317,32 +988,70 @@ class LBRYDaemon(xmlrpc.XMLRPC): blob_dir=self.blobfile_dir, dht_node_port=self.dht_node_port, known_dht_nodes=self.known_dht_nodes, peer_port=self.peer_port, use_upnp=self.use_upnp, wallet=results['wallet']) + self.startup_status = STARTUP_STAGES[2] dl = defer.DeferredList([d1, d2], fireOnOneErrback=True) dl.addCallback(combine_results) dl.addCallback(create_session) dl.addCallback(lambda _: self.session.setup()) + return dl - def _get_lbrycrdd_path(self): - 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() - print self.lbrycrdd_path - - d.addCallback(load_lbrycrdd_path) - return d + # def _check_first_run(self): + # def _set_first_run_false(): + # log.info("Not first run") + # self.first_run = False + # self.session_settings['requested_first_run_credits'] = True + # f = open(self.daemon_conf, "w") + # f.write(json.dumps(self.session_settings)) + # f.close() + # return 0.0 + # + # if self.wallet_type == 'lbryum': + # d = self.session.wallet.is_first_run() + # d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run or not self.requested_first_run_credits + # else _set_first_run_false()) + # else: + # d = defer.succeed(None) + # d.addCallback(lambda _: _set_first_run_false()) + # return d + # + # def _do_first_run(self): + # def send_request(url, data): + # log.info("Requesting first run credits") + # r = requests.post(url, json=data) + # if r.status_code == 200: + # self.requested_first_run_credits = True + # self.session_settings['requested_first_run_credits'] = True + # f = open(self.daemon_conf, "w") + # f.write(json.dumps(self.session_settings)) + # f.close() + # 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 + # + # self.first_run = True + # d = self.session.wallet.get_new_address() + # d.addCallback(request_credits) + # + # return d + # + # def _show_first_run_result(self, credits_received): + # if credits_received != 0.0: + # points_string = locale.format_string("%.2f LBC", (round(credits_received, 2),), grouping=True) + # self.startup_message = "Thank you for testing the alpha version of LBRY! You have been given %s for free because we love you. Please hang on for a few minutes for the next block to be mined. When you refresh this page and see your credits you're ready to go!." % points_string + # else: + # self.startup_message = None def _setup_stream_identifier(self): file_saver_factory = LBRYFileSaverFactory(self.session.peer_finder, self.session.rate_limiter, @@ -355,18 +1064,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, file_opener_factory) return defer.succeed(None) - def _setup_lbry_file_manager(self): - self.lbry_file_metadata_manager = DBLBRYFileMetadataManager(self.db_dir) - d = self.lbry_file_metadata_manager.setup() - - def set_lbry_file_manager(): - self.lbry_file_manager = LBRYFileManager(self.session, self.lbry_file_metadata_manager, self.sd_identifier) - return self.lbry_file_manager.setup() - - d.addCallback(lambda _: set_lbry_file_manager()) - - return d - def _setup_lbry_file_opener(self): downloader_factory = LBRYFileOpenerFactory(self.session.peer_finder, self.session.rate_limiter, @@ -375,75 +1072,150 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, downloader_factory) return defer.succeed(True) - def _download_name(self, name): - def _disp_file(file): - print '[' + str(datetime.now()) + ']' + ' Already downloaded: ' + str(file.stream_hash) - d = self._path_from_lbry_file(file) - return d + def _download_name(self, name, timeout=DEFAULT_TIMEOUT, download_directory=None, + file_name=None, stream_info=None, wait_for_write=True): + """ + Add a lbry file to the file manager, start the download, and return the new lbry file. + If it already exists in the file manager, return the existing lbry file + """ - def _get_stream(name): - def _disp(stream): - print '[' + str(datetime.now()) + ']' + ' Start stream: ' + stream['stream_hash'] - return stream + if not download_directory: + download_directory = self.download_directory + elif not os.path.isdir(download_directory): + download_directory = self.download_directory - d = self.session.wallet.get_stream_info_for_name(name) - stream = GetStream(self.sd_identifier, self.session, self.session.wallet, self.lbry_file_manager, - max_key_fee=self.max_key_fee, data_rate=self.data_rate) - d.addCallback(_disp) - d.addCallback(lambda stream_info: stream.start(stream_info)) - d.addCallback(lambda _: self._path_from_name(name)) + def _remove_from_wait(r): + del self.waiting_on[name] + return r - return d - - d = self._check_history(name) - d.addCallback(lambda lbry_file: _get_stream(name) if not lbry_file else _disp_file(lbry_file)) - d.addCallback(lambda _: self._check_history(name)) - d.addCallback(lambda lbry_file: self._path_from_lbry_file(lbry_file) if lbry_file else 'Not found') - d.addErrback(lambda err: str(err)) - - return d - - def _resolve_name(self, name): - d = defer.Deferred() - d.addCallback(lambda _: self.session.wallet.get_stream_info_for_name(name)) - d.addErrback(lambda _: defer.fail(UnknownNameError)) - - return d - - def _resolve_name_wc(self, name): - d = defer.Deferred() - d.addCallback(lambda _: self.session.wallet.get_stream_info_for_name(name)) - d.addErrback(lambda _: defer.fail(UnknownNameError)) - d.callback(None) - - return d - - def _check_history(self, name): - def _get_lbry_file(path): - f = open(path, 'r') - l = json.loads(f.read()) - f.close() - file_name = l['stream_name'].decode('hex') - lbry_file = [file for file in self.lbry_file_manager.lbry_files if file.stream_name == file_name][0] - return lbry_file - - def _check(info): - stream_hash = info['stream_hash'] - path = os.path.join(self.blobfile_dir, stream_hash) - if os.path.isfile(path): - print "[" + str(datetime.now()) + "] Search for lbry_file, returning: " + stream_hash - return defer.succeed(_get_lbry_file(path)) + def _setup_stream(stream_info): + if 'sources' in stream_info.keys(): + stream_hash = stream_info['sources']['lbry_sd_hash'] else: - print "[" + str(datetime.now()) + "] Search for lbry_file didn't return anything" - return defer.succeed(False) + stream_hash = stream_info['stream_hash'] - d = self._resolve_name(name) - d.addCallbacks(_check, lambda _: False) - d.callback(None) + d = self._get_lbry_file_by_sd_hash(stream_hash) + def _add_results(l): + if l: + if os.path.isfile(os.path.join(self.download_directory, l.file_name)): + return defer.succeed((stream_info, l)) + return defer.succeed((stream_info, None)) + d.addCallback(_add_results) + return d + + def _wait_on_lbry_file(f): + if os.path.isfile(os.path.join(self.download_directory, f.file_name)): + written_file = file(os.path.join(self.download_directory, f.file_name)) + written_file.seek(0, os.SEEK_END) + written_bytes = written_file.tell() + written_file.close() + else: + written_bytes = False + + if not written_bytes: + d = defer.succeed(None) + d.addCallback(lambda _: reactor.callLater(1, _wait_on_lbry_file, f)) + return d + else: + return defer.succeed(_disp_file(f)) + + def _disp_file(f): + file_path = os.path.join(self.download_directory, f.file_name) + log.info("Already downloaded: " + str(f.sd_hash) + " --> " + file_path) + return f + + def _get_stream(stream_info): + def _wait_for_write(): + try: + if os.path.isfile(os.path.join(self.download_directory, self.streams[name].downloader.file_name)): + written_file = file(os.path.join(self.download_directory, self.streams[name].downloader.file_name)) + written_file.seek(0, os.SEEK_END) + written_bytes = written_file.tell() + written_file.close() + else: + written_bytes = False + except: + written_bytes = False + + if not written_bytes: + d = defer.succeed(None) + d.addCallback(lambda _: reactor.callLater(1, _wait_for_write)) + return d + else: + return defer.succeed(None) + + self.streams[name] = GetStream(self.sd_identifier, self.session, self.session.wallet, + self.lbry_file_manager, self.exchange_rate_manager, + max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout, + download_directory=download_directory, file_name=file_name) + d = self.streams[name].start(stream_info, name) + if wait_for_write: + d.addCallback(lambda _: _wait_for_write()) + d.addCallback(lambda _: self.streams[name].downloader) + + return d + + if not stream_info: + self.waiting_on[name] = True + d = self._resolve_name(name) + else: + d = defer.succeed(stream_info) + d.addCallback(_setup_stream) + d.addCallback(lambda (stream_info, lbry_file): _get_stream(stream_info) if not lbry_file else _wait_on_lbry_file(lbry_file)) + if not stream_info: + d.addCallback(_remove_from_wait) + return d + + def _get_long_count_timestamp(self): + return int((datetime.utcnow() - (datetime(year=2012, month=12, day=21))).total_seconds()) + + def _update_claim_cache(self): + f = open(os.path.join(self.db_dir, "stream_info_cache.json"), "w") + f.write(json.dumps(self.name_cache)) + f.close() + return defer.succeed(True) + + def _resolve_name(self, name, force_refresh=False): + try: + verify_name_characters(name) + except AssertionError: + log.error("Bad name") + return defer.fail(InvalidNameError("Bad name")) + + def _cache_stream_info(stream_info): + def _add_txid(txid): + self.name_cache[name]['txid'] = txid + return defer.succeed(None) + + self.name_cache[name] = {'claim_metadata': stream_info, 'timestamp': self._get_long_count_timestamp()} + d = self.session.wallet.get_txid_for_name(name) + d.addCallback(_add_txid) + d.addCallback(lambda _: self._update_claim_cache()) + d.addCallback(lambda _: self.name_cache[name]['claim_metadata']) + + return d + + if not force_refresh: + if name in self.name_cache.keys(): + if (self._get_long_count_timestamp() - self.name_cache[name]['timestamp']) < self.cache_time: + log.info("Returning cached stream info for lbry://" + name) + d = defer.succeed(self.name_cache[name]['claim_metadata']) + else: + log.info("Refreshing stream info for lbry://" + name) + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) + else: + log.info("Resolving stream info for lbry://" + name) + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) + else: + log.info("Resolving stream info for lbry://" + name) + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) return d - def _delete_lbry_file(self, lbry_file): + def _delete_lbry_file(self, lbry_file, delete_file=True): d = self.lbry_file_manager.delete_lbry_file(lbry_file) def finish_deletion(lbry_file): @@ -456,436 +1228,1175 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = self.lbry_file_manager.get_count_for_stream_hash(s_h) # TODO: could possibly be a timing issue here d.addCallback(lambda c: self.stream_info_manager.delete_stream(s_h) if c == 0 else True) + if delete_file: + d.addCallback(lambda _: os.remove(os.path.join(self.download_directory, lbry_file.file_name)) if + os.path.isfile(os.path.join(self.download_directory, lbry_file.file_name)) else defer.succeed(None)) return d d.addCallback(lambda _: finish_deletion(lbry_file)) + d.addCallback(lambda _: log.info("Delete lbry file")) return d - def _path_from_name(self, name): - d = self._check_history(name) - d.addCallback(lambda lbry_file: {'stream_hash': lbry_file.stream_hash, - 'path': os.path.join(self.download_directory, lbry_file.file_name)} - if lbry_file else defer.fail(UnknownNameError)) - return d - - def _path_from_lbry_file(self, lbry_file): - if lbry_file: - r = {'stream_hash': lbry_file.stream_hash, - 'path': os.path.join(self.download_directory, lbry_file.file_name)} - return defer.succeed(r) - else: - return defer.fail(UnknownNameError) - def _get_est_cost(self, name): def _check_est(d, name): - if type(d.result) is float: - print '[' + str(datetime.now()) + '] Cost est for lbry://' + name + ': ' + str(d.result) + 'LBC' + if isinstance(d.result, float): + log.info("Cost est for lbry://" + name + ": " + str(d.result) + "LBC") else: - print '[' + str(datetime.now()) + '] Timeout estimating cost for lbry://' + name + ', using key fee' + log.info("Timeout estimating cost for lbry://" + name + ", using key fee") d.cancel() return defer.succeed(None) - def _to_dict(r): - t = {} - for i in r: - t[i[0]] = i[1] - return t - def _add_key_fee(data_cost): - d = self.session.wallet.get_stream_info_for_name(name) - d.addCallback(lambda info: info['key_fee'] if 'key_fee' in info.keys() else 0.0) - d.addCallback(lambda key_fee: key_fee + data_cost) + d = self._resolve_name(name) + d.addCallback(lambda info: self.exchange_rate_manager.to_lbc(info.get('fee', None))) + d.addCallback(lambda fee: data_cost if fee is None else data_cost + fee.amount) return d - d = self.session.wallet.get_stream_info_for_name(name) - d.addCallback(lambda info: download_sd_blob(self.session, info['stream_hash'], + d = self._resolve_name(name) + d.addCallback(lambda info: info['sources']['lbry_sd_hash']) + d.addCallback(lambda sd_hash: download_sd_blob(self.session, sd_hash, self.blob_request_payment_rate_manager)) d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) d.addCallback(lambda metadata: metadata.validator.info_to_show()) - d.addCallback(_to_dict) - d.addCallback(lambda info: int(info['stream_size'])/1000000*self.data_rate) - d.addCallback(_add_key_fee) - d.addErrback(lambda _: _add_key_fee(0.0)) - reactor.callLater(3.0, _check_est, d, name) + d.addCallback(lambda info: int(dict(info)['stream_size']) / 1000000 * self.data_rate) + d.addCallbacks(_add_key_fee, lambda _: _add_key_fee(0.0)) + reactor.callLater(self.search_timeout, _check_est, d, name) return d - def xmlrpc_get_settings(self): - """ - Get LBRY payment settings + def _get_lbry_file_by_uri(self, name): + def _get_file(stream_info): + sd = stream_info['sources']['lbry_sd_hash'] - @return {'data_rate': float, 'max_key_fee': float} + for l in self.lbry_file_manager.lbry_files: + if l.sd_hash == sd: + return defer.succeed(l) + return defer.succeed(None) + + d = self._resolve_name(name) + d.addCallback(_get_file) + + return d + + def _get_lbry_file_by_sd_hash(self, sd_hash): + for l in self.lbry_file_manager.lbry_files: + if l.sd_hash == sd_hash: + return defer.succeed(l) + return defer.succeed(None) + + def _get_lbry_file_by_file_name(self, file_name): + for l in self.lbry_file_manager.lbry_files: + if l.file_name == file_name: + return defer.succeed(l) + return defer.succeed(None) + + def _get_lbry_file(self, search_by, val, return_json=True): + def _log_get_lbry_file(f): + if f and val: + log.info("Found LBRY file for " + search_by + ": " + val) + elif val: + log.info("Did not find LBRY file for " + search_by + ": " + val) + return f + + def _get_json_for_return(f): + def _get_file_status(file_status): + message = STREAM_STAGES[2][1] % (file_status.name, file_status.num_completed, file_status.num_known, file_status.running_status) + return defer.succeed(message) + + def _generate_reply(size): + if f.key: + key = binascii.b2a_hex(f.key) + else: + key = None + + if os.path.isfile(os.path.join(self.download_directory, f.file_name)): + written_file = file(os.path.join(self.download_directory, f.file_name)) + written_file.seek(0, os.SEEK_END) + written_bytes = written_file.tell() + written_file.close() + else: + written_bytes = False + + if search_by == "name": + if val in self.streams.keys(): + status = self.streams[val].code + elif f in self.lbry_file_manager.lbry_files: + # if f.stopped: + # status = STREAM_STAGES[3] + # else: + status = STREAM_STAGES[2] + else: + status = [False, False] + else: + status = [False, False] + + if status[0] == DOWNLOAD_RUNNING_CODE: + d = f.status() + d.addCallback(_get_file_status) + d.addCallback(lambda message: {'completed': f.completed, 'file_name': f.file_name, + 'download_directory': f.download_directory, + 'download_path': os.path.join(f.download_directory, f.file_name), + 'mime_type': mimetypes.guess_type(os.path.join(f.download_directory, f.file_name))[0], + 'key': key, + 'points_paid': f.points_paid, 'stopped': f.stopped, + 'stream_hash': f.stream_hash, + 'stream_name': f.stream_name, + 'suggested_file_name': f.suggested_file_name, + 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash, + 'lbry_uri': f.uri, 'txid': f.txid, + 'total_bytes': size, + 'written_bytes': written_bytes, 'code': status[0], + 'message': message}) + else: + d = defer.succeed({'completed': f.completed, 'file_name': f.file_name, 'key': key, + 'download_directory': f.download_directory, + 'download_path': os.path.join(f.download_directory, f.file_name), + 'mime_type': mimetypes.guess_type(os.path.join(f.download_directory, f.file_name))[0], + 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, + 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, + 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash, 'total_bytes': size, + 'written_bytes': written_bytes, 'lbry_uri': f.uri, 'txid': f.txid, + 'code': status[0], 'message': status[1]}) + + return d + + def _add_metadata(message): + def _add_to_dict(metadata): + message['metadata'] = metadata + return defer.succeed(message) + + if f.txid: + d = self._resolve_name(f.uri) + d.addCallbacks(_add_to_dict, lambda _: _add_to_dict("Pending confirmation")) + else: + d = defer.succeed(message) + return d + + if f: + d = f.get_total_bytes() + d.addCallback(_generate_reply) + d.addCallback(_add_metadata) + return d + else: + return False + + if search_by == "name": + d = self._get_lbry_file_by_uri(val) + elif search_by == "sd_hash": + d = self._get_lbry_file_by_sd_hash(val) + elif search_by == "file_name": + d = self._get_lbry_file_by_file_name(val) + d.addCallback(_log_get_lbry_file) + if return_json: + d.addCallback(_get_json_for_return) + return d + + def _get_lbry_files(self): + d = defer.DeferredList([self._get_lbry_file('sd_hash', l.sd_hash) for l in self.lbry_file_manager.lbry_files]) + return d + + def _log_to_slack(self, msg): + URL = "https://hooks.slack.com/services/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" + msg = platform.platform() + ": " + base58.b58encode(self.lbryid)[:20] + ", " + msg + requests.post(URL, json.dumps({"text": msg})) + return defer.succeed(None) + + def _run_scripts(self): + if len([k for k in self.startup_scripts if 'run_once' in k.keys()]): + log.info("Removing one time startup scripts") + remaining_scripts = [s for s in self.startup_scripts if 'run_once' not in s.keys()] + startup_scripts = self.startup_scripts + self.startup_scripts = self.session_settings['startup_scripts'] = remaining_scripts + + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.session_settings)) + f.close() + + for script in startup_scripts: + if script['script_name'] == 'migrateto025': + log.info("Running migrator to 0.2.5") + from lbrynet.lbrynet_daemon.daemon_scripts.migrateto025 import run as run_migrate + run_migrate(self) + + if script['script_name'] == 'Autofetcher': + log.info("Starting autofetcher script") + from lbrynet.lbrynet_daemon.daemon_scripts.Autofetcher import run as run_autofetcher + run_autofetcher(self) + + return defer.succeed(None) + + def _search(self, search): + proxy = Proxy(random.choice(SEARCH_SERVERS)) + return proxy.callRemote('search', search) + + def _render_response(self, result, code): + return defer.succeed({'result': result, 'code': code}) + + def jsonrpc_is_running(self): + """ + Check if lbrynet daemon is running + + Args: + None + Returns: true if daemon completed startup, otherwise false """ - if not self.session_settings: - self.session_settings = {'data_rate': self.data_rate, 'max_key_fee': self.max_key_fee} + log.info("is_running: " + str(self.announced_startup)) - print '[' + str(datetime.now()) + '] Get daemon settings' - return self.session_settings + if self.announced_startup: + return self._render_response(True, OK_CODE) + else: + return self._render_response(False, OK_CODE) - def xmlrpc_set_settings(self, settings): + def jsonrpc_daemon_status(self): """ - Set LBRY payment settings + Get lbrynet daemon status information - @param settings dict: {'data_rate': float, 'max_key_fee': float} + Args: + None + Returns: + 'message': startup status message + 'code': status_code + 'progress': progress, only used in loading_wallet + 'is_lagging': flag set to indicate lag, if set message will contain relevant message """ - self.session_settings = settings - self._update_settings() + r = {'code': self.startup_status[0], 'message': self.startup_status[1], + 'progress': None, 'is_lagging': None, 'problem_code': None} - print '[' + str(datetime.now()) + '] Set daemon settings' - return 'Set' + if self.connection_problem: + r['problem_code'] = self.connection_problem[0] + r['message'] = self.connection_problem[1] + r['is_lagging'] = True + elif self.startup_status[0] == LOADING_WALLET_CODE: + if self.wallet_type == 'lbryum': + if self.session.wallet.blocks_behind_alert != 0: + r['message'] = r['message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") + r['progress'] = self.session.wallet.catchup_progress + else: + r['message'] = "Catching up with the blockchain" + r['progress'] = 0 + else: + r['message'] = "Catching up with the blockchain" + r['progress'] = 0 + log.info("daemon status: " + str(r)) + return self._render_response(r, OK_CODE) - def xmlrpc_start_fetcher(self): + def jsonrpc_is_first_run(self): """ - Start autofetcher + Check if this is the first time lbrynet daemon has been run + + Args: + None + Returns: + True if first run, otherwise False """ - self.fetcher.start() - print '[' + str(datetime.now()) + '] Start autofetcher' - return str('Started autofetching') + log.info("Check if is first run") + try: + d = self.session.wallet.is_first_run() + except: + d = defer.fail(None) - def xmlrpc_stop_fetcher(self): + d.addCallbacks(lambda r: self._render_response(r, OK_CODE), lambda _: self._render_response(None, OK_CODE)) + + return d + + def jsonrpc_get_start_notice(self): """ - Stop autofetcher + Get special message to be displayed at startup + + Args: + None + Returns: + Startup message, such as first run notification """ - self.fetcher.stop() - print '[' + str(datetime.now()) + '] Stop autofetcher' - return str('Started autofetching') + log.info("Get startup notice") - def xmlrpc_fetcher_status(self): + if self.first_run and not self.session.wallet.wallet_balance: + return self._render_response(self.startup_message, OK_CODE) + elif self.first_run: + return self._render_response(None, OK_CODE) + else: + self._render_response(self.startup_message, OK_CODE) + + def jsonrpc_version(self): """ - Start autofetcher + Get lbry version information + + Args: + None + Returns: + "platform": platform string + "os_release": os release string + "os_system": os name + "lbrynet_version: ": lbrynet_version, + "lbryum_version: ": lbryum_version, + "ui_version": commit hash of ui version being used + "remote_lbrynet": most recent lbrynet version available from github + "remote_lbryum": most recent lbryum version available from github """ - print '[' + str(datetime.now()) + '] Get fetcher status' - return str(self.fetcher.check_if_running()) + platform_info = self._get_platform() + msg = { + 'platform': platform_info['platform'], + 'os_release': platform_info['os_release'], + 'os_system': platform_info['os_system'], + 'lbrynet_version': lbrynet_version, + 'lbryum_version': lbryum_version, + 'ui_version': self.ui_version, + 'remote_lbrynet': self.git_lbrynet_version, + 'remote_lbryum': self.git_lbryum_version, + 'lbrynet_update_available': utils.version_is_greater_than(self.git_lbrynet_version, lbrynet_version), + 'lbryum_update_available': utils.version_is_greater_than(self.git_lbryum_version, lbryum_version), + } - def xmlrpc_get_balance(self): + log.info("Get version info: " + json.dumps(msg)) + return self._render_response(msg, OK_CODE) + + def jsonrpc_get_settings(self): """ - Get LBC balance + Get lbrynet daemon settings + + Args: + None + Returns: + 'run_on_startup': bool, + 'data_rate': float, + 'max_key_fee': float, + 'download_directory': string, + 'max_upload': float, 0.0 for unlimited + 'max_download': float, 0.0 for unlimited + 'upload_log': bool, + 'search_timeout': float, + 'download_timeout': int + 'max_search_results': int, + 'wallet_type': string, + 'delete_blobs_on_remove': bool, + 'peer_port': int, + 'dht_node_port': int, + 'use_upnp': bool, + 'start_lbrycrdd': bool, """ - print '[' + str(datetime.now()) + '] Get balance' - return str(self.session.wallet.wallet_balance) + log.info("Get daemon settings") + return self._render_response(self.session_settings, OK_CODE) - def xmlrpc_stop(self): + def jsonrpc_set_settings(self, p): + """ + Set lbrynet daemon settings + + Args: + 'run_on_startup': bool, + 'data_rate': float, + 'max_key_fee': float, + 'download_directory': string, + 'max_upload': float, 0.0 for unlimited + 'max_download': float, 0.0 for unlimited + 'upload_log': bool, + 'download_timeout': int + Returns: + settings dict + """ + + def _log_settings_change(): + log.info("Set daemon settings to " + json.dumps(self.session_settings)) + + d = self._update_settings(p) + d.addErrback(lambda err: log.info(err.getTraceback())) + d.addCallback(lambda _: _log_settings_change()) + d.addCallback(lambda _: self._render_response(self.session_settings, OK_CODE)) + + return d + + def jsonrpc_help(self, p=None): + """ + Function to retrieve docstring for API function + + Args: + optional 'function': function to retrieve documentation for + optional 'callable_during_startup': + Returns: + if given a function, returns given documentation + if given callable_during_startup flag, returns list of functions callable during the startup sequence + if no params are given, returns the list of callable functions + """ + + if not p: + return self._render_response(self._listFunctions(), OK_CODE) + elif 'callable_during_start' in p.keys(): + return self._render_response(ALLOWED_DURING_STARTUP, OK_CODE) + elif 'function' in p.keys(): + func_path = p['function'] + function = self._getFunction(func_path) + return self._render_response(function.__doc__, OK_CODE) + else: + return self._render_response(self.jsonrpc_help.__doc__, OK_CODE) + + def jsonrpc_get_balance(self): + """ + Get balance + + Args: + None + Returns: + balance, float + """ + + log.info("Get balance") + return self._render_response(float(self.session.wallet.wallet_balance), OK_CODE) + + def jsonrpc_stop(self): """ Stop lbrynet-daemon + + Args: + None + Returns: + shutdown message """ def _disp_shutdown(): - print 'Shutting down lbrynet daemon' + log.info("Shutting down lbrynet daemon") d = self._shutdown() d.addCallback(lambda _: _disp_shutdown()) - d.addCallback(lambda _: reactor.callLater(1.0, reactor.stop)) + d.addCallback(lambda _: reactor.callLater(0.0, reactor.stop)) - return defer.succeed('Shutting down') + return self._render_response("Shutting down", OK_CODE) - def xmlrpc_get_lbry_files(self): + def jsonrpc_get_lbry_files(self): """ Get LBRY files - @return: List of managed LBRY files + Args: + None + Returns: + List of lbry files: + 'completed': bool + 'file_name': string + 'key': hex string + 'points_paid': float + 'stopped': bool + 'stream_hash': base 58 string + 'stream_name': string + 'suggested_file_name': string + 'upload_allowed': bool + 'sd_hash': string """ - r = [] - for f in self.lbry_file_manager.lbry_files: - if f.key: - t = {'completed': f.completed, 'file_name': f.file_name, 'key': binascii.b2a_hex(f.key), - 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, - 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, - 'upload_allowed': f.upload_allowed} + d = self._get_lbry_files() + d.addCallback(lambda r: [d[1] for d in r]) + d.addCallback(lambda r: self._render_response(r, OK_CODE) if len(r) else self._render_response(False, OK_CODE)) - else: - t = {'completed': f.completed, 'file_name': f.file_name, 'key': None, 'points_paid': f.points_paid, - 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, - 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed} + return d - r.append(json.dumps(t)) + def jsonrpc_get_lbry_file(self, p): + """ + Get lbry file - print '[' + str(datetime.now()) + '] Get LBRY files' - return r + Args: + 'name': get file by lbry uri, + 'sd_hash': get file by the hash in the name claim, + 'file_name': get file by its name in the downloads folder, + Returns: + 'completed': bool + 'file_name': string + 'key': hex string + 'points_paid': float + 'stopped': bool + 'stream_hash': base 58 string + 'stream_name': string + 'suggested_file_name': string + 'upload_allowed': bool + 'sd_hash': string + """ - def xmlrpc_resolve_name(self, name): + if p.keys()[0] in ['name', 'sd_hash', 'file_name']: + search_type = p.keys()[0] + d = self._get_lbry_file(search_type, p[search_type]) + else: + d = defer.fail() + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_resolve_name(self, p): """ Resolve stream info from a LBRY uri - @param: name - @return: info for name claim + Args: + 'name': name to look up, string, do not include lbry:// prefix + Returns: + metadata from name claim """ - def _disp(info): - log.debug('[' + str(datetime.now()) + ']' + ' Resolved info: ' + str(info['stream_hash'])) - print '[' + str(datetime.now()) + ']' + ' Resolved info: ' + str(info['stream_hash']) - return info + force = p.get('force', False) - d = self._resolve_name(name) - d.addCallbacks(_disp, lambda _: str('UnknownNameError')) - d.callback(None) + if 'name' in p: + name = p['name'] + else: + return self._render_response(None, BAD_REQUEST) + + d = self._resolve_name(name, force_refresh=force) + d.addCallbacks(lambda info: self._render_response(info, OK_CODE), lambda _: server.failure) return d - def xmlrpc_get(self, name): + def jsonrpc_get_claim_info(self, p): + """ + Resolve claim info from a LBRY uri + + Args: + 'name': name to look up, string, do not include lbry:// prefix + Returns: + txid, amount, value, n, height + """ + + def _convert_amount_to_float(r): + r['amount'] = float(r['amount']) / 10**8 + return r + + name = p['name'] + d = self.session.wallet.get_claim_info(name) + d.addCallback(_convert_amount_to_float) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_get(self, p): """ Download stream from a LBRY uri - @param: name - @return: {'stream_hash': hex string, 'path': path of download} + Args: + 'name': name to download, string + 'download_directory': optional, path to directory where file will be saved, string + 'file_name': optional, a user specified name for the downloaded file + 'stream_info': optional, specified stream info overrides name + Returns: + 'stream_hash': hex string + 'path': path of download """ - if name: - d = self._download_name(name) + if 'timeout' not in p.keys(): + timeout = self.download_timeout else: - d = defer.succeed('No name provided') + timeout = p['timeout'] + + if 'download_directory' not in p.keys(): + download_directory = self.download_directory + else: + download_directory = p['download_directory'] + + if 'file_name' in p.keys(): + file_name = p['file_name'] + else: + file_name = None + + if 'stream_info' in p.keys(): + stream_info = p['stream_info'] + if 'sources' in stream_info.keys(): + sd_hash = stream_info['sources']['lbry_sd_hash'] + else: + sd_hash = stream_info['stream_hash'] + else: + stream_info = None + + if 'wait_for_write' in p.keys(): + wait_for_write = p['wait_for_write'] + else: + wait_for_write = True + + if 'name' in p.keys(): + name = p['name'] + if p['name'] not in self.waiting_on.keys(): + d = self._download_name(name=name, timeout=timeout, download_directory=download_directory, + stream_info=stream_info, file_name=file_name, wait_for_write=wait_for_write) + d.addCallback(lambda l: {'stream_hash': sd_hash, + 'path': os.path.join(self.download_directory, l.file_name)} + if stream_info else + {'stream_hash': l.sd_hash, + 'path': os.path.join(self.download_directory, l.file_name)}) + d.addCallback(lambda message: self._render_response(message, OK_CODE)) + else: + d = server.failure + else: + d = server.failure + return d - def xmlrpc_stop_lbry_file(self, stream_hash): - try: - lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == stream_hash][0] - except IndexError: - return defer.fail(UnknownNameError) + def jsonrpc_stop_lbry_file(self, p): + """ + Stop lbry file - if not lbry_file.stopped: - d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) - d.addCallback(lambda _: 'Stream has been stopped') - d.addErrback(lambda err: str(err)) + Args: + 'name': stop file by lbry uri, + 'sd_hash': stop file by the hash in the name claim, + 'file_name': stop file by its name in the downloads folder, + Returns: + confirmation message + """ + + def _stop_file(f): + d = self.lbry_file_manager.toggle_lbry_file_running(f) + d.addCallback(lambda _: "Stopped LBRY file") return d - else: - return defer.succeed('Stream was already stopped') - def xmlrpc_start_lbry_file(self, stream_hash): - try: - lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == stream_hash][0] - except IndexError: - return defer.fail(UnknownNameError) - - if lbry_file.stopped: - d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) - d.callback(None) - return defer.succeed('Stream started') - else: - return defer.succeed('Stream was already running') - - def xmlrpc_render_html(self, html): - """ - Writes html to lbry.html in the downloads directory, then opens it with the browser - - @param html: - """ - - def _make_file(html, path): - f = open(path, 'w') - f.write(html) - f.close() - return defer.succeed(None) - - def _disp_err(err): - print str(err.getTraceback()) - return err - - path = os.path.join(self.download_directory, 'lbry.html') - - d = defer.Deferred() - d.addCallback(lambda _: _make_file(html, path)) - d.addCallback(lambda _: webbrowser.open('file://' + path)) - d.addErrback(_disp_err) - d.callback(None) + if p.keys()[0] in ['name', 'sd_hash', 'file_name']: + search_type = p.keys()[0] + d = self._get_lbry_file(search_type, p[search_type], return_json=False) + d.addCallback(lambda l: _stop_file(l) if not l.stopped else "LBRY file wasn't running") + d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - def xmlrpc_render_gui(self): + def jsonrpc_start_lbry_file(self, p): """ - Opens the lbry web ui in a browser + Stop lbry file + + Args: + 'name': stop file by lbry uri, + 'sd_hash': stop file by the hash in the name claim, + 'file_name': stop file by its name in the downloads folder, + Returns: + confirmation message """ - def _disp_err(err): - print str(err.getTraceback()) - return err - d = defer.Deferred() - d.addCallback(lambda _: webbrowser.open("file://" + str(os.path.join(self.download_directory, "lbryio/view/page/gui.html")))) - d.addErrback(_disp_err) - d.callback(None) + def _start_file(f): + d = self.lbry_file_manager.toggle_lbry_file_running(f) + return defer.succeed("Started LBRY file") + if p.keys()[0] in ['name', 'sd_hash', 'file_name']: + search_type = p.keys()[0] + d = self._get_lbry_file(search_type, p[search_type], return_json=False) + d.addCallback(lambda l: _start_file(l) if l.stopped else "LBRY file was already running") + + d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - def xmlrpc_search_nametrie(self, search): + def jsonrpc_get_est_cost(self, p): """ - Search the nametrie for claims beginning with search + Get estimated cost for a lbry uri - @param search: - @return: + Args: + 'name': lbry uri + Returns: + estimated cost """ - def _return_d(x): - d = defer.Deferred() - d.addCallback(lambda _: x) - d.callback(None) + name = p['name'] + d = self._get_est_cost(name) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d - return d + def jsonrpc_search_nametrie(self, p): + """ + Search the nametrie for claims + + Args: + 'search': search query, string + Returns: + List of search results + """ + + # TODO: change this function to "search", and use cached stream size info from the search server + + if 'search' in p.keys(): + search = p['search'] + else: + return self._render_response(None, BAD_REQUEST) def _clean(n): t = [] for i in n: if i[0]: - if i[1][0][0] and i[1][1][0] and i[1][2][0]: - i[1][0][1]['value'] = str(i[1][0][1]['value']) - t.append([i[1][0][1], i[1][1][1], i[1][2][1]]) + tr = {} + tr.update(i[1][0]['value']) + thumb = tr.get('thumbnail', None) + if thumb is None: + tr['thumbnail'] = "img/Free-speech-flag.svg" + tr['name'] = i[1][0]['name'] + tr['cost_est'] = i[1][1] + t.append(tr) return t - def _parse(results): - f = [] - for chain, meta, cost_est in results: - t = {} - if 'name' in chain.keys(): - t['name'] = chain['name'] - if 'thumbnail' in meta.keys(): - t['img'] = meta['thumbnail'] - else: - t['img'] = 'File://' + str(os.path.join(self.download_directory, "lbryio/web/img/Free-speech-flag.svg")) - if 'name' in meta.keys(): - t['title'] = meta['name'] - if 'description' in meta.keys(): - t['description'] = meta['description'] - t['cost_est'] = cost_est - f.append(t) + def get_est_costs(results): + def _save_cost(search_result): + d = self._get_est_cost(search_result['name']) + d.addCallback(lambda p: [search_result, p]) + return d - return f + dl = defer.DeferredList([_save_cost(r) for r in results], consumeErrors=True) + return dl - def _disp(results): - print '[' + str(datetime.now()) + '] Found ' + str(len(results)) + ' results' - return results + log.info('Search: %s' % search) - print '[' + str(datetime.now()) + '] Search nametrie: ' + search - - filtered_results = [n for n in self.session.wallet.get_nametrie() if n['name'].startswith(search)] - if len(filtered_results) > 25: - filtered_results = filtered_results[:25] - filtered_results = [n for n in filtered_results if 'txid' in n.keys()] - resolved_results = [defer.DeferredList([_return_d(n), self._resolve_name_wc(n['name']), - self._get_est_cost(n['name'])]) - for n in filtered_results] - - d = defer.DeferredList(resolved_results) + d = self._search(search) + d.addCallback(lambda claims: claims[:self.max_search_results]) + d.addCallback(get_est_costs) d.addCallback(_clean) - d.addCallback(_parse) - d.addCallback(_disp) + d.addCallback(lambda results: self._render_response(results, OK_CODE)) return d - def xmlrpc_delete_lbry_file(self, file_name): - def _disp(file_name): - print '[' + str(datetime.now()) + '] Deleted: ' + file_name - return defer.succeed(str('Deleted: ' + file_name)) + def jsonrpc_delete_lbry_file(self, p): + """ + Delete a lbry file - lbry_files = [self._delete_lbry_file(f) for f in self.lbry_file_manager.lbry_files if file_name == f.file_name] - d = defer.DeferredList(lbry_files) - d.addCallback(lambda _: _disp(file_name)) + Args: + 'file_name': downloaded file name, string + Returns: + confirmation message + """ + + if 'delete_target_file' in p.keys(): + delete_file = p['delete_target_file'] + else: + delete_file = True + + def _delete_file(f): + file_name = f.file_name + d = self._delete_lbry_file(f, delete_file=delete_file) + d.addCallback(lambda _: "Deleted LBRY file" + file_name) + return d + + if 'name' in p.keys() or 'sd_hash' in p.keys() or 'file_name' in p.keys(): + search_type = [k for k in p.keys() if k != 'delete_target_file'][0] + d = self._get_lbry_file(search_type, p[search_type], return_json=False) + d.addCallback(lambda l: _delete_file(l) if l else False) + + d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - def xmlrpc_check(self, name): - d = self._check_history(name) - d.addCallback(lambda lbry_file: self._path_from_lbry_file(lbry_file) if lbry_file else 'Not found') - d.addErrback(lambda err: str(err)) + def jsonrpc_publish(self, p): + """ + Make a new name claim and publish associated data to lbrynet + + Args: + 'name': name to be claimed, string + 'file_path': path to file to be associated with name, string + 'bid': amount of credits to commit in this claim, float + 'metadata': metadata dictionary + optional 'fee' + Returns: + Claim txid + """ + + name = p['name'] + try: + verify_name_characters(name) + except: + log.error("Bad name") + return defer.fail(InvalidNameError("Bad name")) + bid = p['bid'] + file_path = p['file_path'] + metadata = p['metadata'] + + def _set_address(address, currency): + log.info("Generated new address for key fee: " + str(address)) + metadata['fee'][currency]['address'] = address + return defer.succeed(None) + + def _delete_data(lbry_file): + txid = lbry_file.txid + d = self._delete_lbry_file(lbry_file, delete_file=False) + d.addCallback(lambda _: txid) + return d + + if not self.pending_claim_checker.running: + self.pending_claim_checker.start(30) + + d = self._resolve_name(name, force_refresh=True) + d.addErrback(lambda _: None) + + if 'fee' in p: + metadata['fee'] = p['fee'] + assert len(metadata['fee']) == 1, "Too many fees" + for c in metadata['fee']: + if 'address' not in metadata['fee'][c]: + d.addCallback(lambda _: self.session.wallet.get_new_address()) + d.addCallback(lambda addr: _set_address(addr, c)) + + pub = Publisher(self.session, self.lbry_file_manager, self.session.wallet) + d.addCallback(lambda _: self._get_lbry_file_by_uri(name)) + d.addCallbacks(lambda l: None if not l else _delete_data(l), lambda _: None) + d.addCallback(lambda r: pub.start(name, file_path, bid, metadata, r)) + d.addCallback(lambda txid: self._add_to_pending_claims(name, txid)) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addErrback(lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) return d - def xmlrpc_publish(self, metadata): - metadata = json.loads(metadata) + def jsonrpc_abandon_name(self, p): + """ + Abandon a name and reclaim credits from the claim - required = ['name', 'file_path', 'bid'] + Args: + 'txid': txid of claim, string + Return: + Confirmation message + """ - for r in required: - if not r in metadata.keys(): - return defer.fail() - - # if not os.path.isfile(metadata['file_path']): - # return defer.fail() - - if not type(metadata['bid']) is float and metadata['bid'] > 0.0: - return defer.fail() - - name = metadata['name'] - file_path = metadata['file_path'] - bid = metadata['bid'] - - if 'title' in metadata.keys(): - title = metadata['title'] + if 'txid' in p.keys(): + txid = p['txid'] else: - title = None + return server.failure - if 'description' in metadata.keys(): - description = metadata['description'] - else: - description = None - - if 'thumbnail' in metadata.keys(): - thumbnail = metadata['thumbnail'] - else: - thumbnail = None - - if 'key_fee' in metadata.keys(): - if not float(metadata['key_fee']) == 0.0: - if not 'key_fee_address' in metadata.keys(): - return defer.fail() - key_fee = metadata['key_fee'] - else: - key_fee = 0.0 - - if 'key_fee_address' in metadata.keys(): - key_fee_address = metadata['key_fee_address'] - else: - key_fee_address = None - - if 'content_license' in metadata.keys(): - content_license = metadata['content_license'] - else: - content_license = None - - p = Publisher(self.session, self.lbry_file_manager, self.session.wallet) - d = p.start(name, file_path, bid, title, description, thumbnail, key_fee, key_fee_address, content_license) - - return d - - def xmlrpc_abandon_name(self, txid): - def _disp(txid, tx): - print '[' + str(datetime.now()) + '] Spent coins from claim tx ' + txid + ' --> ' + tx - return tx + def _disp(x): + log.info("Abandoned name claim tx " + str(x)) + return self._render_response(x, OK_CODE) d = defer.Deferred() d.addCallback(lambda _: self.session.wallet.abandon_name(txid)) - d.addCallback(lambda tx: _disp(txid, tx)) - d.addErrback(lambda err: str(err.getTraceback())) + d.addCallback(_disp) d.callback(None) return d - def xmlrpc_get_name_claims(self): + def jsonrpc_get_name_claims(self): + """ + Get my name claims + + Args: + None + Returns + list of name claims + """ + def _clean(claims): for c in claims: for k in c.keys(): - if type(c[k]) == Decimal: + if isinstance(c[k], Decimal): c[k] = float(c[k]) - return claims + return defer.succeed(claims) d = self.session.wallet.get_name_claims() d.addCallback(_clean) + d.addCallback(lambda claims: self._render_response(claims, OK_CODE)) return d - def xmlrpc_get_time_behind_blockchain(self): - d = self.session.wallet.get_most_recent_blocktime() - d.addCallback(get_time_behind_blockchain) + def jsonrpc_get_transaction_history(self): + """ + Get transaction history + + Args: + None + Returns: + list of transactions + """ + + d = self.session.wallet.get_history() + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_get_transaction(self, p): + """ + Get a decoded transaction from a txid + + Args: + txid: txid hex string + Returns: + JSON formatted transaction + """ + + + txid = p['txid'] + d = self.session.wallet.get_tx_json(txid) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_get_public_key_from_wallet(self, p): + """ + Get public key from wallet address + + Args: + wallet: wallet address, base58 + Returns: + public key + """ + + wallet = p['wallet'] + d = self.session.wallet.get_pub_keys(wallet) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + + def jsonrpc_get_time_behind_blockchain(self): + """ + Get number of blocks behind the blockchain + + Args: + None + Returns: + number of blocks behind blockchain, int + """ + + def _get_time_behind(): + try: + local_height = self.session.wallet.network.get_local_height() + remote_height = self.session.wallet.network.get_server_height() + return defer.succeed(remote_height - local_height) + except: + return defer.fail() + + d = _get_time_behind() + d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d + def jsonrpc_get_new_address(self): + """ + Generate a new wallet address -def main(): - daemon = LBRYDaemon() - daemon.setup() - reactor.listenTCP(7080, server.Site(daemon)) - reactor.run() + Args: + None + Returns: + new wallet address, base 58 string + """ -if __name__ == '__main__': - main() \ No newline at end of file + def _disp(address): + log.info("Got new wallet address: " + address) + return defer.succeed(address) + + d = self.session.wallet.get_new_address() + d.addCallback(_disp) + d.addCallback(lambda address: self._render_response(address, OK_CODE)) + return d + + def jsonrpc_send_amount_to_address(self, p): + """ + Send credits to an address + + Args: + amount: the amount to send + address: the address of the recipient + Returns: + True if payment successfully scheduled + """ + + if 'amount' in p.keys() and 'address' in p.keys(): + amount = p['amount'] + address = p['address'] + else: + return server.failure + + reserved_points = self.session.wallet.reserve_points(address, amount) + if reserved_points is None: + return defer.fail(InsufficientFundsError()) + d = self.session.wallet.send_points_to_address(reserved_points, amount) + d.addCallback(lambda _: self._render_response(True, OK_CODE)) + return d + + def jsonrpc_get_best_blockhash(self): + """ + Get hash of most recent block + + Args: + None + Returns: + Hash of most recent block + """ + + d = self.session.wallet.get_best_blockhash() + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_get_block(self, p): + """ + Get contents of a block + + Args: + blockhash: hash of the block to look up + Returns: + requested block + """ + + if 'blockhash' in p.keys(): + blockhash = p['blockhash'] + else: + return server.failure + + d = self.session.wallet.get_block(blockhash) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_get_claims_for_tx(self, p): + """ + Get claims for tx + + Args: + txid: txid of a name claim transaction + Returns: + any claims contained in the requested tx + """ + + if 'txid' in p.keys(): + txid = p['txid'] + else: + return server.failure + + d = self.session.wallet.get_claims_from_tx(txid) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_get_nametrie(self): + """ + Get the nametrie + + Args: + None + Returns: + Name claim trie + """ + + d = self.session.wallet.get_nametrie() + d.addCallback(lambda r: [i for i in r if 'txid' in i.keys()]) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_set_miner(self, p): + """ + Start of stop the miner, function only available when lbrycrd is set as the wallet + + Args: + run: True/False + Returns: + miner status, True/False + """ + + stat = p['run'] + if stat: + d = self.session.wallet.start_miner() + else: + d = self.session.wallet.stop_miner() + d.addCallback(lambda _: self.session.wallet.get_miner_status()) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_get_miner_status(self): + """ + Get status of miner + + Args: + None + Returns: + True/False + """ + + d = self.session.wallet.get_miner_status() + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_log(self, p): + """ + Log message + + Args: + 'message': message to be logged + Returns: + True + """ + + message = p['message'] + log.info("API client log request: %s" % message) + return self._render_response(True, OK_CODE) + + def jsonrpc_upload_log(self, p=None): + """ + Upload log + + Args, optional: + 'name_prefix': prefix to indicate what is requesting the log upload + 'exclude_previous': true/false, whether or not to exclude previous sessions from upload, defaults on true + Returns: + True + """ + + if p: + if 'name_prefix' in p.keys(): + log_type = p['name_prefix'] + '_api' + elif 'log_type' in p.keys(): + log_type = p['log_type'] + '_api' + else: + log_type = None + + if 'exclude_previous' in p.keys(): + exclude_previous = p['exclude_previous'] + else: + exclude_previous = True + + if 'message' in p.keys(): + log.info("Upload log message: " + str(p['message'])) + + if 'force' in p.keys(): + force = p['force'] + else: + force = False + else: + log_type = "api" + exclude_previous = True + + d = self._upload_log(log_type=log_type, exclude_previous=exclude_previous, force=force) + if 'message' in p.keys(): + d.addCallback(lambda _: self._log_to_slack(p['message'])) + d.addCallback(lambda _: self._render_response(True, OK_CODE)) + return d + + def jsonrpc_configure_ui(self, p): + """ + Configure the UI being hosted + + Args, optional: + 'branch': a branch name on lbryio/lbry-web-ui + 'path': path to a ui folder + """ + + if 'check_requirements' in p: + check_require = p['check_requirements'] + else: + check_require = True + + if 'path' in p: + d = self.lbry_ui_manager.setup(user_specified=p['path'], check_requirements=check_require) + elif 'branch' in p: + d = self.lbry_ui_manager.setup(branch=p['branch'], check_requirements=check_require) + else: + d = self.lbry_ui_manager.setup(check_requirements=check_require) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + + return d + + def jsonrpc_reveal(self, p): + """ + Reveal a file or directory in file browser + + Args: + 'path': path to be selected in file browser + Returns: + True, opens file browser + """ + path = p['path'] + if sys.platform == "darwin": + d = threads.deferToThread(subprocess.Popen, ['open', '-R', path]) + else: + # No easy way to reveal specific files on Linux, so just open the containing directory + d = threads.deferToThread(subprocess.Popen, ['xdg-open', os.dirname(path)]) + + d.addCallback(lambda _: self._render_response(True, OK_CODE)) + return d + + +def get_lbrynet_version_from_github(): + """Return the latest released version from github.""" + response = requests.get('https://api.github.com/repos/lbryio/lbry/releases/latest') + release = response.json() + tag = release['tag_name'] + # githubs documentation claims this should never happen, but we'll check just in case + if release['prerelease']: + raise Exception('Release {} is a pre-release'.format(tag)) + return get_version_from_tag(tag) + + +def get_version_from_tag(tag): + match = re.match('v([\d.]+)', tag) + if match: + return match.group(1) + else: + raise Exception('Failed to parse version from tag {}'.format(tag)) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py b/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py new file mode 100644 index 000000000..0b5d0ba0c --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py @@ -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() diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py new file mode 100644 index 000000000..1bfa62eab --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -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() diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py new file mode 100644 index 000000000..3f2107079 --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -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 diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py b/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py deleted file mode 100644 index 4e42708e9..000000000 --- a/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py +++ /dev/null @@ -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() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 93bfbfa09..62d9cd33d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -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(): - 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 + # 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)) - self.stream_hash = self.stream_info['stream_hash'] + 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) - else: - print 'InvalidStreamInfoError' - raise InvalidStreamInfoError(self.stream_info) + 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) - 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 + def start(self, stream_info, name): + def _cause_timeout(err): + log.error(err) + log.debug('Forcing a timeout') + self.timeout_counter = self.timeout * 2 - 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) + 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 - return d + 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'] + self.stream_hash = self.stream_info['sources']['lbry_sd_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)) + + self.checker.start(1) + + 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) + + 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() + d = _pay_key_fee() - 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"] diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py new file mode 100644 index 000000000..0af938954 --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -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 + } + }) \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index 81a8e59e3..3e1b78325 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -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) diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py new file mode 100644 index 000000000..404c47170 --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -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) diff --git a/lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py deleted file mode 100644 index 62ded697b..000000000 --- a/lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import json -import webbrowser -import xmlrpclib, sys - - -def render_video(path): - r = r'
' - 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 = "" + json.dumps(r) + "" - 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:]) diff --git a/lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist b/lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist deleted file mode 100644 index 4f5993ff1..000000000 --- a/lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist +++ /dev/null @@ -1,117 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleDisplayName - LBRYURIHandler - CFBundleDocumentTypes - - - CFBundleTypeOSTypes - - **** - fold - disk - - CFBundleTypeRole - Viewer - - - CFBundleExecutable - LBRYURIHandler - -CFBundleURLTypes - - - CFBundleURLName - LBRYURIHandler - CFBundleURLSchemes - - lbry - - - -NSUIElement - - - CFBundleIconFile - PythonApplet.icns - CFBundleIdentifier - org.pythonmac.unspecified.LBRYURIHandler - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - LBRYURIHandler - CFBundlePackageType - APPL - CFBundleShortVersionString - 0.0.0 - CFBundleSignature - ???? - CFBundleVersion - 0.0.0 - LSHasLocalizedDisplayName - - NSAppleScriptEnabled - - NSHumanReadableCopyright - Copyright not specified - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - PyMainFileNames - - __boot__ - - PyOptions - - alias - - argv_emulation - - emulate_shell_environment - - no_chdir - - prefer_ppc - - site_packages - - use_faulthandler - - use_pythonpath - - verbose - - - PyResourcePackages - - - PyRuntimeLocations - - @executable_path/../Frameworks/Python.framework/Versions/2.7/Python - - PythonInfoDict - - PythonExecutable - /Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python - PythonLongVersion - 2.7.10 (v2.7.10:15c95b7d81dc, May 23 2015, 09:33:12) -[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] - PythonShortVersion - 2.7 - py2app - - alias - - template - app - version - 0.9 - - - - diff --git a/lbrynet/lbrynet_daemon/LBRYURIHandler/setup.py b/lbrynet/lbrynet_daemon/LBRYURIHandler/setup.py deleted file mode 100644 index fbb83f5dc..000000000 --- a/lbrynet/lbrynet_daemon/LBRYURIHandler/setup.py +++ /dev/null @@ -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'], -) diff --git a/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py b/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py new file mode 100644 index 000000000..cd7ed02cb --- /dev/null +++ b/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py @@ -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() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/daemon_scripts/__init__.py b/lbrynet/lbrynet_daemon/daemon_scripts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py b/lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py new file mode 100644 index 000000000..79fb156d0 --- /dev/null +++ b/lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py @@ -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() diff --git a/lbrynet/lbrynet_gui/GuiApp.py b/lbrynet/lbrynet_gui/GuiApp.py deleted file mode 100644 index 94c977759..000000000 --- a/lbrynet/lbrynet_gui/GuiApp.py +++ /dev/null @@ -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("", 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('') - - def cut_command(): - self.uri_entry.event_generate('') - - def paste_command(): - self.uri_entry.event_generate('') - - 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("", 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('', 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("") - - 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("", 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() \ No newline at end of file diff --git a/lbrynet/lbrynet_gui/Ic_arrow_drop_down_48px.svg.LICENSE b/lbrynet/lbrynet_gui/Ic_arrow_drop_down_48px.svg.LICENSE deleted file mode 100644 index 69face7d0..000000000 --- a/lbrynet/lbrynet_gui/Ic_arrow_drop_down_48px.svg.LICENSE +++ /dev/null @@ -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. \ No newline at end of file diff --git a/lbrynet/lbrynet_gui/LBRYGui.py b/lbrynet/lbrynet_gui/LBRYGui.py deleted file mode 100644 index dcc20f649..000000000 --- a/lbrynet/lbrynet_gui/LBRYGui.py +++ /dev/null @@ -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()) diff --git a/lbrynet/lbrynet_gui/StreamFrame.py b/lbrynet/lbrynet_gui/StreamFrame.py deleted file mode 100644 index 5f071e1a5..000000000 --- a/lbrynet/lbrynet_gui/StreamFrame.py +++ /dev/null @@ -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)) \ No newline at end of file diff --git a/lbrynet/lbrynet_gui/__init__.py b/lbrynet/lbrynet_gui/__init__.py deleted file mode 100644 index f58b75595..000000000 --- a/lbrynet/lbrynet_gui/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""A gui application for downloading LBRY files from LBRYnet""" \ No newline at end of file diff --git a/lbrynet/lbrynet_gui/close.gif b/lbrynet/lbrynet_gui/close.gif deleted file mode 100644 index 9c9e74a2f..000000000 Binary files a/lbrynet/lbrynet_gui/close.gif and /dev/null differ diff --git a/lbrynet/lbrynet_gui/close1.png b/lbrynet/lbrynet_gui/close1.png deleted file mode 100644 index 6c7cae01d..000000000 Binary files a/lbrynet/lbrynet_gui/close1.png and /dev/null differ diff --git a/lbrynet/lbrynet_gui/close2.gif b/lbrynet/lbrynet_gui/close2.gif deleted file mode 100644 index 5d1cf62e5..000000000 Binary files a/lbrynet/lbrynet_gui/close2.gif and /dev/null differ diff --git a/lbrynet/lbrynet_gui/drop_down.gif b/lbrynet/lbrynet_gui/drop_down.gif deleted file mode 100644 index a61f65c6a..000000000 Binary files a/lbrynet/lbrynet_gui/drop_down.gif and /dev/null differ diff --git a/lbrynet/lbrynet_gui/gui.py b/lbrynet/lbrynet_gui/gui.py deleted file mode 100644 index 2457d52ea..000000000 --- a/lbrynet/lbrynet_gui/gui.py +++ /dev/null @@ -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() \ No newline at end of file diff --git a/lbrynet/lbrynet_gui/hide_options.gif b/lbrynet/lbrynet_gui/hide_options.gif deleted file mode 100644 index 9c95b5a9d..000000000 Binary files a/lbrynet/lbrynet_gui/hide_options.gif and /dev/null differ diff --git a/lbrynet/lbrynet_gui/lbry-dark-242x80.gif b/lbrynet/lbrynet_gui/lbry-dark-242x80.gif deleted file mode 100755 index 65ce976d4..000000000 Binary files a/lbrynet/lbrynet_gui/lbry-dark-242x80.gif and /dev/null differ diff --git a/lbrynet/lbrynet_gui/lbry-dark-icon.ico b/lbrynet/lbrynet_gui/lbry-dark-icon.ico deleted file mode 100755 index 91cd30d56..000000000 Binary files a/lbrynet/lbrynet_gui/lbry-dark-icon.ico and /dev/null differ diff --git a/lbrynet/lbrynet_gui/lbry-dark-icon.xbm b/lbrynet/lbrynet_gui/lbry-dark-icon.xbm deleted file mode 100644 index 75b81527c..000000000 --- a/lbrynet/lbrynet_gui/lbry-dark-icon.xbm +++ /dev/null @@ -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 }; diff --git a/lbrynet/lbrynet_gui/lbry.conf b/lbrynet/lbrynet_gui/lbry.conf deleted file mode 100644 index 536c3c99e..000000000 --- a/lbrynet/lbrynet_gui/lbry.conf +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lbrynet/lbrynet_gui/show_options.gif b/lbrynet/lbrynet_gui/show_options.gif deleted file mode 100644 index c5ed0b6fd..000000000 Binary files a/lbrynet/lbrynet_gui/show_options.gif and /dev/null differ diff --git a/packaging/osx/add-key.sh b/packaging/osx/add-key.sh new file mode 100755 index 000000000..ac06aa373 --- /dev/null +++ b/packaging/osx/add-key.sh @@ -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 + diff --git a/packaging/osx/certs/.gitignore b/packaging/osx/certs/.gitignore new file mode 100644 index 000000000..9746cae2b --- /dev/null +++ b/packaging/osx/certs/.gitignore @@ -0,0 +1,2 @@ +dist.cer +dist.p12 diff --git a/packaging/osx/certs/dist.cer.enc b/packaging/osx/certs/dist.cer.enc new file mode 100644 index 000000000..6431def6b --- /dev/null +++ b/packaging/osx/certs/dist.cer.enc @@ -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== diff --git a/packaging/osx/certs/dist.p12.enc b/packaging/osx/certs/dist.p12.enc new file mode 100644 index 000000000..595f15773 --- /dev/null +++ b/packaging/osx/certs/dist.p12.enc @@ -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== diff --git a/packaging/osx/lbry-osx-app/.gitignore b/packaging/osx/lbry-osx-app/.gitignore new file mode 100644 index 000000000..bc318a057 --- /dev/null +++ b/packaging/osx/lbry-osx-app/.gitignore @@ -0,0 +1,12 @@ + +*.pyc + +*.pyo + +*.so + +*.xml + +*.iml + +id.conf diff --git a/packaging/osx/lbry-osx-app/app.icns b/packaging/osx/lbry-osx-app/app.icns new file mode 100644 index 000000000..b4d00d2f2 Binary files /dev/null and b/packaging/osx/lbry-osx-app/app.icns differ diff --git a/packaging/osx/lbry-osx-app/lbry_uri_handler/LBRYURIHandler.py b/packaging/osx/lbry-osx-app/lbry_uri_handler/LBRYURIHandler.py new file mode 100644 index 000000000..f6990cfea --- /dev/null +++ b/packaging/osx/lbry-osx-app/lbry_uri_handler/LBRYURIHandler.py @@ -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:]) diff --git a/packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py b/packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py new file mode 100644 index 000000000..12315369c --- /dev/null +++ b/packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py @@ -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() diff --git a/packaging/osx/lbry-osx-app/lbrygui/LBRYNotify.py b/packaging/osx/lbry-osx-app/lbrygui/LBRYNotify.py new file mode 100644 index 000000000..d4a88e7ce --- /dev/null +++ b/packaging/osx/lbry-osx-app/lbrygui/LBRYNotify.py @@ -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) \ No newline at end of file diff --git a/packaging/osx/lbry-osx-app/lbrygui/__init__.py b/packaging/osx/lbry-osx-app/lbrygui/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/packaging/osx/lbry-osx-app/lbrygui/app.icns b/packaging/osx/lbry-osx-app/lbrygui/app.icns new file mode 100644 index 000000000..b4d00d2f2 Binary files /dev/null and b/packaging/osx/lbry-osx-app/lbrygui/app.icns differ diff --git a/packaging/osx/lbry-osx-app/lbrygui/main.py b/packaging/osx/lbry-osx-app/lbrygui/main.py new file mode 100644 index 000000000..d7ca4dc00 --- /dev/null +++ b/packaging/osx/lbry-osx-app/lbrygui/main.py @@ -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() diff --git a/packaging/osx/lbry-osx-app/libgmp.10.dylib b/packaging/osx/lbry-osx-app/libgmp.10.dylib new file mode 100755 index 000000000..09f809987 Binary files /dev/null and b/packaging/osx/lbry-osx-app/libgmp.10.dylib differ diff --git a/packaging/osx/lbry-osx-app/setup_app.py b/packaging/osx/lbry-osx-app/setup_app.py new file mode 100644 index 000000000..28e0ca981 --- /dev/null +++ b/packaging/osx/lbry-osx-app/setup_app.py @@ -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, +) diff --git a/packaging/osx/lbry-osx-app/setup_app.sh b/packaging/osx/lbry-osx-app/setup_app.sh new file mode 100755 index 000000000..ad3ca8449 --- /dev/null +++ b/packaging/osx/lbry-osx-app/setup_app.sh @@ -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 diff --git a/packaging/osx/lbry-osx-app/setup_uri_handler.py b/packaging/osx/lbry-osx-app/setup_uri_handler.py new file mode 100644 index 000000000..26097d8a4 --- /dev/null +++ b/packaging/osx/lbry-osx-app/setup_uri_handler.py @@ -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'], +) \ No newline at end of file diff --git a/packaging/travis/install_dependencies_and_run_tests.sh b/packaging/travis/install_dependencies_and_run_tests.sh new file mode 100755 index 000000000..422e74229 --- /dev/null +++ b/packaging/travis/install_dependencies_and_run_tests.sh @@ -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 diff --git a/packaging/travis/setup_osx.sh b/packaging/travis/setup_osx.sh new file mode 100755 index 000000000..aeae7824b --- /dev/null +++ b/packaging/travis/setup_osx.sh @@ -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 + diff --git a/packaging/ubuntu/README.md b/packaging/ubuntu/README.md new file mode 100644 index 000000000..d1a3d81f8 --- /dev/null +++ b/packaging/ubuntu/README.md @@ -0,0 +1,5 @@ +# package scripts + +How to build LBRY packages. + +For best results, run on a fresh image. diff --git a/packaging/ubuntu/icons/lbry128.png b/packaging/ubuntu/icons/lbry128.png new file mode 100644 index 000000000..de083fcbc Binary files /dev/null and b/packaging/ubuntu/icons/lbry128.png differ diff --git a/packaging/ubuntu/icons/lbry256.png b/packaging/ubuntu/icons/lbry256.png new file mode 100644 index 000000000..659957406 Binary files /dev/null and b/packaging/ubuntu/icons/lbry256.png differ diff --git a/packaging/ubuntu/icons/lbry32.png b/packaging/ubuntu/icons/lbry32.png new file mode 100644 index 000000000..ece10998c Binary files /dev/null and b/packaging/ubuntu/icons/lbry32.png differ diff --git a/packaging/ubuntu/icons/lbry48.png b/packaging/ubuntu/icons/lbry48.png new file mode 100644 index 000000000..bba3a8e9d Binary files /dev/null and b/packaging/ubuntu/icons/lbry48.png differ diff --git a/packaging/ubuntu/icons/lbry96.png b/packaging/ubuntu/icons/lbry96.png new file mode 100644 index 000000000..195c31ea7 Binary files /dev/null and b/packaging/ubuntu/icons/lbry96.png differ diff --git a/packaging/ubuntu/lbry b/packaging/ubuntu/lbry new file mode 100755 index 000000000..a139556cf --- /dev/null +++ b/packaging/ubuntu/lbry @@ -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" diff --git a/packaging/ubuntu/lbry-init.conf b/packaging/ubuntu/lbry-init.conf new file mode 100644 index 000000000..c1e192deb --- /dev/null +++ b/packaging/ubuntu/lbry-init.conf @@ -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 diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop new file mode 100644 index 000000000..7005175b3 --- /dev/null +++ b/packaging/ubuntu/lbry.desktop @@ -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 diff --git a/packaging/ubuntu/postinst_append b/packaging/ubuntu/postinst_append new file mode 100755 index 000000000..48e54f006 --- /dev/null +++ b/packaging/ubuntu/postinst_append @@ -0,0 +1,15 @@ + +( + +if hash zenity 2>/dev/null; then + sleep 3 + + zenity --info --icon-name="system-software-install" \ + --text="\ +LBRY Installed\n\nLBRY has been installed.\n\n\ +Please start LBRY by running lbry from the command line or selecting LBRY from the application menu.\n\n\ +If you need help or have any questions, join us on Slack (https://slack.lbry.io) or email hello@lbry.io.\ +" + +fi +) & diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh new file mode 100755 index 000000000..9d74901e8 --- /dev/null +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -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 : use the specified branch of the lbry repo" + echo "-w : set the webui branch" + echo "-d : 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 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..800ecf2ac --- /dev/null +++ b/requirements.txt @@ -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 \ No newline at end of file diff --git a/scripts/migrate_lbryum_to_lbrycrd.py b/scripts/migrate_lbryum_to_lbrycrd.py new file mode 100644 index 000000000..fdafacd6e --- /dev/null +++ b/scripts/migrate_lbryum_to_lbrycrd.py @@ -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()) diff --git a/setup.py b/setup.py index 948dff97d..847b69ee1 100644 --- a/setup.py +++ b/setup.py @@ -1,47 +1,47 @@ #!/usr/bin/env python +from lbrynet import __version__ + import ez_setup ez_setup.use_setuptools() - +import sys +import os from setuptools import setup, find_packages +base_dir = os.path.abspath(os.path.dirname(__file__)) + + +console_scripts = ['lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUploader:launch_stdin_uploader', + 'lbrynet-stdout-downloader = lbrynet.lbrynet_console.LBRYStdoutDownloader:launch_stdout_downloader', + 'lbrynet-create-network = lbrynet.create_network:main', + 'lbrynet-launch-node = lbrynet.dht.node:main', + 'lbrynet-launch-rpc-node = lbrynet.rpc_node:main', + 'lbrynet-rpc-node-cli = lbrynet.node_rpc_cli:main', + 'lbrynet-lookup-hosts-for-hash = lbrynet.dht_scripts:get_hosts_for_hash_in_dht', + 'lbrynet-announce_hash_to_dht = lbrynet.dht_scripts:announce_hash_to_dht', + 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonControl:start', + 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonControl:stop', + 'lbrynet-cli = lbrynet.lbrynet_daemon.LBRYDaemonCLI:main'] + +requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', + 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', + 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0', 'base58', 'googlefinance'] + setup(name='lbrynet', - version='0.0.4', - packages=find_packages(), - install_requires=['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', 'leveldb'], - entry_points={ - 'console_scripts': [ - 'lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch_lbry_console', - 'lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUploader:launch_stdin_uploader', - 'lbrynet-stdout-downloader = lbrynet.lbrynet_console.LBRYStdoutDownloader:launch_stdout_downloader', - 'lbrynet-create-network = lbrynet.create_network:main', - 'lbrynet-launch-node = lbrynet.dht.node:main', - 'lbrynet-launch-rpc-node = lbrynet.rpc_node:main', - 'lbrynet-rpc-node-cli = lbrynet.node_rpc_cli:main', - 'lbrynet-gui = lbrynet.lbrynet_gui.gui:start_gui', - 'lbrynet-lookup-hosts-for-hash = lbrynet.dht_scripts:get_hosts_for_hash_in_dht', - 'lbrynet-announce_hash_to_dht = lbrynet.dht_scripts:announce_hash_to_dht', - 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemon:main', - 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonStopper:main', - ] - }, + description='A decentralized media library and marketplace', + version=__version__, + maintainer='Alex Grintsvayg', + maintainer_email='grin@lbry.io', + packages=find_packages(base_dir), + install_requires=requires, + entry_points={'console_scripts': console_scripts}, data_files=[ ('lbrynet/lbrynet_console/plugins', [ - 'lbrynet/lbrynet_console/plugins/blindrepeater.yapsy-plugin', + os.path.join(base_dir, 'lbrynet', 'lbrynet_console', 'plugins', + 'blindrepeater.yapsy-plugin') ] ), - ('lbrynet/lbrynet_gui', - [ - 'lbrynet/lbrynet_gui/close2.gif', - 'lbrynet/lbrynet_gui/lbry-dark-242x80.gif', - 'lbrynet/lbrynet_gui/lbry-dark-icon.xbm', - 'lbrynet/lbrynet_gui/lbry-dark-icon.ico', - 'lbrynet/lbrynet_gui/drop_down.gif', - 'lbrynet/lbrynet_gui/show_options.gif', - 'lbrynet/lbrynet_gui/hide_options.gif', - 'lbrynet/lbrynet_gui/lbry.conf', - ] - ) - ] - ) \ No newline at end of file + ], + dependency_links=['https://github.com/lbryio/lbryum/tarball/master/#egg=lbryum'], + ) diff --git a/setup_win32.py b/setup_win32.py index b414606a7..1fc0f0d58 100644 --- a/setup_win32.py +++ b/setup_win32.py @@ -7,7 +7,7 @@ import os import sys from cx_Freeze import setup, Executable - +import requests.certs def find_data_file(filename): if getattr(sys, 'frozen', False): @@ -48,9 +48,9 @@ bdist_msi_options = { build_exe_options = { 'include_msvcr': True, 'includes': [], - 'packages': ['os', 'twisted', 'miniupnpc', 'unqlite', 'seccure', + 'packages': ['six', 'os', 'twisted', 'miniupnpc', 'unqlite', 'seccure', 'requests', 'bitcoinrpc', 'txjsonrpc', 'win32api', 'Crypto', - 'gmpy', 'yapsy'], + 'gmpy', 'yapsy', 'lbryum', 'google.protobuf'], 'excludes': ['zope.interface._zope_interface_coptimizations'], 'include_files': [os.path.join('lbrynet', 'lbrynet_gui', 'close.gif'), os.path.join('lbrynet', 'lbrynet_gui', 'close1.png'), @@ -63,6 +63,7 @@ build_exe_options = { os.path.join('lbrynet', 'lbrynet_gui', 'show_options.gif'), os.path.join('lbrycrdd.exe'), # Not included in repo os.path.join('lbrycrd-cli.exe'), # Not included in repo + (requests.certs.where(), 'cacert.pem'), ], 'namespace_packages': ['zope']} diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lbrynet/__init__.py b/tests/lbrynet/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lbrynet/core/__init__.py b/tests/lbrynet/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lbrynet/core/server/__init__.py b/tests/lbrynet/core/server/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lbrynet/core/server/test_BlobRequestHandler.py b/tests/lbrynet/core/server/test_BlobRequestHandler.py new file mode 100644 index 000000000..5c55af574 --- /dev/null +++ b/tests/lbrynet/core/server/test_BlobRequestHandler.py @@ -0,0 +1,127 @@ +import StringIO + +import mock +from twisted.internet import defer, protocol +from twisted.test import proto_helpers +from twisted.trial import unittest + +from lbrynet.core import Peer +from lbrynet.core.server import BlobRequestHandler + + +class TestBlobRequestHandlerQueries(unittest.TestCase): + def setUp(self): + self.blob_manager = mock.Mock() + self.payment_rate_manager = mock.Mock() + self.handler = BlobRequestHandler.BlobRequestHandler( + self.blob_manager, None, self.payment_rate_manager) + + def test_empty_response_when_empty_query(self): + self.assertEqual( + {}, self.successResultOf(self.handler.handle_queries({}))) + + def test_error_set_when_rate_is_missing(self): + query = {'requested_blob': 'blob'} + deferred = self.handler.handle_queries(query) + response = {'incoming_blob': {'error': 'RATE_UNSET'}} + self.assertEqual(response, self.successResultOf(deferred)) + + def test_error_set_when_rate_too_low(self): + self.payment_rate_manager.accept_rate_blob_data.return_value = False + query = { + 'blob_data_payment_rate': 'way_too_low', + 'requested_blob': 'blob' + } + deferred = self.handler.handle_queries(query) + response = { + 'blob_data_payment_rate': 'RATE_TOO_LOW', + 'incoming_blob': {'error': 'RATE_UNSET'} + } + self.assertEqual(response, self.successResultOf(deferred)) + + def test_response_when_rate_too_low(self): + self.payment_rate_manager.accept_rate_blob_data.return_value = False + query = { + 'blob_data_payment_rate': 'way_too_low', + } + deferred = self.handler.handle_queries(query) + response = { + 'blob_data_payment_rate': 'RATE_TOO_LOW', + } + self.assertEqual(response, self.successResultOf(deferred)) + + def test_blob_unavailable_when_blob_not_validated(self): + self.payment_rate_manager.accept_rate_blob_data.return_value = True + blob = mock.Mock() + blob.is_validated.return_value = False + self.blob_manager.get_blob.return_value = defer.succeed(blob) + query = { + 'blob_data_payment_rate': 'rate', + 'requested_blob': 'blob' + } + deferred = self.handler.handle_queries(query) + response = { + 'blob_data_payment_rate': 'RATE_ACCEPTED', + 'incoming_blob': {'error': 'BLOB_UNAVAILABLE'} + } + self.assertEqual(response, self.successResultOf(deferred)) + + def test_blob_unavailable_when_blob_cannot_be_opened(self): + self.payment_rate_manager.accept_rate_blob_data.return_value = True + blob = mock.Mock() + blob.is_validated.return_value = True + blob.open_for_reading.return_value = None + self.blob_manager.get_blob.return_value = defer.succeed(blob) + query = { + 'blob_data_payment_rate': 'rate', + 'requested_blob': 'blob' + } + deferred = self.handler.handle_queries(query) + response = { + 'blob_data_payment_rate': 'RATE_ACCEPTED', + 'incoming_blob': {'error': 'BLOB_UNAVAILABLE'} + } + self.assertEqual(response, self.successResultOf(deferred)) + + def test_blob_details_are_set_when_all_conditions_are_met(self): + self.payment_rate_manager.accept_rate_blob_data.return_value = True + blob = mock.Mock() + blob.is_validated.return_value = True + blob.open_for_reading.return_value = True + blob.blob_hash = 'DEADBEEF' + blob.length = 42 + self.blob_manager.get_blob.return_value = defer.succeed(blob) + query = { + 'blob_data_payment_rate': 'rate', + 'requested_blob': 'blob' + } + deferred = self.handler.handle_queries(query) + response = { + 'blob_data_payment_rate': 'RATE_ACCEPTED', + 'incoming_blob': { + 'blob_hash': 'DEADBEEF', + 'length': 42 + } + } + self.assertEqual(response, self.successResultOf(deferred)) + + +class TestBlobRequestHandlerSender(unittest.TestCase): + def test_nothing_happens_if_not_currently_uploading(self): + handler = BlobRequestHandler.BlobRequestHandler(None, None, None) + handler.currently_uploading = None + deferred = handler.send_blob_if_requested(None) + self.assertEqual(True, self.successResultOf(deferred)) + + def test_file_is_sent_to_consumer(self): + # TODO: also check that the expected payment values are set + consumer = proto_helpers.StringTransport() + test_file = StringIO.StringIO('test') + handler = BlobRequestHandler.BlobRequestHandler(None, None, None) + handler.peer = mock.create_autospec(Peer.Peer) + handler.currently_uploading = mock.Mock() + handler.read_handle = test_file + handler.send_blob_if_requested(consumer) + while consumer.producer: + consumer.producer.resumeProducing() + self.assertEqual(consumer.value(), 'test') diff --git a/tests/lbrynet/core/test_LBRYExchangeRateManager.py b/tests/lbrynet/core/test_LBRYExchangeRateManager.py new file mode 100644 index 000000000..2a6457536 --- /dev/null +++ b/tests/lbrynet/core/test_LBRYExchangeRateManager.py @@ -0,0 +1,38 @@ +import mock +from lbrynet.core import LBRYMetadata +from lbrynet.lbrynet_daemon import LBRYExchangeRateManager + +from twisted.trial import unittest + + +class LBRYFeeFormatTest(unittest.TestCase): + def test_fee_created_with_correct_inputs(self): + fee_dict = { + 'USD': { + 'amount': 10.0, + 'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9" + } + } + fee = LBRYMetadata.LBRYFeeValidator(fee_dict) + self.assertEqual(10.0, fee['USD']['amount']) + + +class LBRYFeeTest(unittest.TestCase): + def setUp(self): + self.patcher = mock.patch('time.time') + self.time = self.patcher.start() + self.time.return_value = 0 + + def tearDown(self): + self.time.stop() + + def test_fee_converts_to_lbc(self): + fee_dict = { + 'USD': { + 'amount': 10.0, + 'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9" + } + } + rates = {'BTCLBC': {'spot': 3.0, 'ts': 2}, 'USDBTC': {'spot': 2.0, 'ts': 3}} + manager = LBRYExchangeRateManager.DummyExchangeRateManager(rates) + self.assertEqual(60.0, manager.to_lbc(fee_dict).amount) \ No newline at end of file diff --git a/tests/lbrynet/core/test_LBRYMetadata.py b/tests/lbrynet/core/test_LBRYMetadata.py new file mode 100644 index 000000000..e5c3255b3 --- /dev/null +++ b/tests/lbrynet/core/test_LBRYMetadata.py @@ -0,0 +1,129 @@ +from lbrynet.core import LBRYMetadata +from twisted.trial import unittest + + +class MetadataTest(unittest.TestCase): + def test_assertion_if_source_is_missing(self): + metadata = {} + with self.assertRaises(AssertionError): + LBRYMetadata.Metadata(metadata) + + def test_metadata_works_without_fee(self): + metadata = { + 'license': 'Oscilloscope Laboratories', + 'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.', + 'language': 'en', + 'title': "It's a Disaster", + 'author': 'Written and directed by Todd Berger', + 'sources': { + 'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'}, + 'content-type': 'audio/mpeg', + 'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg' + } + m = LBRYMetadata.Metadata(metadata) + self.assertFalse('key' in m) + + def test_assertion_if_invalid_source(self): + metadata = { + 'license': 'Oscilloscope Laboratories', + 'fee': {'LBC': {'amount': 50.0, 'address': 'bRQJASJrDbFZVAvcpv3NoNWoH74LQd5JNV'}}, + 'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.', + 'language': 'en', + 'title': "It's a Disaster", + 'author': 'Written and directed by Todd Berger', + 'sources': { + 'fake': 'source'}, + 'content-type': 'audio/mpeg', + 'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg' + } + with self.assertRaises(AssertionError): + LBRYMetadata.Metadata(metadata) + + def test_assertion_if_missing_v001_field(self): + metadata = { + 'license': 'Oscilloscope Laboratories', + 'fee': {'LBC': {'amount': 50.0, 'address': 'bRQJASJrDbFZVAvcpv3NoNWoH74LQd5JNV'}}, + 'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.', + 'language': 'en', + 'author': 'Written and directed by Todd Berger', + 'sources': { + 'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'}, + 'content-type': 'audio/mpeg', + 'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg' + } + with self.assertRaises(AssertionError): + LBRYMetadata.Metadata(metadata) + + def test_version_is_001_if_all_fields_are_present(self): + metadata = { + 'license': 'Oscilloscope Laboratories', + 'fee': {'LBC': {'amount': 50.0, 'address': 'bRQJASJrDbFZVAvcpv3NoNWoH74LQd5JNV'}}, + 'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.', + 'language': 'en', + 'title': "It's a Disaster", + 'author': 'Written and directed by Todd Berger', + 'sources': { + 'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'}, + 'content-type': 'audio/mpeg', + 'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg' + } + m = LBRYMetadata.Metadata(metadata) + self.assertEquals('0.0.1', m.meta_version) + + def test_assertion_if_there_is_an_extra_field(self): + metadata = { + 'license': 'NASA', + 'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}}, + 'ver': '0.0.2', + 'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines', + 'language': 'en', + 'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger', + 'title': 'Thermonuclear Art', + 'sources': { + 'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'}, + 'nsfw': False, + 'content-type': 'video/mp4', + 'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg', + 'MYSTERYFIELD': '?' + } + with self.assertRaises(AssertionError): + LBRYMetadata.Metadata(metadata) + + def test_version_is_002_if_all_fields_are_present(self): + metadata = { + 'license': 'NASA', + 'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}}, + 'ver': '0.0.2', + 'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines', + 'language': 'en', + 'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger', + 'title': 'Thermonuclear Art', + 'sources': { + 'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'}, + 'nsfw': False, + 'content-type': 'video/mp4', + 'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg' + } + m = LBRYMetadata.Metadata(metadata) + self.assertEquals('0.0.2', m.meta_version) + + def test_version_claimed_is_001_but_version_is_002(self): + metadata = { + 'license': 'NASA', + 'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}}, + 'ver': '0.0.1', + 'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines', + 'language': 'en', + 'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger', + 'title': 'Thermonuclear Art', + 'sources': { + 'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'}, + 'nsfw': False, + 'content-type': 'video/mp4', + 'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg' + } + with self.assertRaises(AssertionError): + LBRYMetadata.Metadata(metadata) + + + diff --git a/tests/lbrynet/core/test_utils.py b/tests/lbrynet/core/test_utils.py new file mode 100644 index 000000000..9fc14f93a --- /dev/null +++ b/tests/lbrynet/core/test_utils.py @@ -0,0 +1,19 @@ +from lbrynet.core import utils + +from twisted.trial import unittest + + +class CompareVersionTest(unittest.TestCase): + def test_compare_versions_isnot_lexographic(self): + self.assertTrue(utils.version_is_greater_than('0.3.10', '0.3.6')) + + def test_same_versions_return_false(self): + self.assertFalse(utils.version_is_greater_than('1.3.9', '1.3.9')) + + def test_same_release_is_greater_then_beta(self): + self.assertTrue(utils.version_is_greater_than('1.3.9', '1.3.9b1')) + + def test_version_can_have_four_parts(self): + self.assertTrue(utils.version_is_greater_than('1.3.9.1', '1.3.9')) + + diff --git a/tests/lbrynet/lbrynet_daemon/__init__.py b/tests/lbrynet/lbrynet_daemon/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py b/tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py new file mode 100644 index 000000000..104e06c01 --- /dev/null +++ b/tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py @@ -0,0 +1,38 @@ +import mock +import requests +from twisted.trial import unittest + +from lbrynet.lbrynet_daemon import LBRYDaemon + + +class MiscTests(unittest.TestCase): + def test_get_lbrynet_version_from_github(self): + response = mock.create_autospec(requests.Response) + # don't need to mock out the entire response from the api + # but at least need 'tag_name' + response.json.return_value = { + "url": "https://api.github.com/repos/lbryio/lbry/releases/3685199", + "assets_url": "https://api.github.com/repos/lbryio/lbry/releases/3685199/assets", + "html_url": "https://github.com/lbryio/lbry/releases/tag/v0.3.8", + "id": 3685199, + "tag_name": "v0.3.8", + "prerelease": False + } + with mock.patch('lbrynet.lbrynet_daemon.LBRYDaemon.requests') as req: + req.get.return_value = response + self.assertEqual('0.3.8', LBRYDaemon.get_lbrynet_version_from_github()) + + def test_error_is_thrown_if_prerelease(self): + response = mock.create_autospec(requests.Response) + response.json.return_value = { + "tag_name": "v0.3.8", + "prerelease": True + } + with mock.patch('lbrynet.lbrynet_daemon.LBRYDaemon.requests') as req: + req.get.return_value = response + with self.assertRaises(Exception): + LBRYDaemon.get_lbrynet_version_from_github() + + def test_error_is_thrown_when_version_cant_be_parsed(self): + with self.assertRaises(Exception): + LBRYDaemon.get_version_from_tag('garbage') diff --git a/tests/lbrynet_test_bot.py b/tests/lbrynet_test_bot.py deleted file mode 100644 index a76a366d8..000000000 --- a/tests/lbrynet_test_bot.py +++ /dev/null @@ -1,60 +0,0 @@ -import xmlrpclib -import json -from datetime import datetime -from time import sleep -from slackclient import SlackClient - -def get_conf(): - f = open('testbot.conf', 'r') - token = f.readline().replace('\n', '') - channel = f.readline().replace('\n', '') - f.close() - return token, channel - -def test_lbrynet(lbry, slack, channel): - logfile = open('lbrynet_test_log.txt', 'a') - - try: - path = lbry.get('testlbrynet')['path'] - except: - msg = '[' + str(datetime.now()) + '] ! Failed to obtain LBRYnet test file' - slack.rtm_connect() - slack.rtm_send_message(channel, msg) - print msg - logfile.write(msg + '\n') - - file_name = path.split('/')[len(path.split('/'))-1] - - for n in range(10): - files = [f for f in lbry.get_lbry_files() if (json.loads(f)['file_name'] == file_name) and json.loads(f)['completed']] - if files: - break - sleep(30) - - if files: - msg = '[' + str(datetime.now()) + '] LBRYnet download test successful' - slack.rtm_connect() - # slack.rtm_send_message(channel, msg) - print msg - logfile.write(msg + '\n') - - else: - msg = '[' + str(datetime.now()) + '] ! Failed to obtain LBRYnet test file' - slack.rtm_connect() - slack.rtm_send_message(channel, msg) - print msg - logfile.write(msg + '\n') - - lbry.delete_lbry_file('test.jpg') - logfile.close() - -token, channel = get_conf() - -sc = SlackClient(token) -sc.rtm_connect() -print 'Connected to slack' -daemon = xmlrpclib.ServerProxy("http://localhost:7080") - -while True: - test_lbrynet(daemon, sc, channel) - sleep(600)