Repository: fbdtemme/torrenttools Branch: main Commit: 337d6a6f6b3c Files: 177 Total size: 7.1 MB Directory structure: gitextract_bkmqqxe5/ ├── .dockerignore ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── documentation.yml │ ├── linux.yml │ ├── macos.yml │ ├── package.yml │ └── windows.yml ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── README.md ├── benchmark/ │ ├── benchmark.csv │ ├── benchmark.ipynb │ └── benchmark.sh ├── cmake/ │ ├── FindISALCrypto.cmake │ ├── FindNASM.cmake │ ├── FindSphinx.cmake │ ├── FindTBB.cmake │ └── SanitizersConfig.cmake ├── docs/ │ ├── CMakeLists.txt │ ├── bep-support.csv │ ├── commands/ │ │ ├── create.rst │ │ ├── edit.rst │ │ ├── info.rst │ │ ├── magnet.rst │ │ ├── pad.rst │ │ ├── show.rst │ │ └── verify.rst │ ├── comparison.rst │ ├── conf.py.in │ ├── configuration.rst │ ├── index.rst │ ├── topics/ │ │ ├── bencode.rst │ │ ├── bittorrent-metafile-v1.rst │ │ └── glossary.rst │ └── why-torrenttools.rst ├── external/ │ ├── CLI11.cmake │ ├── Catch2.cmake │ ├── bencode.cmake │ ├── cliprogress.cmake │ ├── ctre.cmake │ ├── date.cmake │ ├── dottorrent.cmake │ ├── expected-lite.cmake │ ├── external.cmake │ ├── fmt.cmake │ ├── gsl-lite.cmake │ ├── isa-l_crypto.cmake │ ├── nlohmann_json.cmake │ ├── re2.cmake │ ├── sigslot.cmake │ ├── termcontrol.cmake │ └── yaml-cpp.cmake ├── include/ │ ├── app_data.hpp │ ├── argument_parsers.hpp │ ├── cli_helpers.hpp │ ├── common.hpp │ ├── config.hpp.in │ ├── config_parser.hpp │ ├── create.hpp │ ├── edit.hpp │ ├── escape_binary_fields.hpp │ ├── exceptions.hpp │ ├── file_matcher.hpp │ ├── formatters.hpp │ ├── help_formatter.hpp │ ├── indicator.hpp │ ├── info.hpp │ ├── list_edit_mode.hpp │ ├── ls_colors.hpp │ ├── magnet.hpp │ ├── main_app.hpp │ ├── natural_sort.hpp │ ├── pad.hpp │ ├── profile.hpp │ ├── profile_config_formatter.hpp │ ├── progress.hpp │ ├── show.hpp │ ├── tracker_database.hpp │ ├── tree_view.hpp │ ├── utils.hpp │ └── verify.hpp ├── packages/ │ ├── appimage/ │ │ ├── build_appimage.sh │ │ ├── torrenttools-appimage-recipe.yml │ │ └── torrenttools.desktop │ ├── arch/ │ │ └── PKGBUILD │ ├── cpack_dispatch.cmake │ ├── debian/ │ │ ├── changelog │ │ ├── compat │ │ ├── control │ │ ├── copyright │ │ ├── rules │ │ └── source/ │ │ └── format │ ├── macos-productbuild.cmake │ ├── package_summary.txt │ ├── packages.cmake │ ├── productbuild/ │ │ └── postflight.sh │ ├── rpm/ │ │ └── torrenttools.spec │ ├── ubuntu/ │ │ ├── 20.04/ │ │ │ └── changelog │ │ └── 21.04/ │ │ └── changelog │ ├── windows-wix.cmake │ └── wix/ │ ├── broadcast_env_change.xml │ ├── copy-config.xml │ ├── create-localappdata-folder.wxs │ ├── disable_feature_advertise.xml │ └── update_path.xml ├── resources/ │ ├── config.yml │ └── trackers.json ├── scripts/ │ └── fetch_dependencies.sh ├── src/ │ ├── app_data.cpp │ ├── argument_parsers.cpp │ ├── common.cpp │ ├── config_parser.cpp │ ├── create.cpp │ ├── edit.cpp │ ├── escape_binary_fields.cpp │ ├── formatters.cpp │ ├── indicator.cpp │ ├── info.cpp │ ├── ls_colors.cpp │ ├── magnet.cpp │ ├── main.cpp │ ├── main_app.cpp │ ├── pad.cpp │ ├── profile.cpp │ ├── progress.cpp │ ├── show.cpp │ ├── tracker_database.cpp │ ├── tree_view.cpp │ └── verify.cpp └── tests/ ├── CMakeLists.txt ├── main.cpp ├── private.torrent/ │ └── test_file.txt.torrent ├── resources/ │ ├── CAMELYON17.torrent │ ├── COVID-19-image-dataset-collection.torrent │ ├── Fedora-Workstation-Live-x86_64-30.torrent │ ├── RSNA_Pneumonia_Detection_Challenge.torrent │ ├── bittorrent-v2-hybrid-test.torrent │ ├── bittorrent-v2-test.torrent │ ├── checksums.torrent │ ├── collection.torrent │ ├── config/ │ │ ├── config.yml │ │ └── trackers.json │ ├── dht-node.torrent │ ├── http-seeds.torrent │ ├── lorem_ipsum.txt │ ├── private.torrent │ ├── resources-hybrid.torrent │ ├── resources.torrent │ ├── similar-v1.torrent │ ├── similar-v2.torrent │ ├── test-torrent/ │ │ ├── dirA/ │ │ │ ├── fileA1 │ │ │ └── fileA2 │ │ └── dirB/ │ │ ├── fileB1 │ │ └── fileB2 │ ├── test_file │ ├── tests.torrent │ ├── tree_index_test.torrent │ ├── ubuntu-20.04.1-live-server-amd64.iso.torrent │ └── web-seed.torrent ├── test_create.cpp ├── test_edit.cpp ├── test_file_matcher.cpp ├── test_info.cpp ├── test_ls_colors.cpp ├── test_magnet.cpp ├── test_main.cpp ├── test_pad.cpp ├── test_profile.cpp ├── test_resources.hpp ├── test_show.cpp ├── test_tracker_database.cpp ├── test_tree_view.cpp ├── test_utils.cpp └── test_verify.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ .github/ .vscode/ cmake-build-*/ Dockerfile .dockerignore ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: fbdtemme ================================================ FILE: .github/workflows/documentation.yml ================================================ name: Documentation on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - name: Install dependencies run: | sudo python3 -m pip install sphinx sudo python3 -m pip install furo - name: Configure run: | mkdir build cmake -B build -S . \ -DTORRENTTOOLS_BUILD_CORE=OFF \ -DTORRENTTOOLS_BUILD_TESTS=OFF \ -DTORRENTTOOLS_INSTALL=OFF \ -DTORRENTTOOLS_BUILD_DOCS=ON \ -DTORRENTTOOLS_TBB=OFF - name: build run: cmake --build build --target Sphinx - name: Deploy to GitHub Pages uses: JamesIves/github-pages-deploy-action@3.7.1 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BRANCH: gh-pages FOLDER: build/docs/sphinx CLEAN: true ================================================ FILE: .github/workflows/linux.yml ================================================ name: Linux on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: name: Build runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - name: Install build dependencies run: sudo apt-get install -y libstdc++-10-dev g++-10 gcc-10 cmake libssl-dev libtbb-dev libre2-dev libyaml-cpp-dev nlohmann-json3-dev - name: Configure run: | cmake -S . -B cmake-build-debug -DCMAKE_CXX_COMPILER=g++-10 -DCMAKE_BUILD_TYPE=Debug -DTORRENTTOOLS_BUILD_TESTS=ON - name: Build run: cmake --build cmake-build-debug - name: Save build artifacts uses: actions/upload-artifact@v2 with: name: torrenttools-binaries path: | cmake-build-debug !cmake-build-debug/_deps retention-days: 1 test: name: Test needs: Build runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - name: Install runtime dependencies run: sudo apt-get install -y libstdc++-10-dev g++-10 gcc-10 libssl-dev libtbb-dev libre2-dev libyaml-cpp-dev nlohmann-json3-dev - name: Download build artifacts uses: actions/download-artifact@v2 with: name: torrenttools-binaries path: cmake-build-debug - name: Display structure of downloaded files run: ls -R - name: Test run: | chmod +x cmake-build-debug/tests/torrenttools-tests cd cmake-build-debug && ctest ================================================ FILE: .github/workflows/macos.yml ================================================ name: macOS on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: name: Build runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Install build dependencies run: | brew install cmake brew install gcc brew install openssl brew install autoconf brew install automake brew install libtool brew install nasm - name: Configure run: | cmake -G "Unix Makefiles" -S . -B cmake-build-debug \ -DCMAKE_CXX_COMPILER=g++-11 \ -DCMAKE_C_COMPILER=gcc-11 \ -DOPENSSL_ROOT_DIR=/usr/local/Cellar/openssl@1.1/1.1.1l \ -DCMAKE_BUILD_TYPE=Debug \ -DTORRENTTOOLS_BUILD_TESTS=ON \ -DTORRENTTOOLS_TBB=OFF \ -DDOTTORRENT_MB_CRYPTO_LIB=isal \ -DDOTTORRENT_CRYPTO_LIB=openssl - name: Build run: cmake --build cmake-build-debug - name: Save build artifacts uses: actions/upload-artifact@v2 with: name: torrenttools-binaries-macos path: | cmake-build-debug !cmake-build-debug/_deps retention-days: 1 test: name: Test needs: Build runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: Install runtime dependencies run: | brew install cmake brew install gcc brew install openssl - name: Download build artifacts uses: actions/download-artifact@v2 with: name: torrenttools-binaries-macos path: cmake-build-debug - name: Display structure of downloaded files run: ls -R - name: Test run: | chmod +x cmake-build-debug/tests/torrenttools-tests cd cmake-build-debug && ctest ================================================ FILE: .github/workflows/package.yml ================================================ name: Package on: workflow_dispatch: branches: [ main, develop ] inputs: linux: description: 'Create a linux AppImage' required: true default: 'on' windows: description: 'Create a windows installer' required: true default: 'on' macos: description: 'Create a macOS installer' required: true default: 'on' jobs: package-tarball: name: "Tarball" runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install build dependencies run: sudo apt-get install -y libstdc++-10-dev g++-10 gcc-10 cmake libssl-dev libtbb-dev libre2-dev libyaml-cpp-dev nlohmann-json3-dev - name: Fetch external projects run: scripts/fetch_dependencies.sh - name: Configure run: | cmake -S . -B cmake-build-release -DCMAKE_CXX_COMPILER=g++-10 -DCMAKE_BUILD_TYPE=Release -DTORRENTTOOLS_BUILD_TESTS=ON - name: Build tarball working-directory: cmake-build-release run: | cpack --config CPackSourceConfig.cmake -G TGZ - name: Determine Version id: cmake_version run: | CMAKE_PROJECT_VERSION=$(grep -oP "(?<=CMAKE_PROJECT_VERSION:STATIC=).*" cmake-build-release/CMakeCache.txt) echo "::set-output name=cmake_project_version::${CMAKE_PROJECT_VERSION}" echo "${CMAKE_PROJECT_VERSION}" > version.txt - name: Save version.txt uses: actions/upload-artifact@v2 with: name: version.txt path: version.txt retention-days: 1 - name: Upload tarball as artifact uses: actions/upload-artifact@v2 with: name: torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}.tar.gz path: cmake-build-release/torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}.tar.gz retention-days: 1 package-windows: name: "Windows installer" if: ${{ github.event.inputs.windows == 'on' }} runs-on: windows-latest needs: [ package-tarball ] defaults: run: shell: msys2 {0} steps: - uses: msys2/setup-msys2@v2 with: update: true install: >- make mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-yasm mingw-w64-x86_64-openssl mingw-w64-x86_64-intel-tbb - name: Download version file uses: actions/download-artifact@v2 with: name: version.txt - name: Retrieve version id : cmake_version run: | CMAKE_PROJECT_VERSION="$(cat version.txt)" echo "::set-output name=cmake_project_version::${CMAKE_PROJECT_VERSION}" - name: Download tarball artifact uses: actions/download-artifact@v2 with: name: torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}.tar.gz - name: Unpack sources tarball run: | tar -zxf torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}.tar.gz \ --exclude torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}/external/CLI11/tests \ --exclude torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}/packages/ubuntu - name: Configure run: | cmake -G "MSYS Makefiles" \ -B cmake-build-release \ -S torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }} \ -DDOTTORRENT_CRYPTO_LIB=openssl \ -DDOTTORRENT_MB_CRYPTO_LIB=isal \ -DCMAKE_BUILD_TYPE=Release \ -DTORRENTTOOLS_BUILD_TESTS=OFF \ -DTORRENTTOOLS_BUILD_DOCS=OFF - name: Build run: cmake --build cmake-build-release --target torrenttools - name: Check linkage run: ldd cmake-build-release/torrenttools.exe - name: Package run: | cd cmake-build-release cpack --verbose -G WIX - name: Upload installer as artifact uses: actions/upload-artifact@v2 with: name: torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}-windows-x86_64.msi path: cmake-build-release/torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}.msi retention-days: 1 package-macos: name: "macOS installer" if: ${{ github.event.inputs.macos == 'on' }} runs-on: macos-latest needs: [ package-tarball ] steps: - name: Download version file uses: actions/download-artifact@v2 with: name: version.txt - name: Retrieve version id: cmake_version run: | CMAKE_PROJECT_VERSION="$(cat version.txt)" echo "::set-output name=cmake_project_version::${CMAKE_PROJECT_VERSION}" - name: Download tarball artifact uses: actions/download-artifact@v2 with: name: torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}.tar.gz - name: Unpack sources tarball run: | tar -zxf torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}.tar.gz \ --exclude torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}/external/CLI11/tests - name: Install build dependencies run: | brew install cmake brew install gcc brew install openssl brew install autoconf brew install automake brew install libtool brew install nasm - name: List working dir run: ls -al . - name: Configure run: | cmake -G "Unix Makefiles" \ -B cmake-build-release \ -S torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }} \ -DCMAKE_CXX_COMPILER=g++-11 \ -DCMAKE_C_COMPILER=gcc-11 \ -DOPENSSL_ROOT_DIR=/usr/local/Cellar/openssl@1.1/1.1.1l \ -DCMAKE_BUILD_TYPE=Release \ -DTORRENTTOOLS_BUILD_TESTS=OFF \ -DTORRENTTOOLS_TBB=OFF \ -DDOTTORRENT_MB_CRYPTO_LIB=isal \ -DDOTTORRENT_CRYPTO_LIB=openssl - name: Build run: cmake --build cmake-build-release - name: Package run: | cd cmake-build-release cpack --verbose -G productbuild - name: Upload installer as artifact uses: actions/upload-artifact@v2 with: name: torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}-macos-x86_64.pkg path: cmake-build-release/torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}.pkg retention-days: 1 package-linux: name: "Linux AppImage" if: ${{ github.event.inputs.linux == 'on' }} runs-on: ubuntu-20.04 needs: [ package-tarball ] steps: - name: Download version file uses: actions/download-artifact@v2 with: name: version.txt - name: Retrieve version id: cmake_version run: | CMAKE_PROJECT_VERSION="$(cat version.txt)" echo "::set-output name=cmake_project_version::${CMAKE_PROJECT_VERSION}" - name: Download tarball artifact uses: actions/download-artifact@v2 with: name: torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}.tar.gz - name: Unpack sources tarball run: | tar -zxf torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}.tar.gz \ --exclude torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}/external/CLI11/tests - name: Install build dependencies run: | sudo apt-get install -y libstdc++-10-dev g++-10 gcc-10 cmake \ libssl-dev libtbb-dev nasm libtool automake autoconf - name: Configure run: | cmake -S torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }} \ -B cmake-build-release \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_COMPILER=g++-10 \ -DTORRENTTOOLS_BUILD_TESTS=OFF \ -DDOTTORRENT_MB_CRYPTO_LIB=isal \ -DDOTTORRENT_CRYPTO_LIB=openssl \ -DSYSCONF_INSTALL_DIR=/etc - name: Build run: cmake --build cmake-build-release --target torrenttools - name: Prepare AppDir run: | SRCDIR="torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}" DESTDIR=AppDir cmake --install cmake-build-release --prefix=/usr --component torrenttools mkdir -p AppDir/usr/share/icons/256x256/apps cp $SRCDIR/resources/icons/256x256/torrenttools.png AppDir/usr/share/icons/256x256/apps/ mkdir -p AppDir/usr/share/applications/ cp $SRCDIR/packages/appimage/torrenttools.desktop AppDir/usr/share/applications/torrenttools.desktop - name: Build AppImage uses: AppImageCrafters/build-appimage@master with: recipe: torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}/packages/appimage/torrenttools-appimage-recipe.yml env: UPDATE_INFO: gh-releases-zsync|AppImageCrafters|appimage-demo-qt5|latest|*x86_64.AppImage.zsync - name: Upload AppImage as artifact uses: actions/upload-artifact@v2 with: name: torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}-x86_64.AppImage path: torrenttools-${{ steps.cmake_version.outputs.cmake_project_version }}-x86_64.AppImage retention-days: 1 ================================================ FILE: .github/workflows/windows.yml ================================================ name: Windows on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: name: Build runs-on: windows-latest defaults: run: shell: msys2 {0} steps: - uses: msys2/setup-msys2@v2 with: update: true install: >- make mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-yasm mingw-w64-x86_64-openssl mingw-w64-x86_64-intel-tbb - uses: actions/checkout@v2 - name: Configure run: | mkdir build; cmake --help cmake -G "MSYS Makefiles" -B cmake-build-debug -S . \ -DDOTTORRENT_CRYPTO_LIB=openssl \ -DDOTTORRENT_MB_CRYPTO_LIB=isal \ -DCMAKE_BUILD_TYPE=Debug \ -DTORRENTTOOLS_BUILD_TESTS=ON \ -DTORRENTTOOLS_BUILD_DOCS=OFF - name: Build run: cmake --build cmake-build-debug - name: Save build artifacts uses: actions/upload-artifact@v2 with: name: torrenttools-binaries-windows path: | cmake-build-debug !cmake-build-debug/_deps retention-days: 1 test: name: Test needs: Build runs-on: windows-latest defaults: run: shell: msys2 {0} steps: - uses: msys2/setup-msys2@v2 with: update: true install: >- make mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-yasm mingw-w64-x86_64-openssl mingw-w64-x86_64-intel-tbb - uses: actions/checkout@v2 - name: Download build artifacts uses: actions/download-artifact@v2 with: name: torrenttools-binaries-windows path: cmake-build-debug - name: Display structure of downloaded files run: ls -R - name: Test run: | chmod +x cmake-build-debug/tests/torrenttools-tests cd cmake-build-debug && ctest ================================================ FILE: .gitignore ================================================ venv/** **/.idea/** **/.vscode/** **/cmake-build-*/** **/.DS_Store ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [v0.6.2] - 2021-08-31 ### Changed * Workaround crashes on Windows due to re2 with MinGW issues. ## [v0.6.1] - 2021-08-07 ### Added * Add missing --simple-progress option. ### Changed * Fix issues with torrents containing one file under the root directory. (#22) ## [v0.6.0] - 2021-08-02 ### Added * Enable new metafiles for cross-seeding by default. * Add versions of crypto backends to --version output ### Changed * Read user-config location from XDG_CONFIG_HOME on linux. * Fix macOS installer bundling system library leading to linking errors. * Add support for more trackers in the default trackers.json file (thanks @Audionut) * Fix freeze when LS_COLORS contains 8-bit or 24-bit colors. * Increase re2 compiler memory to support long regexes. ## [v0.5.1] - 2021-07-26 ### Changed * Fix misconfiguration of global config directory on linux. ## [v0.5.0] - 2021-07-25 ### Added * Add support for windows and macOS. * Add support for create and edit profiles. * Add missing show subcommands and info entries for dht-nodes and web-seeds. * Add support for BEP 38: Finding Local Data Via Torrent File Hints (#9). * Add support for BEP-17: http-seeding (#8). * Add support for printing per-file checksums in sha1sum format (#7) * Add progress reporting while scanning for files. (#10) ### Changed * Improve formatting of info command output. * Fix invalid v1 infohash reporting for hybrid torrents. * Fix rare race condition in progress bar causing deadlocks. * Report progress when scanning filesystem and preparing metafile. * Optionally accelerate sorting when linked to Intel TBB. * Skip file tree for metafiles with more than 1000 files. * Fix v2 and hybrid issues with empty files. ## [v0.4.1] - 2021-04-15 ### Changed * Fix undefined behaviour in verify command when output is piped. * Fix missing progress lines for create command when output is piped. ## [v0.4.0] - 2021-04-10 ### Added * Add named tracker groups. * Add `--config` and `--trackers-config` to pass custom configuration files locations. * Add colored file tree using LS_COLORS environment variable. * Add docker container. * Add limited wolfssl support. ### Changed * Fix progressbar for verify command. * Fix hybrid torrent issues for both v1 and v2 verification. * Fix infinite loop on unrecoverable IO error during hashing. * Fix threads options ignored when verifying. * Fix progress reporting when there are no files inside the torrent. * Fix issue with worker threads exiting due to uninitialized stop state. ## [v0.3.2] - 2021-02-20 ### Changed * Fix announce url substitution overriding announce url ## [v0.3.1] - 2021-02-19 ### Changed * Fix regression in create command --output option. ## [v0.3.0] - 2021-02-19 ### Added * Faster hashing backend using Intel ISA-L Crypto multi-buffer hashing. * Read target name from input stream. ### Changed * Fix announce url not properly set for single tracker metafiles. * Progress bar now shows total progress instead of per-file progress and ETA. * Reduced resource consumption by removing busy waiting. ## [v0.2.2] - 2021-01-30 ### Changed * Fix source tag not being set in create command. ## [v0.2.1] - 2021-01-28 ### Added * magnet command to generate magnet URI's from a bittorrent metafile. * Simpler progress reporting when output is piped (eg. ruTorrent task output). * Add "show size" command. ### Changed * Fix race triggered when hashing multiple small files in v2 hasher. ## [v0.2.0] - 2021-01-25 ### Added * Add experimental windows support and installer. * Add show command to query specific fields of a metafile. * Add edit command to edit an existing metafile. * Add "--creation-date" option to override the creation date. * Add "--created-by" option to override the default created by string (program and version) * Add "--stdout" options enable writing torrent file to the standard output. * Add "--version" option to show program version. ### Changed * Fix behavior of options accepting multiple values: "--announce", "--web-seeds", "--dht-nodes". * Fix loading config files from user-local directory. * Allow to include hidden files when they match an include regex without specifying the --include-hidden flag. * Move query options of info to the new show command. * Fix --name option being ignored. * Fix multiple announce urls being invalidly serialized. * Allow serialization of metafiles with zero-length files. * Fix dht-node serialization and deserialization. [comment]: <> (### Removed) ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) # Version string cmake_policy(SET CMP0048 NEW) cmake_policy(SET CMP0077 NEW) if (UNIX AND NOT APPLE) set(LINUX TRUE) endif() project(torrenttools DESCRIPTION "A commandline tool for creating, inspecting and modifying BitTorrent metafiles." LANGUAGES CXX VERSION 0.6.2 HOMEPAGE_URL https://www.github.com/fbdtemme/torrenttools) # Make Find modules in cmake dir available set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) if (APPLE) set(CMAKE_INSTALL_NAME_DIR "@executable_path/../lib") endif() if (TORRENTTOOLS_PACKAGES_ONLY) include(packages/packages.cmake) return() endif() include(CTest) include(GNUInstallDirs) # Path including images, config files etc set(RESOURCES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/resources") include(CMakeDependentOption) option(TORRENTTOOLS_BUILD_TESTS "Build tests" OFF) cmake_dependent_option(TORRENTTOOLS_BUILD_TESTS_COVERAGE "Build tests with coverage." OFF "TORRENTTOOLS_BUILD_TESTS" OFF) option(TORRENTTOOLS_BUILD_DOCS "Generate documentation" OFF) option(TORRENTTOOLS_INSTALL "Generate an install target" ON) option(TORRENTTOOLS_TBB "Accelerate using Intel TBB library." ON) #add_subdirectory(../cliprogress cliprogress) #add_subdirectory(../dottorrent dottorrent) #add_subdirectory(../termcontrol termcontrol) #add_subdirectory(../bencode bencode) include(external/external.cmake) if (TORRENTTOOLS_TBB) find_package(TBB REQUIRED) endif() add_executable(torrenttools src/app_data.cpp src/argument_parsers.cpp src/common.cpp src/config_parser.cpp src/create.cpp src/main_app.cpp src/edit.cpp src/escape_binary_fields.cpp src/formatters.cpp src/indicator.cpp src/info.cpp src/magnet.cpp src/main.cpp src/pad.cpp src/progress.cpp src/show.cpp src/tracker_database.cpp src/tree_view.cpp src/verify.cpp src/profile.cpp src/ls_colors.cpp ) target_include_directories(torrenttools PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) target_compile_features(torrenttools PUBLIC cxx_std_20) target_link_libraries(torrenttools PRIVATE dottorrent::dottorrent termcontrol::termcontrol cliprogress::cliprogress fmt::fmt date::date gsl::gsl-lite-v1 CLI11::CLI11 re2::re2 yaml-cpp nlohmann_json::nlohmann_json ) if (TORRENTTOOLS_TBB) message(STATUS "Using Intel TBB library.") target_link_libraries(torrenttools PRIVATE TBB::tbb) target_compile_definitions(torrenttools PRIVATE TORRENTTOOLS_USE_TBB) endif() # Set the linker to lld to get decent link times on MinGW if (MINGW) find_program(HAS_LLD_LINKER "lld") if (HAS_LLD_LINKER) message(STATUS "Using lld linker for MinGW generator.") target_link_options(torrenttools PRIVATE -fuse-ld=lld) endif() endif() if (TORRENTTOOLS_BUILD_TESTS) add_subdirectory(tests) endif() if(TORRENTTOOLS_BUILD_DOCS) add_subdirectory(docs) endif() # Set the install prefix to /usr/local/bin/torrenttools message(DEBUG ${CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT}) if (MINGW OR WIN32) # Set the install prefix to Program Files instead of Program Files (x86) message(DEBUG ${CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT}) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) message(DEBUG "Changing default install prefix") SET(CMAKE_INSTALL_PREFIX "C:/Program Files/${PROJECT_NAME}" CACHE PATH "Install path prefix, prepended onto install directories." FORCE) endif() elseif (APPLE) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) message(DEBUG "Changing default install prefix") set(CMAKE_INSTALL_PREFIX "/Library/${PROJECT_NAME}" CACHE PATH "Install path prefix, prepended onto install directories." FORCE) endif() endif() message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") ## Load the sysconf install dir as set by the RPM and DEB if(NOT DEFINED SYSCONF_INSTALL_DIR) set(SYSCONF_INSTALL_DIR ${CMAKE_INSTALL_FULL_SYSCONFDIR}) message(DEBUG "Setting SYSCONF_INSTALL_DIR: ${SYSCONF_INSTALL_DIR}") else() message(DEBUG "Setting SYSCONF_INSTALL_DIR: ${SYSCONF_INSTALL_DIR}") endif() # torrenttools_sysconf_dir: Full path used to lookup system scope configuration files. if (LINUX) set(torrenttools_sysconf_dir "${SYSCONF_INSTALL_DIR}/torrenttools") elseif (WIN32) # Dump everything in the install prefix on windows set(torrenttools_sysconf_dir "${CMAKE_INSTALL_PREFIX}") elseif (APPLE) set(torrenttools_sysconf_dir "${CMAKE_INSTALL_PREFIX}/etc") endif () message(STATUS "Install sysconf dir: ${torrenttools_sysconf_dir}") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/config.hpp.in ${CMAKE_CURRENT_SOURCE_DIR}/include/config.hpp) if (TORRENTTOOLS_INSTALL) if (LINUX) # install trackers to system config dir since it is global data. install(FILES resources/trackers.json resources/config.yml COMPONENT torrenttools DESTINATION ${torrenttools_sysconf_dir}) # install executable install(TARGETS torrenttools COMPONENT torrenttools RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() if (MINGW OR WIN32) # install resources in the install prefix install(FILES resources/trackers.json resources/config.yml COMPONENT torrenttools DESTINATION ".") # install executable install(TARGETS torrenttools COMPONENT torrenttools RUNTIME DESTINATION ".") # Install mingw shared libraries to the project dir # Filter out core windows libraries with regex install(CODE [[ set(LIBRARY_PATH $ENV{PATH}) string(REPLACE "\\" "/" LIBRARY_PATH "${LIBRARY_PATH}") message(STATUS "Searching for libraries in: ${LIBRARY_PATH}") file(GET_RUNTIME_DEPENDENCIES EXECUTABLES $ DIRECTORIES ${LIBRARY_PATH} RESOLVED_DEPENDENCIES_VAR _r_deps UNRESOLVED_DEPENDENCIES_VAR _u_deps PRE_EXCLUDE_REGEXES ".*-ms-win-.*" ".*ext-ms-.*" POST_INCLUDE_REGEXES ".*libcrypto.*" POST_EXCLUDE_REGEXES ".*system32.*" ) foreach(_file ${_r_deps}) file(INSTALL ${_file} DESTINATION "${CMAKE_INSTALL_PREFIX}" FOLLOW_SYMLINK_CHAIN) endforeach() list(LENGTH _u_deps _u_length) if("${_u_length}" GREATER 0) message(STATUS "${_u_deps}") message(WARNING "Unresolved dependencies detected!") foreach(_u_dep ${_u_deps}) message(WARNING "Dependency unresolved: ${_u_dep}") endforeach() endif() ]] COMPONENT torrenttools) endif() if (APPLE) # install executable install(TARGETS torrenttools COMPONENT torrenttools DESTINATION "bin") # install configuration install(FILES resources/trackers.json resources/config.yml COMPONENT torrenttools DESTINATION "etc") install(CODE [[ file(GET_RUNTIME_DEPENDENCIES EXECUTABLES $ DIRECTORIES "/usr/bin/local/Cellar" "/usr/bin/local" RESOLVED_DEPENDENCIES_VAR _r_deps UNRESOLVED_DEPENDENCIES_VAR _u_deps PRE_EXCLUDE_REGEXES ".*libSystem.B.*" ) foreach(_file ${_r_deps}) file(INSTALL ${_file} DESTINATION "${CMAKE_INSTALL_PREFIX}/lib" FOLLOW_SYMLINK_CHAIN) get_filename_component(_file_name "${_file}" NAME) execute_process(COMMAND install_name_tool -change "${_file}" "@executable_path/../lib/${_file_name}" "${CMAKE_INSTALL_PREFIX}/bin/torrenttools" ) endforeach() list(LENGTH _u_deps _u_length) if("${_u_length}" GREATER 0) message(STATUS "${_u_deps}") message(WARNING "Unresolved dependencies detected!") foreach(_u_dep ${_u_deps}) message(WARNING "Dependency unresolved: ${_u_dep}") endforeach() endif() ]] COMPONENT torrenttools) endif() endif() include(packages/packages.cmake) ================================================ FILE: Dockerfile ================================================ FROM alpine:latest AS build-stage RUN apk add --update-cache \ git \ make \ cmake \ autoconf \ automake \ libtool \ g++ \ nasm \ openssl-dev \ libtbb-dev # Copy source files #COPY . /torrenttools #WORKDIR /torrenttools ENV VERSION="0.5.0" RUN wget "https://github.com/fbdtemme/torrenttools/releases/download/v$VERSION/torrenttools-$VERSION.tar.gz" RUN tar -xzf "torrenttools-$VERSION.tar.gz" RUN mv torrenttools-$VERSION torrenttools # Generate build files RUN cmake -S torrenttools -B cmake-build-relwithdebinfo \ -DCMAKE_CXX_COMPILER=g++ \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DTORRENTTOOLS_BUILD_TESTS=OFF \ -DTORRENTTOOLS_BUILD_DOCS=OFF \ -DDOTTORRENT_MB_CRYPTO_LIB=isal # Build RUN cd cmake-build-relwithdebinfo && make -j$(nproc) torrenttools FROM alpine:latest AS runtime RUN apk add --update-cache openssl libtbb COPY --from=build-stage cmake-build-relwithdebinfo/torrenttools /usr/bin/ RUN chmod +x "/usr/bin/torrenttools" ENTRYPOINT ["/usr/bin/torrenttools"] ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Florian De Temmerman Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ ![](resources/images/torrenttools-banner.svg) ![build](https://github.com/fbdtemme/torrenttools/workflows/build/badge.svg) [![Copr build status](https://copr.fedorainfracloud.org/coprs/fbdtemme/torrenttools/package/torrenttools/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/fbdtemme/torrenttools/package/torrenttools/) [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/fbdtemme/torrenttools)](https://github.com/fbdtemme/torrenttools/releases) [![C++ standard](https://img.shields.io/badge/C%2B%2B-20-blue)](https://isocpp.org/) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/5cc3eec94d8a486dab62afeab5130def)](https://app.codacy.com/manual/floriandetemmerman/torrenttools?utm_source=github.com&utm_medium=referral&utm_content=fbdtemme/bencode&utm_campaign=Badge_Grade_Dashboard) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) A commandline tool for creating, inspecting and modifying bittorrent metafiles. [**Features**](#Status) | [**Documentation**](#Documentation) | [**Binary releases**](#binary-releases) | [**Building**](#Building) | [**License**](#License) ## Features * Creating bittorrent metafiles. * Inspecting bittorrent metafiles. * Verifying bittorrent metafiles against local data. * Editing existing bittorrent metafiles. * Support for the new [v2 and hybrid protocols](https://blog.libtorrent.org/2020/09/bittorrent-v2/) . * Support for tracker abbreviations. * Support for announce substitution parameters. * Fast multi-buffer hashing with Intel ISA-L. ## Example ![](resources/images/create-demo.gif) ## Status This project is under development. The commandline interface can change at any release prior to 1.0.0. ## Performance Following test were performed on a in in-memory filesystem with 1 MiB piece size and as target a file filed with random data totalling 15.0 GiB: The tested CPU is an Intel i7-7700HQ in a Dell XPS 15-9560 machine. ![Benchmark](benchmark/benchmark.svg) ## Documentation Documentation is hosted on [Github Pages](https://fbdtemme.github.io/torrenttools/). ## Binary releases ### Contents * [Windows](#Windows) * [macOS](#macOS) * [Linux](#Linux) * [Fedora](#Fedora) * [CentOS](#CentOS/RHEL) * [Ubuntu](#Ubuntu) * [Debian](#Debian) * [Ubuntu](#Ubuntu) * [OpenSUSE](#OpenSUSE) * [SUSE Linux Enterprise Server](#SUSE-Linux-Enterprise-Server-15) * [Arch](#Arch) * [AppImage](#AppImage) * [Docker](#inux) ### Windows An .msi installer is available as a [release asset](https://github.com/fbdtemme/torrenttools/releases). __IMPORTANT: Please use a modern terminal that supports most ANSI escape codes, like [Windows Terminal](https://www.microsoft.com/en-us/p/windows-terminal/9n0dx20hk701) (not cmd or PowerShell).__ ### macOS A .pkg installer is available as a [release asset](https://github.com/fbdtemme/torrenttools/releases). ### Linux #### Fedora Binary and source packages for Fedora 32, Fedora 33, Fedora 34 and Fedora Rawhide and CentOS stream are available in a [COPR repo](https://copr.fedorainfracloud.org/coprs/fbdtemme/torrenttools/). ```shell sudo dnf copr enable fbdtemme/torrenttools sudo dnf install torrenttools ``` #### CentOS/RHEL Binary and source packages for CentOS8/RHEL8 and CentOS stream are available in a [COPR repo](https://copr.fedorainfracloud.org/coprs/fbdtemme/torrenttools/). ```shell sudo dnf copr enable fbdtemme/torrenttools sudo dnf install torrenttools ``` #### Ubuntu Binary and source packages for Ubuntu 20.04, Ubuntu 20.10, Ubuntu 21.04 are available as a PPA via [launchpad](https://launchpad.net/torrenttools). ```shell sudo add-apt-repository ppa:fbdtemme/torrenttools sudo apt-get update sudo apt install torrenttools ``` #### Debian A binary package is available for Debian Sid. Older debian distributions should use the AppImage. ```shell echo 'deb http://download.opensuse.org/repositories/home:/fbdtemme/Debian_Unstable/ /' | sudo tee /etc/apt/sources.list.d/home:fbdtemme.list curl -fsSL https://download.opensuse.org/repositories/home:fbdtemme/Debian_Unstable/Release.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/home_fbdtemme.gpg > /dev/null sudo apt update sudo apt install torrenttools ``` #### openSUSE For openSUSE Tumbleweed run the following as root: ```shell zypper addrepo https://download.opensuse.org/repositories/home:fbdtemme/openSUSE_Tumbleweed/home:fbdtemme.repo zypper refresh zypper install torrenttools ``` For openSUSE Leap 15.2 run the following as root: ```shell zypper addrepo https://download.opensuse.org/repositories/home:fbdtemme/openSUSE_Leap_15.2/home:fbdtemme.repo zypper refresh zypper install torrenttools ``` For openSUSE Leap 15.3 run the following as root: ```shell zypper addrepo https://download.opensuse.org/repositories/home:fbdtemme/openSUSE_Leap_15.3/home:fbdtemme.repo zypper refresh zypper install torrenttools ``` #### SUSE Linux Enterprise Server 15 For SLE 15 SP2 run the following as root: ```shell zypper addrepo https://download.opensuse.org/repositories/home:fbdtemme/SLE_15_SP2/home:fbdtemme.repo zypper refresh zypper install torrenttools ``` For SLE 15 SP3 run the following as root: ```shell zypper addrepo https://download.opensuse.org/repositories/home:fbdtemme/SLE_15_SP3/home:fbdtemme.repo zypper refresh zypper install torrenttools ``` #### Arch A source package for Arch linux is available on [AUR](https://aur.archlinux.org/packages/torrenttools/). ```shell git clone https://aur.archlinux.org/torrenttools.git cd torrenttools makepkg -is ``` #### AppImage Distributions that have no package yet can use the AppImage that is available for download as a [release asset](https://github.com/fbdtemme/torrenttools/releases). ### Docker A docker image is available on [dockerhub](https://hub.docker.com/repository/docker/fbdtemme/torrenttools). ```shell docker pull fbdtemme/torrenttools ``` ## Building This library depends on following projects: * [CLI11](https://github.com/CLIUtils/CLI11) * [Catch2](https://github.com/catchorg/Catch2) * [CTRE](https://github.com/hanickadot/compile-time-regular-expressions) * [gsl-lite](https://github.com/gsl-lite/gsl-lite) * [RE2](https://github.com/google/re2) * [expected-lite](https://github.com/martinmoene/expected-lite) * [fmt](https://github.com/fmtlib/fmt) * [nlohmann/json](https://github.com/nlohmann/json) * [yaml-cpp](https://github.com/jbeder/yaml-cpp) * [bencode](https://github.com/fbdtemme/bencode) * [date](https://github.com/HowardHinnant/date) * [OpenSSL](https://github.com/openssl/openssl) * Optional: [ISA-L Crypto](https://github.com/intel/isa-l_crypto) Almost all dependencies can be fetched from github during configure time or can be installed manually. OpenSSL has to be installed on the system in advance. ### Installing build dependencies Ubuntu ```shell sudo apt install build-essential git cmake g++-10 libssl-dev ``` Debian ```shell sudo apt install build-essential git cmake g++-10 libssl-dev libtbb-dev ``` Fedora/RHEL/CentOS ```shell sudo dnf install cmake make g++ git openssl-devel automake autoconf ``` ### Configuration | Option | Type | Description | |--------------------------------|----------|------------------------------| | TORRENTTOOLS_BUILD_TESTS | Bool | Build tests. | | TORRENTTOOLS_BUILD_DOCS | Bool | Build documentation. | | TORRENTTOOLS_INSTALL | Bool | Generate an install target. | | DOTTORRENT_MB_CRYPTO_LIB | String | Pass "isal" for fast multibuffer hashing | ### Building This project requires C++20. Currently only GCC 10 or later is supported. This project can be build as every other project which makes use of the CMake build system. ```{bash} mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release .. cmake --build . --target torrenttools ``` ### Installation Installing the project: ```{bash} sudo cmake --install . --component torrentttools ``` ## License Distributed under the MIT license. See `LICENSE` for more information. ================================================ FILE: benchmark/benchmark.csv ================================================ threads,programs,speed_mean,speed_stddev 1,mktorrent,843.5375805340137,29.24016226983715 1,imdl,422.5956018664747,21.94521233645368 1,dottorrent-cli,465.43693577067035,5.708140591930167 1,pyrocore,694.6542989125322,28.286594276984108 1,transmission-create,694.7122046474267,38.43063952671966 1,py3createtorrent,768.842931714354,7.946207871730497 1,torf-cli,847.8770944894188,8.542897593491706 1,buildtorrent,465.63563386940734,3.906536033318984 1,torrenttools (OpenSSL),857.7468673905892,0.016150143162648407 1,torrenttools (ISA-L),2394.788777088055,25.55599825968731 2,mktorrent,1739.7457165731003,0.8165668598526372 2,imdl,422.5956018664747,21.94521233645368 2,dottorrent-cli,465.43693577067035,5.708140591930167 2,pyrocore,694.6542989125322,28.286594276984108 2,transmission-create,694.7122046474267,38.43063952671966 2,py3createtorrent,768.842931714354,7.946207871730497 2,torf-cli,1518.007777768706,20.55486136084764 2,buildtorrent,465.63563386940734,3.906536033318984 2,torrenttools (OpenSSL),1647.8120961785146,0.059603003086080776 2,torrenttools (ISA-L),4267.054895474759,138.34210665689523 3,mktorrent,2401.1029284404635,231.78107838447514 3,imdl,422.5956018664747,21.94521233645368 3,dottorrent-cli,465.43693577067035,5.708140591930167 3,pyrocore,694.6542989125322,28.286594276984108 3,transmission-create,694.7122046474267,38.43063952671966 3,py3createtorrent,768.842931714354,7.946207871730497 3,torf-cli,2026.5801897912054,28.28542069623083 3,buildtorrent,465.63563386940734,3.906536033318984 3,torrenttools (OpenSSL),2448.5222121223073,27.990338548381576 3,torrenttools (ISA-L),4255.814143177331,1.1925163622392736 4,mktorrent,3149.4450558966864,125.745768568161 4,imdl,422.5956018664747,21.94521233645368 4,dottorrent-cli,465.43693577067035,5.708140591930167 4,pyrocore,694.6542989125322,28.286594276984108 4,transmission-create,694.7122046474267,38.43063952671966 4,py3createtorrent,768.842931714354,7.946207871730497 4,torf-cli,2030.287245340613,17.163154357953065 4,buildtorrent,465.63563386940734,3.906536033318984 4,torrenttools (OpenSSL),3269.5331103167205,49.6780674007491 4,torrenttools (ISA-L),4258.630736286791,3.9837368331762373 ================================================ FILE: benchmark/benchmark.ipynb ================================================ { "metadata": { "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.2-final" }, "orig_nbformat": 2, "kernelspec": { "name": "python392jvsc74a57bd0767d51c1340bd893661ea55ea3124f6de3c7a262a8b4abca0554b478b1e2ff90", "display_name": "Python 3.9.2 64-bit ('base': conda)" } }, "nbformat": 4, "nbformat_minor": 2, "cells": [ { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "import re\n", "from IPython.display import Javascript\n", "import getpass" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "password = None\n", "if password is None:\n", " password = getpass.getpass()" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "[sudo] password for fbdtemme: " ] } ], "source": [ "!echo $password | sudo -S mount -t tmpfs -o size=20g tmpfs /mnt/tmpfs/\n", "!head -c 20G /dev/urandom > /mnt/tmpfs/data" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "pwd = \"/mnt/tmpfs\"\n", "max_threads = 4\n", "iterations = 3\n", "target = 'data'" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "fs = !du -b $pwd/$target | grep -o \"[0-9]*\"\n", "file_size = int(fs[0])" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "21474836480" ] }, "metadata": {}, "execution_count": 6 } ], "source": [ "file_size" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "[sudo] password for fbdtemme: Setting cpu: 0\n", "Setting cpu: 1\n", "Setting cpu: 2\n", "Setting cpu: 3\n", "Setting cpu: 4\n", "Setting cpu: 5\n", "Setting cpu: 6\n", "Setting cpu: 7\n" ] } ], "source": [ "# Disable cpu frequency scaling\n", "!echo $password | sudo -S cpupower frequency-set -g performance" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "def parse_real_time(data: str):\n", " results = []\n", " match = re.search(R\"real\\s(\\d+)m([\\d.]+)s\", data)\n", " minutes = float(match.group(1))\n", " seconds = float(match.group(2))\n", " return minutes * 60 + seconds\n", "\n", "def parse_real_time_sequence(data: str):\n", " results = []\n", " for match in re.findall(R\"real\\s(\\d+)m([\\d.]+)s\", data):\n", " print(match[0])\n", " print(match[1])\n", " minutes = float(match[0])\n", " seconds = float(match[1])\n", " results.append(minutes * 60 + seconds)\n", " return results" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "data = {}" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "def run_benchmark(name: str, use_threads:bool = True):\n", " duration = np.ndarray(shape=(iterations, max_threads))\n", " \n", " if use_threads:\n", " for t in range(1, max_threads+1):\n", " for i in range(0, iterations):\n", " result = !./benchmark.sh \"{name}\" \"{pwd}\" \"{target}\" \"{t}\"\n", " result_str = '\\n'.join(result)\n", " print(f\"-- iteration {i+1} | threads {t}\\n{result_str}\")\n", " duration[i, t-1] = parse_real_time(result_str)\n", " else:\n", " for i in range(0, iterations):\n", " t = 1\n", " result = !./benchmark.sh \"{name}\" \"{pwd}\" \"{target}\" \"{t}\"\n", " result_str = '\\n'.join(result)\n", " print(f\"-- iteration {i+1} | threads {t}\\n{result_str}\")\n", " duration[i, :] = parse_real_time(result_str)\n", "\n", " speed = file_size / duration\n", " mean = np.mean(speed, axis=0)\n", " std = np.std(speed, axis=0)\n", " return (mean, std)" ] }, { "source": [ "### mktorrent" ], "cell_type": "markdown", "metadata": {} }, { "source": [ "mktorrent_data = run_benchmark(\"mktorrent\")" ], "cell_type": "code", "metadata": {}, "execution_count": 163, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "-- iteration 1 | threads 1\n", "\n", "real\t0m24.658s\n", "user\t0m24.564s\n", "sys\t0m4.014s\n", "\n", "-- iteration 2 | threads 1\n", "\n", "real\t0m26.743s\n", "user\t0m26.588s\n", "sys\t0m6.149s\n", "\n", "-- iteration 3 | threads 1\n", "\n", "real\t0m25.067s\n", "user\t0m25.211s\n", "sys\t0m4.936s\n", "\n", "-- iteration 1 | threads 2\n", "\n", "real\t0m12.336s\n", "user\t0m24.587s\n", "sys\t0m3.970s\n", "\n", "-- iteration 2 | threads 2\n", "\n", "real\t0m12.345s\n", "user\t0m24.662s\n", "sys\t0m3.876s\n", "\n", "-- iteration 3 | threads 2\n", "\n", "real\t0m12.350s\n", "user\t0m24.630s\n", "sys\t0m3.896s\n", "\n", "-- iteration 1 | threads 3\n", "\n", "real\t0m8.320s\n", "user\t0m24.733s\n", "sys\t0m4.106s\n", "\n", "-- iteration 2 | threads 3\n", "\n", "real\t0m8.427s\n", "user\t0m25.103s\n", "sys\t0m4.126s\n", "\n", "-- iteration 3 | threads 3\n", "\n", "real\t0m10.355s\n", "user\t0m29.589s\n", "sys\t0m5.067s\n", "\n", "-- iteration 1 | threads 4\n", "\n", "real\t0m7.202s\n", "user\t0m28.176s\n", "sys\t0m4.462s\n", "\n", "-- iteration 2 | threads 4\n", "\n", "real\t0m6.749s\n", "user\t0m26.623s\n", "sys\t0m4.683s\n", "\n", "-- iteration 3 | threads 4\n", "\n", "real\t0m6.538s\n", "user\t0m25.863s\n", "sys\t0m4.455s\n", "\n" ] } ] }, { "cell_type": "code", "execution_count": 148, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "-- iteration 1 | threads 1\n", "\n", "real\t0m25.037s\n", "user\t0m25.866s\n", "sys\t0m4.253s\n", "\n", "-- iteration 2 | threads 1\n", "\n", "real\t0m25.036s\n", "user\t0m25.846s\n", "sys\t0m4.554s\n", "\n", "-- iteration 3 | threads 1\n", "\n", "real\t0m25.036s\n", "user\t0m25.782s\n", "sys\t0m4.581s\n", "\n", "-- iteration 1 | threads 2\n", "\n", "real\t0m13.032s\n", "user\t0m26.208s\n", "sys\t0m4.615s\n", "\n", "-- iteration 2 | threads 2\n", "\n", "real\t0m13.032s\n", "user\t0m26.199s\n", "sys\t0m4.722s\n", "\n", "-- iteration 3 | threads 2\n", "\n", "real\t0m13.033s\n", "user\t0m26.259s\n", "sys\t0m4.805s\n", "\n", "-- iteration 1 | threads 3\n", "\n", "real\t0m8.843s\n", "user\t0m26.037s\n", "sys\t0m4.552s\n", "\n", "-- iteration 2 | threads 3\n", "\n", "real\t0m8.631s\n", "user\t0m25.714s\n", "sys\t0m4.321s\n", "\n", "-- iteration 3 | threads 3\n", "\n", "real\t0m8.841s\n", "user\t0m26.020s\n", "sys\t0m4.430s\n", "\n", "-- iteration 1 | threads 4\n", "\n", "real\t0m6.640s\n", "user\t0m25.525s\n", "sys\t0m4.262s\n", "\n", "-- iteration 2 | threads 4\n", "\n", "real\t0m6.639s\n", "user\t0m25.691s\n", "sys\t0m4.259s\n", "\n", "-- iteration 3 | threads 4\n", "\n", "real\t0m6.430s\n", "user\t0m25.464s\n", "sys\t0m4.227s\n", "\n" ] } ], "source": [ "torrenttools_openssl_data = run_benchmark(\"torrenttools_openssl\")" ] }, { "cell_type": "code", "execution_count": 177, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "-- iteration 1 | threads 1\n", "\n", "real\t0m8.834s\n", "user\t0m8.615s\n", "sys\t0m4.127s\n", "\n", "-- iteration 2 | threads 1\n", "\n", "real\t0m9.036s\n", "user\t0m8.813s\n", "sys\t0m4.298s\n", "\n", "-- iteration 3 | threads 1\n", "\n", "real\t0m9.035s\n", "user\t0m8.732s\n", "sys\t0m4.267s\n", "\n", "-- iteration 1 | threads 2\n", "\n", "real\t0m4.839s\n", "user\t0m9.246s\n", "sys\t0m4.572s\n", "\n", "-- iteration 2 | threads 2\n", "\n", "real\t0m5.239s\n", "user\t0m9.935s\n", "sys\t0m4.668s\n", "\n", "-- iteration 3 | threads 2\n", "\n", "real\t0m5.036s\n", "user\t0m9.433s\n", "sys\t0m4.626s\n", "\n", "-- iteration 1 | threads 3\n", "\n", "real\t0m5.045s\n", "user\t0m11.723s\n", "sys\t0m4.717s\n", "\n", "-- iteration 2 | threads 3\n", "\n", "real\t0m5.048s\n", "user\t0m12.280s\n", "sys\t0m4.794s\n", "\n", "-- iteration 3 | threads 3\n", "\n", "real\t0m5.045s\n", "user\t0m11.848s\n", "sys\t0m4.781s\n", "\n", "-- iteration 1 | threads 4\n", "\n", "real\t0m5.036s\n", "user\t0m12.315s\n", "sys\t0m4.881s\n", "\n", "-- iteration 2 | threads 4\n", "\n", "real\t0m5.046s\n", "user\t0m12.140s\n", "sys\t0m4.872s\n", "\n", "-- iteration 3 | threads 4\n", "\n", "real\t0m5.046s\n", "user\t0m12.136s\n", "sys\t0m4.863s\n", "\n" ] } ], "source": [ "torrenttools_isal_data = run_benchmark(\"torrenttools_isal\")" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "-- iteration 1 | threads 1\n", "\n", "real\t0m49.640s\n", "user\t0m45.046s\n", "sys\t0m4.038s\n", "\n", "-- iteration 2 | threads 1\n", "\n", "real\t0m48.473s\n", "user\t0m44.369s\n", "sys\t0m3.654s\n", "\n", "-- iteration 3 | threads 1\n", "\n", "real\t0m54.762s\n", "user\t0m47.667s\n", "sys\t0m5.675s\n", "\n" ] } ], "source": [ "imdl_data = run_benchmark(\"imdl\", use_threads=False)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "-- iteration 1 | threads 1\n", "\n", "real\t0m45.398s\n", "user\t0m32.861s\n", "sys\t0m11.519s\n", "\n", "-- iteration 2 | threads 1\n", "\n", "real\t0m46.758s\n", "user\t0m33.550s\n", "sys\t0m12.022s\n", "\n", "-- iteration 3 | threads 1\n", "\n", "real\t0m46.282s\n", "user\t0m33.563s\n", "sys\t0m11.964s\n", "\n" ] } ], "source": [ "dottorrent_cli_data = run_benchmark(\"dottorrent-cli\", use_threads=False)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "-- iteration 1 | threads 1\n", "\n", "real\t0m29.641s\n", "user\t0m25.230s\n", "sys\t0m4.039s\n", "\n", "-- iteration 2 | threads 1\n", "\n", "real\t0m30.556s\n", "user\t0m25.640s\n", "sys\t0m4.534s\n", "\n", "-- iteration 3 | threads 1\n", "\n", "real\t0m32.703s\n", "user\t0m26.834s\n", "sys\t0m5.418s\n", "\n" ] } ], "source": [ "pyrocore_data = run_benchmark(\"pyrocore\", use_threads=False)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "-- iteration 1 | threads 1\n", "\n", "real\t0m33.512s\n", "user\t0m26.585s\n", "sys\t0m4.951s\n", "\n", "-- iteration 2 | threads 1\n", "rm: cannot remove '*.torrent': No such file or directory\n", "\n", "real\t0m30.008s\n", "user\t0m25.216s\n", "sys\t0m4.041s\n", "\n", "-- iteration 3 | threads 1\n", "rm: cannot remove '*.torrent': No such file or directory\n", "\n", "real\t0m29.511s\n", "user\t0m25.037s\n", "sys\t0m3.791s\n", "\n" ] } ], "source": [ "transmission_create_data = run_benchmark(\"transmission-create\", use_threads=False)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "-- iteration 1 | threads 1\n", "rm: cannot remove '*.torrent': No such file or directory\n", "\n", "real\t0m28.100s\n", "user\t0m24.199s\n", "sys\t0m3.405s\n", "\n", "-- iteration 2 | threads 1\n", "\n", "real\t0m27.531s\n", "user\t0m23.952s\n", "sys\t0m3.169s\n", "\n", "-- iteration 3 | threads 1\n", "\n", "real\t0m28.172s\n", "user\t0m23.977s\n", "sys\t0m3.270s\n", "\n" ] } ], "source": [ "py3createtorrent_data = run_benchmark(\"py3createtorrent\", use_threads=False)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "-- iteration 1 | threads 1\n", "\n", "real\t0m25.609s\n", "user\t0m23.964s\n", "sys\t0m9.948s\n", "\n", "-- iteration 2 | threads 1\n", "\n", "real\t0m24.994s\n", "user\t0m23.313s\n", "sys\t0m9.235s\n", "\n", "-- iteration 3 | threads 1\n", "\n", "real\t0m25.388s\n", "user\t0m23.761s\n", "sys\t0m9.512s\n", "\n", "-- iteration 1 | threads 2\n", "\n", "real\t0m13.950s\n", "user\t0m24.642s\n", "sys\t0m10.750s\n", "\n", "-- iteration 2 | threads 2\n", "\n", "real\t0m14.088s\n", "user\t0m24.869s\n", "sys\t0m11.001s\n", "\n", "-- iteration 3 | threads 2\n", "\n", "real\t0m14.410s\n", "user\t0m25.176s\n", "sys\t0m11.003s\n", "\n", "-- iteration 1 | threads 3\n", "\n", "real\t0m10.548s\n", "user\t0m25.179s\n", "sys\t0m10.208s\n", "\n", "-- iteration 2 | threads 3\n", "\n", "real\t0m10.801s\n", "user\t0m24.889s\n", "sys\t0m10.453s\n", "\n", "-- iteration 3 | threads 3\n", "\n", "real\t0m10.447s\n", "user\t0m24.213s\n", "sys\t0m10.145s\n", "\n", "-- iteration 1 | threads 4\n", "\n", "real\t0m10.540s\n", "user\t0m27.706s\n", "sys\t0m10.462s\n", "\n", "-- iteration 2 | threads 4\n", "\n", "real\t0m10.492s\n", "user\t0m28.131s\n", "sys\t0m10.342s\n", "\n", "-- iteration 3 | threads 4\n", "\n", "real\t0m10.702s\n", "user\t0m25.584s\n", "sys\t0m10.397s\n", "\n" ] } ], "source": [ "torf_data = run_benchmark(\"torf-cli\")" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "-- iteration 1 | threads 1\n", "\n", "real\t0m46.673s\n", "user\t0m41.712s\n", "sys\t0m4.031s\n", "\n", "-- iteration 2 | threads 1\n", "rm: cannot remove '*.torrent': No such file or directory\n", "\n", "real\t0m45.859s\n", "user\t0m41.532s\n", "sys\t0m3.884s\n", "\n", "-- iteration 3 | threads 1\n", "rm: cannot remove '*.torrent': No such file or directory\n", "\n", "real\t0m45.836s\n", "user\t0m41.261s\n", "sys\t0m3.952s\n", "\n" ] } ], "source": [ "buildtorrent_data = run_benchmark(\"buildtorrent\", use_threads=False)" ] }, { "source": [ "maketorrent_data = run_benchmark(\"maketorrent\")" ], "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [] }, { "source": [ "benchmark_data = {\n", " \"mktorrent\": mktorrent_data, \n", " \"imdl\": imdl_data,\n", " \"dottorrent-cli\": dottorrent_cli_data,\n", " \"pyrocore\": pyrocore_data,\n", " \"transmission-create\": transmission_create_data,\n", " \"py3createtorrent\" : py3createtorrent_data,\n", " \"torf-cli\": torf_data,\n", " \"buildtorrent\": buildtorrent_data,\n", " # \"maketorrent\" : maketorrent_data,\n", " \"torrenttools (OpenSSL)\": torrenttools_openssl_data,\n", " \"torrenttools (ISA-L)\": torrenttools_isal_data,\n", "}" ], "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [] }, { "source": [ "programs = list(benchmark_data.keys())\n", "threads = list(range(1, max_threads+1))\n", "index = pd.MultiIndex.from_product((threads, programs), names=[\"threads\", \"programs\"])\n", "\n", "speed_mean_data = {}\n", "speed_stddev_data = {}\n", "for program, data in benchmark_data.items():\n", " speed_mean_data[program] = data[0]\n", " speed_stddev_data[program] = data[1]\n", " \n", "\n", "speed_mean = pd.DataFrame(speed_mean_data).stack()\n", "speed_stddev = pd.DataFrame(speed_stddev_data).stack()" ], "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [] }, { "source": [ "df = pd.DataFrame()\n", "df[\"speed_mean\"], df[\"speed_stddev\"] = speed_mean / 1e6, speed_stddev / 1e6\n", "df.index = index" ], "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [] }, { "cell_type": "code", "execution_count": 181, "metadata": {}, "outputs": [], "source": [ "# df.to_csv(\"benchmark.csv\")" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [], "source": [ "df = pd.read_csv(\"benchmark.csv\")\n", "df.set_index([\"threads\",\"programs\"], inplace=True)" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ " speed_mean speed_stddev\n", "threads programs \n", "1 mktorrent 843.537581 29.240162\n", " imdl 422.595602 21.945212\n", " dottorrent-cli 465.436936 5.708141\n", " pyrocore 694.654299 28.286594\n", " transmission-create 694.712205 38.430640\n", " py3createtorrent 768.842932 7.946208\n", " torf-cli 847.877094 8.542898\n", " buildtorrent 465.635634 3.906536\n", " torrenttools (OpenSSL) 857.746867 0.016150\n", " torrenttools (ISA-L) 2394.788777 25.555998\n", "2 mktorrent 1739.745717 0.816567\n", " imdl 422.595602 21.945212\n", " dottorrent-cli 465.436936 5.708141\n", " pyrocore 694.654299 28.286594\n", " transmission-create 694.712205 38.430640\n", " py3createtorrent 768.842932 7.946208\n", " torf-cli 1518.007778 20.554861\n", " buildtorrent 465.635634 3.906536\n", " torrenttools (OpenSSL) 1647.812096 0.059603\n", " torrenttools (ISA-L) 4267.054895 138.342107\n", "3 mktorrent 2401.102928 231.781078\n", " imdl 422.595602 21.945212\n", " dottorrent-cli 465.436936 5.708141\n", " pyrocore 694.654299 28.286594\n", " transmission-create 694.712205 38.430640\n", " py3createtorrent 768.842932 7.946208\n", " torf-cli 2026.580190 28.285421\n", " buildtorrent 465.635634 3.906536\n", " torrenttools (OpenSSL) 2448.522212 27.990339\n", " torrenttools (ISA-L) 4255.814143 1.192516\n", "4 mktorrent 3149.445056 125.745769\n", " imdl 422.595602 21.945212\n", " dottorrent-cli 465.436936 5.708141\n", " pyrocore 694.654299 28.286594\n", " transmission-create 694.712205 38.430640\n", " py3createtorrent 768.842932 7.946208\n", " torf-cli 2030.287245 17.163154\n", " buildtorrent 465.635634 3.906536\n", " torrenttools (OpenSSL) 3269.533110 49.678067\n", " torrenttools (ISA-L) 4258.630736 3.983737" ], "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
speed_meanspeed_stddev
threadsprograms
1mktorrent843.53758129.240162
imdl422.59560221.945212
dottorrent-cli465.4369365.708141
pyrocore694.65429928.286594
transmission-create694.71220538.430640
py3createtorrent768.8429327.946208
torf-cli847.8770948.542898
buildtorrent465.6356343.906536
torrenttools (OpenSSL)857.7468670.016150
torrenttools (ISA-L)2394.78877725.555998
2mktorrent1739.7457170.816567
imdl422.59560221.945212
dottorrent-cli465.4369365.708141
pyrocore694.65429928.286594
transmission-create694.71220538.430640
py3createtorrent768.8429327.946208
torf-cli1518.00777820.554861
buildtorrent465.6356343.906536
torrenttools (OpenSSL)1647.8120960.059603
torrenttools (ISA-L)4267.054895138.342107
3mktorrent2401.102928231.781078
imdl422.59560221.945212
dottorrent-cli465.4369365.708141
pyrocore694.65429928.286594
transmission-create694.71220538.430640
py3createtorrent768.8429327.946208
torf-cli2026.58019028.285421
buildtorrent465.6356343.906536
torrenttools (OpenSSL)2448.52221227.990339
torrenttools (ISA-L)4255.8141431.192516
4mktorrent3149.445056125.745769
imdl422.59560221.945212
dottorrent-cli465.4369365.708141
pyrocore694.65429928.286594
transmission-create694.71220538.430640
py3createtorrent768.8429327.946208
torf-cli2030.28724517.163154
buildtorrent465.6356343.906536
torrenttools (OpenSSL)3269.53311049.678067
torrenttools (ISA-L)4258.6307363.983737
\n
" }, "metadata": {}, "execution_count": 35 } ], "source": [ "df" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "output_type": "display_data", "data": { "text/plain": "
", "image/svg+xml": "\n\n\n\n \n \n \n \n 2021-04-20T14:17:37.448712\n image/svg+xml\n \n \n Matplotlib v3.3.4, https://matplotlib.org/\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n", "image/png": "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t9+madOmNG/enN9//911fOfOnYSHh9O0aVOeeeYZVFUt6W4LIYQQogBms5latWpJsCqKhUajoVatWoWO2Jd4wDp37lxCQkJyHHv++edJSEggISGB/v37A5CUlMQ333zDvn37WLt2LU888QQOhwOAyZMnExsby+HDhzl8+DBr164t6W4LIYQQohASrIridKPvpxINWE+fPs2vv/7KxIkTb3jt6tWreeihh/Dw8CA4OJimTZuyY8cOUlNTycjIoEOHDmg0GsaOHcuqVatKsttCCCGEEKIcKdGA9bnnnuOdd95Bq835mPnz5xMREcGECRO4cuUKAGfOnKFBgwaua4KCgjhz5gxnzpwhKCgoz/H8xMbGEhUVRVRUFBcuXCiBdySEEEKIspaWlsbHH39cJs+eM2cORqPR9fqtt966rfZ8fX1v6vr777+fY8eOAc4iSdeqm7355puEhoYSERFBZGQkf/31l+seu91O7dq1+b//+78C2128eDFPPfVUnuM9e/Z0xWplqcQC1l9++YW6devStm3bHMcnT57M0aNHSUhIICAggBdffBEg33WpGo2mwOP5mTRpEvHx8cTHx1OnTp1ieBdCCCGEKG9uJWC9tsywoNdFVdwB683Yt28fDoeDxo0b5zi+bds2fvnlF3bt2sXevXtZv359jkHAdevW0bx5c7799tub3gc0ZsyYMvvlILsSyxKwZcsWfvrpJ9asWYPZbCYjI4PRo0fz1Vdfua559NFHXZuxgoKCOHXqlOvc6dOnCQwMJCgoiNOnT+c5LoQQQoiy98bP+0hKySjWNlsG+jNtYGiB56dMmcLRo0eJjIykV69evPPOO7zyyiv89ttvaDQaXnvtNR588EHi4uJ44403CAgIICEhgY8//jjH63/++YcpU6YQFxeHxWLhySef5LHHHiMuLo7p06dTu3ZtEhMTadu2LV999RUffvghKSkpdO/endq1a9O+fXtMJhORkZGEhoaybNky3n//fb744gsAJk6cyHPPPQdQ4PFrUlNTefDBB8nIyMBut/PJJ5/QuXPnHNcsW7aMwYMH5/l6pKamUrt2bTw8PACoXbt2jvPLly/n2Wef5ZNPPmH79u106NChyH8XgwYNonPnzrz66qtFvqcklFjA+vbbb/P2228DEBcXx7vvvstXX31FamoqAQEBAKxcuZKwsDDA+QUZOXIkL7zw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jx492Lt3L8888wzvv/8+GzdupHbt2ly8eJGZM2eyfv16fHx8mD17Nu+//z5Tp069ib/ZWycjrKLMWe0KRmveqYpzGWZm/ppE8/p+PNGtab73ajUa/D2lOIAQQlR10dHRrmAVnCOE10ZRT506xeHDhwFwd3d3jWy2bduW5ORkADp27Mj48eNZsGBBjunz7t274+fnR506dahWrRoDBw4EIDw83HXvNf7+/nh6ejJx4kR+/PFHvL29C237ms2bNzNmzBgA7rnnHi5dukR6ejoA9957Lx4eHtSuXZu6deu6RoSzW79+PU8++aTrdY0aNQDQ6XQMGzYMgD/++IOdO3fSrl07IiMj+eOPPzh2zPmLxrfffkubNm1o3bo1+/btIykpKc8ztm/fTlJSEh07diQyMpIlS5Zw4sSJfP8uSoIMS4kydW0pQG52RWHq6n3YHSozh4Th7pb3dyspDiCEEGXvRiOhpcXHx8f157i4ONavX8+2bdvw9vamW7dumM1mAPR6vWtzrk6nw2537p349NNP+euvv/j111+JjIx0jXB6eFxfiqbVal2vtVqt695r3Nzc2LFjB3/88QfffPMN8+fPZ8OGDQW2fY2aT5Wca33M/vxr/f3oo49YsGABAGvWrEFV1Xw3HHt6eqLT6VzPGDduHG+//XaOa44fP867777L33//TY0aNRg/frzra5W7j7169WL58uV5zpUGGWEVZSrDbEfJ5x/qos3JJJxK45W+zbmzpne+90pxACGEqLr8/PzIzMzM91x6ejo1atTA29ubAwcOsH379hu2d/ToUdq3b8+MGTOoXbs2p06duuk+GQwG0tPT6d+/P3PmzHEFpjdqu0uXLixbtgxwBtu1a9fG39+/wOc8+eSTJCQkkJCQQGBgIL1792b+/Pmu89eWBGTXo0cPvv/+e86fPw84ly+cOHGCjIwMfHx8qFatGufOneO3335z3ZP9axwTE8OWLVs4cuQIAEajkUOHDt301+hWyQirKDNmmwOzLe/UyK4TV/hiy3H6h9enX1hAvvdKcQAhhKjaatWqRceOHQkLC8PLy4t69eq5zvXt25dPP/2UiIgImjdvTkxMzA3be/nllzl8+DCqqtKjRw9atWqVZyT0RjIzMxk8eDBmsxlVVfnggw8KbPt///uf677p06fz8MMPExERgbe3N0uWLLmp57722ms8+eST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\n" }, "metadata": {} } ], "source": [ "import matplotlib.cm\n", "fig, ax = plt.subplots(1, 1, figsize=(8, 8))\n", "\n", "programs = df.index.unique(level=1)\n", "threads = df.index.unique(level=0)\n", "programs_by_max_speed = {}\n", "\n", "for program in programs:\n", " sm = df.loc[df.index.get_level_values(1) == program, \"speed_mean\"]\n", " programs_by_max_speed[program] = np.max(sm)\n", "\n", "sorted_programs = sorted(programs_by_max_speed.keys(), key=lambda x : programs_by_max_speed[x], reverse=True)\n", "\n", "for idx, program in enumerate(sorted_programs):\n", " sm = df.loc[df.index.get_level_values(1) == program, \"speed_mean\"]\n", " sd = df.loc[df.index.get_level_values(1) == program, \"speed_stddev\"]\n", " ax.plot(threads, sm)\n", " ax.fill_between(threads, sm - sd, sm + sd, alpha=0.1)\n", "\n", "ax.set_title(\"Performance\")\n", "ax.set_xlabel(\"# hashing threads\")\n", "ax.set_ylabel(\"Hash rate (MiB/s)\")\n", "ax.set_xticks(list(range(1, max_threads+1)))\n", "ax.legend(sorted_programs, bbox_to_anchor=(1.05, 1), loc='upper left')\n", "fig.patch.set_facecolor(\"white\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ] } ================================================ FILE: benchmark/benchmark.sh ================================================ #!/usr/bin/env bash program_name=$1 working_dir=$2 target=$3 threads=$4 function benchmark_mktorrent() { target="$1" threads="$2" rm *.torrent out="$(time mktorrent -t$threads -l20 "$target" 1> /dev/null 2> /dev/null)"; echo "$out" return; } function benchmark_torrenttools_openssl() { target="$1" threads="$2" rm *.torrent # gdbserver :1234 torrenttools-release create -t$threads -l20 "$target" out="$(time torrenttools-release create -t$threads -l20 "$target" 1> /dev/null 2> /dev/null)"; echo "$out" return; } function benchmark_torrenttools_isal() { target="$1" threads="$2" rm *.torrent # torrenttools-isal-release create -t$threads -l20 "$target" 2>&1 out="$(time torrenttools-isal-release create -t$threads -l20 "$target" 1> /dev/null 2> /dev/null)"; echo "$out" return; } function benchmark_imdl() { target="$1" threads="$2" rm *.torrent out="$(time imdl torrent create --piece-length 1MiB "$target" 1> /dev/null 2> /dev/null)"; echo "$out" return; } function benchmark_dottorrent_cli() { target="$1" threads="$2" rm *.torrent out="$(time dottorrent --piece_size 1M "$target" "output.torrent" 1> /dev/null 2> /dev/null)"; echo "$out" return; } function benchmark_pyrocore() { target="$1" threads="$2" rm *.torrent out="$(time mktor --chunk-min=1M --chunk-max=1M "$target" https://testurl.com/announce 1> /dev/null 2> /dev/null)"; echo "$out" return; } function benchmark_transmission() { target="$1" threads="$2" rm *.torrent out="$(time transmission-create --piece-size 1024 "$target" 1> /dev/null 2> /dev/null)"; echo "$out" return; } function benchmark_py3createtorrent() { target="$1" threads="$2" rm *.torrent out="$(time py3createtorrent -p 1024 "$target" 1> /dev/null 2> /dev/null)" echo "$out" return; } function benchmark_torf_cli() { target="$1" threads="$2" rm *.torrent out="$(time torf --max-piece-size 1024 --threads "$threads" "$target" 1> /dev/null 2> /dev/null)" echo "$out" return; } function benchmark_buildtorrent() { target="$1" threads="$2" rm *.torrent out="$(time buildtorrent --piecesize 20 --announce dummy "$target" output 1> /dev/null 2> /dev/null)" echo "$out" return; } function benchmark_maketorrent() { target="$1" threads="$2" rm *.torrent out="$(time maketorrent --piece-length 20 --announce dummy "$target" 1> /dev/null 2> /dev/null)" echo "$out" return; } cd "$working_dir" if [[ $program_name == "mktorrent" ]]; then benchmark_mktorrent "$target" "$threads" elif [[ $program_name == "torrenttools_openssl" ]]; then benchmark_torrenttools_openssl "$target" "$threads" elif [[ $program_name == "torrenttools_isal" ]]; then benchmark_torrenttools_isal "$target" "$threads" elif [[ $program_name == "imdl" ]]; then benchmark_imdl "$target" "$threads" elif [[ $program_name == "pyrocore" ]]; then benchmark_pyrocore "$target" "$threads" elif [[ $program_name == "dottorrent-cli" ]]; then benchmark_dottorrent_cli "$target" "$threads" elif [[ $program_name == "transmission-create" ]]; then benchmark_transmission "$target" "$threads" elif [[ $program_name == "py3createtorrent" ]]; then benchmark_py3createtorrent "$target" "$threads" elif [[ $program_name == "maketorrent" ]]; then benchmark_maketorrent "$target" "$threads" elif [[ $program_name == "torf-cli" ]]; then benchmark_torf_cli "$target" "$threads" elif [[ $program_name == "buildtorrent" ]]; then benchmark_buildtorrent "$target" "$threads" elif [[ $program_name == "maketorrent" ]]; then benchmark_buildtorrent "$target" "$threads" else echo "Error: Invalid program name: $program_name" fi ================================================ FILE: cmake/FindISALCrypto.cmake ================================================ #.rst: # FindISALCrypto # ----------- # # Find the isal-l_crypto library. # # IMPORTED Targets # ^^^^^^^^^^^^^^^^ # # This module defines :prop_tgt:`IMPORTED` targets: # # ``ISAL::Crypto`` # The isa-l_crypto library, if found. # # Result variables # ^^^^^^^^^^^^^^^^ # # This module defines the following variables: # # :: # # ISALCrypto_FOUND - true if the headers and library were found # ISALCrypto_INCLUDE_DIRS - where to find headers # ISALCrypto_LIBRARIES - list of libraries to link # ISALCrypto_VERSION - library version that was found, if any # use pkg-config to get the directories and then use these values # in the find_path() and find_library() calls find_package(PkgConfig QUIET) pkg_check_modules(PC_ISALCrypto QUIET isa-l_crypto) # find the headers find_path(ISALCrypto_INCLUDE_DIR NAMES isa-l_crypto.h HINTS ${PC_ISALCrypto_INCLUDEDIR} ${PC_ISALCrypto_INCLUDE_DIRS} PATH_SUFFIXES isa-l_crypto ) # find the library find_library(ISALCrypto NAMES isal_crypto libisal_crypto HINTS ${PC_ISALCrypto_LIBDIR} ${PC_ISALCrypto_DIRS} ) # determine the version if(PC_ISALCrypto_VERSION) set(ISALCrypto_VERSION ${PC_ISALCrypto_VERSION}) elseif(ISALCrypto_INCLUDE_DIR AND EXISTS "${ISALCrypto_INCLUDE_DIR}/isa-l_crypto.h") file(STRINGS "${ISALCrypto_INCLUDE_DIR}/isa-l_crypto.h" isal_crypto_version_str REGEX "^#define[\t ]+(ISALCrypto_VERSION_[A-Z]+)[\t ]+[0-9]+") string(REGEX REPLACE ".*#define[\t ]+ISAL_CRYPTO_VERSION_MAJOR[\t ]+([0-9]+).*" "\\1" _isal_crypto_version_major "${isal_crypto_version_str}") string(REGEX REPLACE ".*#define[\t ]+ISAL_CRYPTO_VERSION_MINOR[\t ]+([0-9]+).*" "\\1" _isal_crypto_version_minor "${isal_crypto_version_str}") string(REGEX REPLACE ".*#define[\t ]+ISAL_CRYPTO_VERSION_PATCH[\t ]+([0-9]+).*" "\\1" _isal_crypto_version_patch "${isal_crypto_version_str}") set(ISALCrypto_VERSION "${_isal_crypto_version_major}.${_isal_crypto_version_minor}.${_isal_crypto_version_patch}" CACHE INTERNAL "The version of ISAL_CRYPTO which was detected") endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(ISALCrypto REQUIRED_VARS ISALCrypto ISALCrypto_INCLUDE_DIR VERSION_VAR ISALCrypto_VERSION ) if (ISALCrypto_FOUND) set(ISALCrypto_INCLUDE_DIRS ${ISALCrypto_INCLUDE_DIR} ${PC_ISALCrypto_INCLUDE_DIRS}) set(ISALCrypto_LIBRARIES ${ISALCrypto}) endif() if (ISALCrypto_FOUND AND NOT TARGET ISAL::Crypto) # create the new library target add_library(ISAL::Crypto UNKNOWN IMPORTED) # set the required include dirs for the target if (ISALCrypto_INCLUDE_DIRS) set_target_properties(ISAL::Crypto PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ISALCrypto_INCLUDE_DIRS}" ) endif() # set the required libraries for the target if (EXISTS "${ISALCrypto}") set_target_properties(ISAL::Crypto PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" IMPORTED_LOCATION "${ISALCrypto}" ) endif() endif() mark_as_advanced(ISALCrypto_INCLUDE_DIR ISALCrypto_LIBRARIES ISALCrypto) ================================================ FILE: cmake/FindNASM.cmake ================================================ include(FindPackageHandleStandardArgs) find_program(NASM_EXECUTABLE NAMES nasm yasm DOC "Path to NASM/YASM executable") if (NOT NASM_EXECUTABLE OR NASM_EXECUTABLE STREQUAL NASM_EXECUTABLE-NOTFOUND) if(WIN32) # Search for NASM in a few places: # - %LOCALAPPDATA% (if set) # - %ProgramFiles(x86)%/NASM (if set) # - %ProgramFiles%/NASM if(DEFINED ENV{LOCALAPPDATA}) # Use LOCALAPPDATA. # TODO: Determine LOCALAPPDATA on XP? # NASM only uses it on Win7 (and probably Vista), though... set(LocalAppData "$ENV{LOCALAPPDATA}") endif() # NOTE: Due to CMP0053, we can't directly reference # the "ProgramFiles(x86)" variable. if(DEFINED ENV{ProgramW6432}) # 64-bit Windows. # Use %ProgramFiles(x86)% for 32-bit, # and %ProgramW6432% for 64-bit. set(MYENV "ProgramFiles(x86)") set(ProgramFilesX86 "$ENV{${MYENV}}") set(ProgramFiles "$ENV{ProgramW6432}") unset(MYENV) else() # 32-bit Windows. Use %ProgramFiles% directly. set(ProgramFiles "$ENV{ProgramFiles}") endif() # Replace backslashes with slashes. STRING(REPLACE "\\" "/" LocalAppData "${LocalAppData}") STRING(REPLACE "\\" "/" ProgramFiles "${ProgramFiles}") STRING(REPLACE "\\" "/" ProgramFilesX86 "${ProgramFilesX86}") # Find NASM. find_program(NASM_EXECUTABLE NAMES nasm yasm DOC "Path to NASM/YASM executable" PATHS "${LocalAppData}/NASM" "${ProgramFiles}/NASM" "${ProgramFilesX86}/NASM" ) endif() endif() find_package_handle_standard_args(NASM REQUIRED_VARS NASM_EXECUTABLE FAIL_MESSAGE "NASM/YASM is required" ) ================================================ FILE: cmake/FindSphinx.cmake ================================================ include(FindPackageHandleStandardArgs) #Look for an executable called sphinx-build find_program(SPHINX_EXECUTABLE NAMES sphinx-build DOC "Path to sphinx-build executable") if (NOT SPHINX_EXECUTABLE OR SPHINX_EXECUTABLE STREQUAL SPHINX_EXECUTABLE-NOTFOUND) include(FindPython3) set(FPHSA_NAME_MISMATCHED TRUE) find_package(Python3 QUIET COMPONENTS Interpreter) execute_process( COMMAND python -c "import site; print(site.getusersitepackages())" OUTPUT_VARIABLE Python3_USERLIB OUTPUT_STRIP_TRAILING_WHITESPACE ) find_file( SPHINX_LIB_MAIN_PATH "__main__.py" PATHS ${Python3_SITELIB}/sphinx ${Python3_ARCHLIB}/sphinx ${Python3_USERLIB}/sphinx ) if (SPHINX_LIB_MAIN_PATH) set(SPHINX_EXECUTABLE "${Python3_EXECUTABLE};-m;sphinx") endif() endif() ##Handle standard arguments to find_package like REQUIRED and QUIET find_package_handle_standard_args(Sphinx REQUIRED_VARS SPHINX_EXECUTABLE FAIL_MESSAGE "Failed to find sphinx-build executable or sphinx library" ) ================================================ FILE: cmake/FindTBB.cmake ================================================ # - Find ThreadingBuildingBlocks include dirs and libraries # Use this module by invoking find_package with the form: # find_package(TBB # [REQUIRED] # Fail with error if TBB is not found # ) # # Once done, this will define # # TBB_FOUND - system has TBB # TBB_INCLUDE_DIRS - the TBB include directories # TBB_LIBRARIES - TBB libraries to be lined, doesn't include malloc or # malloc proxy # TBB::tbb - imported target for the TBB library # # TBB_VERSION_MAJOR - Major Product Version Number # TBB_VERSION_MINOR - Minor Product Version Number # TBB_INTERFACE_VERSION - Engineering Focused Version Number # TBB_COMPATIBLE_INTERFACE_VERSION - The oldest major interface version # still supported. This uses the engineering # focused interface version numbers. # # TBB_MALLOC_FOUND - system has TBB malloc library # TBB_MALLOC_INCLUDE_DIRS - the TBB malloc include directories # TBB_MALLOC_LIBRARIES - The TBB malloc libraries to be lined # TBB::malloc - imported target for the TBB malloc library # # TBB_MALLOC_PROXY_FOUND - system has TBB malloc proxy library # TBB_MALLOC_PROXY_INCLUDE_DIRS = the TBB malloc proxy include directories # TBB_MALLOC_PROXY_LIBRARIES - The TBB malloc proxy libraries to be lined # TBB::malloc_proxy - imported target for the TBB malloc proxy library # # # This module reads hints about search locations from variables: # ENV TBB_ARCH_PLATFORM - for eg. set it to "mic" for Xeon Phi builds # ENV TBB_ROOT or just TBB_ROOT - root directory of tbb installation # ENV TBB_BUILD_PREFIX - specifies the build prefix for user built tbb # libraries. Should be specified with ENV TBB_ROOT # and optionally... # ENV TBB_BUILD_DIR - if build directory is different than ${TBB_ROOT}/build # # # Modified by Robert Maynard from the original OGRE source # #------------------------------------------------------------------- # This file is part of the CMake build system for OGRE # (Object-oriented Graphics Rendering Engine) # For the latest info, see http://www.ogre3d.org/ # # The contents of this file are placed in the public domain. Feel # free to make use of it in any way you like. #------------------------------------------------------------------- # #============================================================================= # Copyright 2010-2012 Kitware, Inc. # Copyright 2012 Rolf Eike Beer # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= # (To distribute this file outside of CMake, substitute the full # License text for the above reference.) #============================================================================= # FindTBB helper functions and macros # # Use TBBConfig.cmake if possible. set(_tbb_find_quiet) if (TBB_FIND_QUIETLY) set(_tbb_find_quiet QUIET) endif () set(_tbb_find_components) set(_tbb_find_optional_components) foreach (_tbb_find_component IN LISTS TBB_FIND_COMPONENTS) if (TBB_FIND_REQUIRED_${_tbb_find_component}) list(APPEND _tbb_find_components "${_tbb_find_component}") else () list(APPEND _tbb_find_optional_components "${_tbb_find_component}") endif () endforeach () unset(_tbb_find_component) find_package(TBB CONFIG ${_tbb_find_quiet} COMPONENTS ${_tbb_find_components} OPTIONAL_COMPONENTS ${_tbb_find_optional_components}) unset(_tbb_find_quiet) unset(_tbb_find_components) unset(_tbb_find_optional_components) if (TBB_FOUND) return () endif () #==================================================== # Fix the library path in case it is a linker script #==================================================== function(tbb_extract_real_library library real_library) if(NOT UNIX OR NOT EXISTS ${library}) set(${real_library} "${library}" PARENT_SCOPE) return() endif() #Read in the first 4 bytes and see if they are the ELF magic number set(_elf_magic "7f454c46") file(READ ${library} _hex_data OFFSET 0 LIMIT 4 HEX) if(_hex_data STREQUAL _elf_magic) #we have opened a elf binary so this is what #we should link to set(${real_library} "${library}" PARENT_SCOPE) return() endif() file(READ ${library} _data OFFSET 0 LIMIT 1024) if("${_data}" MATCHES "INPUT \\(([^(]+)\\)") #extract out the .so name from REGEX MATCH command set(_proper_so_name "${CMAKE_MATCH_1}") #construct path to the real .so which is presumed to be in the same directory #as the input file get_filename_component(_so_dir "${library}" DIRECTORY) set(${real_library} "${_so_dir}/${_proper_so_name}" PARENT_SCOPE) else() #unable to determine what this library is so just hope everything works #and pass it unmodified. set(${real_library} "${library}" PARENT_SCOPE) endif() endfunction() #=============================================== # Do the final processing for the package find. #=============================================== macro(findpkg_finish PREFIX TARGET_NAME) if (${PREFIX}_INCLUDE_DIR AND ${PREFIX}_LIBRARY) set(${PREFIX}_FOUND TRUE) set (${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIR}) set (${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARY}) else () if (${PREFIX}_FIND_REQUIRED AND NOT ${PREFIX}_FIND_QUIETLY) message(FATAL_ERROR "Required library ${PREFIX} not found.") endif () endif () if (NOT TARGET "TBB::${TARGET_NAME}") if (${PREFIX}_LIBRARY_RELEASE) tbb_extract_real_library(${${PREFIX}_LIBRARY_RELEASE} real_release) endif () if (${PREFIX}_LIBRARY_DEBUG) tbb_extract_real_library(${${PREFIX}_LIBRARY_DEBUG} real_debug) endif () add_library(TBB::${TARGET_NAME} UNKNOWN IMPORTED) set_target_properties(TBB::${TARGET_NAME} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${${PREFIX}_INCLUDE_DIR}") if (${PREFIX}_LIBRARY_DEBUG AND ${PREFIX}_LIBRARY_RELEASE) set_target_properties(TBB::${TARGET_NAME} PROPERTIES IMPORTED_LOCATION "${real_release}" IMPORTED_LOCATION_DEBUG "${real_debug}" IMPORTED_LOCATION_RELEASE "${real_release}") elseif (${PREFIX}_LIBRARY_RELEASE) set_target_properties(TBB::${TARGET_NAME} PROPERTIES IMPORTED_LOCATION "${real_release}") elseif (${PREFIX}_LIBRARY_DEBUG) set_target_properties(TBB::${TARGET_NAME} PROPERTIES IMPORTED_LOCATION "${real_debug}") endif () endif () #mark the following variables as internal variables mark_as_advanced(${PREFIX}_INCLUDE_DIR ${PREFIX}_LIBRARY ${PREFIX}_LIBRARY_DEBUG ${PREFIX}_LIBRARY_RELEASE) endmacro() #=============================================== # Generate debug names from given release names #=============================================== macro(get_debug_names PREFIX) foreach(i ${${PREFIX}}) set(${PREFIX}_DEBUG ${${PREFIX}_DEBUG} ${i}d ${i}D ${i}_d ${i}_D ${i}_debug ${i}) endforeach() endmacro() #=============================================== # See if we have env vars to help us find tbb #=============================================== macro(getenv_path VAR) set(ENV_${VAR} $ENV{${VAR}}) # replace won't work if var is blank if (ENV_${VAR}) string( REGEX REPLACE "\\\\" "/" ENV_${VAR} ${ENV_${VAR}} ) endif () endmacro() #=============================================== # Couple a set of release AND debug libraries #=============================================== macro(make_library_set PREFIX) if (${PREFIX}_RELEASE AND ${PREFIX}_DEBUG) set(${PREFIX} optimized ${${PREFIX}_RELEASE} debug ${${PREFIX}_DEBUG}) elseif (${PREFIX}_RELEASE) set(${PREFIX} ${${PREFIX}_RELEASE}) elseif (${PREFIX}_DEBUG) set(${PREFIX} ${${PREFIX}_DEBUG}) endif () endmacro() #============================================================================= # Now to actually find TBB # # Get path, convert backslashes as ${ENV_${var}} getenv_path(TBB_ROOT) # initialize search paths set(TBB_PREFIX_PATH ${TBB_ROOT} ${ENV_TBB_ROOT} "/usr") set(TBB_INC_SEARCH_PATH "") set(TBB_LIB_SEARCH_PATH "") # If user built from sources set(TBB_BUILD_PREFIX $ENV{TBB_BUILD_PREFIX}) if (TBB_BUILD_PREFIX AND ENV_TBB_ROOT) getenv_path(TBB_BUILD_DIR) if (NOT ENV_TBB_BUILD_DIR) set(ENV_TBB_BUILD_DIR ${ENV_TBB_ROOT}/build) endif () # include directory under ${ENV_TBB_ROOT}/include list(APPEND TBB_LIB_SEARCH_PATH ${ENV_TBB_BUILD_DIR}/${TBB_BUILD_PREFIX}_release ${ENV_TBB_BUILD_DIR}/${TBB_BUILD_PREFIX}_debug) endif () # For Windows, let's assume that the user might be using the precompiled # TBB packages from the main website. These use a rather awkward directory # structure (at least for automatically finding the right files) depending # on platform and compiler, but we'll do our best to accommodate it. # Not adding the same effort for the precompiled linux builds, though. Those # have different versions for CC compiler versions and linux kernels which # will never adequately match the user's setup, so there is no feasible way # to detect the "best" version to use. The user will have to manually # select the right files. (Chances are the distributions are shipping their # custom version of tbb, anyway, so the problem is probably nonexistent.) if (WIN32 AND MSVC) set(COMPILER_PREFIX "vc7.1") if (MSVC_VERSION EQUAL 1400) set(COMPILER_PREFIX "vc8") elseif(MSVC_VERSION EQUAL 1500) set(COMPILER_PREFIX "vc9") elseif(MSVC_VERSION EQUAL 1600) set(COMPILER_PREFIX "vc10") elseif(MSVC_VERSION EQUAL 1700) set(COMPILER_PREFIX "vc11") elseif(MSVC_VERSION EQUAL 1800) set(COMPILER_PREFIX "vc12") elseif(MSVC_VERSION GREATER_EQUAL 1900) set(COMPILER_PREFIX "vc14") endif () # for each prefix path, add ia32/64\${COMPILER_PREFIX}\lib to the lib search path foreach (dir IN LISTS TBB_PREFIX_PATH) if (CMAKE_CL_64) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/ia64/${COMPILER_PREFIX}/lib) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/lib/ia64/${COMPILER_PREFIX}) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/intel64/${COMPILER_PREFIX}/lib) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/lib/intel64/${COMPILER_PREFIX}) else () list(APPEND TBB_LIB_SEARCH_PATH ${dir}/ia32/${COMPILER_PREFIX}/lib) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/lib/ia32/${COMPILER_PREFIX}) endif () endforeach () endif () # For OS X binary distribution, choose libc++ based libraries for Mavericks (10.9) # and above and AppleClang if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT CMAKE_SYSTEM_VERSION VERSION_LESS 13.0) set (USE_LIBCXX OFF) cmake_policy(GET CMP0025 POLICY_VAR) if (POLICY_VAR STREQUAL "NEW") if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") set (USE_LIBCXX ON) endif () else () if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set (USE_LIBCXX ON) endif () endif () if (USE_LIBCXX) foreach (dir IN LISTS TBB_PREFIX_PATH) list (APPEND TBB_LIB_SEARCH_PATH ${dir}/lib/libc++ ${dir}/libc++/lib) endforeach () endif () endif () # check compiler ABI if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(COMPILER_PREFIX) if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.8) list(APPEND COMPILER_PREFIX "gcc4.8") endif() if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.7) list(APPEND COMPILER_PREFIX "gcc4.7") endif() if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.4) list(APPEND COMPILER_PREFIX "gcc4.4") endif() list(APPEND COMPILER_PREFIX "gcc4.1") elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(COMPILER_PREFIX) if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.0) # Complete guess list(APPEND COMPILER_PREFIX "gcc4.8") endif() if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.6) list(APPEND COMPILER_PREFIX "gcc4.7") endif() list(APPEND COMPILER_PREFIX "gcc4.4") else() # Assume compatibility with 4.4 for other compilers list(APPEND COMPILER_PREFIX "gcc4.4") endif () # if platform architecture is explicitly specified set(TBB_ARCH_PLATFORM $ENV{TBB_ARCH_PLATFORM}) if (TBB_ARCH_PLATFORM) foreach (dir IN LISTS TBB_PREFIX_PATH) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/${TBB_ARCH_PLATFORM}/lib) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/lib/${TBB_ARCH_PLATFORM}) endforeach () endif () foreach (dir IN LISTS TBB_PREFIX_PATH) foreach (prefix IN LISTS COMPILER_PREFIX) if (CMAKE_SIZEOF_VOID_P EQUAL 8) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/lib/intel64) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/lib/intel64/${prefix}) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/intel64/lib) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/intel64/${prefix}/lib) else () list(APPEND TBB_LIB_SEARCH_PATH ${dir}/lib/ia32) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/lib/ia32/${prefix}) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/ia32/lib) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/ia32/${prefix}/lib) endif () endforeach() endforeach () # add general search paths foreach (dir IN LISTS TBB_PREFIX_PATH) list(APPEND TBB_LIB_SEARCH_PATH ${dir}/lib ${dir}/Lib ${dir}/lib/tbb ${dir}/Libs) list(APPEND TBB_INC_SEARCH_PATH ${dir}/include ${dir}/Include ${dir}/include/tbb) endforeach () set(TBB_LIBRARY_NAMES tbb) get_debug_names(TBB_LIBRARY_NAMES) find_path(TBB_INCLUDE_DIR NAMES tbb/tbb.h PATHS ${TBB_INC_SEARCH_PATH}) find_library(TBB_LIBRARY_RELEASE NAMES ${TBB_LIBRARY_NAMES} PATHS ${TBB_LIB_SEARCH_PATH}) find_library(TBB_LIBRARY_DEBUG NAMES ${TBB_LIBRARY_NAMES_DEBUG} PATHS ${TBB_LIB_SEARCH_PATH}) make_library_set(TBB_LIBRARY) findpkg_finish(TBB tbb) #if we haven't found TBB no point on going any further if (NOT TBB_FOUND) return() endif () #============================================================================= # Look for TBB's malloc package set(TBB_MALLOC_LIBRARY_NAMES tbbmalloc) get_debug_names(TBB_MALLOC_LIBRARY_NAMES) find_path(TBB_MALLOC_INCLUDE_DIR NAMES tbb/tbb.h PATHS ${TBB_INC_SEARCH_PATH}) find_library(TBB_MALLOC_LIBRARY_RELEASE NAMES ${TBB_MALLOC_LIBRARY_NAMES} PATHS ${TBB_LIB_SEARCH_PATH}) find_library(TBB_MALLOC_LIBRARY_DEBUG NAMES ${TBB_MALLOC_LIBRARY_NAMES_DEBUG} PATHS ${TBB_LIB_SEARCH_PATH}) make_library_set(TBB_MALLOC_LIBRARY) findpkg_finish(TBB_MALLOC tbbmalloc) #============================================================================= # Look for TBB's malloc proxy package set(TBB_MALLOC_PROXY_LIBRARY_NAMES tbbmalloc_proxy) get_debug_names(TBB_MALLOC_PROXY_LIBRARY_NAMES) find_path(TBB_MALLOC_PROXY_INCLUDE_DIR NAMES tbb/tbbmalloc_proxy.h PATHS ${TBB_INC_SEARCH_PATH}) find_library(TBB_MALLOC_PROXY_LIBRARY_RELEASE NAMES ${TBB_MALLOC_PROXY_LIBRARY_NAMES} PATHS ${TBB_LIB_SEARCH_PATH}) find_library(TBB_MALLOC_PROXY_LIBRARY_DEBUG NAMES ${TBB_MALLOC_PROXY_LIBRARY_NAMES_DEBUG} PATHS ${TBB_LIB_SEARCH_PATH}) make_library_set(TBB_MALLOC_PROXY_LIBRARY) findpkg_finish(TBB_MALLOC_PROXY tbbmalloc_proxy) #============================================================================= #parse all the version numbers from tbb if(NOT TBB_VERSION) if (EXISTS "${TBB_INCLUDE_DIR}/oneapi/tbb/version.h") file(STRINGS "${TBB_INCLUDE_DIR}/oneapi/tbb/version.h" TBB_VERSION_CONTENTS REGEX "VERSION") else() #only read the start of the file file(STRINGS "${TBB_INCLUDE_DIR}/tbb/tbb_stddef.h" TBB_VERSION_CONTENTS REGEX "VERSION") endif() string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1" TBB_VERSION_MAJOR "${TBB_VERSION_CONTENTS}") string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1" TBB_VERSION_MINOR "${TBB_VERSION_CONTENTS}") string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1" TBB_INTERFACE_VERSION "${TBB_VERSION_CONTENTS}") string(REGEX REPLACE ".*#define TBB_COMPATIBLE_INTERFACE_VERSION ([0-9]+).*" "\\1" TBB_COMPATIBLE_INTERFACE_VERSION "${TBB_VERSION_CONTENTS}") endif() ================================================ FILE: cmake/SanitizersConfig.cmake ================================================ # Build Types set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel tsan asan lsan msan ubsan" FORCE) # ThreadSanitizer set(CMAKE_C_FLAGS_TSAN "-fsanitize=thread -g -O1" CACHE STRING "Flags used by the C compiler during ThreadSanitizer builds." FORCE) set(CMAKE_CXX_FLAGS_TSAN "-fsanitize=thread -g -O1" CACHE STRING "Flags used by the C++ compiler during ThreadSanitizer builds." FORCE) # AddressSanitize set(CMAKE_C_FLAGS_ASAN "-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1" CACHE STRING "Flags used by the C compiler during AddressSanitizer builds." FORCE) set(CMAKE_CXX_FLAGS_ASAN "-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1" CACHE STRING "Flags used by the C++ compiler during AddressSanitizer builds." FORCE) # LeakSanitizer set(CMAKE_C_FLAGS_LSAN "-fsanitize=leak -fno-omit-frame-pointer -g -O1" CACHE STRING "Flags used by the C compiler during LeakSanitizer builds." FORCE) set(CMAKE_CXX_FLAGS_LSAN "-fsanitize=leak -fno-omit-frame-pointer -g -O1" CACHE STRING "Flags used by the C++ compiler during LeakSanitizer builds." FORCE) # MemorySanitizer set(CMAKE_C_FLAGS_MSAN "-fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2" CACHE STRING "Flags used by the C compiler during MemorySanitizer builds." FORCE) set(CMAKE_CXX_FLAGS_MSAN "-fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2" CACHE STRING "Flags used by the C++ compiler during MemorySanitizer builds." FORCE) # UndefinedBehaviour set(CMAKE_C_FLAGS_UBSAN "-fsanitize=undefined" CACHE STRING "Flags used by the C compiler during UndefinedBehaviourSanitizer builds." FORCE) set(CMAKE_CXX_FLAGS_UBSAN "-fsanitize=undefined" CACHE STRING "Flags used by the C++ compiler during UndefinedBehaviourSanitizer builds." FORCE) ================================================ FILE: docs/CMakeLists.txt ================================================ include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/FindSphinx.cmake) find_package(Sphinx REQUIRED) #This will be the main output of our command set(SPHINX_CONF_IN ${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in) set(SPHINX_CONF_OUT ${CMAKE_CURRENT_BINARY_DIR}/conf.py) set(SPHINX_CONFIG_DIR ${CMAKE_CURRENT_BINARY_DIR}) #Replace variables inside @@ with the current values configure_file(${SPHINX_CONF_IN} ${SPHINX_CONF_OUT} @ONLY) set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/sphinx) set(SPHINX_INDEX_FILE ${SPHINX_BUILD}/index.html) # Only regenerate Sphinx when: # - Our doc files have been updated # - The Sphinx config has been updated file(GLOB_RECURSE SPHINX_RST_FILES ${CMAKE_CURRENT_LIST_DIR}/**.rst) file(GLOB_RECURSE SPHINX_CSV_FILES ${CMAKE_CURRENT_LIST_DIR}/**.csv) list(APPEND SPHINX_DOC_FILES ${SPHINX_RST_FILES} ${SPHINX_CSV_FILES}) add_custom_command( OUTPUT ${SPHINX_INDEX_FILE} COMMAND ${SPHINX_EXECUTABLE} -b html -c ${SPHINX_CONFIG_DIR} ${SPHINX_SOURCE} ${SPHINX_BUILD} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS # Other docs files you want to track should go here (or in some variable) ${SPHINX_DOC_FILES} MAIN_DEPENDENCY ${SPHINX_CONF_OUT} COMMENT "Generating documentation with Sphinx" COMMAND_EXPAND_LISTS) # Nice named target so we can run the job easily add_custom_target(Sphinx ALL DEPENDS ${SPHINX_INDEX_FILE}) # Add an install target to install the docs if (TORRENTTOOLS_INSTALL) install(DIRECTORY ${SPHINX_BUILD} DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT documentation) endif() ================================================ FILE: docs/bep-support.csv ================================================ BEP, Status, Title `03 `_,✅,The BitTorrent Protocol Specification `05 `_,✅,DHT Protocol `09 `_,✅,Extension for Peers to Send Metadata Files `12 `_,✅,Multitracker Metadata Extension `17 `_,✅,HTTP Seeding `19 `_,✅,WebSeed - HTTP/FTP Seeding (GetRight style) `27 `_,✅,Private Torrents `30 `_,❌,Merkle hash torrent extension `35 `_,❌,Torrent Signing `38 `_,✅,Finding Local Data Via Torrent File Hints `39 `_,❌,Updating Torrents Via Feed URL `46 `_,❌,Updating Torrents Via DHT Mutable Items `47 `_,✅,Padding files and extended file attributes `49 `_,❌,Distributed Torrent Feeds `52 `_,✅,The BitTorrent Protocol Specification v2 `53 `_,❌,Magnet URI extension - Select specific file indices for download ================================================ FILE: docs/commands/create.rst ================================================ .. _create_command: Create ====== The create command is used to create new BitTorrent metafiles. The basic invocation requires only a target directory or file to create a .torrent from. .. code-block:: bash torrenttools create [OPTIONS] Either specify all options first or give the target first followed by all options. Overview -------- .. code-block:: none Create BitTorrent metafiles. Usage: torrenttools create [OPTIONS] target Positionals: target Target filename or directory Options: -h,--help Print this help message and exit -v,--protocol Set the bittorrent protocol to use. Options are 1, 2 or hybrid. [default: 1] -o,--output Set the filename and/or output directory of the created file. [default: .torrent] Use a path with trailing slash to only set the output directory. -a,--announce ... Add one or multiple announces urls. Multiple trackers will be added in seperate tiers by default. Use square brackets to groups urls in a single tier: eg. "--announce url1 [url1 url2]" -g,--announce-group ... Add the announce-urls defined from an announce group specified in the configuration file. Multiple groups can be passed. eg. "--announce-group group1 group2" -w,--web-seed ... Add one or multiple HTTP/FTP urls as seeds. -d,--dht-node ... Add one or multiple DHT nodes. -c,--comment Add a comment. -p,--private <[on|off]> Set the private flag to disable DHT and PEX. -l,--piece-size Set the piece size. When no unit is specified block size will be either 2^ bytes, or bytes if n is larger or equal to 16384. Piece size must be a power of two in range [16K, 64M]. Leave empty or set to auto to determine by total file size. [default: auto] -s,--source Add a source tag to facilitate cross-seeding. -n,--name Set the name of the torrent. This changes the filename for single file torrents or the root directory name for multi-file torrents. [default: ] -t,--threads Set the number of threads to use for hashing pieces. [default: 2] --checksum ... Include a per file checksum of given algorithm. --no-creation-date Do not include the creation date. --creation-date Override the value of the creation date field as ISO-8601 time or POSIX time. eg.: "2021-01-22T18:21:46+0100" --no-created-by Do not include the name and version of this program. --created-by Override the value of the created by field. --include ... Only add files matching given regex to the metafile. --exclude ... Do not add files matching given regex to the metafile. --include-hidden Do not skip hidden files. --io-block-size The size of blocks read from storage. Must be larger or equal to the piece size. Options ------- ``-v,--protocol`` +++++++++++++++++ Set the bittorrent protocol to generate a metafile for. Available options are: 1, 2 or hybrid. The default options is to create v1-only metafiles. 1 and 2 can be prefixed with a v. .. code-block:: bash torrenttools create --protocol v1 test-dir torrenttools create -v1 test-dir torrenttools create -vhybrid test-dir ``-o,--output`` +++++++++++++++ Set the filename and/or output directory of the created file. If no options are given the torrent will be written to .torrent in the current directory, with name the name of target directory or file. .. code-block:: bash torrenttools create test-dir --output "my-name.torrent" Use a path with trailing slash to only set the output directory and keep the name to the default .. code-block:: bash torrenttools create test-dir --output ~/torrents/ Will create a torrent with full path ~/torrents/test-dir.torrent ``-a,--announce`` ++++++++++++++++++ Add one or multiple announces urls. .. code-block:: bash torrenttools create test-dir --announce "url1" "url2" Multiple trackers will be added in seperate tiers by default. Use square brackets to groups urls in a single tier. .. code-block:: bash torrenttools create test-dir --announce "[url1 url2]" `` -g,--announce-group`` +++++++++++++++++++++++++ Add all announces inside a tracker group to the metafile. .. code-block:: bash torrenttools create test-dir --announce-group "public-trackers" ``--stdout`` ++++++++++++ Write the torrent to the standard output. Normal output will be redirected to stderr. ``-w,--web-seed`` +++++++++++++++++ Add one or multiple HTTP/FTP urls as seeds. ``-d,--dht-node`` +++++++++++++++++ Add one or multiple DHT nodes. The expected format is :. .. code-block:: torrenttools create test-dir ---dht-node "127.0.0.1:8686" "192.168.0.0:9999: ``-c,--comment`` +++++++++++++++++ Add a comment. .. code-block: torrenttools create test-dir --comment "Hello there!" ``-p,--private`` ++++++++++++++++ Set the private flag to disable DHT and PEX. When no options are given this will enable the private flag. Pass "on" or "off" to override the defaults when using supported trackers . ``-l,--piece-size`` +++++++++++++++++++ Set the piece size. When no unit is specified block size will be either 2^ bytes or bytes if n is larger or equal to 16384. Piece size must be a power of two in range 16K to 64M. Leave empty or pass "auto" to determine by total file size. Piece size as a power of two. (2**20 = 1MiB) .. code-block:: torrenttools create test-dir --piece-size 20 Piece size as a size in bytes. (65536 = 64 KiB) .. code-block:: torrenttools create test-dir --piece-size 65536 Piece size with unit .. code-block:: torrenttools create test-dir --piece-size 2M torrenttools create test-dir --piece-size 2MiB torrenttools create test-dir --piece-size "2 MiB" ``-s,--source`` +++++++++++++++ Add a source tag to facilitate cross-seeding. ``-n,--name`` +++++++++++++ Set the name of the torrent. The default option to use the basename of the target. .. warning:: This options changes the filename inside the torrent for single file torrents or the root directory name for multi-file torrents. Use with caution. ``-t,--threads`` ++++++++++++++++ Set the number of threads to use for hashing pieces. Default is 2. .. note:: The hashing bottleneck is usually the maximum sequential read speed of you storage device so only increasing this as long as you notice a difference. Increasing this usually makes sense only for very fast SSD or Optane storage. ``--checksum`` +++++++++++++++ Include a per file checksum for given algorithm. The possible options depend on the cryptographic library used. All possible options can be listed with: .. code-block:: torrenttools --checksum-algorithms .. note:: This is only useful for v1 metafiles. v2 and hybrid metafiles have per-file merkle roots which makes this options redundant. ``--no-creation-date`` ++++++++++++++++++++++ Do not include the current date in the creation date field. ``--creation-date`` +++++++++++++++++++ Override the value of the creation date field as an ISO-8601 time or POSIX time string. .. code-block:: torrentools create test-dir --creation-date "2021-01-22T18:21:46+0100" torrentools create test-dir --creation-date 1611339706 ``--no-created-by`` +++++++++++++++++++ Do not include the name and version of this program. ``--created-by`` ++++++++++++++++ Override the value of the created by field. .. code-block:: torrenttools test-dir --created-by "Me" ``--include-hidden`` ++++++++++++++++++++ Do not skip hidden files when scanning the target directory for files. .. code-block:: torrenttools create test-dir --include-hidden ``--include`` +++++++++++++ .. code-block:: torrenttools create test-dir --include " .. note:: When the include pattern matches hidden files these will be included in the torrent even if --include-hidden was not specified. ``--exclude`` +++++++++++++ Do not add files matching given regex to the metafile. Multiple patterns can be specified. When used together with --include, the include patterns will be evaluated first and further filtered by the exclude patterns. ``--io-block-size`` +++++++++++++++++++ The size of blocks read from storage. Set to a large value for disks used heavy load to reduce the number of IO operations per second. This value must be larger or equal to the piece-size. ================================================ FILE: docs/commands/edit.rst ================================================ .. _edit_command: Edit ===== The edit commands is used to modify metadata fields in an existing bittorrent file. Many of the options are the same as those for the `create `_ command Overview --------- .. code-block:: none Edit bittorrent metafiles. Usage: torrenttools edit [OPTIONS] target Positionals: target Target bittorrent metafile. Options: -h,--help Print this help message and exit -o,--output Set the filename and/or output directory of the edited file. Default [.torrent]. This will overwrite the existing file if the name is the same. Use a path with trailing slash to only set the output directory. -m,--list-mode How to modify options that accept multiple arguments. Options are: append, prepend, replace. [Default: replace] The first character of these options is valid as well. -a,--announce ... Add one or multiple announces urls. Multiple trackers will be added in seperate tiers by default. Use square brackets to groups urls in a single tier: eg. "url1 [url1 url2]" -g,--announce-group ... Add the announce-urls defined from an announce group specified in the configuration file. Multiple groups can be passed. eg. "--announce-group group1 group2" -w,--web-seed ... Add one or multiple HTTP/FTP urls as seeds. -d,--dht-node ... Add one or multiple DHT nodes. -c,--comment Replace the comment. Set to an empty string to remove the field. -p,--private <[on|off]> Set the private flag to disable DHT and PEX. Pass off to disable the flag. -s,--source Replace the source tag. Set to an empty string to remove the field. -n,--name Replace the name of the torrent. This changes the filename for single file torrents or the root directory name for multi-file torrents. --no-creation-date Do not include the creation date. --creation-date Replace the creation date field. Input is expected as an ISO-8601 or POSIX timestamp. Example: "2021-01-22T18:21:46+0100" Set to an empty string to remove the field. --no-created-by Do not include the name and version of this program. --created-by Replace the created-by field. Set to an empty string to remove the field. --stdout Write the edited metafile to the standard output Options -------- ``-o,--output`` ++++++++++++++++++++++ Set the filename and/or output directory of the edited file. Use a trailing slash to specify the output directory. ``-m,--list-mode`` +++++++++++++++++++ Control how the options ``--announce``, ``--web-seed`` and ``dht-node`` behave. The possible options are: replace, append, prepend or the equivalent r, a, p. The default mode for multi-argument options is replace. The follow command will append the new tracker at the end of the announce list. .. code-block:: bash torrenttools edit --list-mode append test.torrent --announce "https://new-announce-server.org/announce" ``a,--announce`` ++++++++++++++++ Edit the announces of a bittorrent metafile. .. code-block:: bash torrenttools edit test-dir --announce "url1" "url2" Multiple trackers will be added in seperate tiers by default. Use square brackets to groups urls in a single tier. .. code-block:: bash torrenttools create test-dir --announce "[url1 url2]" `` -g,--announce-group`` +++++++++++++++++++++++++ Add all announces inside a tracker group to the metafile. Thes eopt .. code-block:: bash torrenttools edit test-dir --announce-group "public-trackers" ``-w,--web-seed`` +++++++++++++++++ Edit the web-seeds of a bittorrent metafile. ``-d,--dht-node `` +++++++++++++++++++++++++++++ ``-c,--comment`` ++++++++++++++++ Replace or remove a comment from a bittorrent metafile. Pass an empty string to remove an existing comment: .. code-block:: torrenttools edit target.torrent --comment "" ``-p,--private`` ++++++++++++++++++ Edit the private flag of a bittorrent metafile. Passing the flag without any arguments or with "on" or 1 will enable the flag. Passing the flag with "off" or 0 will disable the flag. .. code-block:: torrenttools edit public.torrent --private --output private.torrent ``-s,--source`` +++++++++++++++ Edit the source tag in a bittorrent metafile. This field is used by private trackers to avoid the risk of torrents with the same infohash but different announce-urls. It is set to the name of the tracker for supported trackers. .. code-block:: torrenttools edit test.torrent --source "my tracker" ``-n,--name`` ++++++++++++++ Replace the name of the torrent. This changes the filename for single file torrents or the root directory name for multi-file torrents. ``--no-creation-date`` +++++++++++++++++++++++ Do not update the creation date when editing other fields. ``--creation-date`` +++++++++++++++++++ Override the value of the creation date field as an ISO-8601 time or POSIX time string. .. code-block:: torrentools edit test-dir --creation-date "2021-01-22T18:21:46+0100" torrentools edit test-dir --creation-date 1611339706 ``--no-created-by`` +++++++++++++++++++ Do not include the name and version of this program. ``--created-by`` ++++++++++++++++ Override the value of the created by field. .. code-block:: torrenttools test-dir --created-by "Me" ================================================ FILE: docs/commands/info.rst ================================================ .. _info_command: Info ====== The info command is used to print an overview of BitTorrent metafiles features. The basic invocation requires only the target .torrent file. .. code-block:: bash torrenttools info Either specify all options first or give the target first followed by all options. Overview --------- .. code-block:: none General information about bittorrent metafiles. Usage: torrenttools info [OPTIONS] target Positionals: target Target bittorrent metafile. Options: -h,--help Print this help message and exit --raw Print the metafile data formatted as JSON. Binary data is filtered out. --show-pieces Print the metafile data formatted as JSON. Binary data is included as hexadecimal strings. --show-padding-files Show padding files in the file tree. Output looks as follows: .. code-block:: Metafile: bittorrent-v2-hybrid-test.torrent Protocol version: v1 + v2 (hybrid) Infohash: v1: 8c9a2f583949c757c32e085413b581067eed47d0 v2: d8dd32ac93357c368556af3ac1d95c9d76bd0dff6fa9833ecdac3d53134efabb Piece size: 512 KiB (524288 bytes) Created by: libtorrent Created on: 2020-06-03 08:45:06 UTC Private: false Name: bittorrent-v1-v2-hybrid-test Source: Comment: Announces: Files: bittorrent-v1-v2-hybrid-test ├── [6.23 MiB] Darkroom (Stellar, 1994, Amiga ECS) HQ.mp4 ├── [19.6 MiB] Spaceballs-StateOfTheArt.avi ├── [ 326 MiB] cncd_fairlight-ceasefire_(all_falls_down)-1080p.mp4 ├── [58.8 MiB] eld-dust.mkv ├── [ 265 MiB] fairlight_cncd-agenda_circling_forth-1080p30lq.mp4 ├── [42.5 MiB] meet the deadline - Still _ Evoke 2014.mp4 ├── [61.0 B] readme.txt ├── [25.1 MiB] tbl-goa.avi └── [ 111 MiB] tbl-tint.mpg 854.06 MiB in 0 directories, 9 files Options ------- ``--raw`` ++++++++++ This options will print a JSON representation of the torrent with binary fields replaced by a string descibing the content. The torrent from the previous example will output as follows .. code-block:: { "created by": "libtorrent", "creation date": 1591173906, "info": { "file tree": { "Darkroom (Stellar, 1994, Amiga ECS) HQ.mp4": { "": { "length": 6535405, "pieces root": "" } }, "Spaceballs-StateOfTheArt.avi": { "": { "attr": "x", "length": 20506624, "pieces root": "" } }, "cncd_fairlight-ceasefire_(all_falls_down)-1080p.mp4": { "": { "length": 342230630, "pieces root": "" } }, "eld-dust.mkv": { "": { "length": 61638604, "pieces root": "" } }, "fairlight_cncd-agenda_circling_forth-1080p30lq.mp4": { "": { "length": 277889766, "pieces root": "" } }, "meet the deadline - Still _ Evoke 2014.mp4": { "": { "length": 44577773, "pieces root": "" } }, "readme.txt": { "": { "attr": "x", "length": 61, "pieces root": "" } }, "tbl-goa.avi": { "": { "attr": "x", "length": 26296320, "pieces root": "" } }, "tbl-tint.mpg": { "": { "length": 115869700, "pieces root": "" } } }, "files": [ { "length": 6535405, "path": [ "Darkroom (Stellar, 1994, Amiga ECS) HQ.mp4" ] }, { "attr": "p", "length": 280339, "path": [ ".pad", "280339" ] }, { "attr": "x", "length": 20506624, "path": [ "Spaceballs-StateOfTheArt.avi" ] }, { "attr": "p", "length": 464896, "path": [ ".pad", "464896" ] }, { "length": 342230630, "path": [ "cncd_fairlight-ceasefire_(all_falls_down)-1080p.mp4" ] }, { "attr": "p", "length": 129434, "path": [ ".pad", "129434" ] }, { "length": 61638604, "path": [ "eld-dust.mkv" ] }, { "attr": "p", "length": 227380, "path": [ ".pad", "227380" ] }, { "length": 277889766, "path": [ "fairlight_cncd-agenda_circling_forth-1080p30lq.mp4" ] }, { "attr": "p", "length": 507162, "path": [ ".pad", "507162" ] }, { "length": 44577773, "path": [ "meet the deadline - Still _ Evoke 2014.mp4" ] }, { "attr": "p", "length": 510995, "path": [ ".pad", "510995" ] }, { "attr": "x", "length": 61, "path": [ "readme.txt" ] }, { "attr": "p", "length": 524227, "path": [ ".pad", "524227" ] }, { "attr": "x", "length": 26296320, "path": [ "tbl-goa.avi" ] }, { "attr": "p", "length": 442368, "path": [ ".pad", "442368" ] }, { "length": 115869700, "path": [ "tbl-tint.mpg" ] } ], "meta version": 2, "name": "bittorrent-v1-v2-hybrid-test", "piece length": 524288, "pieces": "<1715 piece hashes>" }, "piece layers": { "": "<531 piece hashes>", "": "<653 piece hashes>", "": "<222 piece hashes>", "": "<13 piece hashes>", "": "<118 piece hashes>", "": "<51 piece hashes>", "": "<86 piece hashes>", "": "<40 piece hashes>" } } ``--show-pieces`` +++++++++++++++++ This options must be combined with ``--raw``. Instead of a string like <20 piece hashes> a full list with all pieces in hexadecimal representation will be printed. .. code-block:: { "pieces": [ "", "", "", "", ... ... "", "" ] }, "piece layers": { "": [ "", "", "", ... ], ... } ``--show-padding-files`` +++++++++++++++++++++++++ This option will include padding files for hybrid torrent in the file tree. By default padding files are not listed. ================================================ FILE: docs/commands/magnet.rst ================================================ .. _magnet_command: Magnet ====== .. code-block:: none Usage: torrenttools magnet [OPTIONS] metafile Positionals: metafile Target bittorrent metafile. Options: -h,--help Print this help message and exit -v,--protocol Include only the infohash of the specified protocol for hybrid metafiles. Options are: 1, 2, hybrid [Default: hybrid]. This option is only used for hybrid metafiles. When hybrid is specified, hybrid magnet URI's will include both the v1 and v2 infohash. ================================================ FILE: docs/commands/pad.rst ================================================ .. _pad_command: Pad ====== The pad command is used to generate padding files for BitTorrent metafiles that contain those. BitTorrent clients that do not support padding files will download these padding files the same way as any other file. Since these files do not contain any useful content they are often removed after downloading. When a user later decides to start seeding a torrent again the padding files that were removed are now missing and the torrent will not check 100% in the bittorrent client. The pad command can regenerates these missing files. .. code-block:: none Generate padding files for a BitTorrent metafile. Usage: torrenttools pad [OPTIONS] target metafile Positionals: target Target directory. metafile Target bittorrent metafile. Options: -h,--help Print this help message and exit ================================================ FILE: docs/commands/show.rst ================================================ .. _show_command: Show ===== Overview --------- .. code-block:: none Show specific fields of bittorrent metafiles. Usage: show [OPTIONS] [SUBCOMMAND] Options: -h,--help Print this help message and exit Subcommands: announce Show the announces. protocol Show the protocol. infohash Show the the infohash. piece-size Show the piece size. created-by Show the created-by field. creation-date Show the creation-date field. private Show the private flag. name Show the metafile name. comment Show the comment field. source Show the source field. query Show data referenced by a bencode pointer. .. code-block: Usage: torrenttools show announce [OPTIONS] target Positionals: target Target bittorrent metafile. Options: -h,--help Print this help message and exit --flat Flatten announce tiers. This results in the output containing one announce per line. ================================================ FILE: docs/commands/verify.rst ================================================ .. _verify_command: Verify ======= Overview --------- .. code-block:: none Verify local data against bittorrent metafiles. Usage: torrenttools verify [OPTIONS] metafile target Positionals: metafile Metafile path. target Target filename or directory to verify pieces for. Options: -h,--help Print this help message and exit -v,--protocol Set the bittorrent protocol to use. Options are 1, 2 or hybrid. [default: 1] -t,--threads Set the number of threads to use for hashing. [default: 2] ================================================ FILE: docs/comparison.rst ================================================ Other metafile utilities ========================= Multiple other BitTorrent metafile utilities exist. This feature table provides some information on their capabilities. .. csv-table:: :align: center :header-rows: 1 , mktorrent , pyrocore , torf-cli , py3createtorrent , imdl , torrenttools multithreaded hashing , ✓ , , ✓ , , ✓ , ✓ v2 support , , , , , , ✓ edit torrents , , ✓ , ✓ , , , ✓ show torrent information , , ✓ , ✓ , , ✓ , ✓ verify torrents , , , ✓ , , ✓ , ✓ generate magnet links , , , ✓ , , ✓ , ✓ generate padding files , , , , , , ✓ trackers aliases , , , , ✓ , , ✓ trackers groups , , , , ✓ , , ✓ ================================================ FILE: docs/conf.py.in ================================================ # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. source_encoding = 'utf-8' # General information about extensions = ["furo"] master_doc = "index" html_theme = "furo" html_logo = "@CMAKE_SOURCE_DIR@/resources/icons/scalable/torrenttools.svg" html_title = "torrenttools - @CMAKE_PROJECT_VERSION@" project = "@PROJECT_NAME@" author = "@PROJECT_AUTHOR@" version = "@CMAKE_PROJECT_VERSION@" release = "@CMAKE_PROJECT_VERSION@" copyright = "© @PROJECT_AUTHOR@" ================================================ FILE: docs/configuration.rst ================================================ Configuration +++++++++++++ Configuration files =================== There are two places for configuration: one that is system-wide were the default configuration is stored, and one that is specific for the current user. On linux : * system location: /etc * user location: $XDG_CONFIG_HOME/torrenttools (defaults to: $HOME/.config/torrenttools) On macOS: * system location: /Library/torrenttools/etc/ * user location: $HOME/Library/Application Support/torrenttools/ On windows: * system location: inside the install prefix (defaults to: C:\Program Files\torrenttools) * user location: %APPDATA%\torrenttools (defaults to: C:\Users\{username}\AppData\Roaming\torrenttools)== If there are files found in the user location these will be used first. Only if there is no user configuration, the files in the system location will be used. The files found in the system location will then be copied to the user location. This behavior ensures that global configuration default are updates with new packages/installers and that user-local configuration persists between installations. Named trackers =============== "Named trackers" are trackers which are declared in the trackers.json configuration file. Announce url substitution -------------------------- When a tracker is defined in the trackers.json file you can specify the full name or abbreviation to the ``-a|--announce`` option in the create or edit command. The tracker name will be replaced by the announce url in the BitTorrent metafile. Some private trackers have unique identifiers per user in the tracker announce url. These are supported as well by defining announce-url parameters between curly brackets in the announce-url. The announce-url parameter will be replaced by the value defined in the config.yml configuration file. .. code-block:: json :caption: Declaration of a private tracker with parameter *pid* in trackers.json. { "name": "PrivateTracker", "abbreviation": "PT", "announce_url": "http://private-tracker.org:8600/{pid}/announce", "private": true } .. code-block:: yaml :caption: Definition of the *pid* parameter in config.yml tracker-parameters: PrivateTracker: pid: 01qwqwdlc922_sample_pid_d93fqd6fji9 Private flag selection ---------------------- When passing the full name or abbreviation to the ``-a|--announce`` parameter, the private flag will be set according to the value specified in the tracker database. The user can always override this value by explicitly passing a different value to the ``-p|-private`` option. Source field ------------- When passing the full name or abbreviation to the ``-a|--announce`` parameter, the source field will be set to the full name of the tracker if the tracker is marked as a private tracker. The source tag can be overridden on the commandline or removed by passing the ``-s|--source|-s`` option with an empty string. BitTorrent metafile default filename ------------------------------------- Named trackers will prefix the abbreviation between square brackets to the default filename of the generated BitTorrent metafile. For a tracker named PrivateTracker and abbreviation PT the default filename for a target directory "content" would be: ``[PT]example.torrent``. Tracker groups =============== A tracker group is an alias that refers to a group of trackers. Instead of having to pass each tracker on the commandline, the name of the alias can be used to add all trackers in the group to a BitTorrent metafile. .. code-block:: yaml :caption: Definition of "public-trackers" tracker group containing three trackers. tracker-groups: public-trackers: - http://tracker.opentrackr.org:1337/announce - udp://tracker.openbittorrent.com:6969/announce - udp://exodus.desync.com:6969/announce Named trackers can be used inside the definition of the tracker group. A list with 20 public trackers named "public-trackers" is included in the default configuration file. Profiles ======== Profiles are a way to store a set of commandline options in the configuration file and apply them using a single commandline argument. Profiles are defined under the profiles key. Following options can be used for create profiles: .. hlist:: :columns: 3 * announce * announce-group * checksum * collection * comment * created-by * creation-date * dht-node * exclude * http-seed * include * include-hidden * io-block-size * name * output * piece-size * private * protocol * set-created-by * set-creation-date * similar * source * threads * web-sees Following options can be used for edit profiles: .. hlist:: :columns: 3 * announce * announce-group * collection * comment * created-by * creation-date * dht-node * http-seed * list-mode * name * output * private * set-created-by * set-creation-date * similar * source * web-seed For the behaviour of each flag we refer to the documentation on the commandline arguments for the respective subcommand. .. code-block:: yaml :caption: Schema for a profile. profiles: : command: [create|edit] options: