├── .env ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.rst ├── LICENSE ├── MANIFEST.in ├── README.md ├── assets ├── index.html └── video │ └── rtcontrol-curses.ogg ├── bootstrap.sh ├── debian ├── changelog └── control ├── docs ├── Makefile ├── SUPPORT.rst ├── _static │ ├── css │ │ └── custom.css │ ├── img │ │ ├── favicon.ico │ │ ├── help.png │ │ ├── logo-75.png │ │ ├── logo-wide.svg │ │ ├── logo.png │ │ └── logo.svg │ └── js │ │ └── custom.js ├── advanced-monitoring.rst ├── advanced-queue.rst ├── advanced-rtcontrol.rst ├── advanced-rtxmlrpc.rst ├── advanced-tree-watch.rst ├── advanced.rst ├── api.rst ├── apidoc │ ├── pyrocore.daemon.rst │ ├── pyrocore.rst │ ├── pyrocore.scripts.rst │ ├── pyrocore.torrent.rst │ ├── pyrocore.ui.rst │ ├── pyrocore.util.rst │ └── tempita.rst ├── conf.py ├── contributing.rst ├── custom-fields.rst ├── custom-jobs.rst ├── custom-scripts.rst ├── custom.rst ├── examples │ ├── conky-rtorstat.png │ ├── ratio_histo.png │ ├── rt-backseat │ ├── rt-down-stats.py │ ├── rt-heatmap.py │ ├── rt-stuck-trackers.py │ ├── rt_cron_throttle_seed │ ├── rtcontrol-colors.png │ ├── rtorrent-ex.png │ ├── rtorrent.rc │ ├── rtorstat.png │ ├── rtuptime │ ├── start.sh │ └── tmux.conf ├── experimental.rst ├── howto.rst ├── include-api-uml.rst ├── include-contacts.rst ├── include-xmlrpc-dialects.rst ├── index.rst ├── installation.rst ├── license.rst ├── overview.rst ├── references-cli-usage.rst ├── references.rst ├── requirements.txt ├── setup.rst ├── tempita.rst ├── troubleshooting.rst ├── updating.rst ├── usage-cli-tools.rst ├── usage-rtcontrol.rst ├── usage-std-config.rst ├── usage-templates.rst ├── usage.rst └── videos │ ├── bash-completion.gif │ └── rtcontrol-curses.gif ├── pavement.py ├── paver-minilib.zip ├── pylint.cfg ├── requirements-dev.txt ├── requirements-torque.txt ├── requirements.txt ├── setup.cfg ├── setup.py ├── src ├── pyrocore │ ├── __init__.py │ ├── config.py │ ├── daemon │ │ ├── __init__.py │ │ └── webapp.py │ ├── data │ │ ├── config │ │ │ ├── bash-completion │ │ │ ├── color-schemes │ │ │ │ ├── default-16.rc │ │ │ │ ├── default-256.rc │ │ │ │ ├── default-8.rc │ │ │ │ ├── happy-pastel.rc │ │ │ │ ├── solarized-blue.rc │ │ │ │ └── solarized-yellow.rc │ │ │ ├── config.ini │ │ │ ├── config.py │ │ │ ├── logging.cron.ini │ │ │ ├── logging.scripts.ini │ │ │ ├── logging.torque.ini │ │ │ ├── rtorrent-pyro.rc │ │ │ ├── rtorrent.d │ │ │ │ ├── 00-default.rc │ │ │ │ ├── auto-scrape.rc │ │ │ │ ├── bind-navigation-keys.rc │ │ │ │ ├── categories.rc │ │ │ │ ├── collapse-built-in-views.rc │ │ │ │ ├── commands.rc │ │ │ │ ├── helper-methods.rc │ │ │ │ ├── logging.rc │ │ │ │ ├── quick-help.rc │ │ │ │ ├── theming.rc │ │ │ │ ├── timestamps.rc │ │ │ │ ├── torque.rc │ │ │ │ ├── view-datasize.rc │ │ │ │ ├── view-indemand.rc │ │ │ │ ├── view-last_xfer.rc │ │ │ │ ├── view-messages.rc │ │ │ │ ├── view-ratio.rc │ │ │ │ ├── view-tagged.rc │ │ │ │ ├── view-trackers.rc │ │ │ │ └── view-uploaded.rc │ │ │ ├── templates │ │ │ │ ├── conky │ │ │ │ │ ├── conkyrc │ │ │ │ │ └── rtorstat.txt │ │ │ │ ├── irc_status.txt │ │ │ │ ├── json │ │ │ │ ├── orphans.txt │ │ │ │ ├── rss.xml │ │ │ │ └── rtorstat.html │ │ │ └── torque.ini │ │ ├── htdocs │ │ │ ├── css │ │ │ │ ├── custom.css │ │ │ │ └── style.css │ │ │ ├── favicon.ico │ │ │ ├── img │ │ │ │ └── pyroscope.png │ │ │ ├── index.html │ │ │ └── js │ │ │ │ └── charts.js │ │ ├── img │ │ │ └── rt-logo.png │ │ └── screenlet │ │ │ ├── icon.png │ │ │ └── themes │ │ │ ├── blueish │ │ │ └── theme.conf │ │ │ └── default │ │ │ └── theme.conf │ ├── error.py │ ├── scripts │ │ ├── __init__.py │ │ ├── base.py │ │ ├── chtor.py │ │ ├── hashcheck.py │ │ ├── lstor.py │ │ ├── mktor.py │ │ ├── pyroadmin.py │ │ ├── pyrotorque.py │ │ ├── rtcontrol.py │ │ ├── rtevent.py │ │ ├── rtmv.py │ │ ├── rtsweep.py │ │ └── rtxmlrpc.py │ ├── torrent │ │ ├── __init__.py │ │ ├── broom.py │ │ ├── engine.py │ │ ├── filter.py │ │ ├── formatting.py │ │ ├── jobs.py │ │ ├── queue.py │ │ ├── rtorrent.py │ │ └── watch.py │ ├── ui │ │ ├── __init__.py │ │ ├── categories.py │ │ └── theming.py │ └── util │ │ ├── __init__.py │ │ ├── algo.py │ │ ├── load_config.py │ │ ├── matching.py │ │ ├── metafile.py │ │ ├── osmagic.py │ │ ├── pymagic.py │ │ ├── stats.py │ │ ├── traits.py │ │ └── xmlrpc.py ├── scripts │ ├── add-categories.sh │ ├── make-rtorrent-config.sh │ ├── migrate_rtorrent_rc.sh │ └── rt09x-command-names.sed └── tests │ ├── __init__.py │ ├── fifotest.sh │ ├── logging.cfg │ ├── private.torrent │ ├── test.torrent │ ├── test_algo.py │ ├── test_config.py │ ├── test_engine.py │ ├── test_flexget.py │ ├── test_formatting.py │ ├── test_matching.py │ ├── test_metafile.py │ ├── test_osmagic.py │ ├── test_pymagic.py │ ├── test_rtorrent.py │ ├── test_traits.py │ └── test_xmlrpc.py ├── update-to-head.sh └── util.sh /.env: -------------------------------------------------------------------------------- 1 | # autoenv activation 2 | if [[ -f bin/activate ]]; then . bin/activate; fi 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | # Subprojects 57 | auvyon/ 58 | pyrobase/ 59 | 60 | # virtualenv 61 | bin/ 62 | include/ 63 | local/ 64 | man/ 65 | share/ 66 | 67 | wiki/ 68 | pip-selfcheck.json 69 | docs/uml_images/ 70 | docs/apidocs/ 71 | EGG-INFO/ 72 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://docs.travis-ci.com/user/customizing-the-build/ 3 | language: python 4 | python: 5 | - "2.7" 6 | #before_install: 7 | # - sudo apt-get update -qq 8 | # - sudo apt-get install -qq python-apt python-pycurl 9 | install: 10 | - pip install -U pip 11 | - pip install -U setuptools 12 | - pip install -U wheel 13 | - pip install -r requirements-dev.txt 14 | - pip install -e .[templates,repl] 15 | script: 16 | - paver test 17 | - paver functest 18 | - paver installtest 19 | 20 | # gitter.im 21 | notifications: 22 | webhooks: 23 | urls: 24 | - https://webhooks.gitter.im/e/f8b72e277648046791cf 25 | on_success: change # options: [always|never|change] default: always 26 | on_failure: always # options: [always|never|change] default: always 27 | on_start: never # options: [always|never|change] default: always 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Contributing Guidelines 2 | ======================= 3 | 4 | See `contribution-guide.org`_ for the basics on contributing 5 | to an open source project. 6 | 7 | 8 | .. _issue-reporting: 9 | 10 | Reporting an Issue, or Requesting a Feature 11 | ------------------------------------------- 12 | 13 | Any defects and feature requests are managed using GitHub's 14 | *issue tracker*. 15 | If you never opened an issue on GitHub before, consult the 16 | `Mastering Issues`_ guide. 17 | 18 | Before creating a bug report, please read the `Trouble-Shooting Guide`_ 19 | and also see ``contribution-guide.org``'s `Submitting Bugs`_. 20 | 21 | .. _`Mastering Issues`: https://guides.github.com/features/issues/ 22 | .. _`contribution-guide.org`: http://www.contribution-guide.org/ 23 | .. _`Submitting Bugs`: http://www.contribution-guide.org/#submitting-bugs 24 | .. _`Trouble-Shooting Guide`: https://pyrocore.readthedocs.org/en/latest/troubleshooting.html 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Hints for building the source distribution 2 | # See http://docs.python.org/distutils/sourcedist.html 3 | 4 | include bootstrap.sh update-to-head.sh util.sh 5 | include setup.py requirements*.txt pavement.py paver-minilib.zip 6 | include LICENSE CONTRIBUTING.rst 7 | include .env .gitignore .travis.yml pylint.cfg 8 | 9 | recursive-include debian changelog control 10 | 11 | recursive-include assets *.html 12 | #recursive-include assets/video *.ogg 13 | 14 | recursive-include docs *.rst *.py Makefile requirements.txt 15 | recursive-include docs/_static *.ico *.png 16 | recursive-include docs/examples rt-backseat rt_cron_throttle_seed *.conf *.png *.rc *.sh 17 | #recursive-include docs/videos *.gif 18 | 19 | recursive-include src/tests *.py *.sh *.cfg *.torrent 20 | recursive-include src/scripts *.sh *.sed 21 | recursive-include src/pyrocore/data/config rtorrent-*.rc *.ini *.py bash-completion 22 | recursive-include src/pyrocore/data/config/color-schemes *.rc 23 | recursive-include src/pyrocore/data/config/rtorrent.d *.rc 24 | recursive-include src/pyrocore/data/config/templates *.txt *.html conkyrc json 25 | recursive-include src/pyrocore/data/htdocs *.css *.ico *.png *.html *.js 26 | recursive-include src/pyrocore/data/screenlet *.png *.conf 27 | recursive-include src/pyrocore/data/img *.png 28 | 29 | prune docs/build 30 | prune docs/videos 31 | prune assets/video 32 | 33 | # END of MANIFEST 34 | -------------------------------------------------------------------------------- /assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | Logo 8 | 9 | 10 |

11 | pyroscope 12 |

13 | Python Torrent Tools 14 |
15 | 16 |

PyroScope is a collection of tools for 17 | the BitTorrent protocol and especially 18 | the rTorrent client.

It offers the following components: 19 |

20 | 21 | 24 | 25 |

26 | To get started right away, see the QuickStartGuide. 27 | If you want to get a first impression without installing the software, look at the 28 | ScreenShotGallery. It's also very easy to 29 | WriteYourOwnScripts to automate anything 30 | that the standard commands can't do (also see the API documentation). 31 |

32 | To get in contact and share your experiences with other users of 33 | PyroScope, join the 34 | pyroscope-users 35 | mailing list or the inofficial 36 | #rtorrent 37 | channel.

38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /assets/video/rtcontrol-curses.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/assets/video/rtcontrol-curses.ogg -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | # This script has to be sourced in a shell and is thus NOT executable. 2 | # 3 | # Set up project 4 | # 5 | # Copyright (c) 2010-2017 The PyroScope Project 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | 21 | SCRIPTNAME="$0" 22 | test "$SCRIPTNAME" != "-bash" -a "$SCRIPTNAME" != "-/bin/bash" || SCRIPTNAME="${BASH_SOURCE[0]}" 23 | 24 | export DEBFULLNAME=pyroscope 25 | export DEBEMAIL=pyroscope.project@gmail.com 26 | 27 | deactivate 2>/dev/null || true 28 | test -z "$PYTHON" -a -x "/usr/bin/python2" && PYTHON="/usr/bin/python2" 29 | test -z "$PYTHON" -a -x "/usr/bin/python" && PYTHON="/usr/bin/python" 30 | test -z "$PYTHON" && PYTHON="python" 31 | 32 | git_projects="pyrobase auvyon" 33 | . ./util.sh || return 1 # load funcs 34 | 35 | # generic bootstrap 36 | test -f ./bin/activate || install_venv --no-site-packages 37 | update_venv ./bin/pip 38 | ln -nfs python ./bin/python-pyrocore 39 | . ./bin/activate || abend "venv activate failed" || return 1 40 | 41 | grep DEBFULLNAME bin/activate >/dev/null || cat >>bin/activate </dev/null \ 47 | && "$PROJECT_ROOT"/bin/pip uninstall -y distribute || true 48 | 49 | # tools 50 | pip_install -U "pip" 51 | pip_install -U "setuptools>=38" 52 | pip_install -U "packaging" 53 | pip_install -U "paver>=1.0.5" 54 | ##pip_install -U "nose>=1.0" 55 | ##pip_install -U "coverage>=3.4" 56 | pip_install -U "yolk3k" 57 | ##pip_install -U "PasteScript>=1.7.3" 58 | 59 | # Harmless options (just install them, but ignore errors) 60 | pip_install_opt -U "Tempita>=0.5.1" 61 | pip_install_opt -U "APScheduler>=2.0.2,<3" 62 | pip_install_opt -U "waitress>=0.8.2" 63 | pip_install_opt -U "WebOb>=1.2.3" 64 | ##pip_install_opt -U "psutil>=0.6.1" 65 | 66 | # pyrobase 67 | test ! -d pyrobase || ( builtin cd pyrobase && ../bin/paver -q develop -U) 68 | 69 | # essential tools 70 | test -x ./bin/paver || pip_install -U "paver>=1.0.1" 71 | 72 | # package dependencies (optional) 73 | for pkgreq in "Tempita>=0.5.1" "APScheduler>=2.0.2,<3"; do 74 | pip_install_opt "$pkgreq" 75 | done 76 | 77 | # git dependencies 78 | this_paver="$PWD/bin/paver" 79 | for project in $git_projects; do 80 | if test -f ../$project/setup.py; then 81 | ( builtin cd ../$project && $this_paver -q develop -U ) 82 | elif test -f $project/setup.py; then 83 | ( builtin cd $project && $this_paver -q develop -U ) 84 | else 85 | abend "Project '$project' is not initialized!" 86 | return 1 87 | fi 88 | done 89 | 90 | # project 91 | ./bin/paver -q develop -U || abend "installing $(basename $(pwd)) into venv failed" 92 | ./bin/paver bootstrap || abend "bootstrapping $(basename $(pwd)) failed" 93 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | # stub file to make dch work - no .deb packaging yet 2 | 3 | Build-Depends: build-essential, devtools, python-dev 4 | 5 | Depends: python2.5, python-virtualenv 6 | 7 | -------------------------------------------------------------------------------- /docs/SUPPORT.rst: -------------------------------------------------------------------------------- 1 | How to Get Support Efficiently? 2 | =============================== 3 | 4 | To resolve any problems with or questions about your configuration 5 | and software installation, there are several ways that you should try 6 | from top to bottom: 7 | 8 | #. *Always* look into the “Troubleshooting Guide” of the manual as a first measure, 9 | which is often the fastest way to get back to a working system. 10 | That guide also explains how to efficiently report your problem 11 | when you cannot fix it yourself. 12 | #. To get in contact with other users of `PyroScope`_ projects, join the 13 | `pyroscope-users`_ mailing list or the inofficial ``##rtorrent`` 14 | channel on ``irc.freenode.net``. 15 | #. *Do* add problems that are clearly a bug to the issue tracker on GitHub 16 | – however use the support channels above for questions about the documentation 17 | or use of the software. 18 | 19 | .. _`PyroScope`: https://github.com/pyroscope 20 | .. _`pyroscope-users`: http://groups.google.com/group/pyroscope-users 21 | .. _`rTorrent-PS`: https://github.com/pyroscope/rtorrent-ps 22 | -------------------------------------------------------------------------------- /docs/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | /* Custom Sphinx Styles */ 2 | body { 3 | color: #000; 4 | } 5 | -------------------------------------------------------------------------------- /docs/_static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/docs/_static/img/favicon.ico -------------------------------------------------------------------------------- /docs/_static/img/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/docs/_static/img/help.png -------------------------------------------------------------------------------- /docs/_static/img/logo-75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/docs/_static/img/logo-75.png -------------------------------------------------------------------------------- /docs/_static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/docs/_static/img/logo.png -------------------------------------------------------------------------------- /docs/_static/js/custom.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | $('dt[id]').each(function() { 3 | $('\u00B6'). 4 | attr('href', '#' + this.id). 5 | attr('title', _('Permalink to this definition')). 6 | appendTo(this); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /docs/advanced.rst: -------------------------------------------------------------------------------- 1 | Advanced Features 2 | ================= 3 | 4 | .. note:: 5 | 6 | Using these features requires some knowledge in the area Linux, Bash, 7 | and Python beyond a novice level, but they enable you to customize 8 | your setup even further and handle very specific use-cases. 9 | 10 | 11 | .. _advanced-rtcontrol: 12 | 13 | Advanced ‘rtcontrol’ 14 | -------------------- 15 | 16 | .. include:: advanced-rtcontrol.rst 17 | 18 | 19 | .. _RtXmlRpcExamples: 20 | 21 | Using ‘rtxmlrpc’ 22 | ---------------- 23 | 24 | .. include:: advanced-rtxmlrpc.rst 25 | 26 | 27 | .. _QueueManager: 28 | 29 | rTorrent Queue Manager 30 | ---------------------- 31 | 32 | .. include:: advanced-queue.rst 33 | 34 | 35 | .. _tree-watch: 36 | 37 | Using the Tree Watch Job 38 | ------------------------ 39 | 40 | .. include:: advanced-tree-watch.rst 41 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | API Documentation 4 | ================= 5 | 6 | This is the full ``pyrocore`` API documentation, generated from source. 7 | 8 | Packages & Modules 9 | ------------------ 10 | 11 | .. toctree:: 12 | :maxdepth: 4 13 | 14 | apidoc/pyrocore 15 | 16 | .. include : : include-api-uml.rst 17 | 18 | 19 | Tempita Templating API 20 | ---------------------- 21 | 22 | .. toctree:: 23 | :maxdepth: 4 24 | 25 | apidoc/tempita 26 | -------------------------------------------------------------------------------- /docs/apidoc/pyrocore.daemon.rst: -------------------------------------------------------------------------------- 1 | pyrocore.daemon package 2 | ======================= 3 | 4 | .. automodule:: pyrocore.daemon 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | pyrocore.daemon.webapp module 13 | ----------------------------- 14 | 15 | .. automodule:: pyrocore.daemon.webapp 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/apidoc/pyrocore.rst: -------------------------------------------------------------------------------- 1 | pyrocore package 2 | ================ 3 | 4 | .. automodule:: pyrocore 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Subpackages 10 | ----------- 11 | 12 | .. toctree:: 13 | 14 | pyrocore.daemon 15 | pyrocore.scripts 16 | pyrocore.torrent 17 | pyrocore.ui 18 | pyrocore.util 19 | 20 | Submodules 21 | ---------- 22 | 23 | pyrocore.config module 24 | ---------------------- 25 | 26 | .. automodule:: pyrocore.config 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pyrocore.error module 32 | --------------------- 33 | 34 | .. automodule:: pyrocore.error 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | -------------------------------------------------------------------------------- /docs/apidoc/pyrocore.scripts.rst: -------------------------------------------------------------------------------- 1 | pyrocore.scripts package 2 | ======================== 3 | 4 | .. automodule:: pyrocore.scripts 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | pyrocore.scripts.base module 13 | ---------------------------- 14 | 15 | .. automodule:: pyrocore.scripts.base 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | pyrocore.scripts.chtor module 21 | ----------------------------- 22 | 23 | .. automodule:: pyrocore.scripts.chtor 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | pyrocore.scripts.hashcheck module 29 | --------------------------------- 30 | 31 | .. automodule:: pyrocore.scripts.hashcheck 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | pyrocore.scripts.lstor module 37 | ----------------------------- 38 | 39 | .. automodule:: pyrocore.scripts.lstor 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | pyrocore.scripts.mktor module 45 | ----------------------------- 46 | 47 | .. automodule:: pyrocore.scripts.mktor 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | pyrocore.scripts.pyroadmin module 53 | --------------------------------- 54 | 55 | .. automodule:: pyrocore.scripts.pyroadmin 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | pyrocore.scripts.pyrotorque module 61 | ---------------------------------- 62 | 63 | .. automodule:: pyrocore.scripts.pyrotorque 64 | :members: 65 | :undoc-members: 66 | :show-inheritance: 67 | 68 | pyrocore.scripts.rtcontrol module 69 | --------------------------------- 70 | 71 | .. automodule:: pyrocore.scripts.rtcontrol 72 | :members: 73 | :undoc-members: 74 | :show-inheritance: 75 | 76 | pyrocore.scripts.rtevent module 77 | ------------------------------- 78 | 79 | .. automodule:: pyrocore.scripts.rtevent 80 | :members: 81 | :undoc-members: 82 | :show-inheritance: 83 | 84 | pyrocore.scripts.rtmv module 85 | ---------------------------- 86 | 87 | .. automodule:: pyrocore.scripts.rtmv 88 | :members: 89 | :undoc-members: 90 | :show-inheritance: 91 | 92 | pyrocore.scripts.rtsweep module 93 | ------------------------------- 94 | 95 | .. automodule:: pyrocore.scripts.rtsweep 96 | :members: 97 | :undoc-members: 98 | :show-inheritance: 99 | 100 | pyrocore.scripts.rtxmlrpc module 101 | -------------------------------- 102 | 103 | .. automodule:: pyrocore.scripts.rtxmlrpc 104 | :members: 105 | :undoc-members: 106 | :show-inheritance: 107 | 108 | 109 | -------------------------------------------------------------------------------- /docs/apidoc/pyrocore.torrent.rst: -------------------------------------------------------------------------------- 1 | pyrocore.torrent package 2 | ======================== 3 | 4 | .. automodule:: pyrocore.torrent 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | pyrocore.torrent.broom module 13 | ----------------------------- 14 | 15 | .. automodule:: pyrocore.torrent.broom 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | pyrocore.torrent.engine module 21 | ------------------------------ 22 | 23 | .. automodule:: pyrocore.torrent.engine 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | pyrocore.torrent.filter module 29 | ------------------------------ 30 | 31 | .. automodule:: pyrocore.torrent.filter 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | pyrocore.torrent.formatting module 37 | ---------------------------------- 38 | 39 | .. automodule:: pyrocore.torrent.formatting 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | pyrocore.torrent.jobs module 45 | ---------------------------- 46 | 47 | .. automodule:: pyrocore.torrent.jobs 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | pyrocore.torrent.queue module 53 | ----------------------------- 54 | 55 | .. automodule:: pyrocore.torrent.queue 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | pyrocore.torrent.rtorrent module 61 | -------------------------------- 62 | 63 | .. automodule:: pyrocore.torrent.rtorrent 64 | :members: 65 | :undoc-members: 66 | :show-inheritance: 67 | 68 | pyrocore.torrent.watch module 69 | ----------------------------- 70 | 71 | .. automodule:: pyrocore.torrent.watch 72 | :members: 73 | :undoc-members: 74 | :show-inheritance: 75 | 76 | 77 | -------------------------------------------------------------------------------- /docs/apidoc/pyrocore.ui.rst: -------------------------------------------------------------------------------- 1 | pyrocore.ui package 2 | =================== 3 | 4 | .. automodule:: pyrocore.ui 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | pyrocore.ui.categories module 13 | ----------------------------- 14 | 15 | .. automodule:: pyrocore.ui.categories 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | pyrocore.ui.theming module 21 | -------------------------- 22 | 23 | .. automodule:: pyrocore.ui.theming 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/apidoc/pyrocore.util.rst: -------------------------------------------------------------------------------- 1 | pyrocore.util package 2 | ===================== 3 | 4 | .. automodule:: pyrocore.util 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | pyrocore.util.algo module 13 | ------------------------- 14 | 15 | .. automodule:: pyrocore.util.algo 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | pyrocore.util.load\_config module 21 | --------------------------------- 22 | 23 | .. automodule:: pyrocore.util.load_config 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | pyrocore.util.matching module 29 | ----------------------------- 30 | 31 | .. automodule:: pyrocore.util.matching 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | pyrocore.util.metafile module 37 | ----------------------------- 38 | 39 | .. automodule:: pyrocore.util.metafile 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | pyrocore.util.osmagic module 45 | ---------------------------- 46 | 47 | .. automodule:: pyrocore.util.osmagic 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | pyrocore.util.pymagic module 53 | ---------------------------- 54 | 55 | .. automodule:: pyrocore.util.pymagic 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | pyrocore.util.stats module 61 | -------------------------- 62 | 63 | .. automodule:: pyrocore.util.stats 64 | :members: 65 | :undoc-members: 66 | :show-inheritance: 67 | 68 | pyrocore.util.traits module 69 | --------------------------- 70 | 71 | .. automodule:: pyrocore.util.traits 72 | :members: 73 | :undoc-members: 74 | :show-inheritance: 75 | 76 | pyrocore.util.xmlrpc module 77 | --------------------------- 78 | 79 | .. automodule:: pyrocore.util.xmlrpc 80 | :members: 81 | :undoc-members: 82 | :show-inheritance: 83 | 84 | 85 | -------------------------------------------------------------------------------- /docs/apidoc/tempita.rst: -------------------------------------------------------------------------------- 1 | tempita package 2 | =============== 3 | 4 | .. automodule:: tempita 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | tempita.compat3 module 13 | ---------------------- 14 | 15 | .. automodule:: tempita.compat3 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. Include text from the place where GitHub sees it. 2 | .. include:: ../CONTRIBUTING.rst 3 | 4 | Performing a Release 5 | -------------------- 6 | 7 | #. Check for and fix ``pylint`` violations:: 8 | 9 | paver lint -m 10 | 11 | #. Verify ``debian/changelog`` for completeness and the correct version, and bump the release date:: 12 | 13 | dch -r 14 | 15 | #. Check Travis CI status at https://travis-ci.org/pyroscope/pyrocore 16 | 17 | #. Remove ‘dev’ version tagging from ``setup.cfg``, and perform a release check:: 18 | 19 | sed -i -re 's/^(tag_[a-z ]+=)/##\1/' setup.cfg 20 | paver release 21 | 22 | #. Commit and tag the release:: 23 | 24 | git status # check all is committed 25 | tag="v$(dpkg-parsechangelog | grep '^Version:' | awk '{print $2}')" 26 | git tag -a "$tag" -m "Release $tag" 27 | 28 | #. Build the final release and upload it to PyPI:: 29 | 30 | paver dist_clean sdist bdist_wheel 31 | twine upload dist/*.{zip,whl} 32 | -------------------------------------------------------------------------------- /docs/custom-jobs.rst: -------------------------------------------------------------------------------- 1 | .. Included in custom.rst 2 | 3 | First off, you really need to know a good amount of Python to be able to do this. 4 | But if you do, you can easily add your own background processing, 5 | more versatile and more efficient than calling ``rtcontrol`` in a cron job. 6 | The description here is terse, and mostly just tells you where to look for code examples, 7 | and the basics of how a job implementation interacts with the core system. 8 | 9 | .. note:: 10 | 11 | While some effort will be spent on keeping the API backwards compatible, 12 | there is no guarantee of a stable API. 13 | Follow the commit log and changelogs of releases 14 | to get notified when you need to adapt your code. 15 | 16 | Jobs are created during ``pyrotorque`` startup and registered with the scheduler. 17 | Configuration is taken from the ``[TORQUE]`` section of ``torque.ini``, 18 | and any ``job.«job-name».«param-name»`` setting contributes to a job named ``job-name``. 19 | The ``handler``, ``schedule``, and ``active`` settings are used by the core, 20 | the rest is passed to the ``handler`` class for customization and depends on the job type. 21 | 22 | To locate the job implementation, ``handler`` contains a ``module.path:ClassName`` coordinate of its class. 23 | So ``job.foo.handler = my.code:FooJob`` registers ``FooJob`` under the name ``foo``. 24 | This means a job can be scheduled several times, 25 | given the right configuration and if the job implementation is designed for it. 26 | The given module must be importable of course, 27 | i.e. ``pip install`` it into your ``pyrocore`` virtualenv. 28 | 29 | The ``schedule`` defines the call frequency of the job's ``run`` method, 30 | and ``active`` allows to easily disable a job without removing its configuration 31 | – which is used to provide all the default jobs and their settings. 32 | A job with ``active = False`` is simply ignored and not added to the scheduler on startup. 33 | 34 | The most simple of jobs is the :any:`EngineStats` one. 35 | Click on the link and then on ``[source]`` to see its source code. 36 | Some noteworthy facts: 37 | 38 | * the initializer gets passed a ``config`` parameter, holding all the settings from ``torque.ini`` 39 | for a particular job instance, with the ``job.«name»`` prefix removed. 40 | * ``pyrocore.config`` is imported as ``config_ini``, to not clash with the ``config`` dict passed into jobs. 41 | * create a ``LOG`` attribute as shown, for your logging needs. 42 | * to interact with *rTorrent*, open a proxy connection in ``run``. 43 | * the :any:`InfluxDB` job shows how to access config parameters, e.g. ``self.config.dbname``. 44 | * raise :any:`UserError` in the initializer to report configuration mishaps and prevent ``pyrotorque`` from starting. 45 | 46 | More complex jobs that you can look at are the 47 | :class:`pyrocore.torrent.watch.TreeWatch` and 48 | :class:`pyrocore.torrent.queue.QueueManager` ones. 49 | -------------------------------------------------------------------------------- /docs/custom.rst: -------------------------------------------------------------------------------- 1 | Custom Python Code 2 | ================== 3 | 4 | You can write your own code for ``pyrocore`` implementing custom features, 5 | by adding fields, your own command line scripts, or ``pyrotorque`` jobs. 6 | You probably need a `solid grasp of Python`_ for this. 7 | 8 | .. _`solid grasp of Python`: https://github.com/TechBookHunter/Free-Python-Books#free-python-books 9 | 10 | 11 | .. _CustomFields: 12 | 13 | Defining Custom Fields 14 | ---------------------- 15 | 16 | .. include:: custom-fields.rst 17 | 18 | 19 | .. _custom-template-helpers: 20 | 21 | Adding Custom Template Helpers 22 | ------------------------------ 23 | 24 | In templating contexts, there is an empty ``c`` namespace (think ``custom`` or ``config``), 25 | just like ``h`` for helpers. 26 | You can populate that namespace with your own helpers as you need them, 27 | from simple string transformations to calling external programs or web interfaces. 28 | 29 | The following example illustrates the concept, and belongs into ``~/.pyroscope/config.py``. 30 | 31 | .. code-block:: python 32 | 33 | def _hostname(ip): 34 | """Helper to e.g. look up peer IPs.""" 35 | import socket 36 | 37 | return socket.gethostbyaddr(ip)[0] if ip else ip 38 | 39 | custom_template_helpers.hostname = _hostname 40 | 41 | This demonstrates the call of that helper using a custom field, 42 | a real use-case would be to resolve peer IPs and the like. 43 | 44 | .. code-block:: shell 45 | 46 | $ rtcontrol -qo '{{d.fetch("custom_ip")}} → {{d.fetch("custom_ip") | c.hostname}}' // -/1 47 | 8.8.8.8 → google-public-dns-a.google.com 48 | 49 | 50 | .. _scripts: 51 | 52 | 53 | Writing Your Own Scripts 54 | ------------------------ 55 | 56 | .. include:: custom-scripts.rst 57 | 58 | 59 | .. _torque-custom-jobs: 60 | 61 | Writing Custom Jobs 62 | ------------------- 63 | 64 | .. include:: custom-jobs.rst 65 | -------------------------------------------------------------------------------- /docs/examples/conky-rtorstat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/docs/examples/conky-rtorstat.png -------------------------------------------------------------------------------- /docs/examples/ratio_histo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/docs/examples/ratio_histo.png -------------------------------------------------------------------------------- /docs/examples/rt-backseat: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # Throttle rTorrent for a certain amount of time 3 | 4 | # 5 | # CONFIGURATION 6 | # 7 | timeout="now + 10 minutes" # default timeout 8 | throttled=42 # throttled speed 9 | unit=1024 # unit on command line, default KiB/s 10 | queue=r 11 | 12 | # 13 | # HERE BE DRAGONS! 14 | # 15 | set -e 16 | set +x 17 | 18 | case "$1" in 19 | -h | --help) 20 | echo >&2 "Usage: $0 [«speed» [«timespec»]]" 21 | exit 1 22 | ;; 23 | *) : ;; 24 | esac 25 | 26 | if test -n "$(echo $1 | tr -d 0-9)"; then 27 | echo >&2 "ERROR: Non-numeric speed" 28 | exit 1 29 | fi 30 | 31 | if test -n "$1"; then 32 | throttled="$1" 33 | shift 34 | fi 35 | throttled=$(( $throttled * $unit )) 36 | 37 | if test -n "$1"; then 38 | timeout="$@" 39 | fi 40 | 41 | if test -n "$(atq -q $queue)"; then 42 | # If there are jobs pending, run 1st one now, and then delete them 43 | at -c $(atq -q $queue | cut -f1 | head -n1) | /bin/sh 44 | atrm $(atq -q $queue | cut -f1) 45 | fi 46 | 47 | current=$(rtxmlrpc throttle.global_down.max_rate) 48 | 49 | # Schedule new job to reset rate, and then throttle it 50 | result=$(at -q $queue $timeout <&1 51 | rtxmlrpc -q throttle.global_down.max_rate.set '' $current 52 | EOF 53 | ) || : 54 | if [[ $result =~ .*(error|arbled).* ]]; then 55 | echo >&2 "ERROR: $result" 56 | exit 1 57 | fi 58 | echo $result | sed -re "s~warning: commands will be executed using /bin/sh~~" 59 | rtxmlrpc -q throttle.global_down.max_rate.set '' $throttled 60 | 61 | echo "Speed throttled to $(( $throttled / 1024 )) KiB/s," \ 62 | "back to $(( $current / 1024 )) KiB/s at $timeout." 63 | -------------------------------------------------------------------------------- /docs/examples/rt-down-stats.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python-pyrocore 2 | # -*- coding: utf-8 -*- 3 | from __future__ import division, print_function 4 | 5 | from collections import namedtuple 6 | 7 | from pyrobase import fmt 8 | from pyrocore import config 9 | from pyrocore.util import os 10 | from pyrocore.scripts import base 11 | 12 | 13 | def fmt_duration(secs): 14 | """Format a duration in seconds.""" 15 | return ' '.join(fmt.human_duration(secs, 0, precision=2, short=True).strip().split()) 16 | 17 | 18 | def disk_free(path): 19 | """Return free bytes on partition holding `path`.""" 20 | stats = os.statvfs(path) 21 | return stats.f_bavail * stats.f_frsize 22 | 23 | 24 | class DownloadStats(base.ScriptBaseWithConfig): 25 | """ 26 | Show stats about currently active & pending downloads. 27 | """ 28 | 29 | # argument description for the usage information 30 | ARGS_HELP = "" 31 | 32 | # set your own version 33 | VERSION = '1.0' 34 | 35 | FIELDS = ('is_active', 'left_bytes', 'size_bytes', 'down.rate', 'priority') 36 | MIN_STALLED_RATE = 5 * 1024 37 | STALLED_PERCENT = 10 38 | 39 | def add_options(self): 40 | """ Add program options. 41 | """ 42 | super(DownloadStats, self).add_options() 43 | 44 | def mainloop(self): 45 | proxy = config.engine.open() 46 | all_items = list(config.engine.multicall("incomplete", self.FIELDS)) 47 | 48 | pending = [d for d in all_items if not d.is_active and d.priority > 0] 49 | print("Queued items: ", 50 | fmt.human_size(sum(d.size_bytes for d in pending)), 51 | 'in', len(pending), 'item(s)', 52 | '[{} free]'.format(fmt.human_size(disk_free(proxy.directory.default())).strip())) 53 | 54 | items = [d for d in all_items if d.is_active] 55 | if not items: 56 | print("No active downloads!") 57 | return 58 | 59 | good_rates = [d.down_rate for d in items if d.down_rate > self.MIN_STALLED_RATE] 60 | stalled_rate = max( 61 | self.MIN_STALLED_RATE, 62 | self.STALLED_PERCENT / 100 * sum(good_rates) / len(good_rates) if good_rates else 0) 63 | stalled_count = sum(d.down_rate < stalled_rate for d in items) 64 | global_down_rate = proxy.throttle.global_down.rate() 65 | 66 | total_size = sum(d.size_bytes for d in items) 67 | total_left = sum(d.left_bytes for d in items) 68 | eta_list = [0] 69 | if stalled_count < len(items): 70 | eta_list = [d.left_bytes / d.down_rate for d in items if d.down_rate >= stalled_rate] 71 | eta_max = total_left / (global_down_rate or 1) 72 | 73 | stalled_info = ', {} stalled below {}/s'.format( 74 | stalled_count, fmt.human_size(stalled_rate).strip()) if stalled_count else '' 75 | print("Size left to download: ", 76 | fmt.human_size(total_left), 'of', fmt.human_size(total_size).strip()) 77 | print("Overall download speed:", fmt.human_size(global_down_rate) + '/s') 78 | print("ETA (min → max): ", 79 | fmt_duration(min(eta_list)), '→', fmt_duration(eta_max), '…', fmt_duration(max(eta_list)), 80 | '[{} item(s){}]'.format(len(items), stalled_info), 81 | ) 82 | 83 | 84 | if __name__ == '__main__': 85 | base.ScriptBase.setup() 86 | DownloadStats().run() 87 | -------------------------------------------------------------------------------- /docs/examples/rt-heatmap.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python-pyrocore 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | import subprocess 5 | 6 | import pandas as pd 7 | 8 | from pyrobase.osutil import shell_escape as quoted 9 | 10 | from pyrocore import config 11 | from pyrocore.scripts import base 12 | 13 | 14 | class HeatMap(base.ScriptBase): 15 | """ 16 | Create a heatmap based on JSON data generated via `rtcontrol`. 17 | 18 | THIS IS EXPERIMENTAL, THERE'S NO SUPPORT WHATSOEVER! 19 | 20 | To run this script, you need to first install `seaborn`: 21 | 22 | ~/.local/pyroscope/bin/pip install seaborn 23 | 24 | Then, try this: 25 | 26 | rtcontrol --json -qo name,alias,ratio -A dupes= loaded=-1w \ 27 | | docs/examples/rt-heapmap.py -o name alias ratio 28 | 29 | rtcontrol --json -qo tv_series,alias,ratio -A dupes= ratio=+4 loaded=-6m tv_series=\! \ 30 | | docs/examples/rt-heapmap.py -o tv_series alias ratio 31 | """ 32 | 33 | # argument description for the usage information 34 | ARGS_HELP = " " 35 | 36 | # set your own version 37 | VERSION = '1.0' 38 | 39 | # (optionally) define your licensing 40 | COPYRIGHT = u'Copyright (c) 2018 PyroScope Project' 41 | 42 | # Minimum upper range for color map 43 | CMAP_MIN_MAX = 4.0 44 | 45 | 46 | def add_options(self): 47 | """ Add program options. 48 | """ 49 | super(HeatMap, self).add_options() 50 | 51 | # basic options 52 | self.add_bool_option('-o', '--open', 53 | help="open the resulting image file in your viewer") 54 | 55 | def heatmap(self, df, imagefile): 56 | """ Create the heat map. 57 | """ 58 | import seaborn as sns 59 | import matplotlib.ticker as tkr 60 | import matplotlib.pyplot as plt 61 | from matplotlib.colors import LinearSegmentedColormap 62 | 63 | sns.set() 64 | with sns.axes_style('whitegrid'): 65 | fig, ax = plt.subplots(figsize=(5, 11)) # inches 66 | 67 | cmax = max(df[self.args[2]].max(), self.CMAP_MIN_MAX) 68 | csteps = { 69 | 0.0: 'darkred', 0.3/cmax: 'red', 0.6/cmax: 'orangered', 0.9/cmax: 'coral', 70 | 1.0/cmax: 'skyblue', 1.5/cmax: 'blue', 1.9/cmax: 'darkblue', 71 | 2.0/cmax: 'darkgreen', 3.0/cmax: 'green', 72 | (self.CMAP_MIN_MAX - .1)/cmax: 'palegreen', 1.0: 'yellow'} 73 | cmap = LinearSegmentedColormap.from_list('RdGrYl', sorted(csteps.items()), N=256) 74 | 75 | dataset = df.pivot(*self.args) 76 | 77 | sns.heatmap(dataset, mask=dataset.isnull(), annot=False, linewidths=.5, square=True, ax=ax, cmap=cmap, 78 | annot_kws=dict(stretch='condensed')) 79 | ax.tick_params(axis='y', labelrotation=30, labelsize=8) 80 | # ax.get_yaxis().set_major_formatter(tkr.FuncFormatter(lambda x, p: x)) 81 | plt.savefig(imagefile) 82 | 83 | def mainloop(self): 84 | """ The main loop. 85 | """ 86 | #proxy = config.engine.open() 87 | 88 | if len(self.args) != 3: 89 | self.fatal("You MUST provide names for index (row), column, and value!") 90 | 91 | # Load data 92 | df = pd.read_json(sys.stdin, orient='records') 93 | df[self.args[0]] = df[self.args[0]].str.slice(0, 42) 94 | #print(df[self.args[0]]) 95 | #print(df.head(5)) 96 | df = df.groupby(self.args[:2], as_index=False).mean() 97 | 98 | # Create image 99 | imagefile = 'heatmap.png' 100 | self.heatmap(df, imagefile) 101 | 102 | # Optionally show created image 103 | if self.options.open: 104 | subprocess.call("xdg-open {} &".format(quoted(imagefile)), shell=True) 105 | 106 | #self.LOG.info("XMLRPC stats: %s" % proxy) 107 | 108 | 109 | if __name__ == "__main__": 110 | base.ScriptBase.setup() 111 | HeatMap().run() 112 | -------------------------------------------------------------------------------- /docs/examples/rt-stuck-trackers.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python-pyrocore 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyrocore import config 5 | from pyrocore.scripts import base 6 | 7 | 8 | class StuckTrackers(base.ScriptBaseWithConfig): 9 | """ 10 | List started items whose announces are stuck, i.e. where 11 | last activity is older than the normal announce interval. 12 | """ 13 | 14 | # argument description for the usage information 15 | ARGS_HELP = "" 16 | 17 | 18 | def add_options(self): 19 | """ Add program options. 20 | """ 21 | super(StuckTrackers, self).add_options() 22 | 23 | # basic options 24 | self.add_bool_option("-a", "--all", 25 | help="list ALL items, not just stuck ones") 26 | self.add_bool_option("-s", "--stuck-only", 27 | help="list just stuck items / skip 'no enabled trackers' check") 28 | self.add_bool_option("-t", "--to-tagged", 29 | help="add stuck items to 'tagged' view") 30 | 31 | 32 | def mainloop(self): 33 | import time 34 | from urlparse import urlparse 35 | from collections import namedtuple, Counter 36 | 37 | from pyrobase import fmt 38 | from pyrocore.util import xmlrpc 39 | 40 | proxy = config.engine.open() 41 | now = int(time.time()) 42 | fields = ('is_enabled is_busy url min_interval normal_interval' 43 | ' activity_time_last success_counter failed_counter scrape_counter').split() 44 | t_multicall = namedtuple('multicall', fields) 45 | rows = proxy.d.multicall('started', 'd.hash=', 't.multicall=,{}'.format( 46 | ','.join(['t.{}='.format(i) for i in fields]))) 47 | stuck = Counter() 48 | 49 | view = 'tagged' 50 | if self.options.to_tagged and view not in proxy.view.list(): 51 | proxy.view.add(xmlrpc.NOHASH, view) 52 | 53 | print('{:>5s} {:>2s} {:>5s} {:>5s} {:>6s} {:>13s} {:40s} {}' 54 | .format('S#', 'T#', 'OK', 'Error', 'Scrape', 'Last Announce', 55 | 'Infohash', 'Tracker Domain')) 56 | for idx, (infohash, trackers) in enumerate(rows, 1): 57 | trackers = [t_multicall(*t) for t in trackers] 58 | 59 | if not any(t.is_enabled for t in trackers): 60 | if self.options.stuck_only: 61 | continue 62 | if self.options.to_tagged: 63 | proxy.d.views.push_back_unique(infohash, view) 64 | proxy.view.set_visible(infohash, view) 65 | domain = 'ALL TRACKERS DISABLED' if trackers else 'NO TRACKERS' 66 | stuck[domain] += 1 67 | print('{i:5d} {n:>2s} {n:>5s} {n:>5s} {n:>5s} {delta:>13s} {hash} {domain}' 68 | .format(i=idx, n='-', hash=infohash, delta='N/A', domain=domain)) 69 | continue 70 | 71 | for num, t in enumerate(trackers, 1): 72 | if not t.is_enabled: 73 | continue 74 | 75 | delta = now - t.activity_time_last 76 | if self.options.all or delta > t.normal_interval: 77 | if self.options.to_tagged: 78 | proxy.d.views.push_back_unique(infohash, view) 79 | proxy.view.set_visible(infohash, view) 80 | domain = urlparse(t.url).netloc.split(':')[0] 81 | stuck[domain] += 1 82 | 83 | print('{i:5d} {n:2d} ' 84 | '{t.success_counter:5d} {t.scrape_counter:5d} {t.failed_counter:5d} ' 85 | '{delta} {hash} {domain}' 86 | .format(t=t, i=idx, n=num, hash=infohash, domain=domain, 87 | delta=fmt.human_duration(t.activity_time_last, 88 | precision=2, short=True))) 89 | 90 | if sum(stuck.values()): 91 | if self.options.to_tagged: 92 | proxy.ui.current_view.set(view) 93 | self.LOG.info("Stuck items: TOTAL={}, {}".format(sum(stuck.values()), 94 | ', '.join(['{}={}'.format(*i) for i in stuck.most_common()]))) 95 | self.LOG.debug("XMLRPC stats: %s" % proxy) 96 | 97 | 98 | if __name__ == "__main__": 99 | base.ScriptBase.setup() 100 | StuckTrackers().run() 101 | -------------------------------------------------------------------------------- /docs/examples/rt_cron_throttle_seed: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python-pyrocore 2 | import logging 3 | 4 | from pyrocore import config 5 | from pyrocore.util import fmt, matching 6 | from pyrocore.torrent import engine 7 | from pyrocore.scripts import base 8 | 9 | 10 | class SeedThrottle(base.ScriptBaseWithConfig): 11 | """ 12 | Throttle seeding throttle group when other items are active. 13 | All bandwidth values are in KiB/s. 14 | """ 15 | 16 | # argument description for the usage information 17 | ARGS_HELP = " " 18 | 19 | # log level for user-visible standard logging 20 | STD_LOG_LEVEL = logging.DEBUG 21 | 22 | # what items are prioritized? 23 | PRIO_FILTER = "up=+3" 24 | 25 | 26 | def add_options(self): 27 | """ Add program options. 28 | """ 29 | super(SeedThrottle, self).add_options() 30 | 31 | # basic options 32 | ##self.add_bool_option("-n", "--dry-run", 33 | ## help="don't do anything, just tell what would happen") 34 | 35 | 36 | def mainloop(self): 37 | """ The main loop. 38 | """ 39 | # Get proxy and parse rtorrent.rc 40 | proxy = config.engine.open() 41 | 42 | # Get arguments 43 | try: 44 | throttle_name, full_rate, nice_rate = self.args 45 | except ValueError: 46 | self.parser.error("Exactly three arguments expected") 47 | 48 | if throttle_name not in config.throttle_names: 49 | self.parser.error("Unknown throttle %r (expected one of %s)" % ( 50 | throttle_name, ", ".join(config.throttle_names), 51 | )) 52 | 53 | try: 54 | full_rate = int(full_rate, 10) * 1024 55 | except (TypeError, ValueError), exc: 56 | self.parser.error("Bad full rate %r (%s)" % (full_rate, exc)) 57 | 58 | try: 59 | nice_rate = int(nice_rate, 10) * 1024 60 | except (TypeError, ValueError), exc: 61 | self.parser.error("Bad nice rate %r (%s)" % (nice_rate, exc)) 62 | 63 | # Check for manually changed seed rate 64 | current_rate = proxy.throttle.up.max(throttle_name) 65 | self.LOG.debug("Current '%s' rate is %s/s" % ( 66 | throttle_name, fmt.human_size(current_rate).strip() 67 | )) 68 | if current_rate not in (full_rate, nice_rate): 69 | self.LOG.debug("Rate was apparently set manually, won't change it!") 70 | return 71 | 72 | # Check for active items 73 | view = config.engine.view("active", matching.ConditionParser(engine.FieldDefinition.lookup, "name").parse( 74 | "throttle=!%s [ %s ]" % (throttle_name, self.PRIO_FILTER) 75 | )) 76 | prioritized = list(view.items()) 77 | self.LOG.debug("%d active items not in throttle '%s'" % (len(prioritized), throttle_name)) 78 | 79 | # Set chosen bandwidth, if different 80 | rate = nice_rate if prioritized else full_rate 81 | if current_rate != rate: 82 | proxy.throttle.up(throttle_name, str(rate // 1024)) 83 | self.LOG.info("THROTTLE '%s' up=%s/s [%d prioritized]" % ( 84 | throttle_name, 85 | fmt.human_size(proxy.throttle.up.max(throttle_name)).strip(), 86 | len(prioritized), 87 | )) 88 | 89 | self.LOG.debug("XMLRPC stats: %s" % proxy) 90 | 91 | 92 | if __name__ == "__main__": 93 | base.ScriptBase.setup() 94 | SeedThrottle().run() 95 | -------------------------------------------------------------------------------- /docs/examples/rtcontrol-colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/docs/examples/rtcontrol-colors.png -------------------------------------------------------------------------------- /docs/examples/rtorrent-ex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/docs/examples/rtorrent-ex.png -------------------------------------------------------------------------------- /docs/examples/rtorrent.rc: -------------------------------------------------------------------------------- 1 | ### rtorrent settings ####################################################### 2 | # vim: ft=dosini 3 | # This is the standard configuration that supports both PyroScope and 4 | # (optionally) rTorrent-PS features. This file is configured for a 5 | # rTorrent instance located in 6 | # 7 | # RT_HOME 8 | # 9 | # For more info regarding changing configuration defaults, see the docs: 10 | # 11 | # https://pyrocore.readthedocs.io/en/latest/usage.html#std-config 12 | # 13 | # If you run rTorrent-PS, see below on how to unlock additional support 14 | # (but note that with version 1.1 and up, that unlocking is automatic). 15 | # 16 | ############################################################################# 17 | 18 | # Root directory of this instance. 19 | method.insert = cfg.basedir, private|const|string, (cat, "RT_HOME", "/") 20 | 21 | # `system.has` polyfill (the "false=" silences the `catch` command, in rTorrent-PS 1.1+) 22 | catch = {"false=", "method.redirect=system.has,false"} 23 | 24 | # Set "pyro.extended" to 1 to activate rTorrent-PS features! 25 | method.insert = pyro.extended, const|value, (system.has, rtorrent-ps) 26 | 27 | # Set "pyro.bin_dir" to the "bin" directory where you installed the pyrocore tools! 28 | # Make sure you end it with a "/"; if this is left empty, then the shell's path is searched. 29 | method.insert = pyro.bin_dir, const|string, ~/bin/ 30 | 31 | # Set this to '0' to prevent logging of existing key bindings being replaced 32 | ## branch=(pyro.extended), ((ui.bind_key.verbose.set, 0)) 33 | 34 | # Since "network.scgi.open_local" is parsed by "rtcontrol", this must be a literal value, 35 | # and also not moved out of the main configuration file! 36 | network.scgi.open_local = RT_HOME/.scgi_local 37 | 38 | # SCHEDULE: Make SCGI socket group-writable and secure 39 | schedule2 = scgi_permission, 0, 0, "execute.nothrow=chmod,\"g+w,o=\",RT_HOME/.scgi_local" 40 | 41 | 42 | # 43 | # Import settings from "RT_HOME/rtorrent.d" 44 | # 45 | # Prefer to put your own custom settings into additional files in 46 | # that directory, or else "_rtlocal.rc" (see below)! 47 | # 48 | 49 | execute.throw = (cat,(pyro.bin_dir),pyroadmin),-q,--create-import,(cat,(cfg.basedir),"rtorrent.d/*.rc") 50 | import = (cat,(cfg.basedir),"rtorrent.d/.import.rc") 51 | 52 | 53 | # 54 | # Include custom / optional settings 55 | # 56 | 57 | # INCLUDE: Local settings (optional) 58 | try_import = (cat,(cfg.basedir),"_rtlocal.rc") 59 | 60 | # INCLUDE: ruTorrent (optional) 61 | try_import = (cat,(cfg.basedir),"_rutorrent.rc") 62 | 63 | ### END rtorrent.rc ######################################################### 64 | -------------------------------------------------------------------------------- /docs/examples/rtorstat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/docs/examples/rtorstat.png -------------------------------------------------------------------------------- /docs/examples/rtuptime: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # 3 | # Shows some essential information about a rTorrent instance 4 | # 5 | SCGI_SOCKET=~/rtorrent/.scgi_local 6 | 7 | if test ! -S $SCGI_SOCKET; then 8 | echo >&2 "rTorrent is not running (no socket $SCGI_SOCKET)" 9 | exit 1 10 | fi 11 | 12 | echo -n rTorrent $(rtxmlrpc system.client_version)/$(rtxmlrpc system.library_version) 13 | echo -n , up $(rtxmlrpc convert.elapsed_time '' $(ls -l --time-style '+%s' $SCGI_SOCKET | awk '{print $6}')) 14 | echo -n \ [$(rtcontrol -qo"1 %(uploaded)s %(size)s" \* | \ 15 | awk '{ TOT += $1; UP += $2; SUM += $3} END { print TOT " loaded; U: " UP/1024/1024/1024 " GiB; S: " SUM/1024/1024/1024 }') GiB] 16 | echo -n , D: $(rtxmlrpc convert.xb '' $(rtxmlrpc throttle.global_down.total)) 17 | echo -n \ @ $(rtxmlrpc convert.xb '' $(rtxmlrpc throttle.global_down.rate))/s 18 | echo -n \ of $(rtxmlrpc convert.xb '' $(rtxmlrpc throttle.global_down.max_rate))/s 19 | echo -n , U: $(rtxmlrpc convert.xb '' $(rtxmlrpc throttle.global_up.total)) 20 | echo -n \ @ $(rtxmlrpc convert.xb '' $(rtxmlrpc throttle.global_up.rate))/s 21 | echo -n \ of $(rtxmlrpc convert.xb '' $(rtxmlrpc throttle.global_up.max_rate))/s 22 | echo 23 | -------------------------------------------------------------------------------- /docs/examples/start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # rTorrent startup script 4 | # 5 | 6 | NOCRON_DELAY=600 7 | RT_BINDIR="{{ rtorrent_bindir }}" 8 | RT_OPTS=( ) 9 | RT_OPTS+=( -D -I ) # comment this to get deprecated commands 10 | 11 | builtin cd "$(dirname "$0")" 12 | export RT_HOME="$(pwd -P)" 13 | 14 | fail() { 15 | echo "ERROR:" "$@" 16 | exit 1 17 | } 18 | 19 | # Performa a mount check 20 | #test -f "$RT_HOME/work/.mounted" -a -f "$RT_HOME/done/.mounted" \ 21 | # || fail "Data drive(s) not mounted!" 22 | 23 | 24 | if [ "$TERM" = "${TERM%-256color}" ]; then 25 | export TERM="$TERM-256color" 26 | fi 27 | 28 | export LANG=en_US.UTF-8 29 | umask 0027 30 | 31 | RT_OPTS+=( -n -o "import=$RT_HOME/rtorrent.rc" ) 32 | 33 | export RT_SOCKET=$PWD/.scgi_local 34 | test -S $RT_SOCKET && lsof $RT_SOCKET >/dev/null && { echo "rTorrent already running"; exit 1; } 35 | test ! -e $RT_SOCKET || rm $RT_SOCKET 36 | 37 | _at_exit() { 38 | test -z "$TMUX" || tmux set-w automatic-rename on >/dev/null 39 | stty sane 40 | test ! -e $RT_SOCKET || rm $RT_SOCKET 41 | } 42 | trap _at_exit INT TERM EXIT 43 | test -z "$TMUX" || tmux 'rename-w' 'rT-PS' 44 | 45 | if test -n "$RT_BINDIR"; then 46 | RT_BINDIR="${RT_BINDIR%/}/" 47 | 48 | # Try usual suspects if config is wrong 49 | test -x "${RT_BINDIR}rtorrent" || RT_BINDIR="$HOME/bin/" 50 | test -x "${RT_BINDIR}rtorrent" || RT_BINDIR="/opt/rtorrent/bin/" 51 | test -x "${RT_BINDIR}rtorrent" || RT_BINDIR="/usr/local/bin/" 52 | test -x "${RT_BINDIR}rtorrent" || RT_BINDIR="/usr/bin/" 53 | test -x "${RT_BINDIR}rtorrent" || RT_BINDIR="" 54 | fi 55 | #RT_BINDIR="$HOME/src/rtorrent-ps/rtorrent-0.9.6/src/" 56 | export RT_BIN="${RT_BINDIR}rtorrent" 57 | 58 | if which objdump >/dev/null; then 59 | RUNPATH=$(objdump -x "$RT_BIN" | grep RPATH | sed -re 's/ *RPATH *//') 60 | test -n "$RUNPATH" || RUNPATH=$(objdump -x "$RT_BIN" | grep RUNPATH | sed -re 's/ *RUNPATH *//') 61 | test -z "$RUNPATH" || LD_LIBRARY_PATH="$RUNPATH${LD_LIBRARY_PATH:+:}${LD_LIBRARY_PATH}" 62 | fi 63 | 64 | # Stop cron jobs during startup, unless already stopped 65 | rm "$RT_HOME/rtorrent.d/START-NOCRON.rc" 2>/dev/null || : 66 | nocron_delay='' 67 | if test -d "$RT_HOME/rtorrent.d" -a ! -f "~/NOCRON"; then 68 | nocron_delay=$(( $(date +'%s') + $NOCRON_DELAY )) 69 | echo >"$RT_HOME/rtorrent.d/START-NOCRON.rc" \ 70 | "schedule2 = nocron_during_startup, $NOCRON_DELAY, 0, \"execute.nothrow=rm,$HOME/NOCRON\"" 71 | touch "$HOME/NOCRON" 72 | fi 73 | 74 | "$RT_BIN" "${RT_OPTS[@]}" ; RC=$? 75 | test -z "$nocron_delay" -o "$(date +'%s')" -ge "${nocron_delay:-0}" || rm "$HOME/NOCRON" 2>/dev/null || : 76 | exit $RC 77 | -------------------------------------------------------------------------------- /docs/examples/tmux.conf: -------------------------------------------------------------------------------- 1 | # PyroScope default tmux configuration 2 | 3 | # Rebind to Ctrl-a 4 | set -g prefix C-a 5 | unbind C-b 6 | bind a send-prefix 7 | bind C-a last-window 8 | bind '"' choose-window 9 | 10 | # Rebind pane splitting 11 | unbind % 12 | bind - split-window -v 13 | bind _ split-window -h 14 | 15 | # Set status bar 16 | set -g status-bg colour236 17 | set -g status-fg white 18 | set -g status-left '#[fg=green]#H' 19 | 20 | # Highlight active window 21 | set-window-option -g window-status-current-bg colour164 22 | 23 | # Force 256 colors 24 | set -g default-terminal "screen-256color" 25 | -------------------------------------------------------------------------------- /docs/include-api-uml.rst: -------------------------------------------------------------------------------- 1 | UML Diagrams 2 | ------------ 3 | 4 | 5 | All Classes 6 | ^^^^^^^^^^^ 7 | 8 | .. uml:: pyrocore -k 9 | 10 | Exceptions 11 | ^^^^^^^^^^ 12 | 13 | .. uml:: -a2 -b 14 | ../src/pyrocore/error.py 15 | 16 | rTorrent API 17 | ^^^^^^^^^^^^ 18 | 19 | .. uml:: -s1 20 | ../src/pyrocore/torrent/rtorrent.py 21 | 22 | .. uml:: -s2 23 | ../src/pyrocore/torrent/engine.py 24 | 25 | 26 | Filter Rules 27 | ^^^^^^^^^^^^ 28 | 29 | .. uml:: -s1 30 | ../src/pyrocore/util/matching.py 31 | 32 | Scripts 33 | ^^^^^^^ 34 | 35 | .. uml:: pyrocore.scripts 36 | 37 | Configuration 38 | ^^^^^^^^^^^^^ 39 | 40 | .. uml:: ../src/pyrocore/util/load_config.py 41 | 42 | Metafile 43 | ^^^^^^^^ 44 | 45 | .. uml:: ../src/pyrocore/util/metafile.py 46 | 47 | Tree Watch 48 | ^^^^^^^^^^ 49 | 50 | .. uml:: pyrocore.torrent.watch 51 | -------------------------------------------------------------------------------- /docs/include-contacts.rst: -------------------------------------------------------------------------------- 1 | .. included at several places 2 | 3 | .. image:: https://raw.githubusercontent.com/pyroscope/pyrocore/master/docs/_static/img/help.png 4 | :align: left 5 | 6 | To get in contact and share your experiences with other users of `PyroScope`_, 7 | join the `rtorrent-community`_ channel `pyroscope-tools`_ on Gitter. 8 | 9 | This is also the way to resolve any problems with or questions about your configuration 10 | and software installation. 11 | *Always* look into the :doc:`troubleshooting` as a first measure, 12 | which is often the fastest way to get back to a working system. 13 | That guide also explains how to efficiently report your problem when you cannot fix it yourself. 14 | 15 | .. _`PyroScope`: https://github.com/pyroscope 16 | .. _`rtorrent-community`: https://gitter.im/rtorrent-community/ 17 | .. _`pyroscope-tools`: https://gitter.im/rtorrent-community/pyroscope-tools 18 | .. _`pyroscope-users`: http://groups.google.com/group/pyroscope-users 19 | -------------------------------------------------------------------------------- /docs/include-xmlrpc-dialects.rst: -------------------------------------------------------------------------------- 1 | .. included at several places 2 | 3 | .. warning:: 4 | 5 | The syntax of XMLRPC commands changed with rTorrent version 0.8.9, 6 | and continues to change. Make sure that the versions of rTorrent 7 | and PyroScope you plan to install or update to are actually compatible. 8 | There are compensation mechanisms in both projects, but there are limits to those 9 | — scan the respective changelogs for breaking changes. 10 | 11 | *pyrocore* 0.5+ will no longer support the old syntax, and thus not work 12 | with *rTorrent* 0.8.x versions. 13 | *rTorrent* 0.9.6 has the old commands disabled by default, and only a 14 | special command line switch will enable them again, *for now*. 15 | Also, this documentation uses the new syntax (mostly). 16 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pyrocore documentation master file, created by 2 | sphinx-quickstart on Sat May 2 16:17:52 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. image:: _static/img/logo.png 7 | 8 | Welcome to pyrocore's documentation! 9 | ==================================== 10 | 11 | *pyrocore* is a collection of tools for the BitTorrent protocol 12 | and especially the rTorrent client. 13 | They enable you to filter rTorrent's item list for displaying or changing selected items, 14 | also creating, inspecting and changing ``.torrent`` files, and much more. 15 | 16 | An optional daemon process named :command:`pyrotorque` can add flexible queue management for rTorrent, 17 | starting items added in bulk slowly over time according to customizable rules. 18 | 19 | It can also watch a directory tree recursively for new metafiles using *inotify*. 20 | That means ``.torrent`` files you drop anywhere into that watched tree are loaded instantaneously, 21 | without any polling and no extra configuration for nested directories. 22 | 23 | .. note:: 24 | 25 | The *PyroScope* command line utilities (i.e. *pyrocore*) are *not* the same as `rTorrent-PS`_, 26 | and they work perfectly fine without it; the same is true the other 27 | way 'round. 28 | It's just that both projects unsurprisingly have synergies if used together, 29 | and some features *do* only work when both are present. 30 | 31 | You absolutely **must** read the first three chapters 32 | :doc:`overview`, :doc:`installation`, and :doc:`setup`, 33 | and follow their instructions. 34 | Otherwise *pyrocore* utilities won't work at all or not properly, 35 | if you do not provide an adequate :file:`config.ini` file, and also modify 36 | the *rTorrent* one to provide some essential data and commands. 37 | 38 | Once you got everything basically working, :doc:`usage` 39 | will show you all the common commands and use-cases. Further chapters then explain 40 | more complex use-cases and features that might not appeal or apply to you. 41 | 42 | .. include:: include-contacts.rst 43 | 44 | 45 | Contents of This Manual 46 | ======================= 47 | 48 | .. toctree:: 49 | :maxdepth: 2 50 | :caption: Getting Started 51 | 52 | overview 53 | installation 54 | setup 55 | usage 56 | 57 | .. toctree:: 58 | :maxdepth: 2 59 | :caption: Advanced Usage 60 | 61 | howto 62 | advanced 63 | custom 64 | 65 | .. toctree:: 66 | :maxdepth: 2 67 | :caption: Other Topics 68 | 69 | troubleshooting 70 | updating 71 | tempita 72 | references 73 | license 74 | 75 | .. toctree:: 76 | :maxdepth: 2 77 | :caption: Development 78 | 79 | experimental 80 | api 81 | contributing 82 | 83 | 84 | Indices & Tables 85 | ---------------- 86 | 87 | * :ref:`genindex` 88 | * :ref:`modindex` 89 | * :ref:`search` 90 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | .. include:: ../LICENSE 5 | -------------------------------------------------------------------------------- /docs/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | Introduction 5 | ------------ 6 | 7 | *pyrocore* is part of the `PyroScope`_ family of projects, and offers a 8 | collection of tools for the :ref:`bt-protocol` and especially the 9 | *rTorrent* client. This includes: 10 | 11 | * :ref:`command-line-tools` for automation of common tasks, like :ref:`metafile creation `, and 12 | :ref:`filtering and mass-changing your loaded torrents `. 13 | * rTorrent extensions like a :ref:`QueueManager` and statistics (*work in progress*). 14 | * All this is based on the ``pyrocore`` Python package, that you can 15 | use for :ref:`scripts` for any special needs that aren't covered by 16 | the standard tools. 17 | 18 | See the 19 | `ScreenShotGallery `_ 20 | if you want to get a first impression without installing the software. 21 | 22 | .. include:: include-contacts.rst 23 | 24 | .. _`PyroScope`: https://github.com/pyroscope 25 | 26 | 27 | .. _glossary: 28 | 29 | Glossary 30 | -------- 31 | 32 | To help you better understand this manual, 33 | here are the definitions of some key concepts used in it. 34 | 35 | (download) item 36 | An item loaded into rTorrent. 37 | 38 | field 39 | An attribute of a download item, e.g. ``name``, ``completed``, and ``directory``. 40 | Most of these you know from *rTorrent* or *ruTorrent*, but *PyroScope* adds some of its own. 41 | They are used in conditions to filter items using the ``rtcontrol`` tool, 42 | and also name the things you want to print to the console when listing items. 43 | To get a full list, use the ``rtcontrol --help-fields`` command. 44 | 45 | metafile 46 | The term *metafile* means the ``.torrent`` file – using 'torrent' is avoided intentionally, 47 | because it's often used ambiguously to mean *either* the metafile or the *data* of a download item. 48 | 49 | XMLRPC 50 | The protocol used to remotely control a running rTorrent process. 51 | Note that support for XMLRPC is an option that must be activated when compiling 52 | the rTorrent binary, so make sure it's active in your installation 53 | when 'nothing works' for you. A quick way to check is calling the following command: 54 | 55 | .. code-block:: bash 56 | 57 | $ ldd $(command which rtorrent) | grep libxmlrpc.so 58 | libxmlrpc.so.3 => /home/pyroscope/.local/rtorrent/0.9.6-PS-1.0/lib/libxmlrpc.so.3 … 59 | 60 | 61 | Quick Start Guide 62 | ----------------- 63 | 64 | Work through these chapters in order to get the software up and running, 65 | and to learn basic concepts of using the command line tools. 66 | 67 | * :doc:`installation` 68 | * :doc:`setup` 69 | * :doc:`usage` 70 | 71 | Consult the :doc:`troubleshooting` if anything goes wrong. 72 | :ref:`issue-reporting` explains how to provide feedback in case 73 | you encounter a serious problem, or are missing a feature. 74 | 75 | .. warning:: 76 | 77 | If you do a fresh installation of *pyrocore* in addition to an existing 78 | *rTorrent* one, you will need to follow the instructions 79 | to :ref:`backfill-data`, which fills in some data your already 80 | running rTorrent instance is missing otherwise! So do **not** 81 | skip that section. 82 | 83 | 84 | Further Information & Customization 85 | ----------------------------------- 86 | 87 | * :doc:`howto` highlights some specific use-cases and might 88 | give you some inspiration when solving your own problems. 89 | * Using :doc:`advanced` requires some knowledge in the area Linux, 90 | Bash, and Python beyond a novice level, but they enable you to 91 | customize your setup even further and handle very specific use-cases. 92 | * :doc:`custom` tells you about :ref:`scripts` as an easy way to automate anything 93 | that the standard commands can't do. 94 | There are more ways for adding your own custom logic, 95 | amongst them :ref:`CustomFields` for adding user-defined fields, 96 | available in ``rtcontrol`` just like built-in ones. 97 | * :doc:`updating` explains how to get newer versions 98 | of this software after the initial installation. 99 | * :doc:`references` provides details on technical background topics 100 | like XMLRPC, and links into the web with related information. 101 | -------------------------------------------------------------------------------- /docs/references.rst: -------------------------------------------------------------------------------- 1 | References 2 | ========== 3 | 4 | .. _cli-usage: 5 | 6 | PyroScope CLI Tools Usage 7 | ------------------------- 8 | 9 | This section is automatically generated and shows the options available 10 | in the *development* version of the code (git HEAD). 11 | See :doc:`usage` for more details on how to use these commands. 12 | 13 | .. include:: references-cli-usage.rst 14 | 15 | 16 | .. _RtXmlRpcReference: 17 | 18 | rTorrent XMLRPC 19 | --------------- 20 | 21 | See the `Commands Reference`_ in the *rTorrent Handbook* for a list of available commands and what they do. 22 | The `Scripting Guide`_ explains how all these fit together. 23 | 24 | .. _`Commands Reference`: https://rtorrent-docs.readthedocs.io/en/latest/cmd-ref.html 25 | .. _`Scripting Guide`: https://rtorrent-docs.readthedocs.io/en/latest/scripting.html 26 | 27 | XMLRPC Migration 28 | ---------------- 29 | 30 | The syntax of XMLRPC commands changed with rTorrent version 0.8.9, 31 | and continues to change. The old command names and behavior were 32 | replaced with aliases and marked for deprecation, so they still work for now, 33 | but don't rely on that and use the new names instead. 34 | 35 | See `XMLRPC Migration`_ in the GitHub wiki for details. 36 | 37 | .. _`XMLRPC Migration`: https://github.com/rakshasa/rtorrent/wiki/RPC-Migration-0.9 38 | 39 | 40 | Books & Other Knowledge Sources 41 | ------------------------------- 42 | 43 | This and related documentation cannot teach all you need to know in order 44 | to run a torrent client and manage the server it is installed on. 45 | 46 | So here are a few references to either books or web resources that help you 47 | to improve your basic know-how, in case you have trouble following some parts of the docs. 48 | 49 | **Linux / CLI / Administration** 50 | 51 | * `The Debian Administrator's Handbook`_ 52 | * `The Linux Command Line`_ 53 | * `The Art of Command Line`_ 54 | * `Ansible Documentation`_ 55 | 56 | **Python** 57 | 58 | * `The Hitchhiker’s Guide to Python`_ 59 | * `Free Python Books`_ 60 | 61 | .. _The Debian Administrator's Handbook: https://debian-handbook.info/browse/stable/ 62 | .. _The Linux Command Line: http://linuxcommand.org/tlcl.php 63 | .. _The Art of Command Line: https://github.com/jlevy/the-art-of-command-line#the-art-of-command-line 64 | .. _Ansible Documentation: https://docs.ansible.com/#ansible-documentation 65 | 66 | .. _`The Hitchhiker’s Guide to Python`: http://docs.python-guide.org/ 67 | .. _`Free Python Books`: https://github.com/TechBookHunter/Free-Python-Books#free-python-books 68 | 69 | 70 | External Links 71 | -------------- 72 | 73 | * `User Mailing List `_ 74 | * The `rTorrent `_ 75 | and `libtorrent `_ projects 76 | * `rTorrent Community Wiki `_ 77 | and the `rTorrent Handbook `_ 78 | * `Open HUB `_ 79 | * `free(code) `_ 80 | * `Bintray `_ 81 | * `pyrobase `_ 82 | 83 | 84 | .. _bt-protocol: 85 | 86 | BitTorrent Protocol 87 | ------------------- 88 | 89 | Wikipedia: 90 | 91 | * `Protocol `_ 92 | * `bencode `_ 93 | 94 | BitTorrent standards: 95 | 96 | * `Index of BitTorrent Enhancement Proposals `_ 97 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # requirements for use on "Read The Docs" 2 | pylint==1.7.0 3 | ##sphinx-pyreverse==0.0.12 4 | ##https://github.com/pyroscope/sphinx-pyreverse/archive/master.zip#egg=sphinx-pyreverse 5 | 6 | -r ../requirements-torque.txt 7 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | User's Manual 2 | ============= 3 | 4 | This chapter provides an overview of all the command line tools and their everyday use, 5 | focussing on :command:`rtcontrol` as the most powerful of them. 6 | The following chapters then go into more advanced use-cases and features. 7 | 8 | 9 | .. _CommandLineTools: 10 | .. _command-line-tools: 11 | 12 | Command Line Tools 13 | ------------------ 14 | 15 | .. include:: usage-cli-tools.rst 16 | 17 | 18 | .. _rtcontrol-examples: 19 | 20 | ‘rtcontrol’ Examples 21 | -------------------- 22 | 23 | .. include:: usage-rtcontrol.rst 24 | 25 | 26 | .. _output-templates: 27 | 28 | Using Output Templates 29 | ---------------------- 30 | 31 | .. include:: usage-templates.rst 32 | 33 | 34 | .. _std-config: 35 | 36 | Standard Configuration Explained 37 | -------------------------------- 38 | 39 | .. include:: usage-std-config.rst 40 | -------------------------------------------------------------------------------- /docs/videos/bash-completion.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/docs/videos/bash-completion.gif -------------------------------------------------------------------------------- /docs/videos/rtcontrol-curses.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/docs/videos/rtcontrol-curses.gif -------------------------------------------------------------------------------- /paver-minilib.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/paver-minilib.zip -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # Full list of tools for development 2 | Paver==1.2.4 3 | bpython==0.17.1 4 | twine==1.11.0 5 | pip-upgrader 6 | 7 | Sphinx==1.7.5 8 | sphinx-rtd-theme==0.4.0 9 | sphinx-autobuild==0.7.1 10 | 11 | nose==1.3.7 12 | 13 | -r docs/requirements.txt 14 | -------------------------------------------------------------------------------- /requirements-torque.txt: -------------------------------------------------------------------------------- 1 | # pip requirements for "pyrotorque" dependencies 2 | 3 | APScheduler==2.1.0 4 | pyinotify>=0.9.6 5 | waitress>=0.9.0 6 | WebOb>=1.6.1 7 | psutil>=4.3.0 8 | 9 | -r requirements.txt 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # requirements for installation 2 | Tempita>=0.5.1 3 | ProxyTypes==0.9 4 | pyrobase>=0.5 5 | #-e git+https://github.com/pyroscope/pyrobase.git#egg=pyrobase 6 | auvyon>=0.1 7 | requests>=2.10,<3 8 | prompt-toolkit>=1.0.14,<2 9 | six==1.9.0 10 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = dev 3 | tag_date = true 4 | 5 | [sdist] 6 | formats = zip 7 | 8 | [upload] 9 | show_response = 1 10 | 11 | [nosetests] 12 | #where = src 13 | verbosity = 0 14 | detailed-errors = 1 15 | with-coverage = 1 16 | cover-html = 1 17 | cover-html-dir = ../build/coverage 18 | cover-package = pyrocore 19 | 20 | # logging during tests 21 | #quiet = 1 22 | #nocapture = 1 23 | #nologcapture = 1 24 | logging-config = src/tests/logging.cfg 25 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | import paver.tasks 3 | except ImportError: 4 | from os.path import exists 5 | if exists("paver-minilib.zip"): 6 | import sys 7 | sys.path.insert(0, "paver-minilib.zip") 8 | import paver.tasks 9 | 10 | paver.tasks.main() 11 | -------------------------------------------------------------------------------- /src/pyrocore/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Python Torrent Tools Core Package. 4 | 5 | Copyright (c) 2010 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | from __future__ import absolute_import 21 | 22 | 23 | def connect(config_dir=None, optional_config_files=None, cron_cfg="cron"): 24 | """ Initialize everything for interactive use. 25 | 26 | Returns a ready-to-use RtorrentEngine object. 27 | """ 28 | from pyrocore.scripts.base import ScriptBase 29 | from pyrocore.util import load_config 30 | 31 | ScriptBase.setup(cron_cfg=cron_cfg) 32 | load_config.ConfigLoader(config_dir).load(optional_config_files or []) 33 | 34 | from pyrocore import config 35 | config.engine.open() 36 | return config.engine 37 | 38 | 39 | def view(viewname='default', matcher=None, 40 | config_dir=None, optional_config_files=None, cron_cfg="cron"): 41 | """Helper for interactive / high-level API use.""" 42 | return connect(config_dir, optional_config_files, cron_cfg).view(viewname, matcher) 43 | -------------------------------------------------------------------------------- /src/pyrocore/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=I0011,C0103,W0404 3 | """ Configuration. 4 | 5 | For details, see https://pyrocore.readthedocs.io/en/latest/setup.html 6 | 7 | Copyright (c) 2009, 2010, 2011 The PyroScope Project 8 | """ 9 | # This program is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License along 20 | # with this program; if not, write to the Free Software Foundation, Inc., 21 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22 | from __future__ import absolute_import 23 | 24 | from pyrobase.parts import Bunch 25 | 26 | 27 | def lookup_announce_alias(name): 28 | """ Get canonical alias name and announce URL list for the given alias. 29 | """ 30 | for alias, urls in announce.items(): 31 | if alias.lower() == name.lower(): 32 | return alias, urls 33 | 34 | raise KeyError("Unknown alias %s" % (name,)) 35 | 36 | 37 | def map_announce2alias(url): 38 | """ Get tracker alias for announce URL, and if none is defined, the 2nd level domain. 39 | """ 40 | import urlparse 41 | 42 | # Try to find an exact alias URL match and return its label 43 | for alias, urls in announce.items(): 44 | if any(i == url for i in urls): 45 | return alias 46 | 47 | # Try to find an alias URL prefix and return its label 48 | parts = urlparse.urlparse(url) 49 | server = urlparse.urlunparse((parts.scheme, parts.netloc, "/", None, None, None)) 50 | 51 | for alias, urls in announce.items(): 52 | if any(i.startswith(server) for i in urls): 53 | return alias 54 | 55 | # Return 2nd level domain name if no alias found 56 | try: 57 | return '.'.join(parts.netloc.split(':')[0].split('.')[-2:]) 58 | except IndexError: 59 | return parts.netloc 60 | 61 | # Remember predefined names 62 | _PREDEFINED = tuple(_ for _ in globals() if not _.startswith('_')) 63 | 64 | # Set some defaults to shut up pydev / pylint; 65 | # these later get overwritten by loading the config 66 | debug = False 67 | config_dir = None 68 | scgi_url = "" 69 | engine = Bunch(open=lambda: None) 70 | fast_query = 0 71 | formats = {} 72 | sort_fields = "" 73 | announce = {} 74 | config_validator_callbacks = [] 75 | custom_field_factories = [] 76 | custom_template_helpers = Bunch() 77 | xmlrpc = {} 78 | output_header_ecma48 = "" 79 | output_header_frequency = 1 80 | waif_pattern_list = [] 81 | traits_by_alias = {} 82 | torque = {} 83 | magnet_watch = None 84 | influxdb = {} 85 | -------------------------------------------------------------------------------- /src/pyrocore/daemon/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Background Daemon Package. 4 | 5 | Copyright (c) 2012 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/bash-completion: -------------------------------------------------------------------------------- 1 | # bash completion for PyroScope commands 2 | # 3 | # Source this in your ~/.bash_completion or ~/.bashrc, 4 | # or copy it to /etc/bash_completion.d/pyrocore 5 | # 6 | _pyrocore_base() { 7 | COMPREPLY=() 8 | cur="${COMP_WORDS[COMP_CWORD]}" 9 | prev="${COMP_WORDS[COMP_CWORD-1]}" 10 | 11 | case "${prev}" in 12 | --config-dir) 13 | COMPREPLY=( $(compgen -A directory -- "${cur}") ) 14 | return 0 15 | ;; 16 | esac 17 | } 18 | 19 | _pyrocore_generic() { 20 | local cur prev choices 21 | _pyrocore_base 22 | test -z "$COMPREPLY" || return 0 23 | 24 | case "${cur}" in 25 | -*) 26 | choices="$(${COMP_WORDS[0]} --help-completion-options)" 27 | COMPREPLY=( $(compgen -W "${choices}" -- ${cur}) ) 28 | return 0 29 | ;; 30 | esac 31 | } 32 | 33 | _rtxmlrpc() { 34 | local cur prev choices 35 | _pyrocore_base 36 | test -z "$COMPREPLY" || return 0 37 | 38 | case "${cur}" in 39 | -*) 40 | choices="$(rtxmlrpc --help-completion-options)" 41 | COMPREPLY=( $(compgen -W "${choices}" -- ${cur}) ) 42 | return 0 43 | ;; 44 | [a-z]*) 45 | choices="$(rtxmlrpc system.listMethods)" 46 | COMPREPLY=( $(compgen -W "${choices}" -- ${cur}) ) 47 | return 0 48 | ;; 49 | esac 50 | } 51 | 52 | _rtcontrol() { 53 | local cur prev choices 54 | _pyrocore_base 55 | test -z "$COMPREPLY" || return 0 56 | 57 | case "${prev}" in 58 | #--custom=KEY=VALUE" 59 | #--define=KEY=VAL [-D ...]" 60 | 61 | --from-view | --to-view) 62 | choices="$(rtxmlrpc view_list)" 63 | COMPREPLY=( $(compgen -W "${choices}" -- "${cur##*=}") ) 64 | return 0 65 | ;; 66 | 67 | --ignore) 68 | COMPREPLY=( $(compgen -W "0 1" -- "${cur}") ) 69 | return 0 70 | ;; 71 | 72 | -O | --output-template) 73 | COMPREPLY=( $(compgen -A file -- "${cur}") ) 74 | return 0 75 | ;; 76 | 77 | -o | --output-format | -s | --sort-fields) 78 | choices="$(rtcontrol --help-completion-fields | cut -f1 -d=)" 79 | if test "${cur}" == "${cur##*,}"; then 80 | COMPREPLY=( $(compgen -S, -W "${choices}" -- "${cur}") ) 81 | else 82 | COMPREPLY=( $(compgen -P "${cur%,*}," -S, -W "${choices}" -- "${cur##*,}") ) 83 | fi 84 | return 0 85 | ;; 86 | 87 | #--tag="TAG +TAG -TAG..."" 88 | #-T | --throttle=NAME" 89 | esac 90 | 91 | case "${cur}" in 92 | -*) 93 | choices="$(rtcontrol --help-completion-options)" 94 | COMPREPLY=( $(compgen -W "${choices}" -- ${cur}) ) 95 | return 0 96 | ;; 97 | or | O) 98 | COMPREPLY=( OR ) 99 | return 0 100 | ;; 101 | alias=*) 102 | choices="$(rtcontrol -qo alias -s* \* | tr A-Z a-z | uniq)" 103 | if test "${cur}" == "${cur##*,}"; then 104 | COMPREPLY=( $(compgen -S, -W "${choices}" -- "${cur##*=}") ) 105 | else 106 | prev="${cur##*=}" 107 | prev="${prev%,*}" 108 | COMPREPLY=( $(compgen -P "${prev}," -S, -W "${choices}" -- "${cur##*,}") ) 109 | fi 110 | return 0 111 | ;; 112 | [a-z]*=*) 113 | choices="$(rtcontrol --help-completion-fields | egrep ^${cur}. | cut -f2 -d=)" 114 | COMPREPLY=( $(compgen -W "${choices}" -- "${cur##*=*}") ) 115 | return 0 116 | ;; 117 | [a-z]*) 118 | choices="$(rtcontrol --help-completion-fields)" 119 | COMPREPLY=( $(compgen -W "${choices}" -- "${cur}") ) 120 | return 0 121 | ;; 122 | esac 123 | } 124 | 125 | complete -F _rtxmlrpc rtxmlrpc 126 | complete -F _rtcontrol rtcontrol 127 | complete -F _pyrocore_generic pyroadmin 128 | complete -F _pyrocore_generic -f chtor lstor 129 | complete -F _pyrocore_generic -o default hashcheck mktor rtevent rtmv 130 | 131 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/color-schemes/default-16.rc: -------------------------------------------------------------------------------- 1 | # Color scheme for 16 ANSI colors 2 | 3 | pyro.color_theme.name = "Default (16 ANSI colors)" 4 | 5 | ui.color.alarm.set = "bold white on red" 6 | ui.color.complete.set = "bright green" 7 | #ui.color.custom1.set = "gray" 8 | ui.color.even.set = "" 9 | ui.color.focus.set = "reverse" 10 | ui.color.footer.set = "bold bright cyan on blue" 11 | ui.color.incomplete.set = "yellow" 12 | ui.color.info.set = "white" 13 | ui.color.label.set = "bright blue" 14 | ui.color.leeching.set = "bold bright yellow" 15 | ui.color.odd.set = "" 16 | ui.color.progress0.set = "red" 17 | ui.color.progress20.set = "bold bright red" 18 | ui.color.progress40.set = "bold bright magenta" 19 | ui.color.progress60.set = "yellow" 20 | ui.color.progress80.set = "bold bright yellow" 21 | ui.color.progress100.set = "green" 22 | ui.color.progress120.set = "bold bright green" 23 | ui.color.queued.set = "magenta" 24 | ui.color.seeding.set = "bold bright green" 25 | ui.color.stopped.set = "blue" 26 | ui.color.title.set = "bold bright white on blue" 27 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/color-schemes/default-256.rc: -------------------------------------------------------------------------------- 1 | # Color scheme for 256 xterm colors 2 | 3 | pyro.color_theme.name = "Default (256 xterm colors)" 4 | 5 | ui.color.alarm.set = "bold white on red" 6 | ui.color.complete.set = "41" 7 | #ui.color.custom1.set = 242 8 | ui.color.even.set = "on 234" 9 | ui.color.focus.set = "reverse" 10 | ui.color.footer.set = "bright cyan on 20" 11 | ui.color.incomplete.set = "yellow" 12 | ui.color.info.set = "white" 13 | ui.color.label.set = 105 14 | ui.color.leeching.set = "bold bright yellow" 15 | ui.color.odd.set = "on 232" 16 | ui.color.progress0.set = "196" 17 | ui.color.progress100.set = "34" 18 | ui.color.progress120.set = "bold bright 47" 19 | ui.color.progress20.set = "202" 20 | ui.color.progress40.set = "213" 21 | ui.color.progress60.set = "214" 22 | ui.color.progress80.set = "226" 23 | ui.color.queued.set = "magenta" 24 | ui.color.seeding.set = "bold bright 46" 25 | ui.color.stopped.set = "33" 26 | ui.color.title.set = "bold bright white on blue" 27 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/color-schemes/default-8.rc: -------------------------------------------------------------------------------- 1 | # Color scheme for 8 ANSI colors 2 | 3 | pyro.color_theme.name = "Default (8 ANSI colors)" 4 | 5 | ui.color.alarm.set = "white on red" 6 | ui.color.complete.set = "white" 7 | #ui.color.custom1.set = "gray" 8 | ui.color.even.set = "" 9 | ui.color.focus.set = "reverse" 10 | ui.color.footer.set = "white on blue" 11 | ui.color.incomplete.set = "yellow" 12 | ui.color.info.set = "white" 13 | ui.color.label.set = "gray" 14 | ui.color.leeching.set = "yellow" 15 | ui.color.odd.set = "" 16 | ui.color.progress0.set = "red" 17 | ui.color.progress20.set = "red" 18 | ui.color.progress40.set = "magenta" 19 | ui.color.progress60.set = "yellow" 20 | ui.color.progress80.set = "yellow" 21 | ui.color.progress100.set = "green" 22 | ui.color.progress120.set = "green" 23 | ui.color.queued.set = "magenta" 24 | ui.color.seeding.set = "green" 25 | ui.color.stopped.set = "blue" 26 | ui.color.title.set = "white on blue" 27 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/color-schemes/happy-pastel.rc: -------------------------------------------------------------------------------- 1 | # Color scheme for 256 xterm colors - Happy Pastel (less harsh default colors with a happy touch) 2 | 3 | pyro.color_theme.name = "Happy Pastel" 4 | 5 | ui.color.alarm.set = "bold 250 on 124" 6 | ui.color.complete.set = "41" 7 | #ui.color.custom1.set = 242 8 | ui.color.even.set = "on 234" 9 | ui.color.focus.set = "reverse" 10 | ui.color.footer.set = "bright 123 on 160" 11 | ui.color.incomplete.set = "221" 12 | ui.color.info.set = "white" 13 | ui.color.label.set = "241" 14 | ui.color.leeching.set = "bold bright 220" 15 | ui.color.odd.set = "on 232" 16 | ui.color.progress0.set = "196" 17 | ui.color.progress20.set = "207" 18 | ui.color.progress40.set = "202" 19 | ui.color.progress60.set = "214" 20 | ui.color.progress80.set = "226" 21 | ui.color.progress100.set = "34" 22 | ui.color.progress120.set = "bold bright 46" 23 | ui.color.queued.set = "133" 24 | ui.color.seeding.set = "bold bright 40" 25 | ui.color.stopped.set = "33" 26 | ui.color.title.set = "bold bright white on 202" 27 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/color-schemes/solarized-blue.rc: -------------------------------------------------------------------------------- 1 | # Color scheme for 256 xterm colors - Solarized Blue (http://ethanschoonover.com/solarized) 2 | 3 | pyro.color_theme.name = "Solarized Blue" 4 | 5 | ui.color.alarm.set = "bold 254 on 160" 6 | ui.color.complete.set = "33" 7 | #ui.color.custom1.set = 242 8 | ui.color.even.set = "on 234" 9 | ui.color.focus.set = "reverse" 10 | ui.color.footer.set = "bright 254 on 125" 11 | ui.color.incomplete.set = "136" 12 | ui.color.info.set = "245" 13 | ui.color.label.set = "240" 14 | ui.color.leeching.set = "bold bright 166" 15 | ui.color.odd.set = "" 16 | ui.color.progress0.set = "160" 17 | ui.color.progress20.set = "61" 18 | ui.color.progress40.set = "64" 19 | ui.color.progress60.set = "166" 20 | ui.color.progress80.set = "136" 21 | ui.color.progress100.set = "33" 22 | ui.color.progress120.set = "bold bright 37" 23 | ui.color.queued.set = "125" 24 | ui.color.seeding.set = "bold bright 37" 25 | ui.color.stopped.set = "64" 26 | ui.color.title.set = "bold bright 230 on 61" 27 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/color-schemes/solarized-yellow.rc: -------------------------------------------------------------------------------- 1 | # Color scheme for 256 xterm colors - Solarized Yellow (http://ethanschoonover.com/solarized) 2 | 3 | pyro.color_theme.name = "Solarized Yellow" 4 | 5 | ui.color.alarm.set = "bold 254 on 160" 6 | ui.color.complete.set = "136" 7 | #ui.color.custom1.set = 242 8 | ui.color.even.set = "on 234" 9 | ui.color.focus.set = "reverse" 10 | ui.color.footer.set = "bright 254 on 160" 11 | ui.color.incomplete.set = "64" 12 | ui.color.info.set = "245" 13 | ui.color.label.set = "240" 14 | ui.color.leeching.set = "bold bright 70" 15 | ui.color.odd.set = "" 16 | ui.color.progress0.set = "160" 17 | ui.color.progress20.set = "61" 18 | ui.color.progress40.set = "33" 19 | ui.color.progress60.set = "37" 20 | ui.color.progress80.set = "64" 21 | ui.color.progress100.set = "136" 22 | ui.color.progress120.set = "bold bright 166" 23 | ui.color.queued.set = "125" 24 | ui.color.seeding.set = "bold bright 166" 25 | ui.color.stopped.set = "33" 26 | ui.color.title.set = "bold bright 230 on 64" 27 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/config.py: -------------------------------------------------------------------------------- 1 | # The default PyroScope configuration script 2 | # 3 | # For details, see https://pyrocore.readthedocs.io/en/latest/setup.html 4 | # and https://pyrocore.readthedocs.io/en/latest/custom.html#defining-custom-fields 5 | # 6 | 7 | def _custom_fields(): 8 | """ Yield custom field definitions. 9 | """ 10 | # Import some commonly needed modules 11 | import os 12 | from pyrocore.torrent import engine, matching 13 | from pyrocore.util import fmt 14 | 15 | # PUT CUSTOM FIELD CODE HERE 16 | 17 | # Disk space check (as an example) 18 | # see https://pyrocore.readthedocs.io/en/latest/custom.html#has-room 19 | def has_room(obj): 20 | "Check disk space." 21 | pathname = obj.path 22 | if pathname and not os.path.exists(pathname): 23 | pathname = os.path.dirname(pathname) 24 | if pathname and os.path.exists(pathname): 25 | stats = os.statvfs(pathname) 26 | return (stats.f_bavail * stats.f_frsize - int(diskspace_threshold_mb) * 1024**2 27 | > obj.size * (1.0 - obj.done / 100.0)) 28 | else: 29 | return None 30 | 31 | yield engine.DynamicField(engine.untyped, "has_room", 32 | "check whether the download will fit on its target device", 33 | matcher=matching.BoolFilter, accessor=has_room, 34 | formatter=lambda val: "OK" if val else "??" if val is None else "NO") 35 | globals().setdefault("diskspace_threshold_mb", "500") 36 | 37 | 38 | # Register our factory with the system 39 | custom_field_factories.append(_custom_fields) 40 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/logging.cron.ini: -------------------------------------------------------------------------------- 1 | # Logging configuration for command line scripts in cron mode 2 | # 3 | # For details see http://docs.python.org/library/logging.html#configuring-logging 4 | # 5 | 6 | [loggers] 7 | keys = root 8 | 9 | [handlers] 10 | keys = cronlog 11 | 12 | [formatters] 13 | keys = cronlog 14 | 15 | [logger_root] 16 | level = INFO 17 | handlers = cronlog 18 | 19 | [handler_cronlog] 20 | class = FileHandler 21 | args = (HERE + '/log/cron.log', 'a') 22 | level = INFO 23 | formatter = cronlog 24 | 25 | [formatter_cronlog] 26 | format = %(asctime)s %(levelname)-8s %(message)s [%(name)s] 27 | datefmt = %Y-%m-%d %H:%M:%S 28 | 29 | # %(name)s Name of the logger (logging channel). 30 | # %(levelno)s Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). 31 | # %(levelname)s Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). 32 | # %(pathname)s Full pathname of the source file where the logging call was issued (if available). 33 | # %(filename)s Filename portion of pathname. 34 | # %(module)s Module (name portion of filename). 35 | # %(funcName)s Name of function containing the logging call. 36 | # %(lineno)d Source line number where the logging call was issued (if available). 37 | # %(created)f Time when the LogRecord was created (as returned by time.time()). 38 | # %(relativeCreated)d Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded. 39 | # %(asctime)s Human-readable time when the LogRecord was created. By default this is of the form “2003-07-08 16:49:45,896” (the numbers after the comma are millisecond portion of the time). 40 | # %(msecs)d Millisecond portion of the time when the LogRecord was created. 41 | # %(thread)d Thread ID (if available). 42 | # %(threadName)s Thread name (if available). 43 | # %(process)d Process ID (if available). 44 | # %(message)s The logged message, computed as msg % args. 45 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/logging.scripts.ini: -------------------------------------------------------------------------------- 1 | # Logging configuration for command line scripts 2 | # 3 | # For details see http://docs.python.org/library/logging.html#configuring-logging 4 | # 5 | 6 | [loggers] 7 | keys = root 8 | #keys = root, pyrocore 9 | 10 | [handlers] 11 | keys = console 12 | 13 | [formatters] 14 | keys = console 15 | 16 | [logger_root] 17 | level = INFO 18 | handlers = console 19 | 20 | [logger_pyrocore] 21 | level = DEBUG 22 | handlers = console 23 | qualname = pyrocore 24 | propagate = 0 25 | 26 | [handler_console] 27 | class = StreamHandler 28 | level = NOTSET 29 | formatter = console 30 | args = (sys.stdout,) 31 | 32 | [formatter_console] 33 | format = %(levelname)-8s %(message)s 34 | #format = %(asctime)s %(levelname)-8s %(message)s [%(name)s] 35 | #datefmt = %Y-%m-%d %H:%M:%S 36 | 37 | # %(name)s Name of the logger (logging channel). 38 | # %(levelno)s Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). 39 | # %(levelname)s Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). 40 | # %(pathname)s Full pathname of the source file where the logging call was issued (if available). 41 | # %(filename)s Filename portion of pathname. 42 | # %(module)s Module (name portion of filename). 43 | # %(funcName)s Name of function containing the logging call. 44 | # %(lineno)d Source line number where the logging call was issued (if available). 45 | # %(created)f Time when the LogRecord was created (as returned by time.time()). 46 | # %(relativeCreated)d Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded. 47 | # %(asctime)s Human-readable time when the LogRecord was created. By default this is of the form “2003-07-08 16:49:45,896” (the numbers after the comma are millisecond portion of the time). 48 | # %(msecs)d Millisecond portion of the time when the LogRecord was created. 49 | # %(thread)d Thread ID (if available). 50 | # %(threadName)s Thread name (if available). 51 | # %(process)d Process ID (if available). 52 | # %(message)s The logged message, computed as msg % args. 53 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/logging.torque.ini: -------------------------------------------------------------------------------- 1 | # Logging configuration for `pyrotorque` in cron mode 2 | # 3 | # For details see http://docs.python.org/library/logging.html#configuring-logging 4 | # 5 | 6 | [loggers] 7 | keys = root, scheduler 8 | 9 | [logger_root] 10 | level = INFO 11 | handlers = torquelog 12 | 13 | [logger_scheduler] 14 | level = WARN 15 | qualname = apscheduler.scheduler 16 | propagate = 1 17 | handlers = 18 | 19 | [handlers] 20 | keys = torquelog 21 | 22 | [handler_torquelog] 23 | level = DEBUG 24 | formatter = torquelog 25 | class = FileHandler 26 | args = (HERE + '/log/torque.log', 'a') 27 | 28 | [formatters] 29 | keys = torquelog 30 | 31 | [formatter_torquelog] 32 | format = %(asctime)s %(levelname)-8s %(message)s [%(name)s] 33 | datefmt = %Y-%m-%d %H:%M:%S 34 | 35 | # %(name)s Name of the logger (logging channel). 36 | # %(levelno)s Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). 37 | # %(levelname)s Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). 38 | # %(pathname)s Full pathname of the source file where the logging call was issued (if available). 39 | # %(filename)s Filename portion of pathname. 40 | # %(module)s Module (name portion of filename). 41 | # %(funcName)s Name of function containing the logging call. 42 | # %(lineno)d Source line number where the logging call was issued (if available). 43 | # %(created)f Time when the LogRecord was created (as returned by time.time()). 44 | # %(relativeCreated)d Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded. 45 | # %(asctime)s Human-readable time when the LogRecord was created. By default this is of the form “2003-07-08 16:49:45,896” (the numbers after the comma are millisecond portion of the time). 46 | # %(msecs)d Millisecond portion of the time when the LogRecord was created. 47 | # %(thread)d Thread ID (if available). 48 | # %(threadName)s Thread name (if available). 49 | # %(process)d Process ID (if available). 50 | # %(message)s The logged message, computed as msg % args. 51 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent-pyro.rc: -------------------------------------------------------------------------------- 1 | ############################################################################# 2 | # Standard PyroScope rTorrent Configuration 3 | # 4 | # To include THIS file into your main "~/.rtorrent.rc", add these commands to it: 5 | # method.insert = pyro.extended, value|const, 0 6 | # method.insert = pyro.bin_dir, string|const, 7 | # import = ~/.pyroscope/rtorrent-pyro.rc.default 8 | # 9 | # And read https://pyrocore.readthedocs.io/en/latest/setup.html#rtorrent-pyro-rc 10 | # 11 | # Remove the ".default" if you want to change something (else your changes 12 | # get over-written on update, when you put them into ``*.default`` files). 13 | # The BETTER way to apply customizations is to use the ``.rcignore`` file 14 | # to disable the specific ``*.rc.default`` file, and then provide your own 15 | # version in an EXTRA file. 16 | # 17 | # Set "pyro.extended" to 1 to activate rTorrent-PS features, but ONLY if 18 | # you actually run that patched version of rTorrent! 19 | # 20 | 21 | # Import files from "~/.pyroscope/rtorrent.d" (default versions) 22 | execute.throw = (cat,(pyro.bin_dir),pyroadmin),-q,--create-import,~/.pyroscope/rtorrent.d/*.rc.default 23 | import = ~/.pyroscope/rtorrent.d/.import.rc 24 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/00-default.rc: -------------------------------------------------------------------------------- 1 | ### Fundamentals ############################################################ 2 | # vim: ft=dosini 3 | 4 | # Log extension switch setting 5 | branch=pyro.extended=,\ 6 | print=rTorrent-PS\\\ features\\\ active!,\ 7 | print=rTorrent-PS\\\ features\\\ NOT\\\ active! 8 | 9 | # COMMAND: Return startup time (can be used to calculate uptime) 10 | method.insert = system.startup_time, value|const, $system.time= 11 | #method.redirect = startup_time, system.startup_time 12 | 13 | # COMMAND: `do` fall-back? 14 | branch = (not, (system.has, "do=")), ((catch, \ 15 | ((print, "WARNING: Fall-back for 'do' command active (update rTorrent-PS)!")), \ 16 | ((method.redirect, do, catch)) )) 17 | 18 | # COMMAND: Return path to item data (never empty, unlike `d.base_path`); 19 | # multi-file items return a path ending with a '/'. 20 | method.insert = d.data_path, simple,\ 21 | "if=(d.is_multi_file),\ 22 | (cat, (d.directory), /),\ 23 | (cat, (d.directory), /, (d.name))" 24 | 25 | # COMMAND: Return path to session file 26 | method.insert = d.session_file, simple, "cat=(session.path), (d.hash), .torrent" 27 | 28 | # COMMAND: Get mtime of a path, return 2nd argument when path does not exist 29 | method.insert = os.path.mtime, simple, \ 30 | "execute.capture = sh, -c, \ 31 | \"echo -n \$(stat -c '%Y' \\\"$1\\\" 2>/dev/null || echo \\\"$2\\\")\", \ 32 | getmtime, (argument.0), (argument.1)" 33 | 34 | # COMMAND: Fallback through completed / loaded / downloaded event timestamps, for view sorting 35 | # (MUST be protected by the caller using a system.has=d.custom.if_z= check) 36 | method.insert = pyro._view_sort_timestamp, simple|private, \ 37 | "d.custom.if_z = tm_completed, (d.custom.if_z, tm_loaded, (d.custom, tm_downloaded))" 38 | 39 | # COMMAND: Start command for watches, with dynamic behaviour 40 | method.insert.value = cfg.watch.start, 1 41 | method.insert = d.watch.start, simple, "branch=cfg.watch.start=, ((d.start))" 42 | method.insert = d.watch.startable, private|simple, "d.custom.set = watch_start, 1" 43 | method.set_key = event.download.inserted_new, ~watch_start, "branch=d.custom=watch_start,d.watch.start=" 44 | 45 | # COMMAND: Nicer key binds (hide technical complications) 46 | method.insert = pyro._bind_schedule, private|simple,\ 47 | "schedule2=(argument.0), 1, 0, (argument.1)" 48 | method.insert = pyro._bind_key_command, private|simple,\ 49 | "cat=\"ui.bind_key=download_list,\",$argument.0=,\",\\\"\",$argument.1=, \"\\\"\"" 50 | method.insert = pyro.bind_key, private|simple,\ 51 | "branch=(pyro.extended),\ 52 | ((pyro._bind_schedule, (argument.0), (pyro._bind_key_command, (argument.1), (argument.2))))" 53 | 54 | # COMMAND: Safely toggle collapsed state of views 55 | method.insert = pyro.view.collapsed.toggle, private|simple,\ 56 | "branch=(pyro.extended), ((view.collapsed.toggle, (argument.0)))" 57 | method.insert = pyro.collapsed_view.add, private|simple,\ 58 | "view.add=(argument.0) ; pyro.view.collapsed.toggle=(argument.0)" 59 | 60 | # UI/VIEW: Bind "*" to toggle between collapsed and expanded display 61 | branch=(not, (system.has, canvas_v2)), \ 62 | ((pyro.bind_key, collapsed_view_toggle, *, "view.collapsed.toggle=")) 63 | 64 | # UI/VIEW: Default view for filtering results (bound to '^' key in rT-PS) 65 | pyro.collapsed_view.add = rtcontrol 66 | view.filter = rtcontrol, ((false)) 67 | pyro.bind_key = rtcontrol_view, ^, "ui.current_view.set=rtcontrol" 68 | 69 | # UI/Key: Press 'u' for uptime 70 | method.insert = pyro._elapsed_time, private|simple,\ 71 | "branch=(system.has, convert.time_delta=), \ 72 | \"convert.time_delta=$value=$argument.0=\", \ 73 | \"convert.elapsed_time=$value=$argument.0=\"" 74 | method.insert = pyro._print_uptime, private|simple,\ 75 | "print=\"rTorrent \", $system.library_version=,\ 76 | /, $system.client_version=,\ 77 | \" started \", $convert.date=$system.startup_time=,\ 78 | \" \", $convert.time=$system.startup_time=,\ 79 | \", up \", $pyro._elapsed_time=$system.startup_time=,\ 80 | \" [PID=\", $system.pid=,],\ 81 | \" [CWD=\", $system.cwd=,]" 82 | 83 | pyro.bind_key = print_uptime, u, "pyro._print_uptime=" 84 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/auto-scrape.rc: -------------------------------------------------------------------------------- 1 | ### Tracker Auto-Scraping ################################################### 2 | # vim: ft=dosini 3 | # 4 | # To disable this, use 5 | # 6 | # echo >>~/.pyroscope/rtorrent.d/.rcignore "auto-scrape.rc.default" 7 | # 8 | # [devised by @chros73] 9 | 10 | # Regularly update scrape information for all torrents, even stopped ones. 11 | # It won't affect the operation of rtorrent, but it is nice to have these values updated. 12 | # By default, this info is only updated when rtorrent starts or a torrent is added. 13 | # 14 | # Try to balance calls to not fire them up at the same time, since multiscraping 15 | # isn't implemented in libtorrent. 16 | # 17 | # Polls for elapsed scrape intervals every 5 minutes, and splits items into two groups: 18 | # - data-transferring items: update every 10 minutes 19 | # - idle or stopped items: update every 12 hours 20 | # 21 | # To check the scrape status, just list recorded scrape times in order: 22 | # 23 | # rtcontrol --from started -s* -qco custom_tm_last_scrape.raw.delta,name // 24 | # 25 | # To debug, call this after startup (on a test instance with just a few items): 26 | # 27 | # rtxmlrpc pyro.scrape_verbose.set=,1 ; rtxmlrpc pyro.scrape_interval.idle.set=,200 28 | 29 | # VALUE: Enable verbose mode by setting to '1' → log any scrape, not just manual ones 30 | method.insert.value = pyro.scrape_verbose, 0 31 | 32 | # VALUE: Scrape intervals (for active and idle items) 33 | method.insert.value = pyro.scrape_interval.active, 600 34 | method.insert.value = pyro.scrape_interval.idle, 43200 35 | 36 | 37 | # HELPER: Log a scraping event 38 | method.insert = pyro._last_scrape.print, simple|private,\ 39 | "print=\"Sending scrape for #\",$d.hash=,\" \",[,$d.name=,]" 40 | 41 | # HELPER: Set current time in a custom field (tm_last_scrape) and save session 42 | method.insert = pyro._last_scrape.bump, simple|private,\ 43 | "d.custom.set=tm_last_scrape, $cat=$system.time= ; d.save_resume=" 44 | 45 | # COMMAND: Send the scrape request, set 'tm_last_scrape' timestamp, and save session 46 | method.insert = d.tracker.bump_scrape, simple,\ 47 | "d.tracker.send_scrape=0 ; pyro._last_scrape.bump= ; branch=pyro.scrape_verbose=,pyro._last_scrape.print=" 48 | 49 | # HELPER: Check if the required time interval (arg.0) has passed, 50 | # if yes then call 'd.tracker.bump_scrape' 51 | method.insert = pyro._scrape.send_after, simple|private,\ 52 | "branch={(elapsed.greater, $d.custom=tm_last_scrape, $argument.0=), d.tracker.bump_scrape=}" 53 | 54 | # HELPER: Check for non-existing or empty custom field, 55 | # to be able to test its validity later 56 | method.insert = pyro._last_scrape.poll, simple|private,\ 57 | "branch={d.custom=tm_last_scrape, pyro._scrape.send_after=$argument.0=, d.tracker.bump_scrape=}" 58 | 59 | # SCHEDULE: Check for elapsed intervals every 5 minutes, and update scrape info 60 | # for active items and idle/stopped ones according to their interval settings. 61 | schedule2 = pyro_last_scrape_check, 333, 300,\ 62 | ((d.multicall2,default,\ 63 | "branch=\"or={d.up.rate=,d.down.rate=,}\",\ 64 | pyro._last_scrape.poll=$pyro.scrape_interval.active=,\ 65 | pyro._last_scrape.poll=$pyro.scrape_interval.idle=")) 66 | 67 | # EVENT: Initialize 'tm_last_scrape' for newly added items 68 | method.set_key = event.download.inserted_new, pyro_last_scrape_init, "pyro._last_scrape.bump=" 69 | 70 | # UI/Key: Manually send scrape with "&" 71 | pyro.bind_key = manual_scrape, &, "d.tracker.bump_scrape= ; branch=pyro.scrape_verbose=,false=,pyro._last_scrape.print=" 72 | 73 | # Scrape a little after completion (on average after 30 sec) 74 | pyro.collapsed_view.add = scheduled_scrape_once 75 | view.filter = scheduled_scrape_once, ((false)) 76 | method.insert = pyro._add_to_scheduled_scrape_once, simple|private,\ 77 | "d.views.push_back_unique = scheduled_scrape_once ; \ 78 | view.set_visible = scheduled_scrape_once" 79 | method.set_key = event.download.finished, !add_to_scheduled_scrape_once, \ 80 | ((pyro._add_to_scheduled_scrape_once)) 81 | schedule2 = do_scheduled_scrape_once, 127, 60, \ 82 | ((d.multicall2, scheduled_scrape_once, \ 83 | "d.tracker.bump_scrape=", \ 84 | "d.views.remove = scheduled_scrape_once", \ 85 | "view.set_not_visible = scheduled_scrape_once")) 86 | 87 | # END auto-scrape 88 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/bind-navigation-keys.rc: -------------------------------------------------------------------------------- 1 | ### UI/Keys: PgUp/Dn, Home, End ############################################# 2 | # vim: ft=dosini 3 | # 4 | # To disable this, use 5 | # 6 | # echo >>~/.pyroscope/rtorrent.d/.rcignore "bind-navigation-keys.rc.default" 7 | # 8 | # See the "Trouble-Shooting Guide" in the manual for when and why to do that. 9 | 10 | pyro.bind_key = navigation_home, 0406, "ui.focus.home=" 11 | pyro.bind_key = navigation_end, 0550, "ui.focus.end=" 12 | pyro.bind_key = navigation_pgup, 0523, "ui.focus.pgup=" 13 | pyro.bind_key = navigation_pgdn, 0522, "ui.focus.pgdn=" 14 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/collapse-built-in-views.rc: -------------------------------------------------------------------------------- 1 | ### UI/VIEW: Collapse all built-in views #################################### 2 | # vim: ft=dosini 3 | # 4 | # To disable this, use 5 | # 6 | # echo >>~/.pyroscope/rtorrent.d/.rcignore "collapse-built-in-views.rc.default" 7 | # 8 | 9 | # PS 1.1+ has the built-in views collapsed by default! 10 | branch=(not, (system.has, "collapsed-views")), ((do, \ 11 | "pyro.view.collapsed.toggle = active", \ 12 | "pyro.view.collapsed.toggle = complete", \ 13 | "pyro.view.collapsed.toggle = hashing", \ 14 | "pyro.view.collapsed.toggle = incomplete", \ 15 | "pyro.view.collapsed.toggle = leeching", \ 16 | "pyro.view.collapsed.toggle = main", \ 17 | "pyro.view.collapsed.toggle = name", \ 18 | "pyro.view.collapsed.toggle = seeding", \ 19 | "pyro.view.collapsed.toggle = started", \ 20 | "pyro.view.collapsed.toggle = stopped" )) 21 | 22 | # EOF 23 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/commands.rc: -------------------------------------------------------------------------------- 1 | ### UI Helper Commands (Ctrl-X) ############################################# 2 | # vim: ft=dosini 3 | 4 | # UI/CMD: Use rtcontrol filter (^X s=KEYWORD, ^X t=TRACKER, ^X f="FILTER") 5 | method.insert = s, simple|private,\ 6 | "execute.nothrow=\"$cat=$pyro.bin_dir=,rtcontrol\",--detach,-qV,\ 7 | [,\"$cat=*,$argument.0=,*\",OR,\"$cat=custom_displayname=,*,$argument.0=,*\",]" 8 | method.insert = t, simple|private,\ 9 | "execute.nothrow=\"$cat=$pyro.bin_dir=,rtcontrol\",--detach,-qV,\"$cat=\\\"alias=\\\",$argument.0=\"" 10 | method.insert = f, simple|private,\ 11 | "execute.nothrow=\"$cat=$pyro.bin_dir=,rtcontrol\",--detach,-qV,$argument.0=" 12 | 13 | # UI/CMD: Housekeeping (delete item + data) 14 | method.insert = purge, simple|private,\ 15 | "execute.nothrow=\"$cat=$pyro.bin_dir=,rtcontrol\",-q,--detach,--purge,--yes,--from-view,$d.hash=,//" 16 | method.insert = cull, simple|private,\ 17 | "execute.nothrow=\"$cat=$pyro.bin_dir=,rtcontrol\",-q,--detach,--cull,--yes,--from-view,$d.hash=,//" 18 | 19 | # UI/CMD: Add, remove, and show tags 20 | method.insert = tag.show, simple|private,\ 21 | "execute.nothrow=\"$cat=$pyro.bin_dir=,rtcontrol\",--detach,-qV,-otag_show,--from-view,$d.hash=,//,--flush" 22 | #method.insert = tag.show, simple|private,\ 23 | # "print=\"Tags: \",$d.custom=tags" 24 | 25 | method.insert = tag.add, simple|private,\ 26 | "execute.nothrow=\"$cat=$pyro.bin_dir=,rtcontrol\",--detach,-qV,-otag_show,--flush,--yes,--from-view,$d.hash=,//,--tag,$argument.0=" 27 | 28 | method.insert = tag.rm, simple|private,\ 29 | "execute.nothrow=\"$cat=$pyro.bin_dir=,rtcontrol\",--detach,-qV,-otag_show,--flush,--yes,--from-view,$d.hash=,//,--tag,\"$cat=-,$argument.0=\"" 30 | 31 | # UI/Key: Ctrl-G shows the tags of the current item 32 | pyro.bind_key = tag_show, ^G, "tag.show=" 33 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/helper-methods.rc: -------------------------------------------------------------------------------- 1 | # vim: ft=dosini 2 | # refactoring made this empty for now 3 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/logging.rc: -------------------------------------------------------------------------------- 1 | ### Event Logging ########################################################### 2 | # vim: ft=dosini 3 | # 4 | # To disable this, use 5 | # 6 | # echo >>~/.pyroscope/rtorrent.d/.rcignore "logging.rc.default" 7 | 8 | catch = {false=, "method.insert.simple = string.replace, \"cat=(argument.0)\""} 9 | method.insert = pyro._date_now, simple|private, \ 10 | "string.replace = (convert.date, (system.time_seconds)), {/, .}" 11 | 12 | # Logging + UI: Add day break to log 13 | schedule2 = log_new_day, 00:00:05, 24:00:00, ((print, "New day: ", ((pyro._date_now)) )) 14 | 15 | # Emit regular warning when ~/NOCRON exists 16 | schedule2 = log_nocron, 900, 1800, "branch = (value, (os.path.mtime, ~/NOCRON, 0)), \ 17 | ((print, \"WARNING: Cron jobs disabled by ~/NOCRON\"))" 18 | 19 | # EVENTS: Logging (don't log "opened", or you get swamped at startup) 20 | method.insert = pyro._log_on_event, simple|private, \ 21 | "print = $argument.0=, $d.name=, \" [\", $pyro._date_now=, \"]\"" 22 | 23 | method.set_key = event.download.inserted_new, ~log, ((pyro._log_on_event, "LOADED ")) 24 | method.set_key = event.download.finished, ~log, ((pyro._log_on_event, "COMPLETED ")) 25 | method.set_key = event.download.closed, ~log, ((pyro._log_on_event, "CLOSED ")) 26 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/quick-help.rc: -------------------------------------------------------------------------------- 1 | ### UI: Show quick help resources ########################################### 2 | # vim: ft=dosini 3 | # 4 | # To disable this, use 5 | # 6 | # echo >>~/.pyroscope/rtorrent.d/.rcignore "quick-help.rc.default" 7 | 8 | method.insert = pyro.print_help, multi|rlookup|static 9 | method.set_key = pyro.print_help, !!intro1, ((print, "")) 10 | method.set_key = pyro.print_help, !!intro2, ((print, ((cat,\ 11 | "rTorrent QUICK HELP RESOURCES" )) )) 12 | method.set_key = pyro.print_help, !!intro3, ((print, ((cat,\ 13 | "=============================" )) )) 14 | method.set_key = pyro.print_help, !10handbook, ((print, ((cat,\ 15 | "rTorrent Handbook http://rtorrent-docs.readthedocs.io/" )) )) 16 | method.set_key = pyro.print_help, !10wiki, ((print, ((cat,\ 17 | "rTorrent Wiki https://github.com/rakshasa/rtorrent/wiki" )) )) 18 | method.set_key = pyro.print_help, !20pyrocore, ((print, ((cat,\ 19 | "pyrocore Manual (rtcontrol) https://pyrocore.readthedocs.io/" )) )) 20 | 21 | # Bind to F2 22 | pyro.bind_key = quick_help, 0412, "pyro.print_help=" 23 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/theming.rc: -------------------------------------------------------------------------------- 1 | ### UI/Colors: Rotate through color schemes ################################# 2 | # vim: ft=dosini 3 | # 4 | # (De-)select a theme: python-pyrocore -m pyrocore.ui.theming -t ‹name(s)› 5 | # Select all themes: python-pyrocore -m pyrocore.ui.theming -a 6 | # List all themes: python-pyrocore -m pyrocore.ui.theming -l 7 | # Rotate to next: python-pyrocore -m pyrocore.ui.theming -qn 8 | # Print current path: python-pyrocore -m pyrocore.ui.theming -qc 9 | # 10 | # Example: 11 | # 12 | # python-pyrocore -m pyrocore.ui.theming -a -t default-256,solarized-blue,solarized-yellow,happy-pastel -l 13 | 14 | # UI/Key: Bind theme rotation to '~' 15 | pyro.bind_key = rotate_theme, ~,\ 16 | "try_import=(execute.capture_nothrow, (cat,(pyro.bin_dir),python-pyrocore), -m, pyrocore.ui.theming, -qn)" 17 | 18 | # HELPER: This is used within theme files, currently just announces the switch 19 | method.insert = pyro.color_theme.name, private|simple,\ 20 | "print = (cat, \"Switched to '\", (argument.0), \"' color theme\")" 21 | 22 | # HELPER: Load the currently active theme (on startup) 23 | method.insert = pyro._load_current_theme, private|simple,\ 24 | "try_import=(execute.capture_nothrow, (cat,(pyro.bin_dir),python-pyrocore), -m, pyrocore.ui.theming, -qc)" 25 | 26 | # Load @ startup 27 | branch=(pyro.extended), ((pyro._load_current_theme)) 28 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/timestamps.rc: -------------------------------------------------------------------------------- 1 | ### EVENTS: Timestamps ###################################################### 2 | # vim: ft=dosini 3 | # tm_loaded = time loaded into client 4 | # tm_started = time of *first* start 5 | # tm_completed = time of completion 6 | 7 | method.insert = pyro._tm_started.now, simple|private,\ 8 | "d.custom.set=tm_started,$cat=$system.time= ; d.save_resume=" 9 | method.insert = pyro._tm_completed.now, simple|private,\ 10 | "d.custom.set=tm_completed,$cat=$system.time= ; d.save_resume=" 11 | 12 | method.set_key = event.download.resumed, !time_stamp,\ 13 | "branch=d.custom=tm_started,false=,pyro._tm_started.now=" 14 | method.set_key = event.download.inserted_new, !time_stamp,\ 15 | "d.custom.set=tm_loaded,$cat=$system.time= ; d.save_resume=" 16 | method.set_key = event.download.finished, !time_stamp,\ 17 | "pyro._tm_completed.now=" 18 | method.set_key = event.download.hash_done, !time_stamp,\ 19 | "branch=\"and={d.complete=,not=$d.custom=tm_completed}\", pyro._tm_completed.now=" 20 | 21 | 22 | # EVENTS: Activation intervals 23 | method.insert = pyro._activations.append, simple|private,\ 24 | "d.custom.set=activations,\"$cat=$d.custom=activations,$argument.0=,$system.time=\" ; d.save_resume=" 25 | method.set_key = event.download.paused, !activations, "pyro._activations.append=P" 26 | method.set_key = event.download.resumed, !activations, "pyro._activations.append=R" 27 | 28 | 29 | # EVENTS: Timestamp 'tm_downloaded' (time when meta (torrent) file was downloaded) 30 | method.insert = pyro._tm_downloaded_init, simple|private,\ 31 | "d.custom.set = tm_downloaded, (os.path.mtime, (d.tied_to_file), (cat, (system.time))) ; d.save_resume=" 32 | method.insert = d.timestamp.downloaded, simple, "d.custom=tm_downloaded" 33 | method.set_key = event.download.inserted_new, set_downloaded_date, ((pyro._tm_downloaded_init)) 34 | 35 | 36 | # SCHEDULE: Set "last_active" custom timestamp field for items that have peers 37 | method.insert = d.timestamp.last_active, simple, "if=$d.peers_connected=,$cat=$system.time=,$d.custom=last_active" 38 | method.insert = d.timestamp.last_active.update, simple|private,\ 39 | "d.custom.set=last_active,$cat=$system.time= ; branch=argument.0=,d.save_resume=" 40 | 41 | schedule2 = pyro_update_last_active, 24, 42,\ 42 | "d.multicall2=started,\"branch=$d.peers_connected=,d.timestamp.last_active.update=\"" 43 | 44 | method.set_key = event.download.resumed, !last_active,\ 45 | "branch=\"or={d.peers_connected=,not=$d.custom=last_active}\", d.timestamp.last_active.update=1" 46 | method.set_key = event.download.finished, !last_active, "d.timestamp.last_active.update=1" 47 | 48 | method.insert = d.timestamp.last_active.print, simple|private,\ 49 | "print=\"$cat={$convert.date=$d.timestamp.last_active=, \\\" \\\", $convert.time=$d.timestamp.last_active=}\"" 50 | 51 | 52 | # SCHEDULE: Set "last_xfer" custom timestamp field for items that transfer data 53 | method.insert.value = pyro.last_xfer.min_rate, 5000 54 | 55 | method.insert = pyro._last_xfer_check_min_rate, simple|private,\ 56 | "greater=argument.0=,pyro.last_xfer.min_rate=" 57 | method.insert = pyro._last_xfer_update, simple|private,\ 58 | "d.custom.set=last_xfer,$cat=$system.time= ; branch=argument.0=,d.save_resume=" 59 | method.insert = d.last_xfer.is_active, simple,\ 60 | "or={pyro._last_xfer_check_min_rate=$d.up.rate=,pyro._last_xfer_check_min_rate=$d.down.rate=}" 61 | method.insert = d.timestamp.last_xfer, simple, "if=$d.last_xfer.is_active=,$cat=$system.time=,$d.custom=last_xfer" 62 | 63 | schedule2 = pyro_update_last_xfer, 33, 17,\ 64 | "d.multicall2=active,\"branch=$d.last_xfer.is_active=,pyro._last_xfer_update=\"" 65 | 66 | method.insert = d.timestamp.last_xfer.print, simple|private,\ 67 | "print=\"$cat={$convert.date=$d.timestamp.last_xfer=, \\\" \\\", $convert.time=$d.timestamp.last_xfer=}\"" 68 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/torque.rc: -------------------------------------------------------------------------------- 1 | ### TORQUE: View for queue manager job ###################################### 2 | # vim: ft=dosini 3 | # 4 | # This is not the buggy internal scheduler controlled by scheduler.max_active! 5 | 6 | pyro.collapsed_view.add = pyrotorque 7 | 8 | view.filter = pyrotorque, ((false)) 9 | schedule2 = filter_pyrotorque, 1, 15,\ 10 | "view.filter = pyrotorque,\"or={d.up.rate=,d.down.rate=,d.peers_connected=,not=$d.complete=}\"" 11 | 12 | pyro.bind_key = pyrotorque_view, Q, "ui.current_view.set=pyrotorque" 13 | 14 | # TORQUE: Daemon watchdog 15 | method.insert = pyro.watchdog, simple|private,\ 16 | "execute.nothrow=bash,-c,\"$cat=\\\"test ! -f \\\",$argument.0=,\\\"/run/pyrotorque || \\\",$pyro.bin_dir=,\\\"pyrotorque --cron \\\",$argument.1=, \\\" || true\\\"\"" 17 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/view-datasize.rc: -------------------------------------------------------------------------------- 1 | ### UI/VIEW: Data size ###################################################### 2 | # vim: ft=dosini 3 | # All items, sorted by their content size. 4 | # Bound to '"' key (will ONLY work if you use rT-PS)! 5 | 6 | pyro.collapsed_view.add = datasize 7 | pyro.bind_key = datasize_view, 0042, "view.sort = datasize ; ui.current_view.set = datasize" 8 | 9 | view.sort_new = datasize, "greater = d.size_bytes=" 10 | view.sort_current = datasize, "greater = d.size_bytes=" 11 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/view-indemand.rc: -------------------------------------------------------------------------------- 1 | ### UI/VIEW: Show items sorted by activity on "indemand" #################### 2 | # vim: ft=dosini 3 | 4 | pyro.collapsed_view.add = indemand 5 | pyro.bind_key = indemand_view, ?, "view.sort = indemand ; ui.current_view.set = indemand" 6 | 7 | view.filter = indemand, ((d.is_open)) 8 | view.filter_on = indemand, event.download.closed, event.download.opened 9 | 10 | schedule2 = sort_indemand, 22, 33, "view.filter = indemand, d.is_open= ; view.sort = indemand" 11 | 12 | branch = (and, (system.has, "compare="), (system.has, "d.custom.if_z=")), \ 13 | "view.sort_new = indemand, \"compare=--+,d.timestamp.last_active=,pyro._view_sort_timestamp=,d.name=\"", \ 14 | "view.sort_new = indemand, greater=d.timestamp.last_active=" 15 | 16 | branch = (and, (system.has, "compare="), (system.has, "d.custom.if_z=")), \ 17 | "view.sort_current = indemand, \"compare=--+,d.timestamp.last_active=,pyro._view_sort_timestamp=,d.name=\"", \ 18 | "view.sort_current = indemand, greater=d.timestamp.last_active=" 19 | 20 | branch = (system.has, "convert.time_delta="), \ 21 | "method.set_key = ui.column.render, \"968:6C21/1C28/1C21/2C28/2:◷ ℞ \", \ 22 | ((if, ((d.peers_connected)), ((cat, \" \", ((convert.magnitude, ((d.peers_connected)) )), \"℞ \")), \ 23 | ((convert.time_delta, ((value, ((d.timestamp.last_active)) )) )) ))", \ 24 | ((print, "WARNING: No LAST ACTIVE column (update rTorrent-PS)!")) 25 | 26 | branch = (system.has, "ui.column.hide="), \ 27 | ((ui.column.hide, 968)), \ 28 | ((print, "WARNING: LAST ACTIVE column is static (update rTorrent-PS)!")) 29 | 30 | branch = (and, (system.has, "string.equals="), (system.has, "ui.column.hide=")), \ 31 | "method.set_key = event.view.show, ~last_active_toggle, \ 32 | \"branch = \\\"string.equals=$ui.current_view=, indemand, last_xfer, seeding\\\", \ 33 | ui.column.show=968, ui.column.hide=968\"" 34 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/view-last_xfer.rc: -------------------------------------------------------------------------------- 1 | ### UI/VIEW: Last data transfer ############################################# 2 | # vim: ft=dosini 3 | # 4 | # All items, sorted by their last_xfer + active timestamps, or event times. 5 | # 6 | # Bound to '¬' key – will ONLY work if you use rT-PS! 7 | # (You might need to add another key bind, this works in Europe) ☺ 8 | # 9 | # Note that to re-sort items, you have to select the view again (no auto-update). 10 | # 11 | 12 | pyro.collapsed_view.add = last_xfer 13 | pyro.bind_key = last_xfer_view, 0254, "view.sort = last_xfer ; ui.current_view.set = last_xfer" 14 | 15 | branch = (and, (system.has, "compare="), (system.has, "d.custom.if_z=")), \ 16 | "view.sort_new = last_xfer, \"compare=---+,d.timestamp.last_xfer=,d.timestamp.last_active=,pyro._view_sort_timestamp=,d.name=\"", \ 17 | "view.sort_new = last_xfer, greater=d.timestamp.last_xfer=" 18 | 19 | branch = (and, (system.has, "compare="), (system.has, "d.custom.if_z=")), \ 20 | "view.sort_current = last_xfer, \"compare=---+,d.timestamp.last_xfer=,d.timestamp.last_active=,pyro._view_sort_timestamp=,d.name=\"", \ 21 | "view.sort_current = last_xfer, greater=d.timestamp.last_xfer=" 22 | 23 | branch = (and, (system.has, "convert.time_delta="), (system.has, "d.custom.if_z=")), \ 24 | "method.set_key = ui.column.render, \"962:6C21/1C23/1C21/2C23/2:◷ ↑↓ \", \ 25 | ((convert.time_delta, ((value, ((d.custom.if_z, last_xfer, ((d.timestamp.last_active)) )) )) ))", \ 26 | ((print, "WARNING: No LAST XFER column (update rTorrent-PS)!")) 27 | 28 | branch = (system.has, "ui.column.hide="), \ 29 | ((ui.column.hide, 962)), \ 30 | ((print, "WARNING: LAST XFER column is static (update rTorrent-PS)!")) 31 | 32 | branch = (and, (system.has, "string.equals="), (system.has, "ui.column.hide=")), \ 33 | "method.set_key = event.view.show, ~last_xfer_toggle, \ 34 | \"branch = \\\"string.equals=$ui.current_view=, last_xfer, started, seeding, leeching\\\", \ 35 | ui.column.show=962, ui.column.hide=962\"" 36 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/view-messages.rc: -------------------------------------------------------------------------------- 1 | ### UI/VIEW: Show current messages (bound to '!' in rT-PS) ################## 2 | # vim: ft=dosini 3 | 4 | pyro.collapsed_view.add = messages 5 | view.filter = messages, ((d.message)) 6 | 7 | branch=pyro.extended=,false=,"view.sort_new = messages,less=d.message=" 8 | branch=pyro.extended=,"view.sort_new = messages,\"compare=,d.message=,d.name=\"" 9 | 10 | branch=pyro.extended=,false=,"view.sort_current = messages,less=d.message=" 11 | branch=pyro.extended=,"view.sort_current = messages,\"compare=,d.message=,d.name=\"" 12 | 13 | method.insert = pyro.ui.messages.show, simple,\ 14 | "ui.current_view.set=messages ;view.sort=messages ;print=$view.size=messages,\" message(s)!\"" 15 | 16 | pyro.bind_key = messages_view, !, "pyro.ui.messages.show=" 17 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/view-ratio.rc: -------------------------------------------------------------------------------- 1 | ### UI/VIEW: Ratio ########################################################## 2 | # vim: ft=dosini 3 | # All items, sorted by their current ratio, upload size, or event times. 4 | # Bound to '%' key (will ONLY work if you use rT-PS)! 5 | 6 | pyro.collapsed_view.add = ratio 7 | pyro.bind_key = ratio_view, %, "view.sort = ratio ; ui.current_view.set = ratio" 8 | 9 | branch = (and, (system.has, "compare="), (system.has, "d.custom.if_z=")), \ 10 | "view.sort_new = ratio, \"compare=---+,d.ratio=,d.up.total=,pyro._view_sort_timestamp=,d.name=\"", \ 11 | "view.sort_new = ratio, greater=d.ratio=" 12 | 13 | branch = (and, (system.has, "compare="), (system.has, "d.custom.if_z=")), \ 14 | "view.sort_current = ratio, \"compare=---+,d.ratio=,d.up.total=,pyro._view_sort_timestamp=,d.name=\"", \ 15 | "view.sort_current = ratio, greater=d.ratio=" 16 | 17 | branch = (system.has, "string.substr="), \ 18 | "method.set_key = ui.column.render, \"922:7C93/7: ☯ ‰ \", \ 19 | ((string.substr, ((cat, \" \", ((d.ratio)) )), -6))", \ 20 | ((print, "WARNING: No RATIO column (update rTorrent-PS)!")) 21 | 22 | branch = (system.has, "ui.column.hide="), \ 23 | ((ui.column.hide, 922)), \ 24 | ((print, "WARNING: RATIO column is static (update rTorrent-PS)!")) 25 | 26 | branch = (and, (system.has, "string.equals="), (system.has, "ui.column.hide=")), \ 27 | "method.set_key = event.view.show, ~ratio_toggle, \ 28 | \"branch = \\\"string.equals=$ui.current_view=, ratio, stopped\\\", \ 29 | ui.column.show=922, ui.column.hide=922\"" 30 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/view-tagged.rc: -------------------------------------------------------------------------------- 1 | ### UI/VIEW: Manually tag (select) items #################################### 2 | # vim: ft=dosini 3 | # . = toggle focused item between tagged and untagged 4 | # T = clear all tags 5 | # : = show tagged items view 6 | 7 | pyro.collapsed_view.add = tagged 8 | view.persistent = tagged 9 | 10 | # pyro.view.toggle_visible = ‹viewname› – Toggle visibility of an item for the given view 11 | method.insert = pyro.view.toggle_visible, simple|private,\ 12 | "branch=d.views.has=$argument.0=,view.set_not_visible=$argument.0=,view.set_visible=$argument.0=" 13 | 14 | # Empty the 'tagged' view, and remove visibility for all items 15 | method.insert = pyro._view_tagged_clear, simple|private,\ 16 | "view.filter=tagged,false= ; d.multicall2 = default, d.views.remove=tagged" 17 | 18 | # Key bindings for 'tagged' view management 19 | pyro.bind_key = tagged_toggle, ., "pyro.view.toggle_visible=tagged" 20 | pyro.bind_key = tagged_clear, T, "pyro._view_tagged_clear=" 21 | pyro.bind_key = tagged_view, :, "ui.current_view.set=tagged" 22 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/view-trackers.rc: -------------------------------------------------------------------------------- 1 | #### UI/VIEW: Trackers ###################################################### 2 | # vim: ft=dosini 3 | # All items, sorted by tracker and name. 4 | # This will ONLY work if you use rT-PS! 5 | 6 | pyro.collapsed_view.add = trackers 7 | pyro.bind_key = trackers_view, t, "ui.current_view.set = trackers" 8 | 9 | branch = (system.has, "d.tracker_alias="), ((do, \ 10 | "view.sort_new = trackers, \"compare=++, d.tracker_alias=, d.name=\"", \ 11 | "view.sort_current = trackers, \"compare=++, d.tracker_alias=, d.name=\"" )) 12 | 13 | branch = (and, (pyro.extended), (not, (system.has, "d.tracker_alias="))), ((do, \ 14 | "print = \"WARNING: Inferior sorting of 'trackers' view (update rTorrent-PS)!\"", \ 15 | "view.sort_new = trackers, \"compare=++, d.tracker_domain=, d.name=\"", \ 16 | "view.sort_current = trackers, \"compare=++, d.tracker_domain=, d.name=\"" )) 17 | 18 | branch = (system.has, "convert.time_delta="), \ 19 | "method.set_key = ui.column.render, \"964:6C21/1C28/1C21/2C28/2:◷ ↺⤴⤵ \", \ 20 | ((convert.time_delta, ((value, ((d.custom, tm_last_scrape)) )) ))", \ 21 | ((print, "WARNING: No LAST SCRAPE column (update rTorrent-PS)!")) 22 | 23 | branch = (system.has, "ui.column.hide="), \ 24 | ((ui.column.hide, 964)), \ 25 | ((print, "WARNING: LAST SCRAPE column is static (update rTorrent-PS)!")) 26 | 27 | branch = (and, (system.has, "string.equals="), (system.has, "ui.column.hide=")), \ 28 | "method.set_key = event.view.show, ~last_scrape_toggle, \ 29 | \"branch = \\\"string.equals=$ui.current_view=, trackers, started, stopped\\\", \ 30 | ((ui.column.show, 964)), \ 31 | ((ui.column.hide, 964))\"" 32 | 33 | branch = (system.has, "d.tracker_domain="), \ 34 | "method.set_key = ui.column.render, \"966:?15:Domain\", \ 35 | ((d.tracker_domain))" 36 | 37 | branch = (system.has, "ui.column.hide="), ((ui.column.hide, 966)) 38 | 39 | branch = (and, (system.has, "string.equals="), (system.has, "ui.column.hide=")), \ 40 | "method.set_key = event.view.show, ~trackets_domain_toggle, \ 41 | \"branch = \\\"string.equals=$ui.current_view=, trackers\\\", \ 42 | ((ui.column.show, 966)), \ 43 | ((ui.column.hide, 966))\"" 44 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/rtorrent.d/view-uploaded.rc: -------------------------------------------------------------------------------- 1 | ### UI/VIEW: Uploaded data ################################################## 2 | # vim: ft=dosini 3 | # All items, sorted by upload amount. 4 | # Bound to '°' key (will ONLY work if you use rT-PS)! 5 | 6 | pyro.collapsed_view.add = uploaded 7 | pyro.bind_key = uploaded_view, 0260, "view.sort = uploaded ; ui.current_view.set = uploaded" 8 | 9 | branch = (and, (system.has, "compare="), (system.has, "d.custom.if_z=")), \ 10 | "view.sort_new = uploaded, \"compare=--+,d.up.total=,pyro._view_sort_timestamp=,d.name=\"", \ 11 | "view.sort_new = uploaded, greater=d.up.total=" 12 | 13 | branch = (and, (system.has, "compare="), (system.has, "d.custom.if_z=")), \ 14 | "view.sort_current = uploaded, \"compare=--+,d.up.total=,pyro._view_sort_timestamp=,d.name=\"", \ 15 | "view.sort_current = uploaded, greater=d.up.total=" 16 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/templates/conky/conkyrc: -------------------------------------------------------------------------------- 1 | # Sample conky Configuration 2 | # 3 | # The settings below are just for demo purposes 4 | # Integrate the TEXT portion into your normal ~/.conkyrc 5 | # 6 | # Call it like so to see what it looks like: 7 | # pyroadmin --create-config 8 | # conky -c ~/.pyroscope/templates/conky/conkyrc 9 | 10 | update_interval 5.0 11 | own_window yes 12 | own_window_transparent yes 13 | own_window_hints undecorated,below,skip_taskbar,skip_pager 14 | maximum_width 320 15 | minimum_size 320 600 16 | alignment top_right 17 | 18 | default_shade_color 999999 19 | default_outline_color 222222 20 | draw_outline yes 21 | draw_shades yes 22 | 23 | use_xft yes 24 | xftfont Lucida Sans Typewriter:size=8 25 | #xftfont Bitstream Vera Sans Mono:size=8 26 | xftalpha 0.8 27 | 28 | background yes 29 | double_buffer yes 30 | total_run_times 0 31 | override_utf8_locale yes 32 | text_buffer_size 16768 33 | 34 | TEXT 35 | ${execpi 5 ~/bin/rtcontrol -qO conky/rtorstat.txt --from-view incomplete is_open=yes is_ignored=no} 36 | 37 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/templates/conky/rtorstat.txt: -------------------------------------------------------------------------------- 1 | {{# Template example for conky that is similar to the rtorstat output. 2 | 3 | See the conkyrc file, and http://www.codetrax.org/projects/rtorstat 4 | }}{{py: 5 | global os, time, subprocess, incomplete, active, max_entries, viewdef, df 6 | import time 7 | import subprocess 8 | from pyrocore.util import os 9 | 10 | viewdef = [i.split() for i in ( 11 | "i main", 12 | "< stopped", 13 | "6 incomplete", 14 | "5 active", 15 | )] 16 | 17 | df = os.statvfs(proxy.directory.default()) 18 | df = df.f_bavail * df.f_bsize 19 | 20 | max_entries = 4 21 | incomplete = [i for i in matches if not i.is_complete] 22 | active = [] #[i for i in matches if i.is_complete] 23 | }}{{# 24 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 25 | }}{{def color(c, s)}}${color {{c}}}{{s}}${color }{{enddef}}{{# 26 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 27 | }}{{def header(s)}}{{color('#9bf', s)}}{{enddef}}{{# 28 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 29 | }}{{def label(s)}}{{color('#66d', s)}}{{enddef}}{{# 30 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 31 | }}{{def pc_col(f)}}${color #{{('f00','f33','f63','3c9','3fc')[min(4, int(f/25))]}}}{{enddef}}{{# 32 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 33 | }}{{def show(loop, d)}}{{# 34 | }}{{"%2d." % loop.number|label}} {{d.name}} {{'['|label}}{{d.alias}}{{']'|label}} 35 | {{'S'|label}} {{d.size|sz}} {{'U'|label}} {{d.uploaded|sz}} {{'R'|label}} {{pc_col(d.ratio*100.0)}}{{d.ratio|pc}}%${color }{{# 36 | }}{{enddef}}{{# 37 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 38 | }}{{'rTorrent'|header}} {{'VERSION %CPU %MEM UPTIME'|label}} 39 | {{"%-13.13s" % proxy.session.name()}} {{"%-8s" % proxy.system.client_version()}}{{# 40 | }} {{subprocess.Popen(["ps", "h", "-o", "%cpu,%mem,etime", "-p", str(proxy.system.pid())], stdout=subprocess.PIPE).communicate()[0].rstrip()}} 41 | 42 | {{'Views '|header}} {{for title, name in viewdef}} {{str(proxy.view.size('', name)).ljust(5)}} {{endfor}} 43 | ${voffset -14} ${font Webdings:size=12}{{for title, name in viewdef}}{{title|label}} {{endfor}}${font } 44 | ${voffset 4}{{'Data '|header}} {{'U'|label}}{{proxy.throttle.global_up.total()|sz}} {{'D'|label}}{{proxy.throttle.global_down.total()|sz}}{{# 45 | }} {{'F'|label}}{{df|sz}} 46 | {{'Upload '|label}} {{"%3.0f" % (100.0 * proxy.throttle.global_up.rate() / proxy.throttle.global_up.max_rate())}}% {{# 47 | }}${color #0d6}${execgraph echo {{min(100, 100.0 * proxy.throttle.global_up.rate() / proxy.throttle.global_up.max_rate())}}}${color } 48 | ${voffset -20} {{proxy.throttle.global_up.rate()|sz}}/s 49 | {{pc_col(100.0 * proxy.throttle.global_up.rate() / proxy.throttle.global_up.max_rate())}} ${execbar echo {{min(100, 100.0 * proxy.throttle.global_up.rate() / proxy.throttle.global_up.max_rate())}}}${color } 50 | {{'Download'|label}} {{"%3.0f" % (100.0 * proxy.throttle.global_down.rate() / proxy.throttle.global_down.max_rate())}}% {{# 51 | }}${color #d06}${execgraph echo {{min(100, 100.0 * proxy.throttle.global_down.rate() / proxy.throttle.global_down.max_rate())}}}${color } 52 | ${voffset -20} {{proxy.throttle.global_down.rate()|sz}}/s 53 | {{pc_col(100.0 * proxy.throttle.global_down.rate() / proxy.throttle.global_down.max_rate())}} ${execbar echo {{min(100, 100.0 * proxy.throttle.global_down.rate() / proxy.throttle.global_down.max_rate())}}}${color } 54 | {{if incomplete}} 55 | 56 | {{'Incomplete'|header}} {{'['|label}}{{len(incomplete)}}{{']'|label}} 57 | {{for loop, d in looper(incomplete[:max_entries])}} 58 | {{show(loop, d)}} {{'D'|label}} {{d.down|sz}}/s 59 | {{pc_col(d.done)}}{{"%5.1f" % d.done}}% ${execbar echo {{d.done}}}${color } 60 | {{endfor}} 61 | {{endif}} 62 | {{for i in range(3*len(incomplete), 3*max_entries)}} 63 | {{' ' * 53}} 64 | {{endfor}} 65 | {{if active}} 66 | 67 | {{'Active'|header}} {{'['|label}}{{len(active)}}{{']'|label}} 68 | {{for loop, d in looper(active[:max_entries])}} 69 | {{show(loop, d)}} 70 | {{endfor}} 71 | {{endif}} 72 | ${font Verdana:slant=italic:size=6}${color #666}PyroScope {{version}} at {{time.time() | iso}} ${color }${font } 73 | {{#EOF}} 74 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/templates/irc_status.txt: -------------------------------------------------------------------------------- 1 | {{# Template example that generates a short status display suitable for the shell or IRC. 2 | 3 | Use it like this: 4 | rtcontrol -qO irc_status.txt // 5 | 6 | }}{{py: 7 | import time 8 | }}rTorrent {{proxy.system.client_version()}}/{{proxy.system.library_version()}} - {{# 9 | }}up {{(time.time() - proxy.system.startup_time()) | duration}} - {{view.size()}} items 10 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/templates/json: -------------------------------------------------------------------------------- 1 | {{# This template serializes the result into JSON. 2 | 3 | Use it like this: 4 | rtcontrol -qOjson // 5 | }}[ 6 | {{for loop, item in looper(matches)}}{{item.as_dict() | json}}{{if not loop.last}}, 7 | {{endif}}{{endfor}} 8 | ] 9 | {{#}} 10 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/templates/orphans.txt: -------------------------------------------------------------------------------- 1 | {{# This template lists all paths in the given directory 2 | (or the download directory) NOT loaded into the client. 3 | 4 | Use it like this: 5 | rtcontrol -qO orphans.txt.default // -Ddir=$PWD 6 | }}{{py: 7 | global os, config, checked_dir, entries 8 | from pyrocore import config 9 | from pyrocore.util import os 10 | 11 | checked_dir = os.path.expanduser(getattr(config, 'dir', proxy.directory.default())) 12 | checked_dir = os.path.realpath(checked_dir.rstrip(os.sep)) + os.sep 13 | 14 | # List all non-symlinked entries in given directory 15 | entries = set(i.decode('utf8') 16 | for i in os.listdir(checked_dir) 17 | if not os.path.islink(os.path.join(checked_dir, i))) 18 | 19 | # From that list, remove anything loaded in the client 20 | entries -= set(os.path.basename(d.realpath) 21 | for d in matches 22 | if d.realpath.startswith(checked_dir)) 23 | }}{{for i in sorted(entries)}}{{# 24 | }}{{checked_dir}}{{i}} 25 | {{endfor}} 26 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/templates/rss.xml: -------------------------------------------------------------------------------- 1 | {{# Template that creates a RSS feed from matched items. 2 | 3 | First, install additional requirements: 4 | 5 | python-pyrocore -m pip install feedgen pytz tzlocal 6 | 7 | Then use this command to generate the feed: 8 | 9 | rtcontrol -qO rss.xml.default --from incomplete // 10 | 11 | Finally put it into a CGI script, or write to a file in 12 | the "htdocs" directory of a webserver, from a cronjob. 13 | }}{{py: 14 | import datetime 15 | from feedgen.feed import FeedGenerator 16 | #from pytz import utc as tz 17 | from tzlocal import get_localzone 18 | tz = get_localzone() 19 | 20 | feed = FeedGenerator() 21 | name = ':'.join(proxy.session.name().split()) 22 | feed.id(name) 23 | feed.title('rTorrent %s - incomplete items' % name) 24 | feed.link(href='https://www.reddit.com/r/torrents/comments/f8cy7l') 25 | feed.description('%d incomplete item(s) in this feed.' % len(matches)) 26 | 27 | for item in matches: 28 | entry = feed.add_entry() 29 | entry.guid(item.hash) 30 | entry.published(datetime.datetime.fromtimestamp(item.loaded, tz=tz)) 31 | entry.title('%s [%s - %d%%]' % (item.name, item.alias, item.done)) 32 | entry.summary('%.3f GiB - R %.1f%% - %s' % ( 33 | item.size / (1024.0**3), item.ratio, ' '.join(item.traits), 34 | )) 35 | }}{{ feed.rss_str(pretty=True) }} 36 | -------------------------------------------------------------------------------- /src/pyrocore/data/config/templates/rtorstat.html: -------------------------------------------------------------------------------- 1 | {{# Template example that is similar to the rtorstat output. 2 | 3 | See http://www.codetrax.org/projects/rtorstat 4 | 5 | Use it in a cronjob like this: 6 | rtcontrol -qO rtorstat.html done=-100 OR xfer=+0 -sdone >/var/www/cron/rtorrent.html 7 | }}{{py: 8 | import time 9 | }}{{def title}} 10 | rTorrent {{proxy.system.client_version()}}/{{proxy.system.library_version()}} 11 | - {{proxy.session.name()}} 12 | - up {{(time.time() - proxy.system.startup_time()) | duration}} 13 | {{enddef}}{{def show(loop, d)}} 14 |
15 | {{loop.number}}. {{d.name}} ({{d.alias}}) 16 |
17 |
{{"%.1f" % d.done}}%
18 |
19 | size: {{d.size|sz}} - uploaded: {{d.uploaded|sz}} - ratio: {{d.ratio|pc}}% 20 |
21 | {{enddef}} 22 | 23 | 24 | 25 | 26 | 27 | 28 | {{title}} 29 | 30 | 70 | 71 | 72 |

{{title}}

73 | 74 |

Query

75 | Selected {{len(matches)}} out of {{view.size()}} items using "{{query}}". 76 | 77 |

Incomplete

78 | {{for loop, d in looper(i for i in matches if not i.is_complete)}} 79 | {{show(loop, d)}} 80 | {{endfor}} 81 | 82 |

Active

83 | {{for loop, d in looper(i for i in matches if i.is_complete)}} 84 | {{show(loop, d)}} 85 | {{endfor}} 86 | 87 |
88 | Created by PyroScope {{version}} at {{time.time() | iso}} 89 |
90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/pyrocore/data/htdocs/css/custom.css: -------------------------------------------------------------------------------- 1 | /* This file is left empty, so it can be overloaded in 2 | 3 | ~/.pyroscope/htdocs/css/custom.css 4 | 5 | with your own personal CSS styles. 6 | */ 7 | 8 | -------------------------------------------------------------------------------- /src/pyrocore/data/htdocs/css/style.css: -------------------------------------------------------------------------------- 1 | /* Monitoring styles */ 2 | #heartbeat { 3 | color: #911; 4 | text-align: center; 5 | transition: color 0.15s; 6 | -webkit-transition: color 0.15s; 7 | } 8 | #heartbeat.on { 9 | color: #f33; 10 | } 11 | .value { 12 | overflow: hidden; 13 | white-space: nowrap; 14 | text-overflow: ellipsis; 15 | } 16 | .values { 17 | font-size: 70%; 18 | text-align: right; 19 | } 20 | .transparent { 21 | opacity: 0; 22 | } 23 | .hidden { 24 | display: none !important; 25 | } 26 | #error_msg { 27 | text-align: center; 28 | } 29 | 30 | /* Custom styles on top of Foundation */ 31 | body { 32 | background: #222; 33 | color: #ccc; 34 | font-family: Ubuntu,"Lucida Sans","DejaVu Sans",Consolas,Helvetica,Arial,sans-serif; 35 | } 36 | 37 | h1, h2, h3, h4, h5, h6 { 38 | color: #eee; 39 | } 40 | h1 small, h2 small, h3 small, h4 small, h5 small, h6 small { 41 | color: #ddd; 42 | } 43 | 44 | .panel { 45 | border-color: #444; 46 | background: #333; 47 | margin-bottom: .75em; 48 | padding: .5em; 49 | } 50 | .panel h1, .panel h2, .panel h3, .panel h4, .panel h5, .panel h6, .panel p { 51 | color: #eee; 52 | } 53 | .panel p { 54 | color: #ccc; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/pyrocore/data/htdocs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/src/pyrocore/data/htdocs/favicon.ico -------------------------------------------------------------------------------- /src/pyrocore/data/htdocs/img/pyroscope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/src/pyrocore/data/htdocs/img/pyroscope.png -------------------------------------------------------------------------------- /src/pyrocore/data/img/rt-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/src/pyrocore/data/img/rt-logo.png -------------------------------------------------------------------------------- /src/pyrocore/data/screenlet/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/src/pyrocore/data/screenlet/icon.png -------------------------------------------------------------------------------- /src/pyrocore/data/screenlet/themes/blueish/theme.conf: -------------------------------------------------------------------------------- 1 | # This is a theme-configuration with optional vars, meta-description 2 | # and author-information for the theme. Any lines beginning with #, _, [, 3 | # or are ignored. 4 | 5 | [Theme] 6 | name = blueish 7 | author = pyroscope © 2011 8 | version = 1.0 9 | info = Shades of Blue 10 | 11 | [Options] 12 | frame_color = (0.00, 0.25, 1.00, 0.7) 13 | inner_frame_color = (0.00, 0.10, 0.50, 0.3) 14 | shadow_color = (0.00, 0.10, 0.25, 0.5) 15 | text_color = (0.75, 0.75, 1.00, 0.9) 16 | 17 | -------------------------------------------------------------------------------- /src/pyrocore/data/screenlet/themes/default/theme.conf: -------------------------------------------------------------------------------- 1 | # This is a theme-configuration with optional vars, meta-description 2 | # and author-information for the theme. Any lines beginning with #, _, [, 3 | # or are ignored. 4 | 5 | [Theme] 6 | name = default 7 | author = pyroscope © 2011 8 | version = 1.0 9 | info = Default Theme 10 | 11 | [Options] 12 | frame_color = (0, 0, 0, 0.7) 13 | inner_frame_color = (0, 0, 0, 0.3) 14 | shadow_color = (0, 0, 0, 0.5) 15 | text_color = (1, 1, 1, 0.9) 16 | 17 | -------------------------------------------------------------------------------- /src/pyrocore/error.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=bad-whitespace 3 | """ Exception Classes. 4 | 5 | Copyright (c) 2010 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | 21 | # Return codes according to /usr/include/sysexits.h 22 | EX_OK = 0 # successful termination 23 | EX__BASE = 64 # base value for error messages 24 | EX_USAGE = 64 # command line usage error 25 | EX_DATAERR = 65 # data format error 26 | EX_NOINPUT = 66 # cannot open input 27 | EX_NOUSER = 67 # addressee unknown 28 | EX_NOHOST = 68 # host name unknown 29 | EX_UNAVAILABLE = 69 # service unavailable 30 | EX_SOFTWARE = 70 # internal software error 31 | EX_OSERR = 71 # system error (e.g., can't fork) 32 | EX_OSFILE = 72 # critical OS file missing 33 | EX_CANTCREAT = 73 # can't create (user) output file 34 | EX_IOERR = 74 # input/output error 35 | EX_TEMPFAIL = 75 # temp failure; user is invited to retry 36 | EX_PROTOCOL = 76 # remote error in protocol 37 | EX_NOPERM = 77 # permission denied 38 | EX_CONFIG = 78 # configuration error 39 | EX__MAX = 78 # maximum listed value 40 | 41 | 42 | class LoggableError(Exception): 43 | """ An exception that is intended to be logged instead of passing it to the 44 | runtime environment which will likely produce a full stacktrace. 45 | """ 46 | 47 | 48 | class EngineError(LoggableError): 49 | """ Connection or other backend error. 50 | """ 51 | 52 | 53 | class NetworkError(LoggableError): 54 | """ External connection errors. 55 | """ 56 | 57 | 58 | class UserError(LoggableError): 59 | """ Yes, it was your fault! 60 | """ 61 | -------------------------------------------------------------------------------- /src/pyrocore/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Basic Command Line Scripts. 4 | 5 | Copyright (c) 2009 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | -------------------------------------------------------------------------------- /src/pyrocore/scripts/hashcheck.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Metafile Checker. 4 | 5 | Copyright (c) 2011 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | from __future__ import absolute_import 21 | 22 | from pyrobase import bencode 23 | from pyrocore.scripts.base import ScriptBase, ScriptBaseWithConfig 24 | from pyrocore.util import metafile, os 25 | 26 | 27 | class MetafileChecker(ScriptBaseWithConfig): 28 | """ Check a bittorrent metafile. 29 | """ 30 | 31 | # argument description for the usage information 32 | ARGS_HELP = " []" 33 | 34 | 35 | def add_options(self): # pylint: disable=useless-super-delegation 36 | """ Add program options. 37 | """ 38 | super(MetafileChecker, self).add_options() 39 | 40 | 41 | def mainloop(self): 42 | """ The main loop. 43 | """ 44 | if not self.args: 45 | self.parser.print_help() 46 | self.parser.exit() 47 | elif len(self.args) < 1: 48 | self.parser.error("Expecting at least a metafile name") 49 | 50 | # Read metafile 51 | metapath = self.args[0] 52 | try: 53 | metainfo = bencode.bread(metapath) 54 | except (KeyError, bencode.BencodeError) as exc: 55 | self.fatal("Bad metafile %r (%s)" % (metapath, type(exc).__name__), exc) 56 | raise 57 | else: 58 | # Check metafile integrity 59 | try: 60 | metafile.check_meta(metainfo) 61 | except ValueError as exc: 62 | self.fatal("Metafile %r failed integrity check" % (metapath,), exc) 63 | raise 64 | else: 65 | if len(self.args) > 1: 66 | datapath = self.args[1].rstrip(os.sep) 67 | else: 68 | datapath = metainfo["info"]["name"] 69 | 70 | # Check the hashes 71 | torrent = metafile.Metafile(metapath) 72 | try: 73 | ok = torrent.check(metainfo, datapath, 74 | progress=None if self.options.quiet else metafile.console_progress()) 75 | if not ok: 76 | self.fatal("Metafile %r has checksum errors" % (metapath,)) 77 | sys.exit(1) 78 | except OSError as exc: 79 | self.fatal("Torrent data file missing", exc) 80 | raise 81 | 82 | 83 | def run(): #pragma: no cover 84 | """ The entry point. 85 | """ 86 | ScriptBase.setup() 87 | MetafileChecker().run() 88 | 89 | 90 | if __name__ == "__main__": 91 | run() 92 | -------------------------------------------------------------------------------- /src/pyrocore/scripts/rtevent.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Rtorrent event handler. 4 | 5 | Copyright (c) 2011 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | from __future__ import absolute_import 21 | 22 | import sys 23 | 24 | from pyrocore import config 25 | from pyrocore.scripts.base import ScriptBase, ScriptBaseWithConfig 26 | 27 | 28 | class RtorrentEventHandler(ScriptBaseWithConfig): 29 | ### Keep things wrapped to fit under this comment... ############################## 30 | """ 31 | Handle rTorrent events. 32 | """ 33 | 34 | # argument description for the usage information 35 | ARGS_HELP = " [...]" 36 | 37 | 38 | def add_options(self): 39 | """ Add program options. 40 | """ 41 | super(RtorrentEventHandler, self).add_options() 42 | 43 | # basic options 44 | self.add_bool_option("--no-fork", "--fg", help="Don't fork into background (stay in foreground, default for terminal use)") 45 | 46 | 47 | def mainloop(self): 48 | """ The main loop. 49 | """ 50 | # Print usage if not enough args or bad options 51 | if len(self.args) < 2: 52 | self.parser.error("No event type and info hash given!") 53 | 54 | if sys.stdin.isatty(): 55 | self.options.no_fork = True 56 | 57 | # Need to demonize (single-fork) ouselfves here, since otherwise rTorrent dead-locks 58 | 59 | # TODO: Actually implement something here 60 | 61 | 62 | def run(): #pragma: no cover 63 | """ The entry point. 64 | """ 65 | ScriptBase.setup() 66 | RtorrentEventHandler().run() 67 | 68 | 69 | if __name__ == "__main__": 70 | run() 71 | -------------------------------------------------------------------------------- /src/pyrocore/scripts/rtsweep.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Rtorrent disk space management. 4 | 5 | Copyright (c) 2018 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | from __future__ import absolute_import 21 | 22 | import sys 23 | 24 | from pyrocore import config 25 | from pyrocore.torrent import broom 26 | from pyrocore.scripts.base import ScriptBase, ScriptBaseWithConfig 27 | 28 | 29 | class RtorrentSweep(ScriptBaseWithConfig): 30 | ### Keep things wrapped to fit under this comment... ############################## 31 | """ 32 | Manage disk space by deleting items loaded into rTorrent, including their data, 33 | following configured rules that define an order of what to remove first. 34 | 35 | The required space is passed as the first argument, either in bytes or 36 | qualified with a unit character (K=KiB, M=MiB, G=GiB). Alternatively, you can 37 | pass a metafile path, with the requirement calculated from its content size. 38 | 39 | Use "show" instead to list the active rules, ordered by their priority. 40 | """ 41 | 42 | # argument description for the usage information 43 | ARGS_HELP = "|SHOW" 44 | 45 | 46 | def add_options(self): 47 | """ Add program options. 48 | """ 49 | super(RtorrentSweep, self).add_options() 50 | 51 | # basic options 52 | self.add_bool_option("-n", "--dry-run", 53 | help="do not remove anything, just tell what would happen") 54 | self.add_value_option("-p", "--path", "PATH", 55 | help="path into the filesystem to sweep (else the default download location)") 56 | self.add_value_option("-r", "--rules", "RULESET [-r ...]", 57 | action="append", default=[], 58 | help="name the ruleset(s) to use, instead of the default ones") 59 | 60 | 61 | def mainloop(self): 62 | """ The main loop. 63 | """ 64 | # Print usage if not enough args or bad options 65 | if len(self.args) < 1: 66 | self.parser.error("No space requirement provided!") 67 | 68 | sweeper = broom.DiskSpaceManager(rulesets=self.options.rules) 69 | if self.args[0] == 'show': 70 | # TODO: use some table data package here 71 | fmt = '{:4.4s} {:10.10s} {:15.15s} {:15.15s} {:60.60s}' 72 | print(fmt.format('PRIO', 'RULESET', 'NAME', 'ORDER', 'FILTER')) 73 | print(fmt.format(*('=' * 66,) * len(fmt.split()))) 74 | for rule in sweeper.rules: 75 | print(fmt.format(str(rule.prio).zfill(4), rule.ruleset, rule.name, rule.order, rule.filter)) 76 | self.LOG.info('Protected items: {}'.format(sweeper.protected)) 77 | else: 78 | self.fatal("Not implemented") 79 | # TODO: Actually implement something here 80 | 81 | # XXX: Ensure a lock file or similar is checked here, 82 | # to avoid / delay concurrent execution 83 | 84 | 85 | def run(): #pragma: no cover 86 | """ The entry point. 87 | """ 88 | ScriptBase.setup() 89 | RtorrentSweep().run() 90 | 91 | 92 | if __name__ == "__main__": 93 | run() 94 | -------------------------------------------------------------------------------- /src/pyrocore/torrent/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Torrent Backend Engines Package. 4 | 5 | Copyright (c) 2010 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | # make refactoring of 0.4.1 transparent, so the old config.py examples still work 21 | from __future__ import absolute_import 22 | 23 | from pyrocore.util import matching 24 | -------------------------------------------------------------------------------- /src/pyrocore/torrent/broom.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=I0011,C0103 3 | """ rTorrent Disk Space House-Keeping. 4 | 5 | This is used in the ``rtsweep`` tool and the queue job of the 6 | ``pyrotoque`` daemon to free up disk space for new items, by 7 | deleting old items in a controlled way using a configurable order. 8 | 9 | Copyright (c) 2018 The PyroScope Project 10 | """ 11 | # This program is free software; you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation; either version 2 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # This program is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License along 22 | # with this program; if not, write to the Free Software Foundation, Inc., 23 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 24 | from __future__ import with_statement 25 | from __future__ import absolute_import 26 | 27 | from collections import namedtuple 28 | 29 | from pyrocore import error 30 | from pyrocore import config as config_ini 31 | from pyrocore.torrent import engine, matching 32 | 33 | 34 | SweepRule = namedtuple('SweepRule', 'ruleset name prio order filter') 35 | 36 | 37 | def parse_cond(text): 38 | """Parse a filter condition.""" 39 | return matching.ConditionParser(engine.FieldDefinition.lookup, "name").parse(text) 40 | 41 | 42 | class DiskSpaceManager(object): 43 | """Core implementation of ``rtsweep``.""" 44 | 45 | def __init__(self, config=None, rulesets=None): 46 | self.config = config or config_ini 47 | self.active_rulesets = rulesets or [x.strip() for x in self.config.sweep['default_rules'].split(',')] 48 | self.rules = [] 49 | self.default_order = self.config.sweep['default_order'] 50 | self.protected = parse_cond(self.config.sweep['filter_protected']) 51 | 52 | self._load_rules() 53 | 54 | def _load_rules(self): 55 | """Load rule definitions from config.""" 56 | for ruleset in self.active_rulesets: 57 | section_name = 'sweep_rules_' + ruleset.lower() 58 | try: 59 | ruledefs = getattr(self.config, section_name) 60 | except AttributeError: 61 | raise error.UserError("There is no [{}] section in your configuration" 62 | .format(section_name.upper())) 63 | for ruledef, filtercond in ruledefs.items(): 64 | if ruledef.endswith('.filter'): 65 | rulename = ruledef.rsplit('.', 1)[0] 66 | rule = SweepRule(ruleset, rulename, 67 | int(ruledefs.get(rulename + '.prio', '999')), 68 | ruledefs.get(rulename + '.order', self.default_order), 69 | parse_cond(filtercond)) 70 | self.rules.append(rule) 71 | 72 | self.rules.sort(key=lambda x: (x.prio, x.name)) 73 | 74 | return self.rules 75 | 76 | # test "$1" = "--aggressive" && { shift; activity=5i; } || activity=4h 77 | # $DRY ~/bin/rtcontrol -/1 --cull active=+$activity [ NOT [ $PROTECTED ] ] \ 78 | # -qco loaded,size.sz,uploaded.sz,seedtime,ratio,name "$@" $INTERACTIVE; rc=$? 79 | # log.debug("No matches for {ruleset} 80 | 81 | ##needed=$(( $requested + $RESERVED_GiB * $GiB )) # Add a few GiB for the system 82 | ##log.info"Disk space management started [with $(print_gib $(download_free)) free]") 83 | 84 | # Finally, go through everything sorted by age (with staged activity protection) 85 | ## -s loaded // 86 | ## --aggressive -s loaded // 87 | 88 | # $INFO "Disk space management finished in ${took_secs}s [$(print_gib $(download_free)) free," \ 89 | # "$(print_gib $requested) requested]" 90 | # $INFO "Removed $(( $start_items - $after_items )) item(s)," \ 91 | # "freeing $(print_gib $(( $after_free - $start_free ))) disk space" \ 92 | # "[now $(print_gib $(download_free)) free, took ${took_secs}s]." 93 | -------------------------------------------------------------------------------- /src/pyrocore/torrent/filter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=I0011 3 | """ rTorrent Item Filter Jobs. 4 | 5 | Copyright (c) 2012 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | from __future__ import absolute_import 21 | 22 | from pyrocore import error 23 | from pyrocore.util import xmlrpc, pymagic 24 | 25 | 26 | class FilterJobBase(object): 27 | """ Base class for filter rule jobs. 28 | """ 29 | 30 | def __init__(self, config=None): 31 | """ Set up filter config. 32 | """ 33 | self.config = config or {} 34 | self.LOG = pymagic.get_class_logger(self) 35 | self.LOG.debug("%s created with config %r" % (self.__class__.__name__, self.config)) 36 | 37 | 38 | def run(self): 39 | """ Filter job callback. 40 | """ 41 | from pyrocore import config 42 | 43 | try: 44 | config.engine.open() 45 | # TODO: select view into items 46 | items = [] 47 | self.run_filter(items) 48 | except (error.LoggableError, xmlrpc.ERRORS) as exc: 49 | self.LOG.warn(str(exc)) 50 | 51 | 52 | def run_filter(self, items): 53 | """ Perform job on filtered items. 54 | """ 55 | raise NotImplementedError() 56 | 57 | 58 | class ActionRule(FilterJobBase): 59 | """ Perform an action on selected items. 60 | """ 61 | 62 | def run_filter(self, items): 63 | """ Perform configured action on filtered items. 64 | """ 65 | # TODO: what actions? xmlrpc, delete, cull, stop, etc. for sure. 66 | 67 | 68 | class TorrentMirror(FilterJobBase): 69 | """ Mirror selected items via a specified tracker. 70 | """ 71 | 72 | def run_filter(self, items): 73 | """ Load filtered items into remote client via tracker / watchdir. 74 | """ 75 | # TODO: config is tracker_url, tracker_upload, watch_dir 76 | # create clones of item's metafile, write to watch_dir, and upload 77 | # to tracker_upload (support file: at first, for a local bttrack); 78 | # also, already mirrored items have to be marked somehow 79 | -------------------------------------------------------------------------------- /src/pyrocore/ui/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Curses UI helpers and extensions. 4 | 5 | Copyright (c) 2017 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | -------------------------------------------------------------------------------- /src/pyrocore/ui/categories.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Category management. 4 | 5 | Copyright (c) 2017 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | from __future__ import absolute_import 21 | 22 | from pyrocore import config, error 23 | from pyrocore.util import xmlrpc 24 | from pyrocore.scripts.base import ScriptBase, ScriptBaseWithConfig 25 | 26 | 27 | class CategoryManager(ScriptBaseWithConfig): 28 | """ Rotate through category views. 29 | """ 30 | 31 | PREFIX = 'category_' 32 | PREFIX_LEN = len(PREFIX) 33 | 34 | # argument description for the usage information 35 | ARGS_HELP = "" 36 | 37 | 38 | def add_options(self): 39 | """ Add program options. 40 | """ 41 | super(CategoryManager, self).add_options() 42 | 43 | self.add_bool_option("-l", "--list", 44 | help="list added category views") 45 | self.add_bool_option("-n", "--next", 46 | help="switch to next category view") 47 | self.add_bool_option("-p", "--prev", 48 | help="switch to previous category view") 49 | self.add_bool_option("-u", "--update", 50 | help="filter the current category view again") 51 | 52 | 53 | def mainloop(self): 54 | """ Manage category views. 55 | """ 56 | # Get client state 57 | proxy = config.engine.open() 58 | views = [x for x in sorted(proxy.view.list()) if x.startswith(self.PREFIX)] 59 | 60 | current_view = real_current_view = proxy.ui.current_view() 61 | if current_view not in views: 62 | if views: 63 | current_view = views[0] 64 | else: 65 | raise error.UserError("There are no '{}*' views defined at all!".format(self.PREFIX)) 66 | 67 | # Check options 68 | if self.options.list: 69 | for name in sorted(views): 70 | print("{} {:5d} {}".format( 71 | '*' if name == real_current_view else ' ', 72 | proxy.view.size(xmlrpc.NOHASH, name), 73 | name[self.PREFIX_LEN:])) 74 | 75 | elif self.options.next or self.options.prev or self.options.update: 76 | # Determine next in line 77 | if self.options.update: 78 | new_view = current_view 79 | else: 80 | new_view = (views * 2)[views.index(current_view) + (1 if self.options.next else -1)] 81 | 82 | self.LOG.info("{} category view '{}'.".format( 83 | "Updating" if self.options.update else "Switching to", new_view)) 84 | 85 | # Update and switch to filtered view 86 | proxy.pyro.category.update(xmlrpc.NOHASH, new_view[self.PREFIX_LEN:]) 87 | proxy.ui.current_view.set(new_view) 88 | 89 | else: 90 | self.LOG.info("Current category view is '{}'.".format(current_view[self.PREFIX_LEN:])) 91 | self.LOG.info("Use '--help' to get usage information.") 92 | 93 | 94 | def run(): #pragma: no cover 95 | """ The entry point. 96 | """ 97 | ScriptBase.setup() 98 | CategoryManager().run() 99 | 100 | 101 | if __name__ == "__main__": 102 | run() 103 | -------------------------------------------------------------------------------- /src/pyrocore/util/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=invalid-name 3 | """ Utility Modules. 4 | 5 | Copyright (c) 2010 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | from __future__ import absolute_import 21 | 22 | import os 23 | 24 | # 0.4.1 refactoring, duplicate stuf into the old place 25 | from pyrobase import fmt 26 | 27 | 28 | if not os.path.supports_unicode_filenames: 29 | # Make a Unicode-aware copy of os and os.path 30 | from pyrobase.parts import Bunch 31 | 32 | def _encode_path(text): 33 | """ Return a string suitable for calling file system functions. 34 | """ 35 | if isinstance(text, str): 36 | return text 37 | else: 38 | return text.encode("UTF-8") 39 | 40 | # copy "os" identifiers 41 | _os = Bunch() 42 | for _key in os.__all__: 43 | _os[_key] = getattr(os, _key) 44 | 45 | def _wrap(name, func): 46 | "Wrapping helper." 47 | setattr(_os, name, func) 48 | func.__name__ = name 49 | func.__doc__ = getattr(os, name).__doc__ 50 | 51 | # wrap some os functions 52 | _wrap("makedirs", lambda path, o=os: o.makedirs(_encode_path(path))) 53 | _wrap("readlink", lambda path, o=os: o.readlink(_encode_path(path))) 54 | _wrap("remove", lambda path, o=os: o.remove(_encode_path(path))) 55 | _wrap("rename", lambda src, dst, o=os: o.rename(_encode_path(src), _encode_path(dst))) 56 | _wrap("symlink", lambda src, dst, o=os: o.symlink(_encode_path(src), _encode_path(dst))) 57 | _wrap("listdir", lambda path, o=os: o.listdir(_encode_path(path))) 58 | _wrap("rmdir", lambda path, o=os: o.rmdir(_encode_path(path))) 59 | _wrap("removedirs", lambda path, o=os: o.removedirs(_encode_path(path))) 60 | _wrap("statvfs", lambda path, o=os: o.statvfs(_encode_path(path))) 61 | 62 | # wrap os.path stuff 63 | _unary_fs_functions = [ 64 | 'getsize', 'getmtime', 'getatime', 'getctime', 'islink', 'exists', 'lexists', 65 | 'isdir', 'isfile', 'ismount', 'abspath', 'realpath' 66 | #'samefile', 'sameopenfile', 'samestat', , 'supports_unicode_filenames' 67 | ] 68 | _os_path = Bunch() 69 | for _key in os.path.__all__: 70 | _os_path[_key] = getattr(os.path, _key) 71 | if _key in _unary_fs_functions: 72 | _os_path[_key] = lambda x, _f=_os_path[_key]: _f(_encode_path(x)) 73 | 74 | os = _os 75 | os.path = _os_path 76 | del Bunch, _unary_fs_functions, _key, _wrap, _os, _os_path 77 | -------------------------------------------------------------------------------- /src/pyrocore/util/algo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Helper Algorithms. 4 | 5 | Copyright (c) 2009, 2010 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | from __future__ import absolute_import 21 | 22 | import logging 23 | 24 | log = logging.getLogger(__name__) 25 | 26 | 27 | try: 28 | from itertools import product # @UnusedImport pylint: disable=E0611 29 | except ImportError: 30 | def product(*args, **kwds): 31 | """ product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy 32 | product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111 33 | """ 34 | pools = map(tuple, args) * kwds.get('repeat', 1) 35 | result = [[]] 36 | for pool in pools: 37 | result = [x+[y] for x in result for y in pool] 38 | for prod in result: 39 | yield tuple(prod) 40 | 41 | 42 | def flatten(nested, containers=(list, tuple)): 43 | """ Flatten a nested list in-place and return it. 44 | """ 45 | flat = list(nested) # handle iterators / generators 46 | i = 0 47 | while i < len(flat): 48 | while isinstance(flat[i], containers): 49 | if not flat[i]: 50 | # kill empty list 51 | flat.pop(i) 52 | 53 | # inspect new 'i'th element in outer loop 54 | i -= 1 55 | break 56 | else: 57 | flat[i:i + 1] = (flat[i]) 58 | 59 | # 'i'th element is scalar, proceed 60 | i += 1 61 | 62 | return flat 63 | 64 | 65 | class AttributeMapping(object): 66 | """ Wrap an object's dict so that it can be accessed by the mapping protocol. 67 | """ 68 | 69 | def __init__(self, obj, defaults=None): 70 | """ Store object we want to map, and any default values. 71 | 72 | @param obj: the wrapped object 73 | @type obj: object 74 | @param defaults: default values 75 | @type defaults: dict 76 | """ 77 | self.obj = obj 78 | self.defaults = defaults or {} 79 | 80 | 81 | def __getitem__(self, key): 82 | """ Return object attribute named C{key}. 83 | """ 84 | ##print "GETITEM", key, self.defaults 85 | try: 86 | return getattr(self.obj, key) 87 | except AttributeError as exc: 88 | try: 89 | return self.defaults[key] 90 | except KeyError: 91 | raise AttributeError("%s for %r.%s" % (exc, self.obj, key)) 92 | -------------------------------------------------------------------------------- /src/pyrocore/util/pymagic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=I0011,C0103 3 | """ Python Utility Functions. 4 | 5 | Copyright (c) 2009, 2010 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | from __future__ import absolute_import 21 | 22 | import json 23 | import logging 24 | import pkg_resources 25 | 26 | from peak.util.proxies import LazyProxy 27 | 28 | 29 | # Create aliases to make pydev / pylint happy 30 | resource_isdir = pkg_resources.resource_isdir # @UndefinedVariable pylint: disable=E1101 31 | resource_listdir = pkg_resources.resource_listdir # @UndefinedVariable pylint: disable=E1101 32 | resource_string = pkg_resources.resource_string # @UndefinedVariable pylint: disable=E1101 33 | 34 | 35 | def import_name(module_spec, name=None): 36 | """ Import identifier C{name} from module C{module_spec}. 37 | 38 | If name is omitted, C{module_spec} must contain the name after the 39 | module path, delimited by a colon (like a setuptools entry-point). 40 | 41 | @param module_spec: Fully qualified module name, e.g. C{x.y.z}. 42 | @param name: Name to import from C{module_spec}. 43 | @return: Requested object. 44 | @rtype: object 45 | """ 46 | # Load module 47 | module_name = module_spec 48 | if name is None: 49 | try: 50 | module_name, name = module_spec.split(':', 1) 51 | except ValueError: 52 | raise ValueError("Missing object specifier in %r (syntax: 'package.module:object.attr')" % (module_spec,)) 53 | 54 | try: 55 | module = __import__(module_name, globals(), {}, [name]) 56 | except ImportError as exc: 57 | raise ImportError("Bad module name in %r (%s)" % (module_spec, exc)) 58 | 59 | # Resolve the requested name 60 | result = module 61 | for attr in name.split('.'): 62 | result = getattr(result, attr) 63 | 64 | return result 65 | 66 | 67 | def get_class_logger(obj): 68 | """ Get a logger specific for the given object's class. 69 | """ 70 | return logging.getLogger(obj.__class__.__module__ + '.' + obj.__class__.__name__) 71 | 72 | 73 | def get_lazy_logger(name): 74 | """ Return a logger proxy that is lazily initialized. 75 | 76 | This avoids the problems associated with module-level loggers being created 77 | early (on import), *before* the logging system is properly initialized. 78 | """ 79 | return LazyProxy(lambda n=name: logging.getLogger(n)) 80 | 81 | 82 | class JSONEncoder(json.JSONEncoder): 83 | """Custon JSON encoder.""" 84 | 85 | def default(self, o): # pylint: disable=method-hidden 86 | """Support more object types.""" 87 | if isinstance(o, set): 88 | return list(sorted(o)) 89 | elif hasattr(o, 'as_dict'): 90 | return o.as_dict() 91 | else: 92 | return super(JSONEncoder, self).default(o) 93 | -------------------------------------------------------------------------------- /src/pyrocore/util/stats.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=bad-whitespace 3 | """ Statistics data. 4 | 5 | Copyright (c) 2014 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | from __future__ import absolute_import 21 | 22 | import time 23 | 24 | 25 | def engine_data(engine): 26 | """ Get important performance data and metadata from rTorrent. 27 | """ 28 | views = ("default", "main", "started", "stopped", "complete", 29 | "incomplete", "seeding", "leeching", "active", "messages") 30 | methods = [ 31 | "throttle.global_up.rate", "throttle.global_up.max_rate", 32 | "throttle.global_down.rate", "throttle.global_down.max_rate", 33 | ] 34 | 35 | # Get data via multicall 36 | proxy = engine.open() 37 | calls = [dict(methodName=method, params=[]) for method in methods] \ 38 | + [dict(methodName="view.size", params=['', view]) for view in views] 39 | result = proxy.system.multicall(calls, flatten=True) 40 | 41 | # Build result object 42 | data = dict( 43 | now = time.time(), 44 | engine_id = engine.engine_id, 45 | versions = engine.versions, 46 | uptime = engine.uptime, 47 | upload = [result[0], result[1]], 48 | download = [result[2], result[3]], 49 | views = dict([(name, result[4+i]) 50 | for i, name in enumerate(views) 51 | ]), 52 | ) 53 | 54 | return data 55 | -------------------------------------------------------------------------------- /src/scripts/add-categories.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | # 3 | # Add views and watches to the rTorrent condiguration, 4 | # for the categories provided as arguments. 5 | 6 | set -e 7 | 8 | cat_rc="rtorrent.d/categories.rc" 9 | 10 | touch "$cat_rc" 11 | categories=( $({ grep pyro.category.add "$cat_rc" | tr -d ' ' | \ 12 | cut -f2 -d= | egrep '^[_a-zA-Z0-9]+$'; echo "$@" | tr ' ' \\n; } | sort -u) ) 13 | 14 | cat >$cat_rc <>$cat_rc \ 27 | "\npyro.category.add = $name\nschedule2 =" \ 28 | "category_watch_$(printf '%02d' $i), $((10 + $i)), 10," \ 29 | "((load.category.normal, $name))" 30 | done 31 | 32 | cat "$cat_rc" 33 | echo 34 | echo "################################################" 35 | echo "# Restart rTorrent for changes to take effect! #" 36 | echo "################################################" 37 | -------------------------------------------------------------------------------- /src/scripts/make-rtorrent-config.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | # 3 | # Create rTorrent config using mostly pimp-my-box snippets 4 | # 5 | 6 | export RT_HOME="${RT_HOME:-$HOME/rtorrent}" 7 | 8 | # This can be used to test changes contained in a 'pimp-my-box' workdir 9 | PMB_ROOT_DIR="$1" 10 | 11 | INSTALL_ROOT="$(builtin cd $(dirname "$0") >/dev/null && pwd)" 12 | INSTALL_ROOT="$(dirname $(dirname "$INSTALL_ROOT"))" 13 | 14 | mkdir -p "$RT_HOME" 15 | builtin cd "$RT_HOME" 16 | 17 | # Create "rtorrent.rc" 18 | echo "*** Creating 'rtorrent.rc' in '$RT_HOME'..." 19 | sed -e "s:RT_HOME:$RT_HOME:" <"$INSTALL_ROOT/docs/examples/rtorrent.rc" >"$RT_HOME/rtorrent.rc" 20 | 21 | # Get pimp-my-box source 22 | test -d "rtorrent.d/" || mkdir -p "rtorrent.d" 23 | if test -n "$PMB_ROOT_DIR" -a -d "$PMB_ROOT_DIR"; then 24 | echo "*** Copying 'rtorrent.d' snippets..." 25 | cp -p "$PMB_ROOT_DIR/roles/rtorrent-ps/templates/rtorrent/rtorrent.d"/*.rc{,.include} "rtorrent.d" 26 | else 27 | echo "*** Downloading 'rtorrent.d' snippets..." 28 | curl -L -o /tmp/$USER-pimp-my-box.tgz \ 29 | "https://github.com/pyroscope/pimp-my-box/archive/master.tar.gz" 30 | ( builtin cd rtorrent.d/ >/dev/null && tar -xz --strip-components=6 -f /tmp/$USER-pimp-my-box.tgz \ 31 | "pimp-my-box-master/roles/rtorrent-ps/templates/rtorrent/rtorrent.d" ) 32 | fi 33 | 34 | if test ! -f ~/bin/_event.download.finished; then 35 | echo -e >~/bin/_event.download.finished '#/bin/bash\necho "$@"' 36 | chmod a+rx ~/bin/_event.download.finished 37 | fi 38 | 39 | # Replace Ansible variables 40 | echo "*** Configuring 'rtorrent.d' snippets..." 41 | ( builtin cd rtorrent.d >/dev/null && for i in *.rc{,.include}; do \ 42 | sed -i -re 's/\{\{ item }}/'"$i/" -e '/^# !.+!$/d' "$i" \ 43 | -e 's:~/rtorrent/:'"$RT_HOME/:" -e "s:$HOME/:~/:" \ 44 | -e "s:'rtorrent' user:'$USER' user:"; \ 45 | done ) 46 | sed -i -re 's/\{\{ inventory_hostname }}/'"$(hostname)/" rtorrent.d/20-session-name.rc 47 | sed -i -r \ 48 | -e 's/\{\{ rt_pieces_memory }}/1200M/' \ 49 | -e 's/\{\{ rt_xmlrpc_size_limit }}/16M/' \ 50 | -e 's/\{\{ rt_global_up_rate_kb }}/115000/' \ 51 | -e 's/\{\{ rt_global_down_rate_kb }}/115000/' \ 52 | -e 's/\{\{ .+rt_system_umask.+ }}/0027/' \ 53 | -e 's/\{\{ rt_keys_layout }}/qwerty/' \ 54 | rtorrent.d/20-host-var-settings.rc 55 | 56 | if test "$(realpath $HOME/rtorrent)" = "$(realpath /var/torrent)"; then 57 | ( builtin cd $(dirname $(realpath rtorrent.rc)) && sed -i -re "s~$HOME/rtorrent~/var/torrent~g" rtorrent.rc ) 58 | fi 59 | 60 | # Check that there are no overlooked Ansible variables 61 | echo 62 | echo "*****************************************************************************" 63 | 64 | if egrep -m1 '\{\{.+?}}' rtorrent.d/*.rc{,.include} >/dev/null; then 65 | echo "Check the following output, you need to insert your own settings everywhere" 66 | echo "a '{{ ... }}' placeholder appears!" 67 | echo 68 | 69 | egrep -nH --color=yes '\{\{.+?}}' rtorrent.d/*.rc{,.include} 70 | else 71 | echo "Your configuration is ready." 72 | echo 73 | ls -lR rtorrent.rc rtorrent.d/*.rc{,.include} 74 | fi 75 | 76 | # END of "make-rtorrent-config.sh" 77 | -------------------------------------------------------------------------------- /src/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Unit Tests. 4 | 5 | Copyright (c) 2009, 2010 The PyroScope Project 6 | """ 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | import sys 21 | import logging 22 | 23 | # Add a level more detailed than DEBUG 24 | TRACE = logging.DEBUG-1 25 | 26 | 27 | class TestLogger(logging.Logger): 28 | """A logger with trace().""" 29 | 30 | @classmethod 31 | def initialize(cls): 32 | """ Register test logging. 33 | """ 34 | logging.addLevelName(TRACE, "TRACE") 35 | logging.setLoggerClass(cls) 36 | 37 | if any(i in sys.argv for i in ("-v", "--verbose")): 38 | logging.getLogger().setLevel(TRACE) 39 | elif any(i in sys.argv for i in ("-q", "--quiet")): 40 | logging.getLogger().setLevel(logging.INFO) 41 | 42 | 43 | def trace(self, msg, *args, **kwargs): 44 | """ Micro logging. 45 | """ 46 | return self.log(TRACE, msg, *args, **kwargs) 47 | 48 | 49 | # FlexGet names 50 | debugall = trace 51 | verbose = logging.Logger.info 52 | 53 | 54 | TestLogger.initialize() 55 | -------------------------------------------------------------------------------- /src/tests/fifotest.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # 3 | # CONCURRENT HASHING TEST SCRIPT 4 | # 5 | # Copyright (c) 2010 The PyroScope Project 6 | # 7 | # This program is free software; you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation; either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License along 18 | # with this program; if not, write to the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | 21 | # Do a self-referential listing for piping to pastee 22 | cat $0; echo; echo "~~~ Start of test run ~~~" 23 | 24 | # Preparation 25 | cd $(dirname $(dirname $0)) 26 | test ! -f fifotest.torrent || rm fifotest.torrent 27 | test ! -e fifotest.fifo || rm fifotest.fifo 28 | mkfifo fifotest.fifo 29 | 30 | # Start hashing process 31 | mktor -r concurrent -o fifotest fifotest.fifo OBT -v 2>&1 | sed -e s:$HOME:~: & 32 | 33 | # Start filename emitting process (fake .25 sec latency) 34 | ( for file in $(find tests/ -name "*.py"); do 35 | echo >&2 "$(date -u +'%T.%N') $file is complete!" 36 | echo $file 37 | sleep .25 38 | done ) >fifotest.fifo & 39 | 40 | # Wait for hashing to complete 41 | while test ! -f fifotest.torrent; do 42 | echo "$(date -u +'%T.%N') Waiting for metafile..." 43 | sleep .1 44 | done 45 | echo 46 | 47 | # Show the result 48 | echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" 49 | lstor -q fifotest.torrent 50 | 51 | # Clean up 52 | rm fifotest.* 53 | -------------------------------------------------------------------------------- /src/tests/logging.cfg: -------------------------------------------------------------------------------- 1 | # Logging configuration for unit tests 2 | # 3 | # For details see http://docs.python.org/library/logging.html#configuring-logging 4 | # 5 | 6 | [loggers] 7 | keys=root 8 | 9 | [handlers] 10 | keys=nose 11 | 12 | [formatters] 13 | keys=nose 14 | 15 | [logger_root] 16 | level=INFO 17 | handlers=nose 18 | 19 | [handler_nose] 20 | level = NOTSET 21 | class=StreamHandler 22 | args=(sys.stderr,) 23 | formatter=nose 24 | 25 | [formatter_nose] 26 | format = %(asctime)s,%(msecs)03d %(levelname)-8s [%(name)s] %(message)s 27 | datefmt = %H:%M:%S 28 | #datefmt = %Y-%m-%d %H:%M:%S 29 | -------------------------------------------------------------------------------- /src/tests/private.torrent: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/src/tests/private.torrent -------------------------------------------------------------------------------- /src/tests/test.torrent: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyroscope/pyrocore/e8ededb6d9f3702ede6cd5396e6b473915637b64/src/tests/test.torrent -------------------------------------------------------------------------------- /src/tests/test_algo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Algorithm tests. 4 | 5 | Copyright (c) 2011 The PyroScope Project 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License along 18 | with this program; if not, write to the Free Software Foundation, Inc., 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | """ 21 | import logging 22 | import unittest 23 | 24 | from pyrocore.util import algo 25 | 26 | log = logging.getLogger(__name__) 27 | log.trace("module loaded") 28 | 29 | 30 | class AlgoTest(unittest.TestCase): 31 | 32 | def test_algo(self): 33 | pass 34 | 35 | 36 | if __name__ == "__main__": 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /src/tests/test_config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Configuration tests. 4 | 5 | Copyright (c) 2011 The PyroScope Project 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License along 18 | with this program; if not, write to the Free Software Foundation, Inc., 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | """ 21 | import logging 22 | import unittest 23 | 24 | from pyrocore import config 25 | 26 | log = logging.getLogger(__name__) 27 | log.trace("module loaded") 28 | 29 | 30 | class ConfigTest(unittest.TestCase): 31 | 32 | def test_config(self): 33 | pass 34 | 35 | 36 | if __name__ == "__main__": 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /src/tests/test_engine.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Torrent Engine tests. 4 | 5 | Copyright (c) 2011 The PyroScope Project 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License along 18 | with this program; if not, write to the Free Software Foundation, Inc., 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | """ 21 | import logging 22 | import unittest 23 | 24 | from pyrocore.torrent import engine 25 | 26 | log = logging.getLogger(__name__) 27 | log.trace("module loaded") 28 | 29 | 30 | class IntervalTest(unittest.TestCase): 31 | 32 | INTERVAL_DATA = [ 33 | ("R1377390013R1377390082", dict(end=1377390084), 2), 34 | ("R1353618135P1353618151", dict(start=1353618141), 10), 35 | ] 36 | 37 | def test_interval_sum(self): 38 | for context in (None, "unittest"): 39 | for interval, kwargs, expected in self.INTERVAL_DATA: 40 | kwargs["context"] = context 41 | result = engine._interval_sum(interval, **kwargs) 42 | self.assertEqual(expected, result, "for interval=%r kw=%r" % (interval, kwargs)) 43 | 44 | 45 | class EngineTest(unittest.TestCase): 46 | 47 | def test_engine(self): 48 | pass 49 | 50 | 51 | if __name__ == "__main__": 52 | unittest.main() 53 | -------------------------------------------------------------------------------- /src/tests/test_formatting.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Item Formatting tests. 4 | 5 | Copyright (c) 2011 The PyroScope Project 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License along 18 | with this program; if not, write to the Free Software Foundation, Inc., 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | """ 21 | import logging 22 | import unittest 23 | 24 | from pyrocore.torrent import formatting 25 | 26 | log = logging.getLogger(__name__) 27 | log.trace("module loaded") 28 | 29 | 30 | class FormattingTest(unittest.TestCase): 31 | 32 | def test_formatting(self): 33 | pass 34 | 35 | 36 | if __name__ == "__main__": 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /src/tests/test_metafile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Metafile tests. 4 | 5 | Copyright (c) 2009 The PyroScope Project 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License along 18 | with this program; if not, write to the Free Software Foundation, Inc., 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | """ 21 | 22 | import random 23 | import logging 24 | import unittest 25 | 26 | from pyrocore.util.metafile import * #@UnusedWildImport 27 | 28 | log = logging.getLogger(__name__) 29 | log.trace("module loaded") 30 | 31 | 32 | class MaskTest(unittest.TestCase): 33 | 34 | def test_urls(self): 35 | testcases = ( 36 | "http://example.com:1234/user/ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ/announce", 37 | "http://example.com/announce.php?passkey=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ", 38 | "http://example.com/announce.php?passkey=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ&someparam=0", 39 | "http://example.com/DDDDD/ZZZZZZZZZZZZZZZZ/announce", 40 | "http://example.com/tracker.php/ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ/announce", 41 | "https://example.com/announce.php?passkey=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ", 42 | "http://tracker1.example.com/TrackerServlet/ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ/DDDDDDD/announce", 43 | "http://example.com:12345/ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ/announce", 44 | "http://example.com/announce.php?pid=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ", 45 | "http://example.com:1234/a/ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ/announce", 46 | "http://example.com/announce.php?passkey=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ&uid=DDDDD", 47 | ) 48 | mapping = { 49 | "D": lambda: random.choice("0123456789"), 50 | "Z": lambda: random.choice("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"), 51 | } 52 | 53 | for testcase in testcases: 54 | expected = testcase.replace("D", "*").replace("Z", "*") 55 | randomized = ''.join(mapping.get(i, lambda: i)() for i in testcase) 56 | print expected, randomized 57 | self.failIfEqual(expected, randomized) 58 | self.failUnlessEqual(expected, mask_keys(randomized)) 59 | 60 | if __name__ == "__main__": 61 | unittest.main() 62 | -------------------------------------------------------------------------------- /src/tests/test_osmagic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ OS Magic tests. 4 | 5 | Copyright (c) 2011 The PyroScope Project 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License along 18 | with this program; if not, write to the Free Software Foundation, Inc., 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | """ 21 | import logging 22 | import unittest 23 | 24 | from pyrocore.util import osmagic 25 | 26 | log = logging.getLogger(__name__) 27 | log.trace("module loaded") 28 | 29 | 30 | class DaemonTest(unittest.TestCase): 31 | 32 | def test_detach(self): 33 | pass 34 | 35 | 36 | if __name__ == "__main__": 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /src/tests/test_pymagic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Python utilities tests. 4 | 5 | Copyright (c) 2011 The PyroScope Project 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License along 18 | with this program; if not, write to the Free Software Foundation, Inc., 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | """ 21 | import logging 22 | import unittest 23 | 24 | from pyrocore.util import pymagic 25 | 26 | log = logging.getLogger(__name__) 27 | log.trace("module loaded") 28 | 29 | 30 | class ImportTest(unittest.TestCase): 31 | 32 | def test_import_name(self): 33 | docstr = pymagic.import_name("pyrocore", "__doc__") 34 | assert "Core Package" in docstr 35 | 36 | docstr = pymagic.import_name("pyrocore.util", "__doc__") 37 | assert "Utility Modules" in docstr 38 | 39 | 40 | def test_import_fail(self): 41 | try: 42 | pymagic.import_name("pyrocore.does_not_exit", "__doc__") 43 | except ImportError, exc: 44 | assert "pyrocore.does_not_exit" in str(exc), str(exc) 45 | else: 46 | assert False, "Import MUST fail!" 47 | 48 | 49 | def test_import_colon(self): 50 | docstr = pymagic.import_name("pyrocore:__doc__") 51 | assert "Core Package" in docstr 52 | 53 | 54 | def test_import_missing_colon(self): 55 | try: 56 | pymagic.import_name("pyrocore") 57 | except ValueError, exc: 58 | assert "pyrocore" in str(exc), str(exc) 59 | else: 60 | assert False, "Import MUST fail!" 61 | 62 | 63 | class LogTest(unittest.TestCase): 64 | 65 | def test_get_class_logger(self): 66 | logger = pymagic.get_class_logger(self) 67 | assert logger.name == "tests.test_pymagic.LogTest" 68 | 69 | 70 | if __name__ == "__main__": 71 | unittest.main() 72 | -------------------------------------------------------------------------------- /src/tests/test_rtorrent.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ RTorrent tests. 4 | 5 | Copyright (c) 2011 The PyroScope Project 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License along 18 | with this program; if not, write to the Free Software Foundation, Inc., 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | """ 21 | import logging 22 | import unittest 23 | 24 | from pyrocore.torrent import rtorrent 25 | 26 | log = logging.getLogger(__name__) 27 | log.trace("module loaded") 28 | 29 | 30 | class RTorrentTest(unittest.TestCase): 31 | 32 | def test_rtorrent(self): 33 | pass 34 | 35 | 36 | if __name__ == "__main__": 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /src/tests/test_traits.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ Traits tests. 4 | 5 | Copyright (c) 2011 The PyroScope Project 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License along 18 | with this program; if not, write to the Free Software Foundation, Inc., 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | """ 21 | import logging 22 | import unittest 23 | 24 | from pyrocore.util import traits 25 | 26 | log = logging.getLogger(__name__) 27 | log.trace("module loaded") 28 | 29 | 30 | class TraitsTest(unittest.TestCase): 31 | 32 | def test_traits(self): 33 | pass 34 | 35 | 36 | if __name__ == "__main__": 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /src/tests/test_xmlrpc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable= 3 | """ XMLRPC tests. 4 | 5 | Copyright (c) 2011 The PyroScope Project 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License along 18 | with this program; if not, write to the Free Software Foundation, Inc., 19 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20 | """ 21 | import logging 22 | import unittest 23 | 24 | from pyrocore.util import xmlrpc 25 | 26 | log = logging.getLogger(__name__) 27 | log.trace("module loaded") 28 | 29 | 30 | class XmlRpcTest(unittest.TestCase): 31 | 32 | def test_xmlrpc(self): 33 | pass 34 | 35 | 36 | if __name__ == "__main__": 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /update-to-head.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | git_projects="pyrobase auvyon" 3 | 4 | # Find most suitable Python 5 | echo "~~~ On errors, paste EVERYTHING below ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" 6 | deactivate 2>/dev/null 7 | PYTHON="$1" 8 | #test -z "$PYTHON" -a -x "/usr/bin/python3.8" && PYTHON="/usr/bin/python3.8" 9 | #test -z "$PYTHON" -a -x "/usr/bin/python3" && PYTHON="/usr/bin/python3" 10 | test -z "$PYTHON" -a -x "/usr/bin/python2" && PYTHON="/usr/bin/python2" 11 | test -z "$PYTHON" -a -x "/usr/bin/python" && PYTHON="/usr/bin/python" 12 | test -z "$PYTHON" && PYTHON="python" 13 | #test -z "$PYTHON" && PYTHON="python3" 14 | # Also adapt "assert sys.version_info" below, and venv creation 15 | 16 | set -e 17 | MY_SUM=$(md5sum "$0" | cut -f1 -d' ') 18 | PROJECT_ROOT="$(command cd $(dirname "$0") >/dev/null && pwd)" 19 | command cd "$PROJECT_ROOT" >/dev/null 20 | echo "Installing into $PWD..." 21 | rtfm="DO read 'https://pyrocore.readthedocs.io/en/latest/installation.html'." 22 | 23 | # Fix Generation YouTube's reading disability 24 | for cmd in $PYTHON git; do 25 | which $cmd >/dev/null 2>&1 || { echo >&2 "You need a working '$cmd' on your PATH. $rtfm"; exit 1; } 26 | done 27 | 28 | # People never read docs anyway, so let the machine check... 29 | test -f /proc/1/cgroup -a $(grep -c :/docker /proc/1/cgroup) -gt 0 && in_docker=1 || in_docker=0 30 | test $(id -u) -ne 0 -o $in_docker -eq 1 || { echo "Do NOT install as root! $rtfm"; exit 1; } 31 | test -f ./bin/activate && vpy=$PWD/bin/python || vpy=$PYTHON 32 | cat <<'.' | $vpy 33 | import sys 34 | print("Using Python %s" % sys.version) 35 | assert sys.version_info >= (2, 7), "Use Python 2.7! Read the docs." 36 | assert sys.version_info < (3,), "Use Python 2.7! Read the docs." 37 | . 38 | 39 | echo "Updating your installation..." 40 | 41 | # Bootstrap if script was downloaded... 42 | if test -d .git; then 43 | git pull --ff-only 44 | else 45 | git clone "https://github.com/pyroscope/pyrocore.git" tmp 46 | mv tmp/???* tmp/.??* .; rmdir tmp 47 | MY_SUM="let's start over" 48 | fi 49 | 50 | if test "$MY_SUM" != $(md5sum "$0" | cut -f1 -d' '); then 51 | echo -e "\n\n*** Update script changed, starting over ***\n" 52 | exec "$0" "$@" 53 | fi 54 | 55 | . "$PROJECT_ROOT/util.sh" # load funcs 56 | 57 | # Ensure virtualenv is there 58 | test -f bin/activate || install_venv --never-download 59 | update_venv ./bin/pip 60 | 61 | # Get base packages initially, for old or yet incomplete installations 62 | for project in $git_projects; do 63 | test -d $project || { echo "Getting $project..."; git clone "git://github.com/pyroscope/$project.git" $project; } 64 | done 65 | 66 | # Update source 67 | source bin/activate 68 | for project in $git_projects; do 69 | ( builtin cd $project && git pull -q --ff-only ) 70 | done 71 | source bootstrap.sh 72 | for project in $git_projects; do 73 | ( builtin cd $project && ../bin/python -m pip -q install -e . ) 74 | done 75 | 76 | ln -nfs python ./bin/python-pyrocore 77 | if test -d ${BIN_DIR:-$HOME/bin}; then 78 | # Register new executables 79 | ln -nfs $(egrep -l '(from.pyrocore.scripts|entry_point.*pyrocore.*console_scripts)' $PWD/bin/*) ${BIN_DIR:-$HOME/bin}/ 80 | ln -nfs $PWD/bin/python-pyrocore ${BIN_DIR:-$HOME/bin}/ 81 | 82 | # Link to example scripts 83 | find "$PROJECT_ROOT/docs/examples" -name "rt-*" -executable -type f | \ 84 | while read script; do 85 | name=$(basename "$script") 86 | name="${BIN_DIR:-$HOME/bin}/${name%.py}" 87 | if test -L "$name" -o ! -e "$name"; then 88 | ln -nfs "$script" "$name" 89 | fi 90 | done 91 | fi 92 | 93 | # Make sure people update their main config 94 | rm -f "$PROJECT_ROOT/src/pyrocore/data/config"/rtorrent-0.8.?.rc 2>/dev/null || : 95 | rm -f "$HOME/.pyroscope"/rtorrent-0.8.?.rc.default 2>/dev/null || : 96 | 97 | # Update config defaults 98 | rm -f "$HOME/.pyroscope/rtorrent.d.rc" 2>/dev/null || : 99 | rm -f "$HOME/.pyroscope/rtorrent.d"/view-zz-collapse.rc* 2>/dev/null || : 100 | ./bin/pyroadmin --create-config 101 | ./bin/pyroadmin --create-import "~/.pyroscope/rtorrent.d/*.rc.default" 102 | 103 | # Relocate to ~/.local 104 | test "$PROJECT_ROOT" != "$HOME/lib/pyroscope" || cat <<'EOF' 105 | 106 | ***************************************************************************** 107 | The default install location has changed, consider moving to the new path at 108 | '~/.local/pyroscope'! 109 | 110 | Call these commands: 111 | 112 | mkdir -p ~/.local/pyroscope 113 | cp -p ~/lib/pyroscope/update-to-head.sh ~/.local/pyroscope 114 | ~/.local/pyroscope/update-to-head.sh 115 | 116 | ***************************************************************************** 117 | 118 | EOF 119 | 120 | # Show (hopefully new) version at the end 121 | ./bin/pyroadmin --version 122 | 123 | # Make sure PATH is decent 124 | ( echo $PATH | tr : \\n | egrep "^$HOME/bin/?\$" >/dev/null ) || echo "$HOME/bin is NOT on your PATH, you need to fix that"'!' 125 | -------------------------------------------------------------------------------- /util.sh: -------------------------------------------------------------------------------- 1 | # library of helper functions (needs to be sourced) 2 | 3 | abend() { 4 | echo 5 | echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" 6 | echo -n >&2 "ERROR: " 7 | for i in "$@"; do 8 | echo >&2 "$i" 9 | done 10 | set +e 11 | return 1 12 | } 13 | 14 | fail() { 15 | abend "$@" 16 | exit 1 17 | } 18 | 19 | 20 | SCRIPTNAME="$0" 21 | test "$SCRIPTNAME" != "-bash" -a "$SCRIPTNAME" != "-/bin/bash" || SCRIPTNAME="${BASH_SOURCE[0]}" 22 | 23 | test -f "$PROJECT_ROOT/util.sh" || unset PROJECT_ROOT 24 | PROJECT_ROOT=${PROJECT_ROOT:-$(builtin cd $(dirname "$SCRIPTNAME") >/dev/null && pwd)} 25 | test -f "$PROJECT_ROOT/util.sh" || PROJECT_ROOT=$(dirname "$PROJECT_ROOT") 26 | test -f "$PROJECT_ROOT/util.sh" || abend "Cannot find project root in '$PROJECT_ROOT'" 27 | export PROJECT_ROOT 28 | 29 | 30 | fix_wrappers() { 31 | # Ensure unversioned wrappers exist 32 | for i in "$PROJECT_ROOT"/bin/*-2.*; do 33 | tool=${i%-*} 34 | test -x "$tool" || ln -s $(basename "$i") "$tool" 35 | done 36 | } 37 | 38 | ensure_pip() { 39 | test -x "$PROJECT_ROOT"/bin/pip || "$PROJECT_ROOT"/bin/easy_install -q pip 40 | test -x "$PROJECT_ROOT"/bin/pip || fix_wrappers 41 | test -x "$PROJECT_ROOT"/bin/pip || abend "installing pip into $PROJECT_ROOT failed" 42 | } 43 | 44 | update_venv() { 45 | local pip="${1:?You MUST pass a pip executable}" 46 | 47 | $pip install -U pip 48 | $pip install -U setuptools || : 49 | $pip install -U wheel || : 50 | } 51 | 52 | install_venv() { 53 | venv_url="https://pypi.python.org/packages/d4/0c/9840c08189e030873387a73b90ada981885010dd9aea134d6de30cd24cb8/virtualenv-15.1.0.tar.gz" 54 | mkdir -p "$PROJECT_ROOT/lib" 55 | test -f "$PROJECT_ROOT/lib/virtualenv.tgz" || \ 56 | $PYTHON -c "import urllib2; open('$PROJECT_ROOT/lib/virtualenv.tgz','w').write(urllib2.urlopen('$venv_url').read())" 57 | test -d "$PROJECT_ROOT/lib/virtualenv" || \ 58 | ( mkdir -p lib/virtualenv && cd lib/virtualenv && tar -xz -f ../virtualenv.tgz --strip-components=1 ) 59 | deactivate 2>/dev/null || true 60 | $PYTHON "$PROJECT_ROOT"/lib/virtualenv/virtualenv.py "$@" "$PROJECT_ROOT" 61 | test -f "$PROJECT_ROOT"/bin/activate || abend "creating venv in $PROJECT_ROOT failed" 62 | 63 | ensure_pip 64 | } 65 | 66 | pip_install_opt() { 67 | ensure_pip 68 | "$PROJECT_ROOT"/bin/pip install "$@" 69 | fix_wrappers 70 | } 71 | 72 | pip_install() { 73 | ensure_pip 74 | "$PROJECT_ROOT"/bin/pip install "$@" || abend "'pip install $@' failed" 75 | fix_wrappers 76 | } 77 | --------------------------------------------------------------------------------