diff --git a/docs/changelog/index.html b/docs/changelog/index.html
index 1e06115a..3b37bd09 100644
--- a/docs/changelog/index.html
+++ b/docs/changelog/index.html
@@ -292,8 +292,8 @@
-
- Version 0.7.7
+
+ Version 0.7.8
@@ -608,8 +608,8 @@
-
- Version 0.7.7
+
+ Version 0.7.8
@@ -878,7 +878,7 @@
TorrentFile
-Version 0.7.7
+Version 0.7.8
more updates to logging
major improvements to progress bar
diff --git a/docs/objects.inv b/docs/objects.inv
index b336b510..764d87d8 100644
Binary files a/docs/objects.inv and b/docs/objects.inv differ
diff --git a/docs/search/search_index.json b/docs/search/search_index.json
index 0eac4a8f..dbafaefc 100644
--- a/docs/search/search_index.json
+++ b/docs/search/search_index.json
@@ -1 +1 @@
-{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"TorrentFile \ud83c\udf10 Overview A simple and convenient tool for creating, reviewing, editing, and/or validating bittorrent meta files (aka torrent files). torrentfile supports all versions of Bittorrent files, including hybrid meta files, and has full unicode support. A GUI frontend for this project can be found at https://github.com/alexpdev/TorrentfileQt \ud83d\udd0c Requirements Python 3.7+ Tested on Linux, Windows and Mac \ud83d\udcbb Install via PyPi: pip install torrentfile via Git: git clone https://github.com/alexpdev/torrentfile.git cd torrentfile pip install . Download pre-compiled binaries from the release page . \ud83d\udcda Documentation Documentation can be found here or in the docs directory. torrentfile is under active development, and is subject to significant changes in the codebase between releases. \ud83d\ude80 Usage Usage ===== torrentfile [options] [command-options] ... Create, edit, show details, and check any version Bittorrent file from command line Commands -------- create (c, new) Create a torrent meta file. edit (e) Edit existing torrent meta file. magnet (m) Create magnet url from an existing Bittorrent meta file. recheck (r, check) Calculate amount of torrent meta files content is found on disk. info (i) Show detailed information about a torrent file. Usage examples can be found in the project documentation on the examples page. \ud83d\udcdd License Distributed under Apache v2 software license. See LICENSE for more information. \ud83d\udca1 Issues & Requests If you encounter any bugs or would like to request a new feature please open a new issue. https://github.com/alexpdev/torrentfile/issues Creating Torrents Basic torrent file createion > torrentfile create /path/to/content The -t --tracker -a --announce flags add one or more urls to list of trackers. > torrentfile create /path/to/content --tracker http://tracker1.com > torrentfile create -t http://tracker2 http://tracker3 --private /path/to/content > torrentfile create --tracker http://tracker /path/to/content > torrentfile create -t http://tracker1 http://tracker2 /path/to/content the --private flag indicates use by a private tracker the --source flag can be used to help with cross-seeding > torrentfile create --private --source EXAMPLE --tracker https://url1 https://url2 The 3 options for controlling the progress bar using --prog or --progress : 0 : show no progress bar at all 1 : show 1 progress bar measuring the entire creation process 2 : show a progress bar for each file of the torrent content > torrentfile -t http://tracker.com --progress 2 /path/to/content > torrentfile --prog 0 /path/to/content to specify the save location use the -o or --out flags if the path points to directory the name of torrent is autofilled. > torrentfile create -o /specific/path/name.torrent ./content to create files using bittorrent v2 use --meta-version 2 likewise --meta-version 3 creates a hybrid torrent file. > torrentfile create --meta-version 2 /path/to/content > torrentfile create --meta-version 3 /path/to/content Check/Recheck Torrent recheck torrent file /path/to/some.torrent with /path/to/content > torrentfile recheck /path/to/some.torrent /path/to/content Edit Torrent edit a torrent file > torrentfile edit [options] Create Magnet To create a magnet URI for a pre-existing torrent meta file, use the sub-command magnet or m with the path to the torrent file. > torrentfile magnet /path/to/some.torrent Interactive Mode (expiremental) Alternatively to supplying a bunch of command line arguments, interactive mode allows users to specify program options one at a time from a series of prompts. to activate interactive mode use -i or --interactive flag > torrentfile -i GUI If you prefer a windowed gui please check out the official GUI frontend here","title":"Home"},{"location":"#torrentfile","text":"","title":"TorrentFile"},{"location":"#overview","text":"A simple and convenient tool for creating, reviewing, editing, and/or validating bittorrent meta files (aka torrent files). torrentfile supports all versions of Bittorrent files, including hybrid meta files, and has full unicode support. A GUI frontend for this project can be found at https://github.com/alexpdev/TorrentfileQt","title":"\ud83c\udf10 Overview"},{"location":"#requirements","text":"Python 3.7+ Tested on Linux, Windows and Mac","title":"\ud83d\udd0c Requirements"},{"location":"#install","text":"via PyPi: pip install torrentfile via Git: git clone https://github.com/alexpdev/torrentfile.git cd torrentfile pip install . Download pre-compiled binaries from the release page .","title":"\ud83d\udcbb Install"},{"location":"#documentation","text":"Documentation can be found here or in the docs directory. torrentfile is under active development, and is subject to significant changes in the codebase between releases.","title":"\ud83d\udcda Documentation"},{"location":"#usage","text":"Usage ===== torrentfile [options] [command-options] ... Create, edit, show details, and check any version Bittorrent file from command line Commands -------- create (c, new) Create a torrent meta file. edit (e) Edit existing torrent meta file. magnet (m) Create magnet url from an existing Bittorrent meta file. recheck (r, check) Calculate amount of torrent meta files content is found on disk. info (i) Show detailed information about a torrent file. Usage examples can be found in the project documentation on the examples page.","title":"\ud83d\ude80 Usage"},{"location":"#license","text":"Distributed under Apache v2 software license. See LICENSE for more information.","title":"\ud83d\udcdd License"},{"location":"#issues-requests","text":"If you encounter any bugs or would like to request a new feature please open a new issue. https://github.com/alexpdev/torrentfile/issues","title":"\ud83d\udca1 Issues & Requests"},{"location":"#creating-torrents","text":"Basic torrent file createion > torrentfile create /path/to/content The -t --tracker -a --announce flags add one or more urls to list of trackers. > torrentfile create /path/to/content --tracker http://tracker1.com > torrentfile create -t http://tracker2 http://tracker3 --private /path/to/content > torrentfile create --tracker http://tracker /path/to/content > torrentfile create -t http://tracker1 http://tracker2 /path/to/content the --private flag indicates use by a private tracker the --source flag can be used to help with cross-seeding > torrentfile create --private --source EXAMPLE --tracker https://url1 https://url2 The 3 options for controlling the progress bar using --prog or --progress : 0 : show no progress bar at all 1 : show 1 progress bar measuring the entire creation process 2 : show a progress bar for each file of the torrent content > torrentfile -t http://tracker.com --progress 2 /path/to/content > torrentfile --prog 0 /path/to/content to specify the save location use the -o or --out flags if the path points to directory the name of torrent is autofilled. > torrentfile create -o /specific/path/name.torrent ./content to create files using bittorrent v2 use --meta-version 2 likewise --meta-version 3 creates a hybrid torrent file. > torrentfile create --meta-version 2 /path/to/content > torrentfile create --meta-version 3 /path/to/content","title":"Creating Torrents"},{"location":"#checkrecheck-torrent","text":"recheck torrent file /path/to/some.torrent with /path/to/content > torrentfile recheck /path/to/some.torrent /path/to/content","title":"Check/Recheck Torrent"},{"location":"#edit-torrent","text":"edit a torrent file > torrentfile edit [options] ","title":"Edit Torrent"},{"location":"#create-magnet","text":"To create a magnet URI for a pre-existing torrent meta file, use the sub-command magnet or m with the path to the torrent file. > torrentfile magnet /path/to/some.torrent","title":"Create Magnet"},{"location":"#interactive-mode-expiremental","text":"Alternatively to supplying a bunch of command line arguments, interactive mode allows users to specify program options one at a time from a series of prompts. to activate interactive mode use -i or --interactive flag > torrentfile -i","title":"Interactive Mode (expiremental)"},{"location":"#gui","text":"If you prefer a windowed gui please check out the official GUI frontend here","title":"GUI"},{"location":"Apache2/","text":"Apache License Version 2.0, January 2004 < http://www.apache.org/licenses/ > Terms and Conditions for use, reproduction, and distribution 1. Definitions \u201cLicense\u201d shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. \u201cLicensor\u201d shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. \u201cLegal Entity\u201d shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, \u201ccontrol\u201d means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. \u201cYou\u201d (or \u201cYour\u201d) shall mean an individual or Legal Entity exercising permissions granted by this License. \u201cSource\u201d form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. \u201cObject\u201d form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. \u201cWork\u201d shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). \u201cDerivative Works\u201d shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. \u201cContribution\u201d shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, \u201csubmitted\u201d means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as \u201cNot a Contribution.\u201d \u201cContributor\u201d shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a \u201cNOTICE\u201d text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an \u201cAS IS\u201d BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets [] replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same \u201cprinted page\u201d as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.","title":"License"},{"location":"Apache2/#apache-license","text":"Version 2.0, January 2004 < http://www.apache.org/licenses/ >","title":"Apache License"},{"location":"Apache2/#terms-and-conditions-for-use-reproduction-and-distribution","text":"","title":"Terms and Conditions for use, reproduction, and distribution"},{"location":"Apache2/#1-definitions","text":"\u201cLicense\u201d shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. \u201cLicensor\u201d shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. \u201cLegal Entity\u201d shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, \u201ccontrol\u201d means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. \u201cYou\u201d (or \u201cYour\u201d) shall mean an individual or Legal Entity exercising permissions granted by this License. \u201cSource\u201d form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. \u201cObject\u201d form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. \u201cWork\u201d shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). \u201cDerivative Works\u201d shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. \u201cContribution\u201d shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, \u201csubmitted\u201d means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as \u201cNot a Contribution.\u201d \u201cContributor\u201d shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.","title":"1. Definitions"},{"location":"Apache2/#2-grant-of-copyright-license","text":"Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.","title":"2. Grant of Copyright License"},{"location":"Apache2/#3-grant-of-patent-license","text":"Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.","title":"3. Grant of Patent License"},{"location":"Apache2/#4-redistribution","text":"You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a \u201cNOTICE\u201d text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.","title":"4. Redistribution"},{"location":"Apache2/#5-submission-of-contributions","text":"Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.","title":"5. Submission of Contributions"},{"location":"Apache2/#6-trademarks","text":"This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.","title":"6. Trademarks"},{"location":"Apache2/#7-disclaimer-of-warranty","text":"Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an \u201cAS IS\u201d BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.","title":"7. Disclaimer of Warranty"},{"location":"Apache2/#8-limitation-of-liability","text":"In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.","title":"8. Limitation of Liability"},{"location":"Apache2/#9-accepting-warranty-or-additional-liability","text":"While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS","title":"9. Accepting Warranty or Additional Liability"},{"location":"Apache2/#appendix-how-to-apply-the-apache-license-to-your-work","text":"To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets [] replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same \u201cprinted page\u201d as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.","title":"APPENDIX: How to apply the Apache License to your work"},{"location":"Commands/","text":"TorrentFile CLI Menu Help Messages Main Usage ===== torrentfile [-h] [-i] [-V] [-v] ... CLI Tool for creating, checking, editing... Bittorrent meta files. TorrentFile supports all versions of torrent files. Options ------- -h, --help show this help message and exit -i, --interactive select program options interactively -V, --version show program version and exit -v, --verbose output debug information Actions ------- c (create, new) Create a torrent meta file. e (edit) Edit existing torrent meta file. m (magnet) Create magnet url from an existing Bittorrent meta file. r (recheck, check) Calculate amount of torrent meta file's content is found on disk. i (info) Show detailed information about a torrent file. Create Usage ===== torrentfile c [-h] [-a [ ...]] [-p] [-s ] [-m] [-c ] [-o ] [-t [ ...]] [--noprogress] [--meta-version ] [--piece-length ] [-w [ ...]] Positional Arguments -------------------- Path to content file or directory Optional Arguments ------------------ -h, --help show this help message and exit -a [ ...], --announce [ ...] Alias for -t/--tracker -p, --private Create a private torrent file -s , --source Useful for cross-seeding -m, --magnet Output Magnet Link after creation completes -c , --comment Include a comment in file metadata -o , --out Output path for created .torrent file -t [ ...], --tracker [ ...] One or more Bittorrent tracker announce url(s). --noprogress Disable showing the progress bar during torrent creation. (Minimially improves performance of torrent file creation.) --meta-version Bittorrent metafile version. Options - {1, 2, 3} (1) = Bittorrent v1 (Default) (2) = Bittorrent v2 (3) = Bittorrent v1 & v2 hybrid --piece-length Number of bytes per piece. (Default: None) Acceptable inputs include {14 - 24} as exponent for 2^n, or any acceptable integer value (must be power of 2). Examples:: [--piece-length 14] [--piece-length 16777216] -w [ ...], --web-seed [ ...] One or more url(s) linking to a http server hosting the torrent contents. This is useful if the torrent tracker is ever unreachable. Example:: -w url1 url2 url3 Edit Usage ===== torrentfile e [-h] [--tracker [ ...]] [--web-seed [ ...]] [--private] [--comment ] [--source ] <*.torrent> Positional Arguments -------------------- <*.torrent> path to *.torrent file Optional Arguments ------------------ -h, --help show this help message and exit --tracker [ ...] Replace current list of tracker/announce urls with one or more space seperated Bittorrent tracker announce url(s). --web-seed [ ...] Replace current list of web-seed urls with one or more space seperated url(s) --private Make torrent private. --comment Replaces any existing comment with --source Replaces current source with Recheck Usage ===== torrentfile r [-h] <*.torrent> Positional Arguments -------------------- <*.torrent> path to .torrent file. path to content file or directory Optional Arguments ------------------ -h, --help show this help message and exit Magnet Usage ===== torrentfile m [-h] <*.torrent> Positional Arguments -------------------- <*.torrent> Path to Bittorrent meta file. Optional Arguments ------------------ -h, --help show this help message and exit","title":"Help Messages"},{"location":"Commands/#torrentfile-cli-menu","text":"","title":"TorrentFile CLI Menu"},{"location":"Commands/#help-messages","text":"","title":"Help Messages"},{"location":"Commands/#main","text":"Usage ===== torrentfile [-h] [-i] [-V] [-v] ... CLI Tool for creating, checking, editing... Bittorrent meta files. TorrentFile supports all versions of torrent files. Options ------- -h, --help show this help message and exit -i, --interactive select program options interactively -V, --version show program version and exit -v, --verbose output debug information Actions ------- c (create, new) Create a torrent meta file. e (edit) Edit existing torrent meta file. m (magnet) Create magnet url from an existing Bittorrent meta file. r (recheck, check) Calculate amount of torrent meta file's content is found on disk. i (info) Show detailed information about a torrent file.","title":"Main"},{"location":"Commands/#create","text":"Usage ===== torrentfile c [-h] [-a [ ...]] [-p] [-s ] [-m] [-c ] [-o ] [-t [ ...]] [--noprogress] [--meta-version ] [--piece-length ] [-w [ ...]] Positional Arguments -------------------- Path to content file or directory Optional Arguments ------------------ -h, --help show this help message and exit -a [ ...], --announce [ ...] Alias for -t/--tracker -p, --private Create a private torrent file -s , --source Useful for cross-seeding -m, --magnet Output Magnet Link after creation completes -c , --comment Include a comment in file metadata -o , --out Output path for created .torrent file -t [ ...], --tracker [ ...] One or more Bittorrent tracker announce url(s). --noprogress Disable showing the progress bar during torrent creation. (Minimially improves performance of torrent file creation.) --meta-version Bittorrent metafile version. Options - {1, 2, 3} (1) = Bittorrent v1 (Default) (2) = Bittorrent v2 (3) = Bittorrent v1 & v2 hybrid --piece-length Number of bytes per piece. (Default: None) Acceptable inputs include {14 - 24} as exponent for 2^n, or any acceptable integer value (must be power of 2). Examples:: [--piece-length 14] [--piece-length 16777216] -w [ ...], --web-seed [ ...] One or more url(s) linking to a http server hosting the torrent contents. This is useful if the torrent tracker is ever unreachable. Example:: -w url1 url2 url3","title":"Create"},{"location":"Commands/#edit","text":"Usage ===== torrentfile e [-h] [--tracker [ ...]] [--web-seed [ ...]] [--private] [--comment ] [--source ] <*.torrent> Positional Arguments -------------------- <*.torrent> path to *.torrent file Optional Arguments ------------------ -h, --help show this help message and exit --tracker [ ...] Replace current list of tracker/announce urls with one or more space seperated Bittorrent tracker announce url(s). --web-seed [ ...] Replace current list of web-seed urls with one or more space seperated url(s) --private Make torrent private. --comment Replaces any existing comment with --source Replaces current source with ","title":"Edit"},{"location":"Commands/#recheck","text":"Usage ===== torrentfile r [-h] <*.torrent> Positional Arguments -------------------- <*.torrent> path to .torrent file. path to content file or directory Optional Arguments ------------------ -h, --help show this help message and exit","title":"Recheck"},{"location":"Commands/#magnet","text":"Usage ===== torrentfile m [-h] <*.torrent> Positional Arguments -------------------- <*.torrent> Path to Bittorrent meta file. Optional Arguments ------------------ -h, --help show this help message and exit","title":"Magnet"},{"location":"changelog/","text":"TorrentFile Version 0.7.7 more updates to logging major improvements to progress bar removed tqdm as dependency implemented custom progress bar logic new cli argument controlling the progress bar support for pyben 0.3.1 added threading to recheck module added mixins module unit test updates and improvements Version 0.7.5 updates to logging facility fixed bug in created hybrid torrent files fixed cli when subcomman not chosen doc updates unit test updates and improvements Version 0.7.2 cleaned up readme and help messages removed useless print statements improved CI tooling and checking minor bug fixes Version 0.7.1 split CI integration into separate platform specific files added new cli argument --cwd which changes the default save to location to the current working directory (this will be default in future) added unit tests to cover the new argument Changed license to a the more permissive Apache 2 software license Version 0.7.0 Fixed issues with logging to file. Finished adding tests for Unicode Support Deprecated some unneccessary code Clean up documentation and README removed config files no longer in use. Version 0.6.13 Fixed bug that created a torrent file with no name. Fixed bug that would error if cli path was listed after announce urls Added full unicode support. Added Unittests for new features and bug fixes Version 0.6.11 Fixed bug that occured during recheck when file of 0 length is included. Altered Recheck algorithm to process 0 length files. Only effected meta version 2 and hybrid torrent files. Added unittests to cover the situation. Version 0.6.10 Updates to documentation Integrated Type hints in source code Updated build and CI process Version 0.6.9 The --progress flag is now --noprogress Default behavior is to show progress bar use --noprogress to not show added CLI Help format strings added custom CLI help formatter class Titled Help Message section headers Fixed a bunch of error pages created by mkdocs Version 0.6.8 Documentation for newest features CLI usage examples Improved unittests made progress bar active by default Version 0.6.7 Updates to API Version 0.6.6 bug that created faulty Bittorrent V2 meta files in some instances. back to working as it should. Version 0.6.5 Support for creating Magnet URI's Added optional progress bar for torrent creation Log File handler CLI args page in documentation verbose and logging bugs multi tracker errors bug Version 0.6.4 CLI interface add subcommands added interactive mode Re-wrote the recheck module fixed documentation and docstrings linting and testing errors Version 0.6.3 Fixed Bug that would format list of trackers incorrectly CLI Bug Fixes Version 0.6.2 Bug fixes Documentation error pages Version 0.6.0 cli commands alterations debug logging during creation process Version 0.5.2 Fixed Bug that was adding wrong fields to info dict Version 0.5.0 Slew of new unit tests Stricter linting features Alternative method of -re-check feature Bug Fixes CLI help formatting errors Version 0.4.8 Improved Algorithm performance for ReCheck. Additions to documentation. Version 0.4.7 Fixed A bug that misspelled a field when creating Hybrid torrent files. Re-Check procedure for v2 and hybrid torrent file checking. Version 0.4.6 CLI Help and Usage Messages. Expanded CLI args. Completely new CheckerClass which replaces old Checker Hooks for GUI or other 3rd party apps to hook into Checking Documentation and Unit tests for new CheckerClass Version 0.4.5 Documentation and docstrings improvements Better code formating and more detailed docstrings More unit tests. Version 0.4.2 The ReChecker feature now supports v1, v2, & hybrid .torrent file. Bug in CLI for python < 3.8 Version 0.4.1 Added tests for hybrid class Added logging features new cli flag to activate debug mode Documentation theme. Fixed Bug that allowed improper piece length values. Version 0.4.0 Fixed bugs in creating hybrid files. Bug Fix that broke cli. Version 0.3.0 Added/Improved support for hybrid meta files. Many additions to testing suit including linting and coverage tests. Version 0.2.8 Styling fixes. Bug Fixes. Prelimenary support for bittorrent hybrid meta files. Bug Fixes Version 0.2.7 major imporvements to torrentfile-GUI. minor adjustments to this package for integration. Code consolidation Bug Fixes Documentation additions Implemented CI/CD Integration Version 0.2.3 Bug Fixes Code Style and Formatting Added more unittests Version 0.2.1 Bittorrent Protocol V2 Support v2 metafile options to cli v2 metafile tests Version 0.1.7 Docstrings Improvements. Added documentation rederer. Improved readme file. formatting Version 0.1.2 Added a Command Line Interface Rough Graphical User Interface Minor Bug Fixes Improved unittest coverage Version 0.1.0 added SHA256 support Feeder class for seemless file switching Fixed the primary entrypoint function. Improved docstrings Bug fixes Version 0.0.2 Added Unittests added bencode support added hashing support Version 0.0.1 Initial concept and planning","title":"Changelog"},{"location":"changelog/#torrentfile","text":"","title":"TorrentFile"},{"location":"changelog/#version-077","text":"more updates to logging major improvements to progress bar removed tqdm as dependency implemented custom progress bar logic new cli argument controlling the progress bar support for pyben 0.3.1 added threading to recheck module added mixins module unit test updates and improvements","title":"Version 0.7.7"},{"location":"changelog/#version-075","text":"updates to logging facility fixed bug in created hybrid torrent files fixed cli when subcomman not chosen doc updates unit test updates and improvements","title":"Version 0.7.5"},{"location":"changelog/#version-072","text":"cleaned up readme and help messages removed useless print statements improved CI tooling and checking minor bug fixes","title":"Version 0.7.2"},{"location":"changelog/#version-071","text":"split CI integration into separate platform specific files added new cli argument --cwd which changes the default save to location to the current working directory (this will be default in future) added unit tests to cover the new argument Changed license to a the more permissive Apache 2 software license","title":"Version 0.7.1"},{"location":"changelog/#version-070","text":"Fixed issues with logging to file. Finished adding tests for Unicode Support Deprecated some unneccessary code Clean up documentation and README removed config files no longer in use.","title":"Version 0.7.0"},{"location":"changelog/#version-0613","text":"Fixed bug that created a torrent file with no name. Fixed bug that would error if cli path was listed after announce urls Added full unicode support. Added Unittests for new features and bug fixes","title":"Version 0.6.13"},{"location":"changelog/#version-0611","text":"Fixed bug that occured during recheck when file of 0 length is included. Altered Recheck algorithm to process 0 length files. Only effected meta version 2 and hybrid torrent files. Added unittests to cover the situation.","title":"Version 0.6.11"},{"location":"changelog/#version-0610","text":"Updates to documentation Integrated Type hints in source code Updated build and CI process","title":"Version 0.6.10"},{"location":"changelog/#version-069","text":"The --progress flag is now --noprogress Default behavior is to show progress bar use --noprogress to not show added CLI Help format strings added custom CLI help formatter class Titled Help Message section headers Fixed a bunch of error pages created by mkdocs","title":"Version 0.6.9"},{"location":"changelog/#version-068","text":"Documentation for newest features CLI usage examples Improved unittests made progress bar active by default","title":"Version 0.6.8"},{"location":"changelog/#version-067","text":"Updates to API","title":"Version 0.6.7"},{"location":"changelog/#version-066","text":"bug that created faulty Bittorrent V2 meta files in some instances. back to working as it should.","title":"Version 0.6.6"},{"location":"changelog/#version-065","text":"Support for creating Magnet URI's Added optional progress bar for torrent creation Log File handler CLI args page in documentation verbose and logging bugs multi tracker errors bug","title":"Version 0.6.5"},{"location":"changelog/#version-064","text":"CLI interface add subcommands added interactive mode Re-wrote the recheck module fixed documentation and docstrings linting and testing errors","title":"Version 0.6.4"},{"location":"changelog/#version-063","text":"Fixed Bug that would format list of trackers incorrectly CLI Bug Fixes","title":"Version 0.6.3"},{"location":"changelog/#version-062","text":"Bug fixes Documentation error pages","title":"Version 0.6.2"},{"location":"changelog/#version-060","text":"cli commands alterations debug logging during creation process","title":"Version 0.6.0"},{"location":"changelog/#version-052","text":"Fixed Bug that was adding wrong fields to info dict","title":"Version 0.5.2"},{"location":"changelog/#version-050","text":"Slew of new unit tests Stricter linting features Alternative method of -re-check feature Bug Fixes CLI help formatting errors","title":"Version 0.5.0"},{"location":"changelog/#version-048","text":"Improved Algorithm performance for ReCheck. Additions to documentation.","title":"Version 0.4.8"},{"location":"changelog/#version-047","text":"Fixed A bug that misspelled a field when creating Hybrid torrent files. Re-Check procedure for v2 and hybrid torrent file checking.","title":"Version 0.4.7"},{"location":"changelog/#version-046","text":"CLI Help and Usage Messages. Expanded CLI args. Completely new CheckerClass which replaces old Checker Hooks for GUI or other 3rd party apps to hook into Checking Documentation and Unit tests for new CheckerClass","title":"Version 0.4.6"},{"location":"changelog/#version-045","text":"Documentation and docstrings improvements Better code formating and more detailed docstrings More unit tests.","title":"Version 0.4.5"},{"location":"changelog/#version-042","text":"The ReChecker feature now supports v1, v2, & hybrid .torrent file. Bug in CLI for python < 3.8","title":"Version 0.4.2"},{"location":"changelog/#version-041","text":"Added tests for hybrid class Added logging features new cli flag to activate debug mode Documentation theme. Fixed Bug that allowed improper piece length values.","title":"Version 0.4.1"},{"location":"changelog/#version-040","text":"Fixed bugs in creating hybrid files. Bug Fix that broke cli.","title":"Version 0.4.0"},{"location":"changelog/#version-030","text":"Added/Improved support for hybrid meta files. Many additions to testing suit including linting and coverage tests.","title":"Version 0.3.0"},{"location":"changelog/#version-028","text":"Styling fixes. Bug Fixes. Prelimenary support for bittorrent hybrid meta files. Bug Fixes","title":"Version 0.2.8"},{"location":"changelog/#version-027","text":"major imporvements to torrentfile-GUI. minor adjustments to this package for integration. Code consolidation Bug Fixes Documentation additions Implemented CI/CD Integration","title":"Version 0.2.7"},{"location":"changelog/#version-023","text":"Bug Fixes Code Style and Formatting Added more unittests","title":"Version 0.2.3"},{"location":"changelog/#version-021","text":"Bittorrent Protocol V2 Support v2 metafile options to cli v2 metafile tests","title":"Version 0.2.1"},{"location":"changelog/#version-017","text":"Docstrings Improvements. Added documentation rederer. Improved readme file. formatting","title":"Version 0.1.7"},{"location":"changelog/#version-012","text":"Added a Command Line Interface Rough Graphical User Interface Minor Bug Fixes Improved unittest coverage","title":"Version 0.1.2"},{"location":"changelog/#version-010","text":"added SHA256 support Feeder class for seemless file switching Fixed the primary entrypoint function. Improved docstrings Bug fixes","title":"Version 0.1.0"},{"location":"changelog/#version-002","text":"Added Unittests added bencode support added hashing support","title":"Version 0.0.2"},{"location":"changelog/#version-001","text":"Initial concept and planning","title":"Version 0.0.1"},{"location":"man/","text":"Name torrentfile Synopsis torrentfile [options] [optional] Description torrentfile is a tool for creating and/or manipulating Bittorrent files(.torrent). It also provides tools for reviewing torrent file information, generating magnet links, and checking/validating torrent content against its companion torrent file. Options -h displays all relevant command line options and subcommands. -V displays program and version. -v enables verbose mode as well as a logfile. -i activates interactive mode for selecting subcommands and options. Sub-commands create alias: c torrentfile create [options] torrentfile c [options] The create subcommand is used for generating new torrent files. The only required argument is the path( ) to the contents file or directory. -a -t --announce --tracker Adds the list of url's that follow to the list of trackers for the newly created torrent file. Example -t http://url1 https://url2 ... -w --web-seeds Adds the list of urls that follow to the list of web-seed addresses for the newly created torrent file. --comment Includes the comment that follows to the metadata saved in the newly created torrent file. Example: --comment \"Created for MyTrackerExample.com\" --source Creates a source field in the info dictionary with the string that follows as the value. Frequently used for cross-seeding on private trackers. Example: --source MyTrackerExample -p --private Indicates that the torrent will be used on a private tracker. Disables multi-tracker protocols and DHT. --piece-length Uses the number that follows as the piece-length size for the newly created torrent file. If option isn't used the program will pick the ideal size. Acceptable values include 14-29 which is interpreted as the number to raise 2 by (e.g. 14 is interpreted as 16384), or any perfect power of 2 greater than or equal to 16 KiB and less than 1 GiB. Example: --piece-length 14 or --piece-length 16384 . --meta-version Use the following number as the Bittorrent version the torrent will be used on. The default is 1. Options: 1 - Bittorrent v1, 2 - Bittorrent v2, 3 - Bittorrent v1 & v2 Examples: --meta-version 1 , --meta-version 2 , --meta-version 3 -o --out Specify the full path to the newly created torrent file. The default is to save it adjacent to the content. Example: if content is at /home/user/torrents/content the default would create /home/user/torrents/content.torrent --cwd --current Changes the default save location to the current working directory. Example: if content is at /home/user/torrents/content this option would create ./content.torrent --noprogress Turns off the progress bar indicator shown. info alias: i torrentfile info torrentfile i There are no optional arguments for the info subcommand. /path/to/*.torrent The relative or absolute path to the torrent file. edit alias: e torrentfile edit [options] torrentfile e [options] Each option identifies the field to edit inside the torrent file and what the new value should be. If an option is not used then its field will be ommited in the newly created torrent file. As such if the file is marked as private and it should remain that way, the -p option should be used. -a -t --announce --tracker Adds the list of url's that follow to the list of trackers for the newly created torrent file. Example -t http://url1 https://url2 ... -w --web-seeds Adds the list of urls that follow to the list of web-seed addresses for the newly created torrent file. --comment Includes the comment that follows to the metadata saved in the newly created torrent file. Example: --comment \"Created for MyTrackerExample.com\" --source Creates a source field in the info dictionary with the string that follows as the value. Frequently used for cross-seeding on private trackers. Example: --source MyTrackerExample -p --private Indicates that the torrent will be used on a private tracker. Disables multi-tracker protocols and DHT. recheck alias: r torrentfile recheck torrentfile r There are only two arguments for the recheck command and both are mandatory. The first is the absolute or relative to the torrent file, and the second is the absolute or relative to it's content. This will display a progress bar and at the end output what percent of the torrentfile's content it found at the path indicated. It is also permitted to use the contents parent directory as the second argument and the result will be the same.","title":"Man Page"},{"location":"man/#name","text":"torrentfile","title":"Name"},{"location":"man/#synopsis","text":"torrentfile [options] [optional] ","title":"Synopsis"},{"location":"man/#description","text":"torrentfile is a tool for creating and/or manipulating Bittorrent files(.torrent). It also provides tools for reviewing torrent file information, generating magnet links, and checking/validating torrent content against its companion torrent file.","title":"Description"},{"location":"man/#options","text":"-h displays all relevant command line options and subcommands. -V displays program and version. -v enables verbose mode as well as a logfile. -i activates interactive mode for selecting subcommands and options.","title":"Options"},{"location":"man/#sub-commands","text":"","title":"Sub-commands"},{"location":"man/#create","text":"alias: c torrentfile create [options] torrentfile c [options] The create subcommand is used for generating new torrent files. The only required argument is the path( ) to the contents file or directory. -a -t --announce --tracker Adds the list of url's that follow to the list of trackers for the newly created torrent file. Example -t http://url1 https://url2 ... -w --web-seeds Adds the list of urls that follow to the list of web-seed addresses for the newly created torrent file. --comment Includes the comment that follows to the metadata saved in the newly created torrent file. Example: --comment \"Created for MyTrackerExample.com\" --source Creates a source field in the info dictionary with the string that follows as the value. Frequently used for cross-seeding on private trackers. Example: --source MyTrackerExample -p --private Indicates that the torrent will be used on a private tracker. Disables multi-tracker protocols and DHT. --piece-length Uses the number that follows as the piece-length size for the newly created torrent file. If option isn't used the program will pick the ideal size. Acceptable values include 14-29 which is interpreted as the number to raise 2 by (e.g. 14 is interpreted as 16384), or any perfect power of 2 greater than or equal to 16 KiB and less than 1 GiB. Example: --piece-length 14 or --piece-length 16384 . --meta-version Use the following number as the Bittorrent version the torrent will be used on. The default is 1. Options: 1 - Bittorrent v1, 2 - Bittorrent v2, 3 - Bittorrent v1 & v2 Examples: --meta-version 1 , --meta-version 2 , --meta-version 3 -o --out Specify the full path to the newly created torrent file. The default is to save it adjacent to the content. Example: if content is at /home/user/torrents/content the default would create /home/user/torrents/content.torrent --cwd --current Changes the default save location to the current working directory. Example: if content is at /home/user/torrents/content this option would create ./content.torrent --noprogress Turns off the progress bar indicator shown.","title":"create"},{"location":"man/#info","text":"alias: i torrentfile info torrentfile i There are no optional arguments for the info subcommand. /path/to/*.torrent The relative or absolute path to the torrent file.","title":"info"},{"location":"man/#edit","text":"alias: e torrentfile edit [options] torrentfile e [options] Each option identifies the field to edit inside the torrent file and what the new value should be. If an option is not used then its field will be ommited in the newly created torrent file. As such if the file is marked as private and it should remain that way, the -p option should be used. -a -t --announce --tracker Adds the list of url's that follow to the list of trackers for the newly created torrent file. Example -t http://url1 https://url2 ... -w --web-seeds Adds the list of urls that follow to the list of web-seed addresses for the newly created torrent file. --comment Includes the comment that follows to the metadata saved in the newly created torrent file. Example: --comment \"Created for MyTrackerExample.com\" --source Creates a source field in the info dictionary with the string that follows as the value. Frequently used for cross-seeding on private trackers. Example: --source MyTrackerExample -p --private Indicates that the torrent will be used on a private tracker. Disables multi-tracker protocols and DHT.","title":"edit"},{"location":"man/#recheck","text":"alias: r torrentfile recheck torrentfile r There are only two arguments for the recheck command and both are mandatory. The first is the absolute or relative to the torrent file, and the second is the absolute or relative to it's content. This will display a progress bar and at the end output what percent of the torrentfile's content it found at the path indicated. It is also permitted to use the contents parent directory as the second argument and the result will be the same.","title":"recheck"},{"location":"source/","text":"TorrentFile API and Source Torrent Module module torrentfile. torrent Classes and procedures pertaining to the creation of torrent meta files. Classes TorrentFile construct .torrent file. TorrentFileV2 construct .torrent v2 files using provided data. MetaFile base class for all MetaFile classes. Constants BLOCK_SIZE : int size of leaf hashes for merkle tree. HASH_SIZE : int Length of a sha256 hash. Bittorrent V2 From Bittorrent.org Documentation pages. Implementation details for Bittorrent Protocol v2. Note All strings in a .torrent file that contain text must be UTF-8 encoded. Meta Version 2 Dictionary: \"announce\": The URL of the tracker. \"info\": This maps to a dictionary, with keys described below. \"name\": A display name for the torrent. It is purely advisory. \"piece length\": The number of bytes that each logical piece in the peer protocol refers to. I.e. it sets the granularity of piece, request, bitfield and have messages. It must be a power of two and at least 6KiB. \"meta version\": An integer value, set to 2 to indicate compatibility with the current revision of this specification. Version 1 is not assigned to avoid confusion with BEP3. Future revisions will only increment this issue to indicate an incompatible change has been made, for example that hash algorithms were changed due to newly discovered vulnerabilities. Lementations must check this field first and indicate that a torrent is of a newer version than they can handle before performing other idations which may result in more general messages about invalid files. Files are mapped into this piece address space so that each non-empty \"file tree\": A tree of dictionaries where dictionary keys represent UTF-8 encoded path elements. Entries with zero-length keys describe the properties of the composed path at that point. 'UTF-8 encoded' context only means that if the native encoding is known at creation time it must be converted to UTF-8. Keys may contain invalid UTF-8 sequences or characters and names that are reserved on specific filesystems. Implementations must be prepared to sanitize them. On platforms path components exactly matching '.' and '..' must be sanitized since they could lead to directory traversal attacks and conflicting path descriptions. On platforms that require UTF-8 path components this sanitizing step must happen after normalizing overlong UTF-8 encodings. File is aligned to a piece boundary and occurs in same order as the file tree. The last piece of each file may be shorter than the specified piece length, resulting in an alignment gap. \"length\": Length of the file in bytes. Presence of this field indicates that the dictionary describes a file, not a directory. Which means it must not have any sibling entries. \"pieces root\": For non-empty files this is the the root hash of a merkle tree with a branching factor of 2, constructed from 16KiB blocks of the file. The last block may be shorter than 16KiB. The remaining leaf hashes beyond the end of the file required to construct upper layers of the merkle tree are set to zero. As of meta version 2 SHA2-256 is used as digest function for the merkle tree. The hash is stored in its binary form, not as human-readable string. \"piece layers\": A dictionary of strings. For each file in the file tree that is larger than the piece size it contains one string value. The keys are the merkle roots while the values consist of concatenated hashes of one layer within that merkle tree. The layer is chosen so that one hash covers piece length bytes. For example if the piece size is 16KiB then the leaf hashes are used. If a piece size of 128KiB is used then 3rd layer up from the leaf hashes is used. Layer hashes which exclusively cover data beyond the end of file, i.e. are only needed to balance the tree, are omitted. All hashes are stored in their binary format. A torrent is not valid if this field is absent, the contained hashes do not match the merkle roots or are not from the correct layer. Important The file tree root dictionary itself must not be a file, i.e. it must not contain a zero-length key with a dictionary containing a length key. Bittorrent V1 v1 meta-dictionary announce: The URL of the tracker. info: This maps to a dictionary, with keys described below. name : maps to a UTF-8 encoded string which is the suggested name to save the file (or directory) as. It is purely advisory. piece length : maps to the number of bytes in each piece the file is split into. For the purposes of transfer, files are split into fixed-size pieces which are all the same length except for possibly the last one which may be truncated. piece length : is almost always a power of two, most commonly 2^18 = 256 K pieces : maps to a string whose length is a multiple of 20. It is to be subdivided into strings of length 20, each of which is the SHA1 hash of the piece at the corresponding index. length : In the single file case, maps to the length of the file in bytes. files : If present then the download represents a single file, otherwise it represents a set of files which go in a directory structure. For the purposes of the other keys, the multi-file case is treated as only having a single file by concatenating the files in the order they appear in the files list. The files list is the value files maps to, and is a list of dictionaries containing the following keys: path : A list of UTF-8 encoded strings corresponding to subdirectory names, the last of which is the actual file name length : Maps to the length of the file in bytes. length : Only present if the content is a single file. Maps to the length of the file in bytes. Note In the single file case, the name key is the name of a file, in the muliple file case, it's the name of a directory. Classes MetaFile \u2014 Base Class for all TorrentFile classes. TorrentFile \u2014 Class for creating Bittorrent meta files. TorrentFileV2 \u2014 Class for creating Bittorrent meta v2 files. TorrentFileHybrid \u2014 Construct the Hybrid torrent meta file with provided parameters. ------ Utils Module module torrentfile. utils Utility functions and classes used throughout package. Functions: get_piece_length: calculate ideal piece length for torrent file. sortfiles: traverse directory in sorted order yielding paths encountered. path_size: Sum the sizes of each file in path. get_file_list: Return list of all files contained in directory. path_stat: Get ideal piece length, total size, and file list for directory. path_piece_length: Get ideal piece length based on size of directory. Classes: MissingPathError: Custom exception raised when no path was provided to CLI. PieceLengthValueError: Custom exception raised when incorrect input value used for piece length field. Classes Memo \u2014 Memoice chache object. MissingPathError \u2014 Path parameter is required to specify target content. PieceLengthValueError \u2014 Piece Length parameter must equal a perfect power of 2. Functions get_file_list ( path ) (list) \u2014 Return a sorted list of file paths contained in directory. get_piece_length ( size ) (int) \u2014 Calculate the ideal piece length for bittorrent data. humanize_bytes ( amount ) (str) \u2014 Convert integer into human readable memory sized denomination. next_power_2 ( value ) (int) \u2014 Calculate the next perfect power of 2 equal to or greater than value. normalize_piece_length ( piece_length ) (int) \u2014 Verify input piece_length is valid and convert accordingly. path_piece_length ( path ) (int) \u2014 Calculate piece length for input path and contents. path_size ( path ) (int) \u2014 Return the total size of all files in path recursively. path_stat ( path ) (list) \u2014 Calculate directory statistics. ------ Edit Module module torrentfile. edit Edit torrent module. Provides a facility by which certain properties of a torrent meta file can be edited by the user. The various command line arguments indicate which fields should be edited, and what the new value should be. Depending on what fields are chosen to edit, this command can trigger a new info hash which means the torrent will no longer be able to participate in the same swarm as the original unedited torrent. Keywords private comment source trackers web-seeds Functions edit_torrent ( metafile , args ) (dict) \u2014 Edit the properties and values in a torrent meta file. filter_empty ( args , meta , info ) \u2014 Remove dictionary keys with empty values. ------ Interactive Module module torrentfile. interactive Module contains the procedures used for Interactive Mode. Classes InteractiveEditor \u2014 Interactive dialog class for torrent editing. InteractiveCreator \u2014 Class namespace for interactive program options. Functions create_torrent ( ) \u2014 Create new torrent file interactively. edit_action ( ) \u2014 Edit the editable values of the torrent meta file. get_input ( *args ) (str) \u2014 Determine appropriate input function to call. recheck_torrent ( ) \u2014 Check torrent download completed percentage. select_action ( ) \u2014 Operate TorrentFile program interactively through terminal. showcenter ( txt ) \u2014 Print text to screen in the center position of the terminal. showtext ( txt ) \u2014 Print contents of txt to screen. ------ CLI Module module torrentfile. cli Command Line Interface for TorrentFile project. This module provides the primary command line argument parser for the torrentfile package. The main_script function is automatically invoked when called from command line, and parses accompanying arguments. Functions: main_script: process command line arguments and run program. activate_logger: turns on debug mode and logging facility. Classes TorrentFileHelpFormatter \u2014 Formatting class for help tips provided by the CLI. Functions activate_logger ( ) \u2014 Activate the builtin logging mechanism when passed debug flag from CLI. execute ( args ) \u2014 Initialize Command Line Interface for torrentfile. execute ( args ) \u2014 Initialize Command Line Interface for torrentfile. main ( ) \u2014 Initiate main function for CLI script. ------ Recheck Module module torrentfile. recheck Module container Checker Class. The CheckerClass takes a torrentfile and tha path to it's contents. It will then iterate through every file and directory contained and compare their data to values contained within the torrent file. Completion percentages will be printed to screen for each file and at the end for the torrentfile as a whole. Classes Checker \u2014 Check a given file or directory to see if it matches a torrentfile. FeedChecker \u2014 Validates torrent content. HashChecker \u2014 Verify that root hashes of content files match the .torrent files. ------ Hasher Module module torrentfile. hasher Piece/File Hashers for Bittorrent meta file contents. Classes Hasher \u2014 Piece hasher for Bittorrent V1 files. HasherV2 \u2014 Calculate the root hash and piece layers for file contents. HasherHybrid \u2014 Calculate root and piece hashes for creating hybrid torrent file. Functions merkle_root ( blocks ) (bytes) \u2014 Calculate the merkle root for a seq of sha256 hash digests. ------ torrentfile special Torrentfile can create Bittorrent metafiles for any content. Both Bittorrent v1 and v2 are fully supported. Also included is a torrent torrent file checker, which can verify a .torrent file is formated correctly as well as validate files and folders against metadata. Modules: metafile: Creation/Validation of v1 .torrent files. metafile2: Creation/Validation of v2 .torrent files. torrentfile: torrentfiles Command Line Interface implementation. exceptions: Custom Exceptions used in package. utils: Utilities used throughout package. __main__ special Enable calling the package directly with python from the command line. cli Command Line Interface for TorrentFile project. This module provides the primary command line argument parser for the torrentfile package. The main_script function is automatically invoked when called from command line, and parses accompanying arguments. Functions: main_script: process command line arguments and run program. activate_logger: turns on debug mode and logging facility. TorrentFileHelpFormatter ( HelpFormatter ) Formatting class for help tips provided by the CLI. Subclasses Argparse.HelpFormatter. Source code in torrentfile\\cli.py class TorrentFileHelpFormatter ( HelpFormatter ): \"\"\" Formatting class for help tips provided by the CLI. Subclasses Argparse.HelpFormatter. \"\"\" def __init__ ( self , prog , width = 40 , max_help_positions = 30 ): \"\"\" Construct HelpFormat class for usage output. Parameters ---------- prog : str Name of the program. width : int Max width of help message output. max_help_positions : int max length until line wrap. \"\"\" super () . __init__ ( prog , width = width , max_help_position = max_help_positions ) def _split_lines ( self , text , _ ): \"\"\" Split multiline help messages and remove indentation. Parameters ---------- text : str text that needs to be split _ : int max width for line. \"\"\" lines = text . split ( \" \\n \" ) return [ line . strip () for line in lines if line ] def _format_text ( self , text ): \"\"\" Format text for cli usage messages. Parameters ---------- text : str Pre-formatted text. Returns ------- str Formatted text from input. \"\"\" text = text % dict ( prog = self . _prog ) if \"%(prog)\" in text else text text = self . _whitespace_matcher . sub ( \" \" , text ) . strip () return text + \" \\n\\n \" def _join_parts ( self , part_strings ): \"\"\" Combine different sections of the help message. Parameters ---------- part_strings : list List of argument help messages and headers. Returns ------- str Fully formatted help message for CLI. \"\"\" parts = self . format_headers ( part_strings ) return super () . _join_parts ( parts ) @staticmethod def format_headers ( parts ): \"\"\" Format help message section headers. Parameters ---------- parts : list List of individual lines for help message. Returns ------- list Input list with formatted section headers. \"\"\" if parts and parts [ 0 ] . startswith ( \"usage:\" ): parts [ 0 ] = \"Usage \\n ===== \\n \" + parts [ 0 ][ 6 :] headings = [ i for i in range ( len ( parts )) if parts [ i ] . endswith ( \": \\n \" )] for i in headings [:: - 1 ]: parts [ i ] = parts [ i ][: - 2 ] . title () underline = \"\" . join ([ \" \\n \" , \"-\" * len ( parts [ i ]), \" \\n \" ]) parts . insert ( i + 1 , underline ) return parts __init__ ( self , prog , width = 40 , max_help_positions = 30 ) special Construct HelpFormat class for usage output. Parameters: Name Type Description Default prog str Name of the program. required width int Max width of help message output. 40 max_help_positions int max length until line wrap. 30 Source code in torrentfile\\cli.py def __init__ ( self , prog , width = 40 , max_help_positions = 30 ): \"\"\" Construct HelpFormat class for usage output. Parameters ---------- prog : str Name of the program. width : int Max width of help message output. max_help_positions : int max length until line wrap. \"\"\" super () . __init__ ( prog , width = width , max_help_position = max_help_positions ) format_headers ( parts ) staticmethod Format help message section headers. Parameters: Name Type Description Default parts list List of individual lines for help message. required Returns: Type Description list Input list with formatted section headers. Source code in torrentfile\\cli.py @staticmethod def format_headers ( parts ): \"\"\" Format help message section headers. Parameters ---------- parts : list List of individual lines for help message. Returns ------- list Input list with formatted section headers. \"\"\" if parts and parts [ 0 ] . startswith ( \"usage:\" ): parts [ 0 ] = \"Usage \\n ===== \\n \" + parts [ 0 ][ 6 :] headings = [ i for i in range ( len ( parts )) if parts [ i ] . endswith ( \": \\n \" )] for i in headings [:: - 1 ]: parts [ i ] = parts [ i ][: - 2 ] . title () underline = \"\" . join ([ \" \\n \" , \"-\" * len ( parts [ i ]), \" \\n \" ]) parts . insert ( i + 1 , underline ) return parts activate_logger () Activate the builtin logging mechanism when passed debug flag from CLI. Source code in torrentfile\\cli.py def activate_logger (): \"\"\" Activate the builtin logging mechanism when passed debug flag from CLI. \"\"\" logging . basicConfig ( level = logging . INFO ) logger = logging . getLogger () file_handler = logging . FileHandler ( \"torrentfile.log\" , mode = \"a+\" , encoding = \"utf-8\" ) console_handler = logging . StreamHandler ( stream = sys . stderr ) file_formatter = logging . Formatter ( \" %(asctime)s %(levelno)s %(message)s \" , datefmt = \"%m- %d %H:%M:%S\" , style = \"%\" , ) stream_formatter = logging . Formatter ( \" %(asctime)s %(levelno)s %(message)s \" , datefmt = \"%m- %d %H:%M:%S\" , style = \"%\" , ) file_handler . setFormatter ( file_formatter ) console_handler . setFormatter ( stream_formatter ) file_handler . setLevel ( logging . INFO ) console_handler . setLevel ( logging . INFO ) logger . setLevel ( logging . INFO ) logger . addHandler ( console_handler ) logger . addHandler ( file_handler ) logger . debug ( \"Debug: ON\" ) execute ( args = None ) Initialize Command Line Interface for torrentfile. Parameters: Name Type Description Default args list Commandline arguments. default=None None Source code in torrentfile\\cli.py def execute ( args = None ): \"\"\" Initialize Command Line Interface for torrentfile. Parameters ---------- args : list Commandline arguments. default=None \"\"\" if not args : if sys . argv [ 1 :]: args = sys . argv [ 1 :] else : args = [ \"-h\" ] parser = ArgumentParser ( \"torrentfile\" , description = ( \"Command line tools for creating, editing, checking and \" \"interacting with Bittorrent metainfo files\" ), prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , conflict_handler = \"resolve\" , ) parser . add_argument ( \"-i\" , \"--interactive\" , action = \"store_true\" , dest = \"interactive\" , help = \"select program options interactively\" , ) parser . add_argument ( \"-V\" , \"--version\" , action = \"version\" , version = f \"torrentfile v { version } \" , help = \"show program version and exit\" , ) parser . add_argument ( \"-v\" , \"--verbose\" , action = \"store_true\" , dest = \"debug\" , help = \"output debug information\" , ) parser . set_defaults ( func = parser . print_help ) subparsers = parser . add_subparsers ( title = \"Actions\" , dest = \"command\" , metavar = \"create, edit, magnet, recheck\" , ) create_parser = subparsers . add_parser ( \"create\" , help = \"\"\"Generate a new torrent meta file.\"\"\" , prefix_chars = \"-\" , aliases = [ \"c\" , \"new\" ], formatter_class = TorrentFileHelpFormatter , ) create_parser . add_argument ( \"-a\" , \"-t\" , \"--announce\" , \"--tracker\" , action = \"store\" , dest = \"announce\" , metavar = \"\" , nargs = \"+\" , default = [], help = \"One or more space-seperated torrent tracker url(s).\" , ) create_parser . add_argument ( \"-p\" , \"--private\" , action = \"store_true\" , dest = \"private\" , help = \"Creates private torrent with multi-tracker and DHT turned off.\" , ) create_parser . add_argument ( \"-s\" , \"--source\" , action = \"store\" , dest = \"source\" , metavar = \"\" , help = \"Add a source string. Useful for cross-seeding.\" , ) create_parser . add_argument ( \"-m\" , \"--magnet\" , action = \"store_true\" , dest = \"magnet\" , ) create_parser . add_argument ( \"-c\" , \"--comment\" , action = \"store\" , dest = \"comment\" , metavar = \"\" , help = \"Include a comment in file metadata\" , ) create_parser . add_argument ( \"-o\" , \"--out\" , action = \"store\" , dest = \"outfile\" , metavar = \"\" , help = \"Output save path for created .torrent file\" , ) create_parser . add_argument ( \"--cwd\" , \"--current\" , action = \"store_true\" , dest = \"cwd\" , help = \"Save output .torrent file to current directory\" , ) create_parser . add_argument ( \"--progress\" , \"--prog\" , choices = [ \"0\" , \"1\" , \"2\" ], default = \"1\" , action = \"store\" , dest = \"progress\" , help = \"\"\" Set the progress bar level. Options = 0, 1, 2 (0) = Do not display progress bar. (1) = Display 1 progress bar for the whole torrent (default) (2) = Display a progress bar for each file going into torrent \"\"\" , ) create_parser . add_argument ( \"--meta-version\" , default = \"1\" , choices = [ \"1\" , \"2\" , \"3\" ], action = \"store\" , dest = \"meta_version\" , metavar = \"\" , help = \"\"\" Bittorrent metafile version. Options = 1, 2, 3 (1) = Bittorrent v1 (Default) (2) = Bittorrent v2 (3) = Bittorrent v1 & v2 hybrid \"\"\" , ) create_parser . add_argument ( \"--piece-length\" , action = \"store\" , dest = \"piece_length\" , metavar = \"\" , help = \"\"\" (Default: ) Number of bytes for per chunk of data transmitted by Bittorrent client. Acceptable values include integers 14-26 which will be interpreted as a perfect power of 2. e.g. 14 = 16KiB pieces. Examples:: [--piece-length 14] [--piece-length 20] \"\"\" , ) create_parser . add_argument ( \"-w\" , \"--web-seed\" , action = \"store\" , dest = \"url_list\" , metavar = \"\" , nargs = \"+\" , help = \"list of web addresses where torrent data exists (GetRight).\" , ) create_parser . add_argument ( \"--http-seed\" , action = \"store\" , dest = \"httpseeds\" , metavar = \"\" , nargs = \"+\" , help = \"list of URLs, addresses where content can be found (Hoffman).\" , ) create_parser . add_argument ( \"content\" , action = \"store\" , metavar = \"\" , nargs = \"?\" , help = \"Path to content file or directory\" , ) create_parser . set_defaults ( func = create ) edit_parser = subparsers . add_parser ( \"edit\" , help = \"\"\" Edit existing torrent meta file. \"\"\" , aliases = [ \"e\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) edit_parser . add_argument ( \"metafile\" , action = \"store\" , help = \"path to *.torrent file\" , metavar = \"<*.torrent>\" , ) edit_parser . add_argument ( \"--tracker\" , action = \"store\" , dest = \"announce\" , metavar = \"\" , nargs = \"+\" , help = \"\"\" Replace current list of tracker/announce urls with one or more space seperated Bittorrent tracker announce url(s). \"\"\" , ) edit_parser . add_argument ( \"--web-seed\" , action = \"store\" , dest = \"url_list\" , metavar = \"\" , nargs = \"+\" , help = \"Replace current list of web-seed urls with one or more url(s)\" , ) edit_parser . add_argument ( \"--http-seed\" , action = \"store\" , dest = \"httpseeds\" , metavar = \"\" , nargs = \"+\" , help = \"replace all currently listed addresses with new list (Hoffman).\" , ) edit_parser . add_argument ( \"--private\" , action = \"store_true\" , help = \"Make torrent private.\" , dest = \"private\" , ) edit_parser . add_argument ( \"--comment\" , help = \"Replaces any existing comment with \" , metavar = \"\" , dest = \"comment\" , action = \"store\" , ) edit_parser . add_argument ( \"--source\" , action = \"store\" , dest = \"source\" , metavar = \"\" , help = \"Replaces current source with \" , ) edit_parser . set_defaults ( func = edit ) magnet_parser = subparsers . add_parser ( \"magnet\" , help = \"\"\" Generate magnet url from an existing Bittorrent meta file. \"\"\" , aliases = [ \"m\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) magnet_parser . add_argument ( \"metafile\" , action = \"store\" , help = \"Path to Bittorrent meta file.\" , metavar = \"<*.torrent>\" , ) magnet_parser . set_defaults ( func = magnet ) check_parser = subparsers . add_parser ( \"recheck\" , help = \"\"\" Calculate amount of torrent meta file's content is found on disk. \"\"\" , aliases = [ \"r\" , \"check\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) check_parser . add_argument ( \"metafile\" , action = \"store\" , metavar = \"<*.torrent>\" , help = \"path to .torrent file.\" , ) check_parser . add_argument ( \"content\" , action = \"store\" , metavar = \"\" , help = \"path to content file or directory\" , ) check_parser . set_defaults ( func = recheck ) info_parser = subparsers . add_parser ( \"info\" , help = \"\"\" Show detailed information about a torrent file. \"\"\" , aliases = [ \"i\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) info_parser . add_argument ( \"metafile\" , action = \"store\" , metavar = \"<*.torrent>\" , help = \"path to pre-existing torrent file.\" , ) info_parser . set_defaults ( func = info ) args = parser . parse_args ( args ) if args . debug : activate_logger () if args . interactive : return select_action () return args . func ( args ) main () Initiate main function for CLI script. Source code in torrentfile\\cli.py def main (): \"\"\" Initiate main function for CLI script. \"\"\" execute () main_script ( args = None ) Initialize Command Line Interface for torrentfile. Parameters: Name Type Description Default args list Commandline arguments. default=None None Source code in torrentfile\\cli.py def execute ( args = None ): \"\"\" Initialize Command Line Interface for torrentfile. Parameters ---------- args : list Commandline arguments. default=None \"\"\" if not args : if sys . argv [ 1 :]: args = sys . argv [ 1 :] else : args = [ \"-h\" ] parser = ArgumentParser ( \"torrentfile\" , description = ( \"Command line tools for creating, editing, checking and \" \"interacting with Bittorrent metainfo files\" ), prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , conflict_handler = \"resolve\" , ) parser . add_argument ( \"-i\" , \"--interactive\" , action = \"store_true\" , dest = \"interactive\" , help = \"select program options interactively\" , ) parser . add_argument ( \"-V\" , \"--version\" , action = \"version\" , version = f \"torrentfile v { version } \" , help = \"show program version and exit\" , ) parser . add_argument ( \"-v\" , \"--verbose\" , action = \"store_true\" , dest = \"debug\" , help = \"output debug information\" , ) parser . set_defaults ( func = parser . print_help ) subparsers = parser . add_subparsers ( title = \"Actions\" , dest = \"command\" , metavar = \"create, edit, magnet, recheck\" , ) create_parser = subparsers . add_parser ( \"create\" , help = \"\"\"Generate a new torrent meta file.\"\"\" , prefix_chars = \"-\" , aliases = [ \"c\" , \"new\" ], formatter_class = TorrentFileHelpFormatter , ) create_parser . add_argument ( \"-a\" , \"-t\" , \"--announce\" , \"--tracker\" , action = \"store\" , dest = \"announce\" , metavar = \"\" , nargs = \"+\" , default = [], help = \"One or more space-seperated torrent tracker url(s).\" , ) create_parser . add_argument ( \"-p\" , \"--private\" , action = \"store_true\" , dest = \"private\" , help = \"Creates private torrent with multi-tracker and DHT turned off.\" , ) create_parser . add_argument ( \"-s\" , \"--source\" , action = \"store\" , dest = \"source\" , metavar = \"\" , help = \"Add a source string. Useful for cross-seeding.\" , ) create_parser . add_argument ( \"-m\" , \"--magnet\" , action = \"store_true\" , dest = \"magnet\" , ) create_parser . add_argument ( \"-c\" , \"--comment\" , action = \"store\" , dest = \"comment\" , metavar = \"\" , help = \"Include a comment in file metadata\" , ) create_parser . add_argument ( \"-o\" , \"--out\" , action = \"store\" , dest = \"outfile\" , metavar = \"\" , help = \"Output save path for created .torrent file\" , ) create_parser . add_argument ( \"--cwd\" , \"--current\" , action = \"store_true\" , dest = \"cwd\" , help = \"Save output .torrent file to current directory\" , ) create_parser . add_argument ( \"--progress\" , \"--prog\" , choices = [ \"0\" , \"1\" , \"2\" ], default = \"1\" , action = \"store\" , dest = \"progress\" , help = \"\"\" Set the progress bar level. Options = 0, 1, 2 (0) = Do not display progress bar. (1) = Display 1 progress bar for the whole torrent (default) (2) = Display a progress bar for each file going into torrent \"\"\" , ) create_parser . add_argument ( \"--meta-version\" , default = \"1\" , choices = [ \"1\" , \"2\" , \"3\" ], action = \"store\" , dest = \"meta_version\" , metavar = \"\" , help = \"\"\" Bittorrent metafile version. Options = 1, 2, 3 (1) = Bittorrent v1 (Default) (2) = Bittorrent v2 (3) = Bittorrent v1 & v2 hybrid \"\"\" , ) create_parser . add_argument ( \"--piece-length\" , action = \"store\" , dest = \"piece_length\" , metavar = \"\" , help = \"\"\" (Default: ) Number of bytes for per chunk of data transmitted by Bittorrent client. Acceptable values include integers 14-26 which will be interpreted as a perfect power of 2. e.g. 14 = 16KiB pieces. Examples:: [--piece-length 14] [--piece-length 20] \"\"\" , ) create_parser . add_argument ( \"-w\" , \"--web-seed\" , action = \"store\" , dest = \"url_list\" , metavar = \"\" , nargs = \"+\" , help = \"list of web addresses where torrent data exists (GetRight).\" , ) create_parser . add_argument ( \"--http-seed\" , action = \"store\" , dest = \"httpseeds\" , metavar = \"\" , nargs = \"+\" , help = \"list of URLs, addresses where content can be found (Hoffman).\" , ) create_parser . add_argument ( \"content\" , action = \"store\" , metavar = \"\" , nargs = \"?\" , help = \"Path to content file or directory\" , ) create_parser . set_defaults ( func = create ) edit_parser = subparsers . add_parser ( \"edit\" , help = \"\"\" Edit existing torrent meta file. \"\"\" , aliases = [ \"e\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) edit_parser . add_argument ( \"metafile\" , action = \"store\" , help = \"path to *.torrent file\" , metavar = \"<*.torrent>\" , ) edit_parser . add_argument ( \"--tracker\" , action = \"store\" , dest = \"announce\" , metavar = \"\" , nargs = \"+\" , help = \"\"\" Replace current list of tracker/announce urls with one or more space seperated Bittorrent tracker announce url(s). \"\"\" , ) edit_parser . add_argument ( \"--web-seed\" , action = \"store\" , dest = \"url_list\" , metavar = \"\" , nargs = \"+\" , help = \"Replace current list of web-seed urls with one or more url(s)\" , ) edit_parser . add_argument ( \"--http-seed\" , action = \"store\" , dest = \"httpseeds\" , metavar = \"\" , nargs = \"+\" , help = \"replace all currently listed addresses with new list (Hoffman).\" , ) edit_parser . add_argument ( \"--private\" , action = \"store_true\" , help = \"Make torrent private.\" , dest = \"private\" , ) edit_parser . add_argument ( \"--comment\" , help = \"Replaces any existing comment with \" , metavar = \"\" , dest = \"comment\" , action = \"store\" , ) edit_parser . add_argument ( \"--source\" , action = \"store\" , dest = \"source\" , metavar = \"\" , help = \"Replaces current source with \" , ) edit_parser . set_defaults ( func = edit ) magnet_parser = subparsers . add_parser ( \"magnet\" , help = \"\"\" Generate magnet url from an existing Bittorrent meta file. \"\"\" , aliases = [ \"m\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) magnet_parser . add_argument ( \"metafile\" , action = \"store\" , help = \"Path to Bittorrent meta file.\" , metavar = \"<*.torrent>\" , ) magnet_parser . set_defaults ( func = magnet ) check_parser = subparsers . add_parser ( \"recheck\" , help = \"\"\" Calculate amount of torrent meta file's content is found on disk. \"\"\" , aliases = [ \"r\" , \"check\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) check_parser . add_argument ( \"metafile\" , action = \"store\" , metavar = \"<*.torrent>\" , help = \"path to .torrent file.\" , ) check_parser . add_argument ( \"content\" , action = \"store\" , metavar = \"\" , help = \"path to content file or directory\" , ) check_parser . set_defaults ( func = recheck ) info_parser = subparsers . add_parser ( \"info\" , help = \"\"\" Show detailed information about a torrent file. \"\"\" , aliases = [ \"i\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) info_parser . add_argument ( \"metafile\" , action = \"store\" , metavar = \"<*.torrent>\" , help = \"path to pre-existing torrent file.\" , ) info_parser . set_defaults ( func = info ) args = parser . parse_args ( args ) if args . debug : activate_logger () if args . interactive : return select_action () return args . func ( args ) commands The commands module contains the Action Commands executed by the CLI script. Each function pertains to a command line action/subcommand and drives specific features of the application. Functions create_command info_command edit_command recheck_command magnet_command create ( args ) Execute the create CLI sub-command to create a new torrent metafile. Parameters: Name Type Description Default args list positional and optional CLI arguments. required Returns: Type Description torrentfile.MetaFile object containing the path to created metafile and its contents. Source code in torrentfile\\commands.py def create ( args : list ): \"\"\" Execute the create CLI sub-command to create a new torrent metafile. Parameters ---------- args : argparse.Namespace positional and optional CLI arguments. Returns ------- torrentfile.MetaFile object containing the path to created metafile and its contents. \"\"\" kwargs = vars ( args ) logger . debug ( \"create command invoked with %s \" , args . content ) if args . meta_version == \"2\" : torrent = TorrentFileV2 ( ** kwargs ) elif args . meta_version == \"3\" : torrent = TorrentFileHybrid ( ** kwargs ) else : torrent = TorrentFile ( ** kwargs ) outfile , meta = torrent . write () logger . debug ( \"torrent creation complete.\" ) if args . magnet : magnet ( outfile ) args . torrent = torrent args . kwargs = kwargs args . outfile = outfile args . meta = meta print ( \" \\n Output path: \" , os . path . abspath ( str ( outfile ))) logger . debug ( \"file saved to %s \" , str ( outfile )) return args edit ( args ) Execute the edit CLI sub-command with provided arguments. Parameters: Name Type Description Default args list positional and optional CLI arguments. required Returns: Type Description str path to edited torrent file. Source code in torrentfile\\commands.py def edit ( args : list ): \"\"\" Execute the edit CLI sub-command with provided arguments. Parameters ---------- args : Namespace positional and optional CLI arguments. Returns ------- str path to edited torrent file. \"\"\" metafile = args . metafile logger . info ( \"Editing %s Meta File\" , str ( args . metafile )) editargs = { \"url-list\" : args . url_list , \"httpseeds\" : args . httpseeds , \"announce\" : args . announce , \"source\" : args . source , \"private\" : args . private , \"comment\" : args . comment , } return edit_torrent ( metafile , editargs ) info ( args ) Show torrent metafile details to user via stdout. Parameters: Name Type Description Default args list command line arguements provided by the user. required Source code in torrentfile\\commands.py def info ( args : list ): \"\"\" Show torrent metafile details to user via stdout. Parameters ---------- args : dict command line arguements provided by the user. \"\"\" metafile = args . metafile meta = pyben . load ( metafile ) info = meta [ \"info\" ] del meta [ \"info\" ] meta . update ( info ) if \"private\" in meta and meta [ \"private\" ] == 1 : meta [ \"private\" ] = \"True\" if \"announce-list\" in meta : lst = meta [ \"announce-list\" ] meta [ \"announce-list\" ] = \", \" . join ([ j for i in lst for j in i ]) if \"url-list\" in meta : meta [ \"url-list\" ] = \", \" . join ( meta [ \"url-list\" ]) if \"httpseeds\" in meta : meta [ \"httpseeds\" ] = \", \" . join ( meta [ \"httpseeds\" ]) text = [] longest = max ([ len ( i ) for i in meta . keys ()]) for key , val in meta . items (): if key not in [ \"pieces\" , \"piece layers\" , \"files\" , \"file tree\" ]: prefix = longest - len ( key ) + 1 string = key + ( \" \" * prefix ) + str ( val ) text . append ( string ) most = max ([ len ( i ) for i in text ]) text = [ \"-\" * most , \" \\n \" ] + text + [ \" \\n \" , \"-\" * most ] output = \" \\n \" . join ( text ) print ( output ) return output magnet ( metafile ) Create a magnet URI from a Bittorrent meta file. Parameters: Name Type Description Default metafile (Namespace||str) Namespace class for CLI arguments. required Returns: Type Description str created magnet URI. Source code in torrentfile\\commands.py def magnet ( metafile ): \"\"\" Create a magnet URI from a Bittorrent meta file. Parameters ---------- metafile : (Namespace||str) Namespace class for CLI arguments. Returns ------- str created magnet URI. \"\"\" if hasattr ( metafile , \"metafile\" ): metafile = metafile . metafile if not os . path . exists ( metafile ): raise FileNotFoundError meta = pyben . load ( metafile ) info = meta [ \"info\" ] binfo = pyben . dumps ( info ) infohash = sha1 ( binfo ) . hexdigest () . upper () # nosec logger . info ( \"Magnet Info Hash: %s \" , infohash ) scheme = \"magnet:\" hasharg = \"?xt=urn:btih:\" + infohash namearg = \"&dn=\" + quote_plus ( info [ \"name\" ]) if \"announce-list\" in meta : announce_args = [ \"&tr=\" + quote_plus ( url ) for urllist in meta [ \"announce-list\" ] for url in urllist ] else : announce_args = [ \"&tr=\" + quote_plus ( meta [ \"announce\" ])] full_uri = \"\" . join ([ scheme , hasharg , namearg ] + announce_args ) logger . info ( \"Created Magnet URI %s \" , full_uri ) sys . stdout . write ( \" \\n \" + full_uri + \" \\n \" ) return full_uri recheck ( args ) Execute recheck CLI sub-command. Parameters: Name Type Description Default args Namespace positional and optional arguments. required Returns: Type Description str The percentage of content currently saved to disk. Source code in torrentfile\\commands.py def recheck ( args ): \"\"\" Execute recheck CLI sub-command. Parameters ---------- args : Namespace positional and optional arguments. Returns ------- str The percentage of content currently saved to disk. \"\"\" logger . debug ( \"Program entering Recheck mode.\" ) metafile = args . metafile content = args . content logger . debug ( \"Checking %s against %s contents\" , metafile , content ) checker = Checker ( metafile , content ) logger . debug ( \"Completed initialization of the Checker class\" ) result = checker . results () sys . stdout . write ( str ( result ) + \"% Match \\n \" ) sys . stdout . flush () return result edit Edit torrent module. Provides a facility by which certain properties of a torrent meta file can be edited by the user. The various command line arguments indicate which fields should be edited, and what the new value should be. Depending on what fields are chosen to edit, this command can trigger a new info hash which means the torrent will no longer be able to participate in the same swarm as the original unedited torrent. Keywords private comment source trackers web-seeds edit_torrent ( metafile , args ) Edit the properties and values in a torrent meta file. Parameters: Name Type Description Default metafile str path to the torrent meta file. required args dict key value pairs of the properties to be edited. required Returns: Type Description dict The edited and nested Meta and info dictionaries. Source code in torrentfile\\edit.py def edit_torrent ( metafile : str , args : dict ) -> dict : \"\"\" Edit the properties and values in a torrent meta file. Parameters ---------- metafile : str path to the torrent meta file. args : dict key value pairs of the properties to be edited. Returns ------- dict The edited and nested Meta and info dictionaries. \"\"\" logger . debug ( \"editing torrent file %s \" , metafile ) meta = pyben . load ( metafile ) info = meta [ \"info\" ] filter_empty ( args , meta , info ) if \"comment\" in args : info [ \"comment\" ] = args [ \"comment\" ] if \"source\" in args : info [ \"source\" ] = args [ \"source\" ] if \"private\" in args : info [ \"private\" ] = 1 if \"announce\" in args : val = args . get ( \"announce\" , None ) if isinstance ( val , str ): vallist = val . split () meta [ \"announce\" ] = vallist [ 0 ] meta [ \"announce-list\" ] = [ vallist ] elif isinstance ( val , list ): meta [ \"announce\" ] = val [ 0 ] meta [ \"announce-list\" ] = [ val ] if \"url-list\" in args : val = args . get ( \"url-list\" ) if isinstance ( val , str ): meta [ \"url-list\" ] = val . split () elif isinstance ( val , list ): meta [ \"url-list\" ] = val if \"httpseeds\" in args : val = args . get ( \"httpseeds\" ) if isinstance ( val , str ): meta [ \"httpseeds\" ] = val . split () elif isinstance ( val , list ): meta [ \"httpseeds\" ] = val meta [ \"info\" ] = info os . remove ( metafile ) pyben . dump ( meta , metafile ) return meta filter_empty ( args , meta , info ) Remove dictionary keys with empty values. Parameters: Name Type Description Default args dict Editable metafile properties from user. required meta dict Metafile data dictionary. required info dict Metafile info dictionary. required Source code in torrentfile\\edit.py def filter_empty ( args : dict , meta : dict , info : dict ): \"\"\" Remove dictionary keys with empty values. Parameters ---------- args : dict Editable metafile properties from user. meta : dict Metafile data dictionary. info : dict Metafile info dictionary. \"\"\" for key , val in list ( args . items ()): if val is None : del args [ key ] continue if val == \"\" : if key in meta : del meta [ key ] elif key in info : del info [ key ] del args [ key ] logger . debug ( \"removeing empty fields %s \" , val ) hasher Piece/File Hashers for Bittorrent meta file contents. Hasher ( CbMixin , ProgMixin ) Piece hasher for Bittorrent V1 files. Takes a sorted list of all file paths, calculates sha1 hash for fixed size pieces of file data from each file seemlessly until the last piece which may be smaller than others. Parameters: Name Type Description Default paths list List of files. required piece_length int Size of chuncks to split the data into. required progress int default = None None Source code in torrentfile\\hasher.py class Hasher ( CbMixin , ProgMixin ): \"\"\" Piece hasher for Bittorrent V1 files. Takes a sorted list of all file paths, calculates sha1 hash for fixed size pieces of file data from each file seemlessly until the last piece which may be smaller than others. Parameters ---------- paths : list List of files. piece_length : int Size of chuncks to split the data into. progress : int default = None \"\"\" def __init__ ( self , paths : list , piece_length : int , progress : int = None ): \"\"\"Generate hashes of piece length data from filelist contents.\"\"\" self . piece_length = piece_length self . paths = paths self . progress = progress self . total = sum ([ os . path . getsize ( i ) for i in self . paths ]) self . index = 0 self . current = open ( self . paths [ 0 ], \"rb\" ) if self . progress and self . progress == 2 : total = os . path . getsize ( self . paths [ 0 ]) self . prog_start ( total , self . paths [ 0 ], unit = \"bytes\" ) logger . debug ( \"Hashing v1 torrent file. Size: %s Piece Length: %s \" , humanize_bytes ( self . total ), humanize_bytes ( self . piece_length ), ) def __iter__ ( self ): \"\"\" Iterate through feed pieces. Returns ------- self : iterator Iterator for leaves/hash pieces. \"\"\" return self def _handle_partial ( self , arr : bytearray ) -> bytearray : \"\"\" Define the handling partial pieces that span 2 or more files. Parameters ---------- arr : bytearray Incomplete piece containing partial data Returns ------- digest : bytearray SHA1 digest of the complete piece. \"\"\" while len ( arr ) < self . piece_length and self . next_file (): target = self . piece_length - len ( arr ) temp = bytearray ( target ) size = self . current . readinto ( temp ) arr . extend ( temp [: size ]) self . prog_update ( size ) if size == target : break return sha1 ( arr ) . digest () # nosec def next_file ( self ) -> bool : \"\"\" Seemlessly transition to next file in file list. Returns ------- bool: True if there is a next file otherwise False. \"\"\" self . index += 1 self . prog_close () if self . index < len ( self . paths ): path = self . paths [ self . index ] self . current . close () if self . progress == 2 : self . prog_start ( os . path . getsize ( path ), path , self . progress , unit = \"bytes\" ) self . current = open ( path , \"rb\" ) return True return False def __next__ ( self ) -> bytes : \"\"\" Generate piece-length pieces of data from input file list. Returns ------- bytes SHA1 hash of the piece extracted. \"\"\" while True : piece = bytearray ( self . piece_length ) size = self . current . readinto ( piece ) if size == 0 : if not self . next_file (): raise StopIteration elif size < self . piece_length : self . prog_update ( size ) return self . _handle_partial ( piece [: size ]) else : self . prog_update ( size ) return sha1 ( piece ) . digest () # nosec __init__ ( self , paths , piece_length , progress = None ) special Generate hashes of piece length data from filelist contents. Source code in torrentfile\\hasher.py def __init__ ( self , paths : list , piece_length : int , progress : int = None ): \"\"\"Generate hashes of piece length data from filelist contents.\"\"\" self . piece_length = piece_length self . paths = paths self . progress = progress self . total = sum ([ os . path . getsize ( i ) for i in self . paths ]) self . index = 0 self . current = open ( self . paths [ 0 ], \"rb\" ) if self . progress and self . progress == 2 : total = os . path . getsize ( self . paths [ 0 ]) self . prog_start ( total , self . paths [ 0 ], unit = \"bytes\" ) logger . debug ( \"Hashing v1 torrent file. Size: %s Piece Length: %s \" , humanize_bytes ( self . total ), humanize_bytes ( self . piece_length ), ) __iter__ ( self ) special Iterate through feed pieces. Returns: Type Description iterator Iterator for leaves/hash pieces. Source code in torrentfile\\hasher.py def __iter__ ( self ): \"\"\" Iterate through feed pieces. Returns ------- self : iterator Iterator for leaves/hash pieces. \"\"\" return self __next__ ( self ) special Generate piece-length pieces of data from input file list. Returns: Type Description bytes SHA1 hash of the piece extracted. Source code in torrentfile\\hasher.py def __next__ ( self ) -> bytes : \"\"\" Generate piece-length pieces of data from input file list. Returns ------- bytes SHA1 hash of the piece extracted. \"\"\" while True : piece = bytearray ( self . piece_length ) size = self . current . readinto ( piece ) if size == 0 : if not self . next_file (): raise StopIteration elif size < self . piece_length : self . prog_update ( size ) return self . _handle_partial ( piece [: size ]) else : self . prog_update ( size ) return sha1 ( piece ) . digest () # nosec next_file ( self ) Seemlessly transition to next file in file list. Returns: Type Description bool True if there is a next file otherwise False. Source code in torrentfile\\hasher.py def next_file ( self ) -> bool : \"\"\" Seemlessly transition to next file in file list. Returns ------- bool: True if there is a next file otherwise False. \"\"\" self . index += 1 self . prog_close () if self . index < len ( self . paths ): path = self . paths [ self . index ] self . current . close () if self . progress == 2 : self . prog_start ( os . path . getsize ( path ), path , self . progress , unit = \"bytes\" ) self . current = open ( path , \"rb\" ) return True return False HasherHybrid ( CbMixin , ProgMixin ) Calculate root and piece hashes for creating hybrid torrent file. Create merkle tree layers from sha256 hashed 16KiB blocks of contents. With a branching factor of 2, merge layer hashes until blocks equal piece_length bytes for the piece layer, and then the root hash. Parameters: Name Type Description Default path str path to target file. required piece_length int piece length for data chunks. required progress int default = None None Source code in torrentfile\\hasher.py class HasherHybrid ( CbMixin , ProgMixin ): \"\"\" Calculate root and piece hashes for creating hybrid torrent file. Create merkle tree layers from sha256 hashed 16KiB blocks of contents. With a branching factor of 2, merge layer hashes until blocks equal piece_length bytes for the piece layer, and then the root hash. Parameters ---------- path : str path to target file. piece_length : int piece length for data chunks. progress : int default = None \"\"\" def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Construct Hasher class instances for each file in torrent. \"\"\" self . path = path self . piece_length = piece_length self . pieces = [] self . layer_hashes = [] self . piece_layer = None self . root = None self . padding_piece = None self . padding_file = None if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . amount = piece_length // BLOCK_SIZE logger . debug ( \"Hashing: %s , Piece Size: %s \" , str ( self . path ), humanize_bytes ( self . piece_length ), ) with open ( path , \"rb\" ) as data : self . process_file ( data ) def _pad_remaining ( self , block_count : int ): \"\"\" Generate Hash sized, 0 filled bytes for padding. Parameters ---------- block_count : int current total number of blocks collected. Returns ------- padding : bytes Padding to fill remaining portion of tree. \"\"\" # when the there is only one block for file remaining = self . amount - block_count if not self . layer_hashes : power2 = next_power_2 ( block_count ) remaining = power2 - block_count self . prog_update ( HASH_SIZE * remaining ) return [ bytes ( HASH_SIZE ) for _ in range ( remaining )] def process_file ( self , data : bytearray ): \"\"\" Calculate layer hashes for contents of file. Parameters ---------- data : BytesIO File opened in read mode. \"\"\" while True : plength = self . piece_length blocks = [] piece = sha1 () # nosec total = 0 block = bytearray ( BLOCK_SIZE ) for _ in range ( self . amount ): size = data . readinto ( block ) self . prog_update ( size ) if not size : break total += size plength -= size blocks . append ( sha256 ( block [: size ]) . digest ()) piece . update ( block [: size ]) if not blocks : break if len ( blocks ) != self . amount : padding = self . _pad_remaining ( len ( blocks )) blocks . extend ( padding ) layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) if plength > 0 : self . padding_file = { \"attr\" : \"p\" , \"length\" : plength , \"path\" : [ \".pad\" , str ( plength )], } piece . update ( bytes ( plength )) self . pieces . append ( piece . digest ()) # nosec self . _calculate_root () self . prog_close () def _calculate_root ( self ): \"\"\" Calculate the root hash for opened file. \"\"\" self . piece_layer = b \"\" . join ( self . layer_hashes ) if len ( self . layer_hashes ) > 1 : pad_piece = merkle_root ([ bytes ( 32 ) for _ in range ( self . amount )]) pow2 = next_power_2 ( len ( self . layer_hashes )) remainder = pow2 - len ( self . layer_hashes ) self . layer_hashes += [ pad_piece for _ in range ( remainder )] self . root = merkle_root ( self . layer_hashes ) __init__ ( self , path , piece_length , progress = None ) special Construct Hasher class instances for each file in torrent. Source code in torrentfile\\hasher.py def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Construct Hasher class instances for each file in torrent. \"\"\" self . path = path self . piece_length = piece_length self . pieces = [] self . layer_hashes = [] self . piece_layer = None self . root = None self . padding_piece = None self . padding_file = None if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . amount = piece_length // BLOCK_SIZE logger . debug ( \"Hashing: %s , Piece Size: %s \" , str ( self . path ), humanize_bytes ( self . piece_length ), ) with open ( path , \"rb\" ) as data : self . process_file ( data ) process_file ( self , data ) Calculate layer hashes for contents of file. Parameters: Name Type Description Default data bytearray File opened in read mode. required Source code in torrentfile\\hasher.py def process_file ( self , data : bytearray ): \"\"\" Calculate layer hashes for contents of file. Parameters ---------- data : BytesIO File opened in read mode. \"\"\" while True : plength = self . piece_length blocks = [] piece = sha1 () # nosec total = 0 block = bytearray ( BLOCK_SIZE ) for _ in range ( self . amount ): size = data . readinto ( block ) self . prog_update ( size ) if not size : break total += size plength -= size blocks . append ( sha256 ( block [: size ]) . digest ()) piece . update ( block [: size ]) if not blocks : break if len ( blocks ) != self . amount : padding = self . _pad_remaining ( len ( blocks )) blocks . extend ( padding ) layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) if plength > 0 : self . padding_file = { \"attr\" : \"p\" , \"length\" : plength , \"path\" : [ \".pad\" , str ( plength )], } piece . update ( bytes ( plength )) self . pieces . append ( piece . digest ()) # nosec self . _calculate_root () self . prog_close () HasherV2 ( CbMixin , ProgMixin ) Calculate the root hash and piece layers for file contents. Iterates over 16KiB blocks of data from given file, hashes the data, then creates a hash tree from the individual block hashes until size of hashed data equals the piece-length. Then continues the hash tree until root hash is calculated. Parameters: Name Type Description Default path str Path to file. required piece_length int Size of layer hashes pieces. required progress int default = None None Source code in torrentfile\\hasher.py class HasherV2 ( CbMixin , ProgMixin ): \"\"\" Calculate the root hash and piece layers for file contents. Iterates over 16KiB blocks of data from given file, hashes the data, then creates a hash tree from the individual block hashes until size of hashed data equals the piece-length. Then continues the hash tree until root hash is calculated. Parameters ---------- path : str Path to file. piece_length : int Size of layer hashes pieces. progress : int default = None \"\"\" def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Calculate and store hash information for specific file. \"\"\" self . path = path self . root = None self . piece_layer = None self . layer_hashes = [] self . piece_length = piece_length self . num_blocks = piece_length // BLOCK_SIZE if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) logger . debug ( \"Hashing partial v2 torrent file. Piece Length: %s Path: %s \" , humanize_bytes ( self . piece_length ), str ( self . path ), ) with open ( self . path , \"rb\" ) as fd : self . process_file ( fd ) def process_file ( self , fd : str ): \"\"\" Calculate hashes over 16KiB chuncks of file content. Parameters ---------- fd : str Opened file in read mode. \"\"\" while True : blocks = [] leaf = bytearray ( BLOCK_SIZE ) # generate leaves of merkle tree for _ in range ( self . num_blocks ): size = fd . readinto ( leaf ) self . prog_update ( size ) if not size : break blocks . append ( sha256 ( leaf [: size ]) . digest ()) # blocks is empty mean eof if not blocks : break if len ( blocks ) != self . num_blocks : # when size of file doesn't fill the last block # when the file contains multiple pieces remaining = self . num_blocks - len ( blocks ) if not self . layer_hashes : # when the there is only one block for file power2 = next_power_2 ( len ( blocks )) remaining = power2 - len ( blocks ) # pad the the rest with zeroes to fill remaining space. padding = [ bytes ( 32 ) for _ in range ( remaining )] self . prog_update ( HASH_SIZE * remaining ) blocks . extend ( padding ) # calculate the root hash for the merkle tree up to piece-length layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) self . _calculate_root () self . prog_close () def _calculate_root ( self ): \"\"\" Calculate root hash for the target file. \"\"\" self . piece_layer = b \"\" . join ( self . layer_hashes ) hashes = len ( self . layer_hashes ) if hashes > 1 : pow2 = next_power_2 ( hashes ) remainder = pow2 - hashes pad_piece = [ bytes ( HASH_SIZE ) for _ in range ( self . num_blocks )] for _ in range ( remainder ): self . layer_hashes . append ( merkle_root ( pad_piece )) self . root = merkle_root ( self . layer_hashes ) __init__ ( self , path , piece_length , progress = None ) special Calculate and store hash information for specific file. Source code in torrentfile\\hasher.py def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Calculate and store hash information for specific file. \"\"\" self . path = path self . root = None self . piece_layer = None self . layer_hashes = [] self . piece_length = piece_length self . num_blocks = piece_length // BLOCK_SIZE if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) logger . debug ( \"Hashing partial v2 torrent file. Piece Length: %s Path: %s \" , humanize_bytes ( self . piece_length ), str ( self . path ), ) with open ( self . path , \"rb\" ) as fd : self . process_file ( fd ) process_file ( self , fd ) Calculate hashes over 16KiB chuncks of file content. Parameters: Name Type Description Default fd str Opened file in read mode. required Source code in torrentfile\\hasher.py def process_file ( self , fd : str ): \"\"\" Calculate hashes over 16KiB chuncks of file content. Parameters ---------- fd : str Opened file in read mode. \"\"\" while True : blocks = [] leaf = bytearray ( BLOCK_SIZE ) # generate leaves of merkle tree for _ in range ( self . num_blocks ): size = fd . readinto ( leaf ) self . prog_update ( size ) if not size : break blocks . append ( sha256 ( leaf [: size ]) . digest ()) # blocks is empty mean eof if not blocks : break if len ( blocks ) != self . num_blocks : # when size of file doesn't fill the last block # when the file contains multiple pieces remaining = self . num_blocks - len ( blocks ) if not self . layer_hashes : # when the there is only one block for file power2 = next_power_2 ( len ( blocks )) remaining = power2 - len ( blocks ) # pad the the rest with zeroes to fill remaining space. padding = [ bytes ( 32 ) for _ in range ( remaining )] self . prog_update ( HASH_SIZE * remaining ) blocks . extend ( padding ) # calculate the root hash for the merkle tree up to piece-length layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) self . _calculate_root () self . prog_close () merkle_root ( blocks ) Calculate the merkle root for a seq of sha256 hash digests. Source code in torrentfile\\hasher.py def merkle_root ( blocks : list ) -> bytes : \"\"\" Calculate the merkle root for a seq of sha256 hash digests. \"\"\" if blocks : while len ( blocks ) > 1 : blocks = [ sha256 ( x + y ) . digest () for x , y in zip ( * [ iter ( blocks )] * 2 ) ] return blocks [ 0 ] return blocks interactive Module contains the procedures used for Interactive Mode. InteractiveCreator Class namespace for interactive program options. Source code in torrentfile\\interactive.py class InteractiveCreator : \"\"\" Class namespace for interactive program options. \"\"\" def __init__ ( self ): \"\"\" Initialize interactive meta file creator dialog. \"\"\" self . kwargs = { \"announce\" : None , \"url_list\" : None , \"private\" : None , \"source\" : None , \"comment\" : None , \"piece_length\" : None , \"outfile\" : None , \"path\" : None , \"httpseeds\" : None , } self . outfile , self . meta = self . get_props () def get_props ( self ): \"\"\" Gather details for torrentfile from user. \"\"\" piece_length = get_input ( \"Piece Length (empty=auto): \" , lambda x : x . isdigit () ) self . kwargs [ \"piece_length\" ] = piece_length announce = get_input ( \"Tracker list (empty): \" , lambda x : isinstance ( x , str ) ) if announce : self . kwargs [ \"announce\" ] = announce . split () url_list = get_input ( \"Web Seed {GetRight} list (empty): \" , lambda x : isinstance ( x , str ) ) httpseeds = get_input ( \"Web Seed {Hoffman} list (empty): \" , lambda x : isinstance ( x , str ) ) if url_list : self . kwargs [ \"url_list\" ] = url_list . split () if httpseeds : self . kwargs [ \"httpseeds\" ] = httpseeds . split () comment = get_input ( \"Comment (empty): \" , None ) if comment : self . kwargs [ \"comment\" ] = comment source = get_input ( \"Source (empty): \" , None ) if source : self . kwargs [ \"source\" ] = source private = get_input ( \"Private Torrent? {Y/N}: (N)\" , lambda x : x in \"yYnN\" ) if private and private . lower () == \"y\" : self . kwargs [ \"private\" ] = 1 contents = get_input ( \"Content Path: \" , os . path . exists ) self . kwargs [ \"path\" ] = contents outfile = get_input ( f \"Output Path ( { contents } .torrent): \" , lambda x : os . path . exists ( os . path . dirname ( x )), ) if outfile : self . kwargs [ \"outfile\" ] = outfile meta_version = get_input ( \"Meta Version {1,2,3}: (1)\" , lambda x : x in \"123\" ) showcenter ( f \"creating { outfile } \" ) if meta_version == \"3\" : torrent = TorrentFileHybrid ( ** self . kwargs ) elif meta_version == \"2\" : torrent = TorrentFileV2 ( ** self . kwargs ) else : torrent = TorrentFile ( ** self . kwargs ) return torrent . write () __init__ ( self ) special Initialize interactive meta file creator dialog. Source code in torrentfile\\interactive.py def __init__ ( self ): \"\"\" Initialize interactive meta file creator dialog. \"\"\" self . kwargs = { \"announce\" : None , \"url_list\" : None , \"private\" : None , \"source\" : None , \"comment\" : None , \"piece_length\" : None , \"outfile\" : None , \"path\" : None , \"httpseeds\" : None , } self . outfile , self . meta = self . get_props () get_props ( self ) Gather details for torrentfile from user. Source code in torrentfile\\interactive.py def get_props ( self ): \"\"\" Gather details for torrentfile from user. \"\"\" piece_length = get_input ( \"Piece Length (empty=auto): \" , lambda x : x . isdigit () ) self . kwargs [ \"piece_length\" ] = piece_length announce = get_input ( \"Tracker list (empty): \" , lambda x : isinstance ( x , str ) ) if announce : self . kwargs [ \"announce\" ] = announce . split () url_list = get_input ( \"Web Seed {GetRight} list (empty): \" , lambda x : isinstance ( x , str ) ) httpseeds = get_input ( \"Web Seed {Hoffman} list (empty): \" , lambda x : isinstance ( x , str ) ) if url_list : self . kwargs [ \"url_list\" ] = url_list . split () if httpseeds : self . kwargs [ \"httpseeds\" ] = httpseeds . split () comment = get_input ( \"Comment (empty): \" , None ) if comment : self . kwargs [ \"comment\" ] = comment source = get_input ( \"Source (empty): \" , None ) if source : self . kwargs [ \"source\" ] = source private = get_input ( \"Private Torrent? {Y/N}: (N)\" , lambda x : x in \"yYnN\" ) if private and private . lower () == \"y\" : self . kwargs [ \"private\" ] = 1 contents = get_input ( \"Content Path: \" , os . path . exists ) self . kwargs [ \"path\" ] = contents outfile = get_input ( f \"Output Path ( { contents } .torrent): \" , lambda x : os . path . exists ( os . path . dirname ( x )), ) if outfile : self . kwargs [ \"outfile\" ] = outfile meta_version = get_input ( \"Meta Version {1,2,3}: (1)\" , lambda x : x in \"123\" ) showcenter ( f \"creating { outfile } \" ) if meta_version == \"3\" : torrent = TorrentFileHybrid ( ** self . kwargs ) elif meta_version == \"2\" : torrent = TorrentFileV2 ( ** self . kwargs ) else : torrent = TorrentFile ( ** self . kwargs ) return torrent . write () InteractiveEditor Interactive dialog class for torrent editing. Source code in torrentfile\\interactive.py class InteractiveEditor : \"\"\" Interactive dialog class for torrent editing. \"\"\" def __init__ ( self , metafile : str ): \"\"\" Initialize the Interactive torrent editor guide. Parameters ---------- metafile : str user input string identifying the path to a torrent meta file. \"\"\" self . metafile = metafile self . meta = pyben . load ( metafile ) self . info = self . meta [ \"info\" ] self . args = { \"url-list\" : self . meta . get ( \"url-list\" , None ), \"httpseeds\" : self . meta . get ( \"httpseeds\" , None ), \"announce\" : self . meta . get ( \"announce-list\" , None ), \"source\" : self . info . get ( \"source\" , None ), \"private\" : self . info . get ( \"private\" , None ), \"comment\" : self . info . get ( \"comment\" , None ), } def show_current ( self ): \"\"\" Display the current met file information to screen. \"\"\" out = \"Current properties and values: \\n \" longest = max ([ len ( label ) for label in self . args ]) + 3 for key , val in self . args . items (): txt = ( key . title () + \":\" ) . ljust ( longest ) + str ( val ) out += f \" \\t { txt } \\n \" showtext ( out ) def sanatize_response ( self , key , response ): \"\"\" Convert the input data into a form recognizable by the program. Parameters ---------- key : str name of the property and attribute being eddited. response : str User input value the property is being edited to. \"\"\" if key in [ \"announce\" , \"url-list\" , \"httpseeds\" ]: val = response . split () else : val = response self . args [ key ] = val def edit_props ( self ): \"\"\" Loop continuosly for edits until user signals DONE. \"\"\" while True : showcenter ( \"Choose the number for a propert the needs editing.\" \"Enter DONE when all editing has been completed.\" ) props = { 1 : \"comment\" , 2 : \"source\" , 3 : \"private\" , 4 : \"tracker\" , 5 : \"web-seed\" , 6 : \"httpseeds\" , } args = { 1 : \"comment\" , 2 : \"source\" , 3 : \"private\" , 4 : \"announce\" , 5 : \"url-list\" , 6 : \"httpseeds\" , } txt = \", \" . join (( str ( k ) + \": \" + v ) for k , v in props . items ()) prop = get_input ( txt ) if prop . lower () == \"done\" : break if prop . isdigit () and 0 < int ( prop ) < 6 : key = props [ int ( prop )] key2 = args [ int ( prop )] val = self . args . get ( key2 ) showtext ( \"Enter new property value or leave empty for no value.\" ) response = get_input ( f \" { key . title () } ( { val } ): \" ) self . sanatize_response ( key2 , response ) else : showtext ( \"Invalid input: Try again.\" ) edit_torrent ( self . metafile , self . args ) __init__ ( self , metafile ) special Initialize the Interactive torrent editor guide. Parameters: Name Type Description Default metafile str user input string identifying the path to a torrent meta file. required Source code in torrentfile\\interactive.py def __init__ ( self , metafile : str ): \"\"\" Initialize the Interactive torrent editor guide. Parameters ---------- metafile : str user input string identifying the path to a torrent meta file. \"\"\" self . metafile = metafile self . meta = pyben . load ( metafile ) self . info = self . meta [ \"info\" ] self . args = { \"url-list\" : self . meta . get ( \"url-list\" , None ), \"httpseeds\" : self . meta . get ( \"httpseeds\" , None ), \"announce\" : self . meta . get ( \"announce-list\" , None ), \"source\" : self . info . get ( \"source\" , None ), \"private\" : self . info . get ( \"private\" , None ), \"comment\" : self . info . get ( \"comment\" , None ), } edit_props ( self ) Loop continuosly for edits until user signals DONE. Source code in torrentfile\\interactive.py def edit_props ( self ): \"\"\" Loop continuosly for edits until user signals DONE. \"\"\" while True : showcenter ( \"Choose the number for a propert the needs editing.\" \"Enter DONE when all editing has been completed.\" ) props = { 1 : \"comment\" , 2 : \"source\" , 3 : \"private\" , 4 : \"tracker\" , 5 : \"web-seed\" , 6 : \"httpseeds\" , } args = { 1 : \"comment\" , 2 : \"source\" , 3 : \"private\" , 4 : \"announce\" , 5 : \"url-list\" , 6 : \"httpseeds\" , } txt = \", \" . join (( str ( k ) + \": \" + v ) for k , v in props . items ()) prop = get_input ( txt ) if prop . lower () == \"done\" : break if prop . isdigit () and 0 < int ( prop ) < 6 : key = props [ int ( prop )] key2 = args [ int ( prop )] val = self . args . get ( key2 ) showtext ( \"Enter new property value or leave empty for no value.\" ) response = get_input ( f \" { key . title () } ( { val } ): \" ) self . sanatize_response ( key2 , response ) else : showtext ( \"Invalid input: Try again.\" ) edit_torrent ( self . metafile , self . args ) sanatize_response ( self , key , response ) Convert the input data into a form recognizable by the program. Parameters: Name Type Description Default key str name of the property and attribute being eddited. required response str User input value the property is being edited to. required Source code in torrentfile\\interactive.py def sanatize_response ( self , key , response ): \"\"\" Convert the input data into a form recognizable by the program. Parameters ---------- key : str name of the property and attribute being eddited. response : str User input value the property is being edited to. \"\"\" if key in [ \"announce\" , \"url-list\" , \"httpseeds\" ]: val = response . split () else : val = response self . args [ key ] = val show_current ( self ) Display the current met file information to screen. Source code in torrentfile\\interactive.py def show_current ( self ): \"\"\" Display the current met file information to screen. \"\"\" out = \"Current properties and values: \\n \" longest = max ([ len ( label ) for label in self . args ]) + 3 for key , val in self . args . items (): txt = ( key . title () + \":\" ) . ljust ( longest ) + str ( val ) out += f \" \\t { txt } \\n \" showtext ( out ) create_torrent () Create new torrent file interactively. Source code in torrentfile\\interactive.py def create_torrent (): \"\"\" Create new torrent file interactively. \"\"\" showcenter ( \"Create Torrent\" ) showtext ( \" \\n Enter values for each of the options for the torrent creator, \" \"or leave blank for program defaults. \\n Spaces are considered item \" \"seperators for options that accept a list of values. \\n Values \" \"enclosed in () indicate the default value, while {} holds all \" \"valid choices available for the option. \\n\\n \" ) creator = InteractiveCreator () return creator edit_action () Edit the editable values of the torrent meta file. Source code in torrentfile\\interactive.py def edit_action (): \"\"\" Edit the editable values of the torrent meta file. \"\"\" showcenter ( \"Edit Torrent\" ) metafile = get_input ( \"Metafile(.torrent): \" , os . path . exists ) dialog = InteractiveEditor ( metafile ) dialog . show_current () dialog . edit_props () get_input ( * args ) Determine appropriate input function to call. Parameters: Name Type Description Default args tuple Arbitrary number of args to pass to next function () Returns: Type Description str The results of the function call. Source code in torrentfile\\interactive.py def get_input ( * args : tuple ): # pragma: no cover \"\"\" Determine appropriate input function to call. Parameters ---------- args : tuple Arbitrary number of args to pass to next function Returns ------- str The results of the function call. \"\"\" if len ( args ) == 2 : return _get_input_loop ( * args ) return _get_input ( * args ) recheck_torrent () Check torrent download completed percentage. Source code in torrentfile\\interactive.py def recheck_torrent (): \"\"\" Check torrent download completed percentage. \"\"\" showcenter ( \"Check Torrent\" ) msg = \"Enter path to torrent contents, and corresponding torrent metafile.\" showtext ( msg ) metafile = get_input ( \"Conent Path (downloads/complete/torrentname):\" , os . path . exists ) contents = get_input ( \"Metafile (*.torrent): \" , os . path . exists ) checker = Checker ( metafile , contents ) results = checker . results () showtext ( f \"Completion for { metafile } is { results } %\" ) return results select_action () Operate TorrentFile program interactively through terminal. Source code in torrentfile\\interactive.py def select_action (): \"\"\" Operate TorrentFile program interactively through terminal. \"\"\" showcenter ( \"TorrentFile: Starting Interactive Mode\" ) action = get_input ( \"Enter the action you wish to perform. \\n \" \"Action ( Create (c) | Edit (e) | Recheck (r) ): \" ) action = action . lower () if \"create\" in action or action == \"c\" : return create_torrent () if \"check\" in action or action == \"r\" : return recheck_torrent () if \"edit\" in action or action == \"e\" : return edit_action () print ( \"Unable to recognize input. Please try again.\" ) # pragma: nocover return select_action () # pragma: nocover showcenter ( txt ) Print text to screen in the center position of the terminal. Parameters: Name Type Description Default txt str the preformated message to send to stdout. required Source code in torrentfile\\interactive.py def showcenter ( txt : str ): \"\"\" Print text to screen in the center position of the terminal. Parameters ---------- txt : str the preformated message to send to stdout. \"\"\" termlen = shutil . get_terminal_size () . columns padding = \" \" * int ((( termlen - len ( txt )) / 2 )) string = \"\" . join ([ \" \\n \" , padding , txt , \" \\n \" ]) showtext ( string ) showtext ( txt ) Print contents of txt to screen. Parameters: Name Type Description Default txt str text to print to terminal. required Source code in torrentfile\\interactive.py def showtext ( txt ): \"\"\" Print contents of txt to screen. Parameters ---------- txt : str text to print to terminal. \"\"\" sys . stdout . write ( txt ) mixins Collection of classes that can be used as Mixins with other base classes. Classes such as TorrentFile, TorrentFilev2, and all Hasher classes can use the progress bar mixin. And any class is eligible to use the callback mixin. CbMixin Mixin class to set a callback during hashing procedure. Source code in torrentfile\\mixins.py class CbMixin : \"\"\" Mixin class to set a callback during hashing procedure. \"\"\" _cb = None @classmethod def set_callback ( cls , func ): \"\"\" Assign a callback to the Hashing class. Parameters ---------- func : function the callback function \"\"\" cls . _cb = func # pragma: nocover set_callback ( func ) classmethod Assign a callback to the Hashing class. Parameters: Name Type Description Default func function the callback function required Source code in torrentfile\\mixins.py @classmethod def set_callback ( cls , func ): \"\"\" Assign a callback to the Hashing class. Parameters ---------- func : function the callback function \"\"\" cls . _cb = func # pragma: nocover ProgMixin Progress bar mixin class. Displays progress of hashing individual files, usefull when hashing really big files. Methods prog_start prog_update prog_close Source code in torrentfile\\mixins.py class ProgMixin : \"\"\" Progress bar mixin class. Displays progress of hashing individual files, usefull when hashing really big files. Methods ------- prog_start prog_update prog_close \"\"\" def prog_start ( self , total , path , length = 50 , unit = None ): \"\"\" Generate a new progress bar for the given file path. Parameters ---------- total : int the total amount of units accumulating towards. path : str path to file being hashed. length : int the number of characters of the actual progress bar. unit : str the text representation of the value being measured. \"\"\" title = os . path . basename ( path ) width = shutil . get_terminal_size () . columns length = min ( length , width // 2 ) start = width - int ( length * 1.5 ) self . prog = ProgressBar ( total , title , length , unit , start ) def prog_update ( self , val ): \"\"\" Update progress bar with given amount of progress. Parameters ---------- val : int the number of bytes count the progress bar should increase. \"\"\" if self . is_active (): self . prog . increment ( val ) pbar = self . prog . pbar () output = f \" { self . prog . prefix }{ pbar }{ self . prog . suffix } \\r \" sys . stdout . write ( output ) sys . stdout . flush () def prog_close ( self ): \"\"\" Finalize the last bits of progress bar. Increment the terminal by one line leaving the progress bar in place, and deleting the progress bar object to clear a space for the next one. \"\"\" if self . is_active (): sys . stdout . flush () sys . stdout . write ( \" \\n \" ) del self . prog def is_active ( self ): \"\"\" Test to see if there is an active progress bar for object. Returns ------- bool : True if there is, otherwise False. \"\"\" if hasattr ( self , \"prog\" ): return True return False is_active ( self ) Test to see if there is an active progress bar for object. Source code in torrentfile\\mixins.py def is_active ( self ): \"\"\" Test to see if there is an active progress bar for object. Returns ------- bool : True if there is, otherwise False. \"\"\" if hasattr ( self , \"prog\" ): return True return False prog_close ( self ) Finalize the last bits of progress bar. Increment the terminal by one line leaving the progress bar in place, and deleting the progress bar object to clear a space for the next one. Source code in torrentfile\\mixins.py def prog_close ( self ): \"\"\" Finalize the last bits of progress bar. Increment the terminal by one line leaving the progress bar in place, and deleting the progress bar object to clear a space for the next one. \"\"\" if self . is_active (): sys . stdout . flush () sys . stdout . write ( \" \\n \" ) del self . prog prog_start ( self , total , path , length = 50 , unit = None ) Generate a new progress bar for the given file path. Parameters: Name Type Description Default total int the total amount of units accumulating towards. required path str path to file being hashed. required length int the number of characters of the actual progress bar. 50 unit str the text representation of the value being measured. None Source code in torrentfile\\mixins.py def prog_start ( self , total , path , length = 50 , unit = None ): \"\"\" Generate a new progress bar for the given file path. Parameters ---------- total : int the total amount of units accumulating towards. path : str path to file being hashed. length : int the number of characters of the actual progress bar. unit : str the text representation of the value being measured. \"\"\" title = os . path . basename ( path ) width = shutil . get_terminal_size () . columns length = min ( length , width // 2 ) start = width - int ( length * 1.5 ) self . prog = ProgressBar ( total , title , length , unit , start ) prog_update ( self , val ) Update progress bar with given amount of progress. Parameters: Name Type Description Default val int the number of bytes count the progress bar should increase. required Source code in torrentfile\\mixins.py def prog_update ( self , val ): \"\"\" Update progress bar with given amount of progress. Parameters ---------- val : int the number of bytes count the progress bar should increase. \"\"\" if self . is_active (): self . prog . increment ( val ) pbar = self . prog . pbar () output = f \" { self . prog . prefix }{ pbar }{ self . prog . suffix } \\r \" sys . stdout . write ( output ) sys . stdout . flush () ProgressBar Holds the state and details of the terminal progress bars. Parameters: Name Type Description Default total int the total amount to be accumulated. required title str the subject of the progress tracker required length int the width of the progress bar required unit str the text representation incremented required Source code in torrentfile\\mixins.py class ProgressBar : \"\"\" Holds the state and details of the terminal progress bars. Parameters ---------- total : int the total amount to be accumulated. title : str the subject of the progress tracker length : int the width of the progress bar unit : str the text representation incremented \"\"\" def __init__ ( self , total , title , length , unit , start ): \"\"\" Construct the progress bar object and store state of it's properties. \"\"\" self . total = total self . start = start self . length = length self . fill = chr ( 9608 ) self . empty = chr ( 9617 ) self . state = 0 self . unit = unit if not unit : self . unit = \"\" # pragma: nocover elif unit == \"bytes\" : if self . total > 10000000 : total = math . floor ( self . total / 1048576 ) self . unit = \"MiB\" elif self . total > 10000 : total = math . floor ( self . total / 1024 ) self . unit = \"KiB\" self . suffix = f \"/ { total } { self . unit } \" if len ( title ) > start : title = title [: start - 1 ] padding = ( start - len ( title )) * \" \" self . prefix = \"\" . join ([ title , padding ]) def increment ( self , value ): \"\"\" Increase the state of the progress bar value. Parameters ---------- value : int the amount to increment the state by. \"\"\" self . state += value def pbar ( self ): \"\"\" Return the size of the filled portion of the progress bar. Returns ------- str : the progress bar characters \"\"\" if self . state >= self . total : fill = self . length else : fill = math . ceil (( self . state / self . total ) * self . length ) empty = self . length - fill if self . unit == \"MiB\" : state = math . floor ( self . state / 1048576 ) elif self . unit == \"KiB\" : state = math . floor ( self . state / 1024 ) else : state = self . state progbar = [ \"[\" , self . fill * fill , self . empty * empty , \"] \" , str ( state )] return \"\" . join ( progbar ) __init__ ( self , total , title , length , unit , start ) special Construct the progress bar object and store state of it's properties. Source code in torrentfile\\mixins.py def __init__ ( self , total , title , length , unit , start ): \"\"\" Construct the progress bar object and store state of it's properties. \"\"\" self . total = total self . start = start self . length = length self . fill = chr ( 9608 ) self . empty = chr ( 9617 ) self . state = 0 self . unit = unit if not unit : self . unit = \"\" # pragma: nocover elif unit == \"bytes\" : if self . total > 10000000 : total = math . floor ( self . total / 1048576 ) self . unit = \"MiB\" elif self . total > 10000 : total = math . floor ( self . total / 1024 ) self . unit = \"KiB\" self . suffix = f \"/ { total } { self . unit } \" if len ( title ) > start : title = title [: start - 1 ] padding = ( start - len ( title )) * \" \" self . prefix = \"\" . join ([ title , padding ]) increment ( self , value ) Increase the state of the progress bar value. Parameters: Name Type Description Default value int the amount to increment the state by. required Source code in torrentfile\\mixins.py def increment ( self , value ): \"\"\" Increase the state of the progress bar value. Parameters ---------- value : int the amount to increment the state by. \"\"\" self . state += value pbar ( self ) Return the size of the filled portion of the progress bar. Source code in torrentfile\\mixins.py def pbar ( self ): \"\"\" Return the size of the filled portion of the progress bar. Returns ------- str : the progress bar characters \"\"\" if self . state >= self . total : fill = self . length else : fill = math . ceil (( self . state / self . total ) * self . length ) empty = self . length - fill if self . unit == \"MiB\" : state = math . floor ( self . state / 1048576 ) elif self . unit == \"KiB\" : state = math . floor ( self . state / 1024 ) else : state = self . state progbar = [ \"[\" , self . fill * fill , self . empty * empty , \"] \" , str ( state )] return \"\" . join ( progbar ) waiting ( msg , flag , timeout = 180 ) Show loading message while thread completes processing. Parameters: Name Type Description Default msg str Message string printed before the progress bar required flag list Once flag is filled exit loop required timeout int max amount of time to run the function. 180 Source code in torrentfile\\mixins.py def waiting ( msg , flag , timeout = 180 ): \"\"\" Show loading message while thread completes processing. Parameters ---------- msg : str Message string printed before the progress bar flag : list Once flag is filled exit loop timeout : int max amount of time to run the function. \"\"\" then = time . time () codes , fill = list ( range ( 9617 , 9620 )), chr ( 9619 ) size = idx = 0 total = shutil . get_terminal_size () . columns - len ( msg ) - 20 def output ( text ): \"\"\" Print parameter message to the console. Parameters ---------- text : str output message \"\"\" sys . stdout . write ( text ) sys . stdout . flush () output ( \" \\n \" ) while not flag : time . sleep ( 0.16 ) filled = ( fill * size ) + chr ( codes [ idx ]) + ( \" \" * ( total - size )) output ( f \" { msg } : { filled } \\r \" ) idx = idx + 1 if idx + 1 < len ( codes ) else 0 size = size + 1 if size < total else 0 if time . time () - then > timeout : break output ( \" \\n \" ) recheck Module container Checker Class. The CheckerClass takes a torrentfile and tha path to it's contents. It will then iterate through every file and directory contained and compare their data to values contained within the torrent file. Completion percentages will be printed to screen for each file and at the end for the torrentfile as a whole. Checker ( ProgMixin ) Check a given file or directory to see if it matches a torrentfile. Public constructor for Checker class instance. Parameters: Name Type Description Default metafile str Path to \".torrent\" file. required path str Path where the content is located in filesystem. required Examples: metafile = \"/path/to/torrentfile/content_file_or_dir.torrent\" >> location = \"/path/to/location\" >> os.path.exists(\"/path/to/location/content_file_or_dir\") Out: True >> checker = Checker(metafile, location) Source code in torrentfile\\recheck.py class Checker ( ProgMixin ): \"\"\" Check a given file or directory to see if it matches a torrentfile. Public constructor for Checker class instance. Parameters ---------- metafile : str Path to \".torrent\" file. path : str Path where the content is located in filesystem. Example ------- >> metafile = \"/path/to/torrentfile/content_file_or_dir.torrent\" >> location = \"/path/to/location\" >> os.path.exists(\"/path/to/location/content_file_or_dir\") Out: True >> checker = Checker(metafile, location) \"\"\" _hook = None def __init__ ( self , metafile : str , path : str ): \"\"\" Validate data against hashes contained in .torrent file. Parameters ---------- metafile : str path to .torrent file path : str path to content or contents parent directory. \"\"\" if not os . path . exists ( metafile ): raise FileNotFoundError meta = [] thread = Thread ( target = pyben . loadinto , args = ( metafile , meta )) thread . start () self . last_log = None self . log_msg ( \"Checking: %s , %s \" , metafile , path ) self . metafile = metafile self . meta_version = None self . total = 0 self . paths = [] self . fileinfo = {} thread2 = Thread ( target = waiting , args = ( \"Extracting metadata\" , meta )) if not meta : # pragma: nocover thread2 . start () thread2 . join () self . meta = meta [ 0 ] self . info = self . meta [ \"info\" ] self . name = self . info [ \"name\" ] self . piece_length = self . info [ \"piece length\" ] if \"meta version\" in self . info : if \"pieces\" in self . info : self . meta_version = 3 else : self . meta_version = 2 else : self . meta_version = 1 self . root = self . find_root ( path ) self . check_paths () @classmethod def register_callback ( cls , hook ): \"\"\" Register hooks from 3rd party programs to access generated info. Parameters ---------- hook : function callback function for the logging feature. \"\"\" cls . _hook = hook def hasher ( self ): \"\"\" Return the hasher class related to torrents meta version. Returns ------- hasher.Hasher the hashing implementation for specific torrent meta version. \"\"\" if self . meta_version == 2 : return HasherV2 if self . meta_version == 3 : return HasherHybrid return None def piece_checker ( self ): \"\"\" Check individual pieces of the torrent. Returns ------- HashChecker | FeedChecker Individual piece hasher. \"\"\" if self . meta_version == 1 : return FeedChecker return HashChecker def results ( self ): \"\"\" Generate result percentage and store for future calls. \"\"\" responses = [] for response in self . iter_hashes (): responses . append ( response ) self . log_msg ( \"Final result for %s recheck: %s \" , self . metafile , self . _result ) return self . _result def log_msg ( self , * args , level = logging . INFO ): \"\"\" Log message `msg` to logger and send `msg` to callback hook. Parameters ---------- args : dict formatting args for log message level : int Log level for this message; default=`logging.INFO` \"\"\" message = args [ 0 ] if len ( args ) >= 3 : message = message % tuple ( args [ 1 :]) elif len ( args ) == 2 : message = message % args [ 1 ] # Repeat log messages should be ignored. if message != self . last_log : self . last_log = message logger . log ( level , message ) if self . _hook and level == logging . INFO : self . _hook ( message ) def find_root ( self , path : str ) -> str : \"\"\" Check path for torrent content. The path can be a relative or absolute filesystem path. In the case where the content is a single file, the path may point directly to the the file, or it may point to the parent directory. If content points to a directory. The directory will be checked to see if it matches the torrent's name, if not the directories contents will be searched. The returned value will be the absolute path that matches the torrent's name. Parameters ---------- path : str root path to torrent content Returns ------- str root path to content \"\"\" if not os . path . exists ( path ): self . log_msg ( \"Could not locate torrent content %s .\" , path ) raise FileNotFoundError ( path ) root = Path ( path ) if root . name == self . name : self . log_msg ( \"Content found: %s .\" , str ( root )) return root if self . name in os . listdir ( root ): return root / self . name self . log_msg ( \"Could not locate torrent content in: %s \" , str ( root )) raise FileNotFoundError ( root ) def check_paths ( self ): \"\"\" Gather all file paths described in the torrent file. \"\"\" finfo = self . fileinfo if \"length\" in self . info : self . log_msg ( \" %s points to a single file\" , self . root ) self . total = self . info [ \"length\" ] self . paths . append ( str ( self . root )) finfo [ 0 ] = { \"path\" : self . root , \"length\" : self . info [ \"length\" ], } if self . meta_version > 1 : root = self . info [ \"file tree\" ][ self . name ][ \"\" ][ \"pieces root\" ] finfo [ 0 ][ \"pieces root\" ] = root return # Otherwise Content is more than 1 file. self . log_msg ( \" %s points to a directory\" , self . root ) if self . meta_version == 1 : for i , item in enumerate ( self . info [ \"files\" ]): self . total += item [ \"length\" ] base = os . path . join ( * item [ \"path\" ]) self . fileinfo [ i ] = { \"path\" : str ( self . root / base ), \"length\" : item [ \"length\" ], } self . paths . append ( str ( self . root / base )) return self . walk_file_tree ( self . info [ \"file tree\" ], []) def walk_file_tree ( self , tree : dict , partials : list ): \"\"\" Traverse File Tree dictionary to get file details. Extract full pathnames, length, root hash, and layer hashes for each file included in the .torrent's file tree. Parameters ---------- tree : dict File Tree dict extracted from torrent file. partials : list list of intermediate pathnames. \"\"\" for key , val in tree . items (): # Empty string means the tree's leaf is value if \"\" in val : base = os . path . join ( * partials , key ) roothash = None length = val [ \"\" ][ \"length\" ] roothash = None if not length else val [ \"\" ][ \"pieces root\" ] full = str ( self . root / base ) self . fileinfo [ len ( self . paths )] = { \"path\" : full , \"length\" : length , \"pieces root\" : roothash , } self . paths . append ( full ) self . total += length else : self . walk_file_tree ( val , partials + [ key ]) def iter_hashes ( self ) -> tuple : \"\"\" Produce results of comparing torrent contents piece by piece. Yields ------ chunck : bytes hash of data found on disk piece : bytes hash of data when complete and correct path : str path to file being hashed size : int length of bytes hashed for piece \"\"\" matched = consumed = 0 checker = self . piece_checker () hasher = self . hasher () for chunk , piece , path , size in checker ( self , hasher ): consumed += size matching = 0 if chunk == piece : matching += size matched += size yield chunk , piece , path , size total_consumed = str ( int ( consumed / self . total * 100 )) percent_matched = str ( int ( matched / consumed * 100 )) self . log_msg ( \"Processed: %s%% , Matched: %s%% \" , total_consumed , percent_matched , level = logging . DEBUG , ) self . _result = ( matched / consumed ) * 100 if consumed > 0 else 0 __init__ ( self , metafile , path ) special Validate data against hashes contained in .torrent file. Parameters: Name Type Description Default metafile str path to .torrent file required path str path to content or contents parent directory. required Source code in torrentfile\\recheck.py def __init__ ( self , metafile : str , path : str ): \"\"\" Validate data against hashes contained in .torrent file. Parameters ---------- metafile : str path to .torrent file path : str path to content or contents parent directory. \"\"\" if not os . path . exists ( metafile ): raise FileNotFoundError meta = [] thread = Thread ( target = pyben . loadinto , args = ( metafile , meta )) thread . start () self . last_log = None self . log_msg ( \"Checking: %s , %s \" , metafile , path ) self . metafile = metafile self . meta_version = None self . total = 0 self . paths = [] self . fileinfo = {} thread2 = Thread ( target = waiting , args = ( \"Extracting metadata\" , meta )) if not meta : # pragma: nocover thread2 . start () thread2 . join () self . meta = meta [ 0 ] self . info = self . meta [ \"info\" ] self . name = self . info [ \"name\" ] self . piece_length = self . info [ \"piece length\" ] if \"meta version\" in self . info : if \"pieces\" in self . info : self . meta_version = 3 else : self . meta_version = 2 else : self . meta_version = 1 self . root = self . find_root ( path ) self . check_paths () check_paths ( self ) Gather all file paths described in the torrent file. Source code in torrentfile\\recheck.py def check_paths ( self ): \"\"\" Gather all file paths described in the torrent file. \"\"\" finfo = self . fileinfo if \"length\" in self . info : self . log_msg ( \" %s points to a single file\" , self . root ) self . total = self . info [ \"length\" ] self . paths . append ( str ( self . root )) finfo [ 0 ] = { \"path\" : self . root , \"length\" : self . info [ \"length\" ], } if self . meta_version > 1 : root = self . info [ \"file tree\" ][ self . name ][ \"\" ][ \"pieces root\" ] finfo [ 0 ][ \"pieces root\" ] = root return # Otherwise Content is more than 1 file. self . log_msg ( \" %s points to a directory\" , self . root ) if self . meta_version == 1 : for i , item in enumerate ( self . info [ \"files\" ]): self . total += item [ \"length\" ] base = os . path . join ( * item [ \"path\" ]) self . fileinfo [ i ] = { \"path\" : str ( self . root / base ), \"length\" : item [ \"length\" ], } self . paths . append ( str ( self . root / base )) return self . walk_file_tree ( self . info [ \"file tree\" ], []) find_root ( self , path ) Check path for torrent content. The path can be a relative or absolute filesystem path. In the case where the content is a single file, the path may point directly to the the file, or it may point to the parent directory. If content points to a directory. The directory will be checked to see if it matches the torrent's name, if not the directories contents will be searched. The returned value will be the absolute path that matches the torrent's name. Parameters: Name Type Description Default path str root path to torrent content required Returns: Type Description str root path to content Source code in torrentfile\\recheck.py def find_root ( self , path : str ) -> str : \"\"\" Check path for torrent content. The path can be a relative or absolute filesystem path. In the case where the content is a single file, the path may point directly to the the file, or it may point to the parent directory. If content points to a directory. The directory will be checked to see if it matches the torrent's name, if not the directories contents will be searched. The returned value will be the absolute path that matches the torrent's name. Parameters ---------- path : str root path to torrent content Returns ------- str root path to content \"\"\" if not os . path . exists ( path ): self . log_msg ( \"Could not locate torrent content %s .\" , path ) raise FileNotFoundError ( path ) root = Path ( path ) if root . name == self . name : self . log_msg ( \"Content found: %s .\" , str ( root )) return root if self . name in os . listdir ( root ): return root / self . name self . log_msg ( \"Could not locate torrent content in: %s \" , str ( root )) raise FileNotFoundError ( root ) hasher ( self ) Return the hasher class related to torrents meta version. Returns: Type Description hasher.Hasher the hashing implementation for specific torrent meta version. Source code in torrentfile\\recheck.py def hasher ( self ): \"\"\" Return the hasher class related to torrents meta version. Returns ------- hasher.Hasher the hashing implementation for specific torrent meta version. \"\"\" if self . meta_version == 2 : return HasherV2 if self . meta_version == 3 : return HasherHybrid return None iter_hashes ( self ) Produce results of comparing torrent contents piece by piece. Returns: Type Description tuple hash of data found on disk Source code in torrentfile\\recheck.py def iter_hashes ( self ) -> tuple : \"\"\" Produce results of comparing torrent contents piece by piece. Yields ------ chunck : bytes hash of data found on disk piece : bytes hash of data when complete and correct path : str path to file being hashed size : int length of bytes hashed for piece \"\"\" matched = consumed = 0 checker = self . piece_checker () hasher = self . hasher () for chunk , piece , path , size in checker ( self , hasher ): consumed += size matching = 0 if chunk == piece : matching += size matched += size yield chunk , piece , path , size total_consumed = str ( int ( consumed / self . total * 100 )) percent_matched = str ( int ( matched / consumed * 100 )) self . log_msg ( \"Processed: %s%% , Matched: %s%% \" , total_consumed , percent_matched , level = logging . DEBUG , ) self . _result = ( matched / consumed ) * 100 if consumed > 0 else 0 log_msg ( self , * args , * , level = 20 ) Log message msg to logger and send msg to callback hook. Parameters: Name Type Description Default args dict formatting args for log message () level int Log level for this message; default= logging.INFO 20 Source code in torrentfile\\recheck.py def log_msg ( self , * args , level = logging . INFO ): \"\"\" Log message `msg` to logger and send `msg` to callback hook. Parameters ---------- args : dict formatting args for log message level : int Log level for this message; default=`logging.INFO` \"\"\" message = args [ 0 ] if len ( args ) >= 3 : message = message % tuple ( args [ 1 :]) elif len ( args ) == 2 : message = message % args [ 1 ] # Repeat log messages should be ignored. if message != self . last_log : self . last_log = message logger . log ( level , message ) if self . _hook and level == logging . INFO : self . _hook ( message ) piece_checker ( self ) Check individual pieces of the torrent. Returns: Type Description HashChecker | FeedChecker Individual piece hasher. Source code in torrentfile\\recheck.py def piece_checker ( self ): \"\"\" Check individual pieces of the torrent. Returns ------- HashChecker | FeedChecker Individual piece hasher. \"\"\" if self . meta_version == 1 : return FeedChecker return HashChecker register_callback ( hook ) classmethod Register hooks from 3rd party programs to access generated info. Parameters: Name Type Description Default hook function callback function for the logging feature. required Source code in torrentfile\\recheck.py @classmethod def register_callback ( cls , hook ): \"\"\" Register hooks from 3rd party programs to access generated info. Parameters ---------- hook : function callback function for the logging feature. \"\"\" cls . _hook = hook results ( self ) Generate result percentage and store for future calls. Source code in torrentfile\\recheck.py def results ( self ): \"\"\" Generate result percentage and store for future calls. \"\"\" responses = [] for response in self . iter_hashes (): responses . append ( response ) self . log_msg ( \"Final result for %s recheck: %s \" , self . metafile , self . _result ) return self . _result walk_file_tree ( self , tree , partials ) Traverse File Tree dictionary to get file details. Extract full pathnames, length, root hash, and layer hashes for each file included in the .torrent's file tree. Parameters: Name Type Description Default tree dict File Tree dict extracted from torrent file. required partials list list of intermediate pathnames. required Source code in torrentfile\\recheck.py def walk_file_tree ( self , tree : dict , partials : list ): \"\"\" Traverse File Tree dictionary to get file details. Extract full pathnames, length, root hash, and layer hashes for each file included in the .torrent's file tree. Parameters ---------- tree : dict File Tree dict extracted from torrent file. partials : list list of intermediate pathnames. \"\"\" for key , val in tree . items (): # Empty string means the tree's leaf is value if \"\" in val : base = os . path . join ( * partials , key ) roothash = None length = val [ \"\" ][ \"length\" ] roothash = None if not length else val [ \"\" ][ \"pieces root\" ] full = str ( self . root / base ) self . fileinfo [ len ( self . paths )] = { \"path\" : full , \"length\" : length , \"pieces root\" : roothash , } self . paths . append ( full ) self . total += length else : self . walk_file_tree ( val , partials + [ key ]) FeedChecker ( ProgMixin ) Validates torrent content. Seemlesly validate torrent file contents by comparing hashes in metafile against data on disk. Parameters: Name Type Description Default checker Checker the checker class instance. required hasher Any hashing class for calculating piece hashes. default=None None Source code in torrentfile\\recheck.py class FeedChecker ( ProgMixin ): \"\"\" Validates torrent content. Seemlesly validate torrent file contents by comparing hashes in metafile against data on disk. Parameters ---------- checker : object the checker class instance. hasher : Any hashing class for calculating piece hashes. default=None \"\"\" def __init__ ( self , checker : Checker , hasher = None ): \"\"\" Generate hashes of piece length data from filelist contents. \"\"\" self . piece_length = checker . piece_length self . paths = checker . paths self . pieces = checker . info [ \"pieces\" ] self . fileinfo = checker . fileinfo self . hasher = hasher self . piece_map = {} self . index = 0 self . piece_count = 0 self . it = None def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" self . it = self . iter_pieces () return self def __next__ ( self ): \"\"\" Yield back result of comparison. \"\"\" try : partial = next ( self . it ) except StopIteration as itererror : raise StopIteration from itererror chunck = sha1 ( partial ) . digest () # nosec start = self . piece_count * SHA1 end = start + SHA1 piece = self . pieces [ start : end ] self . piece_count += 1 path = self . paths [ self . index ] return chunck , piece , path , len ( partial ) def iter_pieces ( self ): \"\"\" Iterate through, and hash pieces of torrent contents. Yields ------ piece : bytes hash digest for block of torrent data. \"\"\" partial = bytearray () for i , path in enumerate ( self . paths ): total = self . fileinfo [ i ][ \"length\" ] self . prog_start ( total , path , unit = \"bytes\" ) self . index = i if os . path . exists ( path ): for piece in self . extract ( path , partial ): if ( len ( piece ) == self . piece_length ) or ( i + 1 == len ( self . paths ) ): yield piece else : partial = piece else : length = self . fileinfo [ i ][ \"length\" ] for pad in self . _gen_padding ( partial , length ): if len ( pad ) == self . piece_length : yield pad else : partial = pad self . prog_close () def extract ( self , path : str , partial : bytearray ) -> bytearray : \"\"\" Split file paths contents into blocks of data for hash pieces. Parameters ---------- path : str path to content. partial : bytes any remaining content from last file. Returns ------- bytearray Hash digest for block of .torrent contents. \"\"\" read = 0 length = self . fileinfo [ self . index ][ \"length\" ] partial = bytearray () if len ( partial ) == self . piece_length else partial if path not in self . paths : # pragma: no cover raise MissingPathError ( path ) with open ( path , \"rb\" ) as current : while True : bitlength = self . piece_length - len ( partial ) part = bytearray ( bitlength ) amount = current . readinto ( part ) read += amount partial . extend ( part [: amount ]) if amount < bitlength : if amount > 0 and read == length : self . prog_update ( amount ) yield partial break self . prog_update ( amount ) yield partial partial = bytearray ( 0 ) if length != read : for pad in self . _gen_padding ( partial , length , read ): yield pad def _gen_padding ( self , partial : bytes , length : int , read = 0 ) -> bytes : \"\"\" Create padded pieces where file sizes do not match. Parameters ---------- partial : bytes any remaining data from last file processed. length : int size of space that needs padding read : int portion of length already padded Yields ------ bytes A piece length sized block of zeros. \"\"\" while read < length : left = self . piece_length - len ( partial ) if length - read > left : padding = bytearray ( left ) partial . extend ( padding ) yield partial read += left partial = bytearray ( 0 ) else : partial . extend ( bytearray ( length - read )) read = length yield partial __init__ ( self , checker , hasher = None ) special Generate hashes of piece length data from filelist contents. Source code in torrentfile\\recheck.py def __init__ ( self , checker : Checker , hasher = None ): \"\"\" Generate hashes of piece length data from filelist contents. \"\"\" self . piece_length = checker . piece_length self . paths = checker . paths self . pieces = checker . info [ \"pieces\" ] self . fileinfo = checker . fileinfo self . hasher = hasher self . piece_map = {} self . index = 0 self . piece_count = 0 self . it = None __iter__ ( self ) special Assign iterator and return self. Source code in torrentfile\\recheck.py def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" self . it = self . iter_pieces () return self __next__ ( self ) special Yield back result of comparison. Source code in torrentfile\\recheck.py def __next__ ( self ): \"\"\" Yield back result of comparison. \"\"\" try : partial = next ( self . it ) except StopIteration as itererror : raise StopIteration from itererror chunck = sha1 ( partial ) . digest () # nosec start = self . piece_count * SHA1 end = start + SHA1 piece = self . pieces [ start : end ] self . piece_count += 1 path = self . paths [ self . index ] return chunck , piece , path , len ( partial ) extract ( self , path , partial ) Split file paths contents into blocks of data for hash pieces. Parameters: Name Type Description Default path str path to content. required partial bytearray any remaining content from last file. required Returns: Type Description bytearray Hash digest for block of .torrent contents. Source code in torrentfile\\recheck.py def extract ( self , path : str , partial : bytearray ) -> bytearray : \"\"\" Split file paths contents into blocks of data for hash pieces. Parameters ---------- path : str path to content. partial : bytes any remaining content from last file. Returns ------- bytearray Hash digest for block of .torrent contents. \"\"\" read = 0 length = self . fileinfo [ self . index ][ \"length\" ] partial = bytearray () if len ( partial ) == self . piece_length else partial if path not in self . paths : # pragma: no cover raise MissingPathError ( path ) with open ( path , \"rb\" ) as current : while True : bitlength = self . piece_length - len ( partial ) part = bytearray ( bitlength ) amount = current . readinto ( part ) read += amount partial . extend ( part [: amount ]) if amount < bitlength : if amount > 0 and read == length : self . prog_update ( amount ) yield partial break self . prog_update ( amount ) yield partial partial = bytearray ( 0 ) if length != read : for pad in self . _gen_padding ( partial , length , read ): yield pad iter_pieces ( self ) Iterate through, and hash pieces of torrent contents. Returns: Type Description bytes hash digest for block of torrent data. Source code in torrentfile\\recheck.py def iter_pieces ( self ): \"\"\" Iterate through, and hash pieces of torrent contents. Yields ------ piece : bytes hash digest for block of torrent data. \"\"\" partial = bytearray () for i , path in enumerate ( self . paths ): total = self . fileinfo [ i ][ \"length\" ] self . prog_start ( total , path , unit = \"bytes\" ) self . index = i if os . path . exists ( path ): for piece in self . extract ( path , partial ): if ( len ( piece ) == self . piece_length ) or ( i + 1 == len ( self . paths ) ): yield piece else : partial = piece else : length = self . fileinfo [ i ][ \"length\" ] for pad in self . _gen_padding ( partial , length ): if len ( pad ) == self . piece_length : yield pad else : partial = pad self . prog_close () HashChecker ( ProgMixin ) Verify that root hashes of content files match the .torrent files. Parameters: Name Type Description Default checker Checker the checker instance that maintains variables. required hasher Object the version specific hashing class for torrent content. None Source code in torrentfile\\recheck.py class HashChecker ( ProgMixin ): \"\"\" Verify that root hashes of content files match the .torrent files. Parameters ---------- checker : Object the checker instance that maintains variables. hasher : Object the version specific hashing class for torrent content. \"\"\" def __init__ ( self , checker : Checker , hasher = None ): \"\"\" Construct a HybridChecker instance. \"\"\" self . checker = checker self . paths = checker . paths self . hasher = hasher self . piece_length = checker . piece_length self . fileinfo = checker . fileinfo self . piece_layers = checker . meta [ \"piece layers\" ] self . piece_count = 0 self . it = None def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" self . it = self . iter_paths () return self def __next__ ( self ): \"\"\" Provide the result of comparison. \"\"\" try : value = next ( self . it ) return value except StopIteration as stopiter : raise StopIteration () from stopiter def iter_paths ( self ) -> tuple : \"\"\" Iterate through and compare root file hashes to .torrent file. Yields ------ results : tuple The size of the file and result of match. \"\"\" for i , path in enumerate ( self . paths ): info = self . fileinfo [ i ] length , plength = info [ \"length\" ], self . piece_length roothash = info [ \"pieces root\" ] self . prog_start ( length , path , unit = \"bytes\" ) if roothash in self . piece_layers : pieces = self . piece_layers [ roothash ] else : pieces = roothash if roothash else bytes () amount = len ( pieces ) // SHA256 if not os . path . exists ( path ): for i in range ( amount ): start = i * SHA256 end = start + SHA256 piece = pieces [ start : end ] if length > plength : size = plength else : size = length length -= size block = sha256 ( bytearray ( size )) . digest () yield block , piece , path , size else : hashed = self . hasher ( path , plength ) if len ( hashed . layer_hashes ) == 1 : block = hashed . root piece = roothash size = length self . prog_update ( size ) yield block , piece , path , size else : for i in range ( amount ): start = i * SHA256 end = start + SHA256 piece = pieces [ start : end ] try : block = hashed . piece_layer [ start : end ] except IndexError : # pragma: nocover block = sha256 ( bytearray ( size )) . digest () size = plength if plength < length else length length -= size self . prog_update ( size ) yield block , piece , path , size __init__ ( self , checker , hasher = None ) special Construct a HybridChecker instance. Source code in torrentfile\\recheck.py def __init__ ( self , checker : Checker , hasher = None ): \"\"\" Construct a HybridChecker instance. \"\"\" self . checker = checker self . paths = checker . paths self . hasher = hasher self . piece_length = checker . piece_length self . fileinfo = checker . fileinfo self . piece_layers = checker . meta [ \"piece layers\" ] self . piece_count = 0 self . it = None __iter__ ( self ) special Assign iterator and return self. Source code in torrentfile\\recheck.py def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" self . it = self . iter_paths () return self __next__ ( self ) special Provide the result of comparison. Source code in torrentfile\\recheck.py def __next__ ( self ): \"\"\" Provide the result of comparison. \"\"\" try : value = next ( self . it ) return value except StopIteration as stopiter : raise StopIteration () from stopiter iter_paths ( self ) Iterate through and compare root file hashes to .torrent file. Returns: Type Description tuple The size of the file and result of match. Source code in torrentfile\\recheck.py def iter_paths ( self ) -> tuple : \"\"\" Iterate through and compare root file hashes to .torrent file. Yields ------ results : tuple The size of the file and result of match. \"\"\" for i , path in enumerate ( self . paths ): info = self . fileinfo [ i ] length , plength = info [ \"length\" ], self . piece_length roothash = info [ \"pieces root\" ] self . prog_start ( length , path , unit = \"bytes\" ) if roothash in self . piece_layers : pieces = self . piece_layers [ roothash ] else : pieces = roothash if roothash else bytes () amount = len ( pieces ) // SHA256 if not os . path . exists ( path ): for i in range ( amount ): start = i * SHA256 end = start + SHA256 piece = pieces [ start : end ] if length > plength : size = plength else : size = length length -= size block = sha256 ( bytearray ( size )) . digest () yield block , piece , path , size else : hashed = self . hasher ( path , plength ) if len ( hashed . layer_hashes ) == 1 : block = hashed . root piece = roothash size = length self . prog_update ( size ) yield block , piece , path , size else : for i in range ( amount ): start = i * SHA256 end = start + SHA256 piece = pieces [ start : end ] try : block = hashed . piece_layer [ start : end ] except IndexError : # pragma: nocover block = sha256 ( bytearray ( size )) . digest () size = plength if plength < length else length length -= size self . prog_update ( size ) yield block , piece , path , size torrent Classes and procedures pertaining to the creation of torrent meta files. Classes TorrentFile construct .torrent file. TorrentFileV2 construct .torrent v2 files using provided data. MetaFile base class for all MetaFile classes. Constants BLOCK_SIZE : int size of leaf hashes for merkle tree. HASH_SIZE : int Length of a sha256 hash. Bittorrent V2 From Bittorrent.org Documentation pages. Implementation details for Bittorrent Protocol v2. Note All strings in a .torrent file that contain text must be UTF-8 encoded. Meta Version 2 Dictionary: \"announce\": The URL of the tracker. \"info\": This maps to a dictionary, with keys described below. \"name\": A display name for the torrent. It is purely advisory. \"piece length\": The number of bytes that each logical piece in the peer protocol refers to. I.e. it sets the granularity of piece, request, bitfield and have messages. It must be a power of two and at least 6KiB. \"meta version\": An integer value, set to 2 to indicate compatibility with the current revision of this specification. Version 1 is not assigned to avoid confusion with BEP3. Future revisions will only increment this issue to indicate an incompatible change has been made, for example that hash algorithms were changed due to newly discovered vulnerabilities. Lementations must check this field first and indicate that a torrent is of a newer version than they can handle before performing other idations which may result in more general messages about invalid files. Files are mapped into this piece address space so that each non-empty \"file tree\": A tree of dictionaries where dictionary keys represent UTF-8 encoded path elements. Entries with zero-length keys describe the properties of the composed path at that point. 'UTF-8 encoded' context only means that if the native encoding is known at creation time it must be converted to UTF-8. Keys may contain invalid UTF-8 sequences or characters and names that are reserved on specific filesystems. Implementations must be prepared to sanitize them. On platforms path components exactly matching '.' and '..' must be sanitized since they could lead to directory traversal attacks and conflicting path descriptions. On platforms that require UTF-8 path components this sanitizing step must happen after normalizing overlong UTF-8 encodings. File is aligned to a piece boundary and occurs in same order as the file tree. The last piece of each file may be shorter than the specified piece length, resulting in an alignment gap. \"length\": Length of the file in bytes. Presence of this field indicates that the dictionary describes a file, not a directory. Which means it must not have any sibling entries. \"pieces root\": For non-empty files this is the the root hash of a merkle tree with a branching factor of 2, constructed from 16KiB blocks of the file. The last block may be shorter than 16KiB. The remaining leaf hashes beyond the end of the file required to construct upper layers of the merkle tree are set to zero. As of meta version 2 SHA2-256 is used as digest function for the merkle tree. The hash is stored in its binary form, not as human-readable string. \"piece layers\": A dictionary of strings. For each file in the file tree that is larger than the piece size it contains one string value. The keys are the merkle roots while the values consist of concatenated hashes of one layer within that merkle tree. The layer is chosen so that one hash covers piece length bytes. For example if the piece size is 16KiB then the leaf hashes are used. If a piece size of 128KiB is used then 3rd layer up from the leaf hashes is used. Layer hashes which exclusively cover data beyond the end of file, i.e. are only needed to balance the tree, are omitted. All hashes are stored in their binary format. A torrent is not valid if this field is absent, the contained hashes do not match the merkle roots or are not from the correct layer. Important The file tree root dictionary itself must not be a file, i.e. it must not contain a zero-length key with a dictionary containing a length key. Bittorrent V1 v1 meta-dictionary announce: The URL of the tracker. info: This maps to a dictionary, with keys described below. name : maps to a UTF-8 encoded string which is the suggested name to save the file (or directory) as. It is purely advisory. piece length : maps to the number of bytes in each piece the file is split into. For the purposes of transfer, files are split into fixed-size pieces which are all the same length except for possibly the last one which may be truncated. piece length : is almost always a power of two, most commonly 2^18 = 256 K pieces : maps to a string whose length is a multiple of 20. It is to be subdivided into strings of length 20, each of which is the SHA1 hash of the piece at the corresponding index. length : In the single file case, maps to the length of the file in bytes. files : If present then the download represents a single file, otherwise it represents a set of files which go in a directory structure. For the purposes of the other keys, the multi-file case is treated as only having a single file by concatenating the files in the order they appear in the files list. The files list is the value files maps to, and is a list of dictionaries containing the following keys: path : A list of UTF-8 encoded strings corresponding to subdirectory names, the last of which is the actual file name length : Maps to the length of the file in bytes. length : Only present if the content is a single file. Maps to the length of the file in bytes. Note In the single file case, the name key is the name of a file, in the muliple file case, it's the name of a directory. MetaFile Base Class for all TorrentFile classes. Parameters: Name Type Description Default path str target path to torrent content. Default: None None announce str One or more tracker URL's. Default: None None comment str A comment. Default: None None piece_length int Size of torrent pieces. Default: None None private bool For private trackers. Default: None False outfile str target path to write .torrent file. Default: None None source str Private tracker source. Default: None None progress str level of progress bar displayed Default: \"1\" 1 cwd bool If True change default save location to current directory False httpseeds list one or more web addresses where torrent content can be found. None url_list list one or more web addressess where torrent content exists. None content str alias for 'path' arg. None Source code in torrentfile\\torrent.py class MetaFile : \"\"\" Base Class for all TorrentFile classes. Parameters ---------- path : str target path to torrent content. Default: None announce : str One or more tracker URL's. Default: None comment : str A comment. Default: None piece_length : int Size of torrent pieces. Default: None private : bool For private trackers. Default: None outfile : str target path to write .torrent file. Default: None source : str Private tracker source. Default: None progress : str level of progress bar displayed Default: \"1\" cwd : bool If True change default save location to current directory httpseeds : list one or more web addresses where torrent content can be found. url_list : list one or more web addressess where torrent content exists. content : str alias for 'path' arg. \"\"\" hasher = None @classmethod def set_callback ( cls , func ): \"\"\" Assign a callback function for the Hashing class to call for each hash. Parameters ---------- func : function The callback function which accepts a single paramter. \"\"\" if \"hasher\" in vars ( cls ) and vars ( cls )[ \"hasher\" ]: cls . hasher . set_callback ( func ) def __init__ ( self , path = None , announce = None , comment = None , piece_length = None , private = False , outfile = None , source = None , progress = 1 , cwd = False , httpseeds = None , url_list = None , content = None , ** _ , ): \"\"\" Construct MetaFile superclass and assign local attributes. \"\"\" self . private = private self . cwd = cwd self . outfile = outfile self . progress = int ( progress ) self . comment = comment self . source = source if content : path = content if not path : if announce and len ( announce ) > 1 and os . path . exists ( announce [ - 1 ]): path = announce [ - 1 ] announce = announce [: - 1 ] elif url_list and os . path . exists ( url_list [ - 1 ]): path = url_list [ - 1 ] url_list = url_list [: - 1 ] elif httpseeds and os . path . exists ( httpseeds [ - 1 ]): path = httpseeds [ - 1 ] httpseeds = httpseeds [: - 1 ] else : raise utils . MissingPathError ( \"Path to content is required.\" ) # base path to torrent content. self . path = path logger . debug ( \"path parameter found %s \" , path ) # Format piece_length attribute. if piece_length : self . piece_length = utils . normalize_piece_length ( piece_length ) logger . debug ( \"piece length parameter found %s \" , piece_length ) else : self . piece_length = utils . path_piece_length ( self . path ) logger . debug ( \"piece length calculated %s \" , self . piece_length ) # Assign announce URL to empty string if none provided. if not announce : self . announce , self . announce_list = \"\" , [[ \"\" ]] # Most torrent clients have editting trackers as a feature. elif isinstance ( announce , str ): self . announce , self . announce_list = announce , [[ announce ]] elif isinstance ( announce , Sequence ): self . announce , self . announce_list = announce [ 0 ], [ announce ] self . meta = { \"announce\" : self . announce , \"announce-list\" : self . announce_list , \"created by\" : f \"TorrentFile:v { version } \" , \"creation date\" : int ( datetime . timestamp ( datetime . now ())), \"info\" : {}, } if comment : self . meta [ \"info\" ][ \"comment\" ] = comment logger . debug ( \"comment parameter found %s \" , comment ) if private : self . meta [ \"info\" ][ \"private\" ] = 1 logger . debug ( \"private parameter triggered\" ) if source : self . meta [ \"info\" ][ \"source\" ] = source logger . debug ( \"source parameter found %s \" , source ) if url_list : self . meta [ \"url-list\" ] = url_list logger . debug ( \"url list parameter found %s \" , str ( url_list )) if httpseeds : self . meta [ \"httpseeds\" ] = httpseeds logger . debug ( \"httpseeds parameter found %s \" , str ( httpseeds )) self . meta [ \"info\" ][ \"piece length\" ] = self . piece_length parent , self . name = os . path . split ( self . path ) if not self . name : self . name = os . path . basename ( parent ) self . meta [ \"info\" ][ \"name\" ] = self . name def assemble ( self ): \"\"\" Overload in subclasses. Raises ------ Exception NotImplementedError \"\"\" raise NotImplementedError def sort_meta ( self ): \"\"\"Sort the info and meta dictionaries.\"\"\" logger . debug ( \"sorting dictionary keys\" ) meta = self . meta meta [ \"info\" ] = dict ( sorted ( list ( meta [ \"info\" ] . items ()))) meta = dict ( sorted ( list ( meta . items ()))) return meta def write ( self , outfile = None ) -> tuple : \"\"\" Write meta information to .torrent file. Parameters ---------- outfile : str Destination path for .torrent file. default=None Returns ------- outfile : str Where the .torrent file was writen. meta : dict .torrent meta information. \"\"\" fallback = os . path . join ( os . getcwd (), self . name ) + \".torrent\" if not self . outfile and not outfile : if self . cwd : self . outfile = fallback else : path = str ( self . path ) . rstrip ( \" \\\\ /\" ) self . outfile = path + \".torrent\" elif outfile : self . outfile = outfile if str ( self . outfile )[ - 1 ] in \" \\\\ /\" : self . outfile = self . outfile + ( self . name + \".torrent\" ) self . meta = self . sort_meta () try : pyben . dump ( self . meta , self . outfile ) except PermissionError : self . outfile = fallback pyben . dump ( self . meta , fallback ) return self . outfile , self . meta __init__ ( self , path = None , announce = None , comment = None , piece_length = None , private = False , outfile = None , source = None , progress = 1 , cwd = False , httpseeds = None , url_list = None , content = None , ** _ ) special Construct MetaFile superclass and assign local attributes. Source code in torrentfile\\torrent.py def __init__ ( self , path = None , announce = None , comment = None , piece_length = None , private = False , outfile = None , source = None , progress = 1 , cwd = False , httpseeds = None , url_list = None , content = None , ** _ , ): \"\"\" Construct MetaFile superclass and assign local attributes. \"\"\" self . private = private self . cwd = cwd self . outfile = outfile self . progress = int ( progress ) self . comment = comment self . source = source if content : path = content if not path : if announce and len ( announce ) > 1 and os . path . exists ( announce [ - 1 ]): path = announce [ - 1 ] announce = announce [: - 1 ] elif url_list and os . path . exists ( url_list [ - 1 ]): path = url_list [ - 1 ] url_list = url_list [: - 1 ] elif httpseeds and os . path . exists ( httpseeds [ - 1 ]): path = httpseeds [ - 1 ] httpseeds = httpseeds [: - 1 ] else : raise utils . MissingPathError ( \"Path to content is required.\" ) # base path to torrent content. self . path = path logger . debug ( \"path parameter found %s \" , path ) # Format piece_length attribute. if piece_length : self . piece_length = utils . normalize_piece_length ( piece_length ) logger . debug ( \"piece length parameter found %s \" , piece_length ) else : self . piece_length = utils . path_piece_length ( self . path ) logger . debug ( \"piece length calculated %s \" , self . piece_length ) # Assign announce URL to empty string if none provided. if not announce : self . announce , self . announce_list = \"\" , [[ \"\" ]] # Most torrent clients have editting trackers as a feature. elif isinstance ( announce , str ): self . announce , self . announce_list = announce , [[ announce ]] elif isinstance ( announce , Sequence ): self . announce , self . announce_list = announce [ 0 ], [ announce ] self . meta = { \"announce\" : self . announce , \"announce-list\" : self . announce_list , \"created by\" : f \"TorrentFile:v { version } \" , \"creation date\" : int ( datetime . timestamp ( datetime . now ())), \"info\" : {}, } if comment : self . meta [ \"info\" ][ \"comment\" ] = comment logger . debug ( \"comment parameter found %s \" , comment ) if private : self . meta [ \"info\" ][ \"private\" ] = 1 logger . debug ( \"private parameter triggered\" ) if source : self . meta [ \"info\" ][ \"source\" ] = source logger . debug ( \"source parameter found %s \" , source ) if url_list : self . meta [ \"url-list\" ] = url_list logger . debug ( \"url list parameter found %s \" , str ( url_list )) if httpseeds : self . meta [ \"httpseeds\" ] = httpseeds logger . debug ( \"httpseeds parameter found %s \" , str ( httpseeds )) self . meta [ \"info\" ][ \"piece length\" ] = self . piece_length parent , self . name = os . path . split ( self . path ) if not self . name : self . name = os . path . basename ( parent ) self . meta [ \"info\" ][ \"name\" ] = self . name assemble ( self ) Overload in subclasses. Exceptions: Type Description Exception NotImplementedError Source code in torrentfile\\torrent.py def assemble ( self ): \"\"\" Overload in subclasses. Raises ------ Exception NotImplementedError \"\"\" raise NotImplementedError set_callback ( func ) classmethod Assign a callback function for the Hashing class to call for each hash. Parameters: Name Type Description Default func function The callback function which accepts a single paramter. required Source code in torrentfile\\torrent.py @classmethod def set_callback ( cls , func ): \"\"\" Assign a callback function for the Hashing class to call for each hash. Parameters ---------- func : function The callback function which accepts a single paramter. \"\"\" if \"hasher\" in vars ( cls ) and vars ( cls )[ \"hasher\" ]: cls . hasher . set_callback ( func ) sort_meta ( self ) Sort the info and meta dictionaries. Source code in torrentfile\\torrent.py def sort_meta ( self ): \"\"\"Sort the info and meta dictionaries.\"\"\" logger . debug ( \"sorting dictionary keys\" ) meta = self . meta meta [ \"info\" ] = dict ( sorted ( list ( meta [ \"info\" ] . items ()))) meta = dict ( sorted ( list ( meta . items ()))) return meta write ( self , outfile = None ) Write meta information to .torrent file. Parameters: Name Type Description Default outfile str Destination path for .torrent file. default=None None Returns: Type Description tuple Where the .torrent file was writen. Source code in torrentfile\\torrent.py def write ( self , outfile = None ) -> tuple : \"\"\" Write meta information to .torrent file. Parameters ---------- outfile : str Destination path for .torrent file. default=None Returns ------- outfile : str Where the .torrent file was writen. meta : dict .torrent meta information. \"\"\" fallback = os . path . join ( os . getcwd (), self . name ) + \".torrent\" if not self . outfile and not outfile : if self . cwd : self . outfile = fallback else : path = str ( self . path ) . rstrip ( \" \\\\ /\" ) self . outfile = path + \".torrent\" elif outfile : self . outfile = outfile if str ( self . outfile )[ - 1 ] in \" \\\\ /\" : self . outfile = self . outfile + ( self . name + \".torrent\" ) self . meta = self . sort_meta () try : pyben . dump ( self . meta , self . outfile ) except PermissionError : self . outfile = fallback pyben . dump ( self . meta , fallback ) return self . outfile , self . meta TorrentFile ( MetaFile , ProgMixin ) Class for creating Bittorrent meta files. Construct Torrentfile class instance object. Parameters: Name Type Description Default kwargs dict Dictionary containing torrent file options. {} Source code in torrentfile\\torrent.py class TorrentFile ( MetaFile , ProgMixin ): \"\"\" Class for creating Bittorrent meta files. Construct *Torrentfile* class instance object. Parameters ---------- kwargs : dict Dictionary containing torrent file options. \"\"\" hasher = Hasher def __init__ ( self , ** kwargs ): \"\"\" Construct TorrentFile instance with given keyword args. Parameters ---------- kwargs : dict dictionary of keyword args passed to superclass. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"bittorrent version 1 file assembly\" ) self . assemble () def assemble ( self ): \"\"\" Assemble components of torrent metafile. Returns ------- dict metadata dictionary for torrent file \"\"\" info = self . meta [ \"info\" ] size , filelist = utils . filelist_total ( self . path ) if os . path . isfile ( self . path ): info [ \"length\" ] = size else : info [ \"files\" ] = [ { \"length\" : os . path . getsize ( path ), \"path\" : os . path . relpath ( path , self . path ) . split ( os . sep ), } for path in filelist ] pieces = bytearray () feeder = Hasher ( filelist , self . piece_length , self . progress ) if self . progress != 1 : for piece in feeder : pieces . extend ( piece ) else : total = ( size // self . piece_length ) + 1 title = self . path unit = \"pieces\" self . prog_start ( total , title , unit = unit ) for piece in feeder : pieces . extend ( piece ) self . prog_update ( 1 ) self . prog_close () info [ \"pieces\" ] = pieces hasher ( CbMixin , ProgMixin ) Piece hasher for Bittorrent V1 files. Takes a sorted list of all file paths, calculates sha1 hash for fixed size pieces of file data from each file seemlessly until the last piece which may be smaller than others. Parameters: Name Type Description Default paths list List of files. required piece_length int Size of chuncks to split the data into. required progress int default = None None Source code in torrentfile\\torrent.py class Hasher ( CbMixin , ProgMixin ): \"\"\" Piece hasher for Bittorrent V1 files. Takes a sorted list of all file paths, calculates sha1 hash for fixed size pieces of file data from each file seemlessly until the last piece which may be smaller than others. Parameters ---------- paths : list List of files. piece_length : int Size of chuncks to split the data into. progress : int default = None \"\"\" def __init__ ( self , paths : list , piece_length : int , progress : int = None ): \"\"\"Generate hashes of piece length data from filelist contents.\"\"\" self . piece_length = piece_length self . paths = paths self . progress = progress self . total = sum ([ os . path . getsize ( i ) for i in self . paths ]) self . index = 0 self . current = open ( self . paths [ 0 ], \"rb\" ) if self . progress and self . progress == 2 : total = os . path . getsize ( self . paths [ 0 ]) self . prog_start ( total , self . paths [ 0 ], unit = \"bytes\" ) logger . debug ( \"Hashing v1 torrent file. Size: %s Piece Length: %s \" , humanize_bytes ( self . total ), humanize_bytes ( self . piece_length ), ) def __iter__ ( self ): \"\"\" Iterate through feed pieces. Returns ------- self : iterator Iterator for leaves/hash pieces. \"\"\" return self def _handle_partial ( self , arr : bytearray ) -> bytearray : \"\"\" Define the handling partial pieces that span 2 or more files. Parameters ---------- arr : bytearray Incomplete piece containing partial data Returns ------- digest : bytearray SHA1 digest of the complete piece. \"\"\" while len ( arr ) < self . piece_length and self . next_file (): target = self . piece_length - len ( arr ) temp = bytearray ( target ) size = self . current . readinto ( temp ) arr . extend ( temp [: size ]) self . prog_update ( size ) if size == target : break return sha1 ( arr ) . digest () # nosec def next_file ( self ) -> bool : \"\"\" Seemlessly transition to next file in file list. Returns ------- bool: True if there is a next file otherwise False. \"\"\" self . index += 1 self . prog_close () if self . index < len ( self . paths ): path = self . paths [ self . index ] self . current . close () if self . progress == 2 : self . prog_start ( os . path . getsize ( path ), path , self . progress , unit = \"bytes\" ) self . current = open ( path , \"rb\" ) return True return False def __next__ ( self ) -> bytes : \"\"\" Generate piece-length pieces of data from input file list. Returns ------- bytes SHA1 hash of the piece extracted. \"\"\" while True : piece = bytearray ( self . piece_length ) size = self . current . readinto ( piece ) if size == 0 : if not self . next_file (): raise StopIteration elif size < self . piece_length : self . prog_update ( size ) return self . _handle_partial ( piece [: size ]) else : self . prog_update ( size ) return sha1 ( piece ) . digest () # nosec __init__ ( self , paths , piece_length , progress = None ) special Generate hashes of piece length data from filelist contents. Source code in torrentfile\\torrent.py def __init__ ( self , paths : list , piece_length : int , progress : int = None ): \"\"\"Generate hashes of piece length data from filelist contents.\"\"\" self . piece_length = piece_length self . paths = paths self . progress = progress self . total = sum ([ os . path . getsize ( i ) for i in self . paths ]) self . index = 0 self . current = open ( self . paths [ 0 ], \"rb\" ) if self . progress and self . progress == 2 : total = os . path . getsize ( self . paths [ 0 ]) self . prog_start ( total , self . paths [ 0 ], unit = \"bytes\" ) logger . debug ( \"Hashing v1 torrent file. Size: %s Piece Length: %s \" , humanize_bytes ( self . total ), humanize_bytes ( self . piece_length ), ) __iter__ ( self ) special Iterate through feed pieces. Returns: Type Description iterator Iterator for leaves/hash pieces. Source code in torrentfile\\torrent.py def __iter__ ( self ): \"\"\" Iterate through feed pieces. Returns ------- self : iterator Iterator for leaves/hash pieces. \"\"\" return self __next__ ( self ) special Generate piece-length pieces of data from input file list. Returns: Type Description bytes SHA1 hash of the piece extracted. Source code in torrentfile\\torrent.py def __next__ ( self ) -> bytes : \"\"\" Generate piece-length pieces of data from input file list. Returns ------- bytes SHA1 hash of the piece extracted. \"\"\" while True : piece = bytearray ( self . piece_length ) size = self . current . readinto ( piece ) if size == 0 : if not self . next_file (): raise StopIteration elif size < self . piece_length : self . prog_update ( size ) return self . _handle_partial ( piece [: size ]) else : self . prog_update ( size ) return sha1 ( piece ) . digest () # nosec next_file ( self ) Seemlessly transition to next file in file list. Returns: Type Description bool True if there is a next file otherwise False. Source code in torrentfile\\torrent.py def next_file ( self ) -> bool : \"\"\" Seemlessly transition to next file in file list. Returns ------- bool: True if there is a next file otherwise False. \"\"\" self . index += 1 self . prog_close () if self . index < len ( self . paths ): path = self . paths [ self . index ] self . current . close () if self . progress == 2 : self . prog_start ( os . path . getsize ( path ), path , self . progress , unit = \"bytes\" ) self . current = open ( path , \"rb\" ) return True return False __init__ ( self , ** kwargs ) special Construct TorrentFile instance with given keyword args. Parameters: Name Type Description Default kwargs dict dictionary of keyword args passed to superclass. {} Source code in torrentfile\\torrent.py def __init__ ( self , ** kwargs ): \"\"\" Construct TorrentFile instance with given keyword args. Parameters ---------- kwargs : dict dictionary of keyword args passed to superclass. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"bittorrent version 1 file assembly\" ) self . assemble () assemble ( self ) Assemble components of torrent metafile. Returns: Type Description dict metadata dictionary for torrent file Source code in torrentfile\\torrent.py def assemble ( self ): \"\"\" Assemble components of torrent metafile. Returns ------- dict metadata dictionary for torrent file \"\"\" info = self . meta [ \"info\" ] size , filelist = utils . filelist_total ( self . path ) if os . path . isfile ( self . path ): info [ \"length\" ] = size else : info [ \"files\" ] = [ { \"length\" : os . path . getsize ( path ), \"path\" : os . path . relpath ( path , self . path ) . split ( os . sep ), } for path in filelist ] pieces = bytearray () feeder = Hasher ( filelist , self . piece_length , self . progress ) if self . progress != 1 : for piece in feeder : pieces . extend ( piece ) else : total = ( size // self . piece_length ) + 1 title = self . path unit = \"pieces\" self . prog_start ( total , title , unit = unit ) for piece in feeder : pieces . extend ( piece ) self . prog_update ( 1 ) self . prog_close () info [ \"pieces\" ] = pieces TorrentFileHybrid ( MetaFile , ProgMixin ) Construct the Hybrid torrent meta file with provided parameters. Parameters: Name Type Description Default kwargs dict Keyword arguments for torrent options. {} Source code in torrentfile\\torrent.py class TorrentFileHybrid ( MetaFile , ProgMixin ): \"\"\" Construct the Hybrid torrent meta file with provided parameters. Parameters ---------- kwargs : dict Keyword arguments for torrent options. \"\"\" hasher = HasherHybrid def __init__ ( self , ** kwargs ): \"\"\" Create Bittorrent v1 v2 hybrid metafiles. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"hybrid bittorrent file detected\" ) self . name = os . path . basename ( self . path ) self . hashes = [] self . piece_layers = {} self . pieces = [] self . files = [] self . total = len ( utils . get_file_list ( self . path )) if self . progress == 1 : title = os . path . basename ( self . path ) unit = \"bytes\" self . prog_start ( self . total , title , unit = unit ) self . assemble () self . prog_close () def assemble ( self ): \"\"\" Assemble the parts of the torrentfile into meta dictionary. \"\"\" info = self . meta [ \"info\" ] info [ \"meta version\" ] = 2 if os . path . isfile ( self . path ): info [ \"file tree\" ] = { self . name : self . _traverse ( self . path )} info [ \"length\" ] = os . path . getsize ( self . path ) self . prog_update ( info [ \"length\" ]) else : info [ \"file tree\" ] = self . _traverse ( self . path ) info [ \"files\" ] = self . files info [ \"pieces\" ] = b \"\" . join ( self . pieces ) self . meta [ \"piece layers\" ] = self . piece_layers return info def _traverse ( self , path : str ) -> dict : \"\"\" Build meta dictionary while walking directory. Parameters ---------- path : str Path to target file. \"\"\" if os . path . isfile ( path ): file_size = os . path . getsize ( path ) self . files . append ( { \"length\" : file_size , \"path\" : os . path . relpath ( path , self . path ) . split ( os . sep ), } ) if file_size == 0 : return { \"\" : { \"length\" : file_size }} file_hash = HasherHybrid ( path , self . piece_length , self . progress ) self . prog_update ( file_size ) if file_size > self . piece_length : self . piece_layers [ file_hash . root ] = file_hash . piece_layer self . hashes . append ( file_hash ) self . pieces . extend ( file_hash . pieces ) if file_hash . padding_file : self . files . append ( file_hash . padding_file ) return { \"\" : { \"length\" : file_size , \"pieces root\" : file_hash . root }} tree = {} if os . path . isdir ( path ): for name in sorted ( os . listdir ( path )): tree [ name ] = self . _traverse ( os . path . join ( path , name )) return tree hasher ( CbMixin , ProgMixin ) Calculate root and piece hashes for creating hybrid torrent file. Create merkle tree layers from sha256 hashed 16KiB blocks of contents. With a branching factor of 2, merge layer hashes until blocks equal piece_length bytes for the piece layer, and then the root hash. Parameters: Name Type Description Default path str path to target file. required piece_length int piece length for data chunks. required progress int default = None None Source code in torrentfile\\torrent.py class HasherHybrid ( CbMixin , ProgMixin ): \"\"\" Calculate root and piece hashes for creating hybrid torrent file. Create merkle tree layers from sha256 hashed 16KiB blocks of contents. With a branching factor of 2, merge layer hashes until blocks equal piece_length bytes for the piece layer, and then the root hash. Parameters ---------- path : str path to target file. piece_length : int piece length for data chunks. progress : int default = None \"\"\" def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Construct Hasher class instances for each file in torrent. \"\"\" self . path = path self . piece_length = piece_length self . pieces = [] self . layer_hashes = [] self . piece_layer = None self . root = None self . padding_piece = None self . padding_file = None if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . amount = piece_length // BLOCK_SIZE logger . debug ( \"Hashing: %s , Piece Size: %s \" , str ( self . path ), humanize_bytes ( self . piece_length ), ) with open ( path , \"rb\" ) as data : self . process_file ( data ) def _pad_remaining ( self , block_count : int ): \"\"\" Generate Hash sized, 0 filled bytes for padding. Parameters ---------- block_count : int current total number of blocks collected. Returns ------- padding : bytes Padding to fill remaining portion of tree. \"\"\" # when the there is only one block for file remaining = self . amount - block_count if not self . layer_hashes : power2 = next_power_2 ( block_count ) remaining = power2 - block_count self . prog_update ( HASH_SIZE * remaining ) return [ bytes ( HASH_SIZE ) for _ in range ( remaining )] def process_file ( self , data : bytearray ): \"\"\" Calculate layer hashes for contents of file. Parameters ---------- data : BytesIO File opened in read mode. \"\"\" while True : plength = self . piece_length blocks = [] piece = sha1 () # nosec total = 0 block = bytearray ( BLOCK_SIZE ) for _ in range ( self . amount ): size = data . readinto ( block ) self . prog_update ( size ) if not size : break total += size plength -= size blocks . append ( sha256 ( block [: size ]) . digest ()) piece . update ( block [: size ]) if not blocks : break if len ( blocks ) != self . amount : padding = self . _pad_remaining ( len ( blocks )) blocks . extend ( padding ) layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) if plength > 0 : self . padding_file = { \"attr\" : \"p\" , \"length\" : plength , \"path\" : [ \".pad\" , str ( plength )], } piece . update ( bytes ( plength )) self . pieces . append ( piece . digest ()) # nosec self . _calculate_root () self . prog_close () def _calculate_root ( self ): \"\"\" Calculate the root hash for opened file. \"\"\" self . piece_layer = b \"\" . join ( self . layer_hashes ) if len ( self . layer_hashes ) > 1 : pad_piece = merkle_root ([ bytes ( 32 ) for _ in range ( self . amount )]) pow2 = next_power_2 ( len ( self . layer_hashes )) remainder = pow2 - len ( self . layer_hashes ) self . layer_hashes += [ pad_piece for _ in range ( remainder )] self . root = merkle_root ( self . layer_hashes ) __init__ ( self , path , piece_length , progress = None ) special Construct Hasher class instances for each file in torrent. Source code in torrentfile\\torrent.py def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Construct Hasher class instances for each file in torrent. \"\"\" self . path = path self . piece_length = piece_length self . pieces = [] self . layer_hashes = [] self . piece_layer = None self . root = None self . padding_piece = None self . padding_file = None if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . amount = piece_length // BLOCK_SIZE logger . debug ( \"Hashing: %s , Piece Size: %s \" , str ( self . path ), humanize_bytes ( self . piece_length ), ) with open ( path , \"rb\" ) as data : self . process_file ( data ) process_file ( self , data ) Calculate layer hashes for contents of file. Parameters: Name Type Description Default data bytearray File opened in read mode. required Source code in torrentfile\\torrent.py def process_file ( self , data : bytearray ): \"\"\" Calculate layer hashes for contents of file. Parameters ---------- data : BytesIO File opened in read mode. \"\"\" while True : plength = self . piece_length blocks = [] piece = sha1 () # nosec total = 0 block = bytearray ( BLOCK_SIZE ) for _ in range ( self . amount ): size = data . readinto ( block ) self . prog_update ( size ) if not size : break total += size plength -= size blocks . append ( sha256 ( block [: size ]) . digest ()) piece . update ( block [: size ]) if not blocks : break if len ( blocks ) != self . amount : padding = self . _pad_remaining ( len ( blocks )) blocks . extend ( padding ) layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) if plength > 0 : self . padding_file = { \"attr\" : \"p\" , \"length\" : plength , \"path\" : [ \".pad\" , str ( plength )], } piece . update ( bytes ( plength )) self . pieces . append ( piece . digest ()) # nosec self . _calculate_root () self . prog_close () __init__ ( self , ** kwargs ) special Create Bittorrent v1 v2 hybrid metafiles. Source code in torrentfile\\torrent.py def __init__ ( self , ** kwargs ): \"\"\" Create Bittorrent v1 v2 hybrid metafiles. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"hybrid bittorrent file detected\" ) self . name = os . path . basename ( self . path ) self . hashes = [] self . piece_layers = {} self . pieces = [] self . files = [] self . total = len ( utils . get_file_list ( self . path )) if self . progress == 1 : title = os . path . basename ( self . path ) unit = \"bytes\" self . prog_start ( self . total , title , unit = unit ) self . assemble () self . prog_close () assemble ( self ) Assemble the parts of the torrentfile into meta dictionary. Source code in torrentfile\\torrent.py def assemble ( self ): \"\"\" Assemble the parts of the torrentfile into meta dictionary. \"\"\" info = self . meta [ \"info\" ] info [ \"meta version\" ] = 2 if os . path . isfile ( self . path ): info [ \"file tree\" ] = { self . name : self . _traverse ( self . path )} info [ \"length\" ] = os . path . getsize ( self . path ) self . prog_update ( info [ \"length\" ]) else : info [ \"file tree\" ] = self . _traverse ( self . path ) info [ \"files\" ] = self . files info [ \"pieces\" ] = b \"\" . join ( self . pieces ) self . meta [ \"piece layers\" ] = self . piece_layers return info TorrentFileV2 ( MetaFile , ProgMixin ) Class for creating Bittorrent meta v2 files. Parameters: Name Type Description Default kwargs dict Keyword arguments for torrent file options. {} Source code in torrentfile\\torrent.py class TorrentFileV2 ( MetaFile , ProgMixin ): \"\"\" Class for creating Bittorrent meta v2 files. Parameters ---------- kwargs : dict Keyword arguments for torrent file options. \"\"\" hasher = HasherV2 def __init__ ( self , ** kwargs ): \"\"\" Construct `TorrentFileV2` Class instance from given parameters. Parameters ---------- kwargs : dict keywword arguments to pass to superclass. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"bittorrent v2 file detected\" ) self . piece_layers = {} self . hashes = [] self . total = len ( utils . get_file_list ( self . path )) if self . progress == 1 : title = os . path . basename ( self . path ) total = self . total unit = \"bytes\" self . prog_start ( total , title , unit = unit ) self . assemble () self . prog_close () def assemble ( self ): \"\"\" Assemble then return the meta dictionary for encoding. Returns ------- meta : dict Metainformation about the torrent. \"\"\" info = self . meta [ \"info\" ] if os . path . isfile ( self . path ): info [ \"file tree\" ] = { info [ \"name\" ]: self . _traverse ( self . path )} info [ \"length\" ] = os . path . getsize ( self . path ) self . prog_update ( info [ \"length\" ]) else : info [ \"file tree\" ] = self . _traverse ( self . path ) info [ \"meta version\" ] = 2 self . meta [ \"piece layers\" ] = self . piece_layers def _traverse ( self , path : str ) -> dict : \"\"\" Walk directory tree. Parameters ---------- path : str Path to file or directory. \"\"\" if os . path . isfile ( path ): # Calculate Size and hashes for each file. size = os . path . getsize ( path ) if size == 0 : return { \"\" : { \"length\" : size }} fhash = HasherV2 ( path , self . piece_length , self . progress ) self . prog_update ( size ) if size > self . piece_length : self . piece_layers [ fhash . root ] = fhash . piece_layer return { \"\" : { \"length\" : size , \"pieces root\" : fhash . root }} file_tree = {} if os . path . isdir ( path ): for name in sorted ( os . listdir ( path )): file_tree [ name ] = self . _traverse ( os . path . join ( path , name )) return file_tree hasher ( CbMixin , ProgMixin ) Calculate the root hash and piece layers for file contents. Iterates over 16KiB blocks of data from given file, hashes the data, then creates a hash tree from the individual block hashes until size of hashed data equals the piece-length. Then continues the hash tree until root hash is calculated. Parameters: Name Type Description Default path str Path to file. required piece_length int Size of layer hashes pieces. required progress int default = None None Source code in torrentfile\\torrent.py class HasherV2 ( CbMixin , ProgMixin ): \"\"\" Calculate the root hash and piece layers for file contents. Iterates over 16KiB blocks of data from given file, hashes the data, then creates a hash tree from the individual block hashes until size of hashed data equals the piece-length. Then continues the hash tree until root hash is calculated. Parameters ---------- path : str Path to file. piece_length : int Size of layer hashes pieces. progress : int default = None \"\"\" def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Calculate and store hash information for specific file. \"\"\" self . path = path self . root = None self . piece_layer = None self . layer_hashes = [] self . piece_length = piece_length self . num_blocks = piece_length // BLOCK_SIZE if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) logger . debug ( \"Hashing partial v2 torrent file. Piece Length: %s Path: %s \" , humanize_bytes ( self . piece_length ), str ( self . path ), ) with open ( self . path , \"rb\" ) as fd : self . process_file ( fd ) def process_file ( self , fd : str ): \"\"\" Calculate hashes over 16KiB chuncks of file content. Parameters ---------- fd : str Opened file in read mode. \"\"\" while True : blocks = [] leaf = bytearray ( BLOCK_SIZE ) # generate leaves of merkle tree for _ in range ( self . num_blocks ): size = fd . readinto ( leaf ) self . prog_update ( size ) if not size : break blocks . append ( sha256 ( leaf [: size ]) . digest ()) # blocks is empty mean eof if not blocks : break if len ( blocks ) != self . num_blocks : # when size of file doesn't fill the last block # when the file contains multiple pieces remaining = self . num_blocks - len ( blocks ) if not self . layer_hashes : # when the there is only one block for file power2 = next_power_2 ( len ( blocks )) remaining = power2 - len ( blocks ) # pad the the rest with zeroes to fill remaining space. padding = [ bytes ( 32 ) for _ in range ( remaining )] self . prog_update ( HASH_SIZE * remaining ) blocks . extend ( padding ) # calculate the root hash for the merkle tree up to piece-length layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) self . _calculate_root () self . prog_close () def _calculate_root ( self ): \"\"\" Calculate root hash for the target file. \"\"\" self . piece_layer = b \"\" . join ( self . layer_hashes ) hashes = len ( self . layer_hashes ) if hashes > 1 : pow2 = next_power_2 ( hashes ) remainder = pow2 - hashes pad_piece = [ bytes ( HASH_SIZE ) for _ in range ( self . num_blocks )] for _ in range ( remainder ): self . layer_hashes . append ( merkle_root ( pad_piece )) self . root = merkle_root ( self . layer_hashes ) __init__ ( self , path , piece_length , progress = None ) special Calculate and store hash information for specific file. Source code in torrentfile\\torrent.py def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Calculate and store hash information for specific file. \"\"\" self . path = path self . root = None self . piece_layer = None self . layer_hashes = [] self . piece_length = piece_length self . num_blocks = piece_length // BLOCK_SIZE if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) logger . debug ( \"Hashing partial v2 torrent file. Piece Length: %s Path: %s \" , humanize_bytes ( self . piece_length ), str ( self . path ), ) with open ( self . path , \"rb\" ) as fd : self . process_file ( fd ) process_file ( self , fd ) Calculate hashes over 16KiB chuncks of file content. Parameters: Name Type Description Default fd str Opened file in read mode. required Source code in torrentfile\\torrent.py def process_file ( self , fd : str ): \"\"\" Calculate hashes over 16KiB chuncks of file content. Parameters ---------- fd : str Opened file in read mode. \"\"\" while True : blocks = [] leaf = bytearray ( BLOCK_SIZE ) # generate leaves of merkle tree for _ in range ( self . num_blocks ): size = fd . readinto ( leaf ) self . prog_update ( size ) if not size : break blocks . append ( sha256 ( leaf [: size ]) . digest ()) # blocks is empty mean eof if not blocks : break if len ( blocks ) != self . num_blocks : # when size of file doesn't fill the last block # when the file contains multiple pieces remaining = self . num_blocks - len ( blocks ) if not self . layer_hashes : # when the there is only one block for file power2 = next_power_2 ( len ( blocks )) remaining = power2 - len ( blocks ) # pad the the rest with zeroes to fill remaining space. padding = [ bytes ( 32 ) for _ in range ( remaining )] self . prog_update ( HASH_SIZE * remaining ) blocks . extend ( padding ) # calculate the root hash for the merkle tree up to piece-length layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) self . _calculate_root () self . prog_close () __init__ ( self , ** kwargs ) special Construct TorrentFileV2 Class instance from given parameters. Parameters: Name Type Description Default kwargs dict keywword arguments to pass to superclass. {} Source code in torrentfile\\torrent.py def __init__ ( self , ** kwargs ): \"\"\" Construct `TorrentFileV2` Class instance from given parameters. Parameters ---------- kwargs : dict keywword arguments to pass to superclass. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"bittorrent v2 file detected\" ) self . piece_layers = {} self . hashes = [] self . total = len ( utils . get_file_list ( self . path )) if self . progress == 1 : title = os . path . basename ( self . path ) total = self . total unit = \"bytes\" self . prog_start ( total , title , unit = unit ) self . assemble () self . prog_close () assemble ( self ) Assemble then return the meta dictionary for encoding. Returns: Type Description dict Metainformation about the torrent. Source code in torrentfile\\torrent.py def assemble ( self ): \"\"\" Assemble then return the meta dictionary for encoding. Returns ------- meta : dict Metainformation about the torrent. \"\"\" info = self . meta [ \"info\" ] if os . path . isfile ( self . path ): info [ \"file tree\" ] = { info [ \"name\" ]: self . _traverse ( self . path )} info [ \"length\" ] = os . path . getsize ( self . path ) self . prog_update ( info [ \"length\" ]) else : info [ \"file tree\" ] = self . _traverse ( self . path ) info [ \"meta version\" ] = 2 self . meta [ \"piece layers\" ] = self . piece_layers utils Utility functions and classes used throughout package. Functions: get_piece_length: calculate ideal piece length for torrent file. sortfiles: traverse directory in sorted order yielding paths encountered. path_size: Sum the sizes of each file in path. get_file_list: Return list of all files contained in directory. path_stat: Get ideal piece length, total size, and file list for directory. path_piece_length: Get ideal piece length based on size of directory. Classes: MissingPathError: Custom exception raised when no path was provided to CLI. PieceLengthValueError: Custom exception raised when incorrect input value used for piece length field. Memo Memoice chache object. Parameters: Name Type Description Default func function The function that is being memoized. required Source code in torrentfile\\utils.py class Memo : \"\"\" Memoice chache object. Parameters ---------- func : function The function that is being memoized. \"\"\" def __init__ ( self , func ): \"\"\" Construct for memoization. \"\"\" self . func = func self . counter = 0 self . cache = {} def __call__ ( self , path ): \"\"\" Invoke each time memo function is called. Parameters ---------- path : str The relative or absolute path being used as key in cache dict. Returns ------- Any : The results of calling the function with path. \"\"\" if path in self . cache and os . path . exists ( path ): self . counter += 1 return self . cache [ path ] result = self . func ( path ) self . cache [ path ] = result return result __call__ ( self , path ) special Invoke each time memo function is called. Parameters: Name Type Description Default path str The relative or absolute path being used as key in cache dict. required Source code in torrentfile\\utils.py def __call__ ( self , path ): \"\"\" Invoke each time memo function is called. Parameters ---------- path : str The relative or absolute path being used as key in cache dict. Returns ------- Any : The results of calling the function with path. \"\"\" if path in self . cache and os . path . exists ( path ): self . counter += 1 return self . cache [ path ] result = self . func ( path ) self . cache [ path ] = result return result __init__ ( self , func ) special Construct for memoization. Source code in torrentfile\\utils.py def __init__ ( self , func ): \"\"\" Construct for memoization. \"\"\" self . func = func self . counter = 0 self . cache = {} MissingPathError ( Exception ) Path parameter is required to specify target content. Creating a .torrent file with no contents seems rather silly. Parameters: Name Type Description Default message str Message for user (optional). None Source code in torrentfile\\utils.py class MissingPathError ( Exception ): \"\"\" Path parameter is required to specify target content. Creating a .torrent file with no contents seems rather silly. Parameters ---------- message : str Message for user (optional). \"\"\" def __init__ ( self , message : str = None ): \"\"\" Raise when creating a meta file without specifying target content. The `message` argument is a message to pass to Exception base class. \"\"\" self . message = f \"Path arguement is missing and required { str ( message ) } \" super () . __init__ ( message ) __init__ ( self , message = None ) special Raise when creating a meta file without specifying target content. The message argument is a message to pass to Exception base class. Source code in torrentfile\\utils.py def __init__ ( self , message : str = None ): \"\"\" Raise when creating a meta file without specifying target content. The `message` argument is a message to pass to Exception base class. \"\"\" self . message = f \"Path arguement is missing and required { str ( message ) } \" super () . __init__ ( message ) PieceLengthValueError ( Exception ) Piece Length parameter must equal a perfect power of 2. Parameters: Name Type Description Default message str Message for user (optional). None Source code in torrentfile\\utils.py class PieceLengthValueError ( Exception ): \"\"\" Piece Length parameter must equal a perfect power of 2. Parameters ---------- message : str Message for user (optional). \"\"\" def __init__ ( self , message : str = None ): \"\"\" Raise when creating a meta file with incorrect piece length value. The `message` argument is a message to pass to Exception base class. \"\"\" self . message = f \"Incorrect value for piece length: { str ( message ) } \" super () . __init__ ( message ) __init__ ( self , message = None ) special Raise when creating a meta file with incorrect piece length value. The message argument is a message to pass to Exception base class. Source code in torrentfile\\utils.py def __init__ ( self , message : str = None ): \"\"\" Raise when creating a meta file with incorrect piece length value. The `message` argument is a message to pass to Exception base class. \"\"\" self . message = f \"Incorrect value for piece length: { str ( message ) } \" super () . __init__ ( message ) get_file_list ( path ) Return a sorted list of file paths contained in directory. Parameters: Name Type Description Default path str target file or directory. required Returns: Type Description list sorted list of file paths. Source code in torrentfile\\utils.py def get_file_list ( path : str ) -> list : \"\"\" Return a sorted list of file paths contained in directory. Parameters ---------- path : str target file or directory. Returns ------- list sorted list of file paths. \"\"\" _ , filelist = filelist_total ( path ) return filelist get_piece_length ( size ) Calculate the ideal piece length for bittorrent data. Parameters: Name Type Description Default size int Total bits of all files incluided in .torrent file. required Returns: Type Description int Ideal piece length. Source code in torrentfile\\utils.py def get_piece_length ( size : int ) -> int : \"\"\" Calculate the ideal piece length for bittorrent data. Parameters ---------- size : int Total bits of all files incluided in .torrent file. Returns ------- int Ideal piece length. \"\"\" exp = 14 while size / ( 2 ** exp ) > 200 and exp < 25 : exp += 1 return 2 ** exp humanize_bytes ( amount ) Convert integer into human readable memory sized denomination. Parameters: Name Type Description Default amount int total number of bytes. required Returns: Type Description str human readable representation of the given amount of bytes. Source code in torrentfile\\utils.py def humanize_bytes ( amount : int ) -> str : \"\"\" Convert integer into human readable memory sized denomination. Parameters ---------- amount : int total number of bytes. Returns ------- str human readable representation of the given amount of bytes. \"\"\" if amount < 1024 : return str ( amount ) if 1024 <= amount < 1_048_576 : return f \" { amount // 1024 } KiB\" if 1_048_576 <= amount < 1_073_741_824 : return f \" { amount // 1_048_576 } MiB\" return f \" { amount // 1073741824 } GiB\" next_power_2 ( value ) Calculate the next perfect power of 2 equal to or greater than value. Parameters: Name Type Description Default value int integer value that is less than some perfect power of 2. required Returns: Type Description int The next power of 2 greater than value, or value if already power of 2. Source code in torrentfile\\utils.py def next_power_2 ( value : int ) -> int : \"\"\" Calculate the next perfect power of 2 equal to or greater than value. Parameters ---------- value : int integer value that is less than some perfect power of 2. Returns ------- int The next power of 2 greater than value, or value if already power of 2. \"\"\" if not value & ( value - 1 ) and value : return value start = 1 while start < value : start <<= 1 return start normalize_piece_length ( piece_length ) Verify input piece_length is valid and convert accordingly. Parameters: Name Type Description Default piece_length int The piece length provided by user. required Exceptions: Type Description PieceLengthValueError : If piece length is improper value. Returns: Type Description int normalized piece length. Source code in torrentfile\\utils.py def normalize_piece_length ( piece_length : int ) -> int : \"\"\" Verify input piece_length is valid and convert accordingly. Parameters ---------- piece_length : int | str The piece length provided by user. Returns ------- int normalized piece length. Raises ------ PieceLengthValueError : If piece length is improper value. \"\"\" if isinstance ( piece_length , str ): if piece_length . isnumeric (): piece_length = int ( piece_length ) else : raise PieceLengthValueError ( piece_length ) if 13 < piece_length < 26 : return 2 ** piece_length if piece_length <= 13 : raise PieceLengthValueError ( piece_length ) log = int ( math . log2 ( piece_length )) if 2 ** log == piece_length : return piece_length raise PieceLengthValueError path_piece_length ( path ) Calculate piece length for input path and contents. Parameters: Name Type Description Default path str The absolute path to directory and contents. required Returns: Type Description int The size of pieces of torrent content. Source code in torrentfile\\utils.py def path_piece_length ( path : str ) -> int : \"\"\" Calculate piece length for input path and contents. Parameters ---------- path : str The absolute path to directory and contents. Returns ------- int The size of pieces of torrent content. \"\"\" psize = path_size ( path ) return get_piece_length ( psize ) path_size ( path ) Return the total size of all files in path recursively. Parameters: Name Type Description Default path str path to target file or directory. required Returns: Type Description int total size of files. Source code in torrentfile\\utils.py def path_size ( path : str ) -> int : \"\"\" Return the total size of all files in path recursively. Parameters ---------- path : str path to target file or directory. Returns ------- int total size of files. \"\"\" total_size , _ = filelist_total ( path ) return total_size path_stat ( path ) Calculate directory statistics. Parameters: Name Type Description Default path str The path to start calculating from. required Returns: Type Description tuple List of all files contained in Directory Source code in torrentfile\\utils.py def path_stat ( path : str ) -> tuple : \"\"\" Calculate directory statistics. Parameters ---------- path : str The path to start calculating from. Returns ------- list List of all files contained in Directory int Total sum of bytes from all contents of dir int The size of pieces of the torrent contents. \"\"\" total_size , filelist = filelist_total ( path ) piece_length = get_piece_length ( total_size ) return ( filelist , total_size , piece_length ) version Holds the release version number. torrentfile.cli Command Line Interface for TorrentFile project. This module provides the primary command line argument parser for the torrentfile package. The main_script function is automatically invoked when called from command line, and parses accompanying arguments. Functions: main_script: process command line arguments and run program. activate_logger: turns on debug mode and logging facility. TorrentFileHelpFormatter ( HelpFormatter ) Formatting class for help tips provided by the CLI. Subclasses Argparse.HelpFormatter. Source code in torrentfile\\cli.py class TorrentFileHelpFormatter ( HelpFormatter ): \"\"\" Formatting class for help tips provided by the CLI. Subclasses Argparse.HelpFormatter. \"\"\" def __init__ ( self , prog , width = 40 , max_help_positions = 30 ): \"\"\" Construct HelpFormat class for usage output. Parameters ---------- prog : str Name of the program. width : int Max width of help message output. max_help_positions : int max length until line wrap. \"\"\" super () . __init__ ( prog , width = width , max_help_position = max_help_positions ) def _split_lines ( self , text , _ ): \"\"\" Split multiline help messages and remove indentation. Parameters ---------- text : str text that needs to be split _ : int max width for line. \"\"\" lines = text . split ( \" \\n \" ) return [ line . strip () for line in lines if line ] def _format_text ( self , text ): \"\"\" Format text for cli usage messages. Parameters ---------- text : str Pre-formatted text. Returns ------- str Formatted text from input. \"\"\" text = text % dict ( prog = self . _prog ) if \"%(prog)\" in text else text text = self . _whitespace_matcher . sub ( \" \" , text ) . strip () return text + \" \\n\\n \" def _join_parts ( self , part_strings ): \"\"\" Combine different sections of the help message. Parameters ---------- part_strings : list List of argument help messages and headers. Returns ------- str Fully formatted help message for CLI. \"\"\" parts = self . format_headers ( part_strings ) return super () . _join_parts ( parts ) @staticmethod def format_headers ( parts ): \"\"\" Format help message section headers. Parameters ---------- parts : list List of individual lines for help message. Returns ------- list Input list with formatted section headers. \"\"\" if parts and parts [ 0 ] . startswith ( \"usage:\" ): parts [ 0 ] = \"Usage \\n ===== \\n \" + parts [ 0 ][ 6 :] headings = [ i for i in range ( len ( parts )) if parts [ i ] . endswith ( \": \\n \" )] for i in headings [:: - 1 ]: parts [ i ] = parts [ i ][: - 2 ] . title () underline = \"\" . join ([ \" \\n \" , \"-\" * len ( parts [ i ]), \" \\n \" ]) parts . insert ( i + 1 , underline ) return parts __init__ ( self , prog , width = 40 , max_help_positions = 30 ) special Construct HelpFormat class for usage output. Parameters: Name Type Description Default prog str Name of the program. required width int Max width of help message output. 40 max_help_positions int max length until line wrap. 30 Source code in torrentfile\\cli.py def __init__ ( self , prog , width = 40 , max_help_positions = 30 ): \"\"\" Construct HelpFormat class for usage output. Parameters ---------- prog : str Name of the program. width : int Max width of help message output. max_help_positions : int max length until line wrap. \"\"\" super () . __init__ ( prog , width = width , max_help_position = max_help_positions ) format_headers ( parts ) staticmethod Format help message section headers. Parameters: Name Type Description Default parts list List of individual lines for help message. required Returns: Type Description list Input list with formatted section headers. Source code in torrentfile\\cli.py @staticmethod def format_headers ( parts ): \"\"\" Format help message section headers. Parameters ---------- parts : list List of individual lines for help message. Returns ------- list Input list with formatted section headers. \"\"\" if parts and parts [ 0 ] . startswith ( \"usage:\" ): parts [ 0 ] = \"Usage \\n ===== \\n \" + parts [ 0 ][ 6 :] headings = [ i for i in range ( len ( parts )) if parts [ i ] . endswith ( \": \\n \" )] for i in headings [:: - 1 ]: parts [ i ] = parts [ i ][: - 2 ] . title () underline = \"\" . join ([ \" \\n \" , \"-\" * len ( parts [ i ]), \" \\n \" ]) parts . insert ( i + 1 , underline ) return parts activate_logger () Activate the builtin logging mechanism when passed debug flag from CLI. Source code in torrentfile\\cli.py def activate_logger (): \"\"\" Activate the builtin logging mechanism when passed debug flag from CLI. \"\"\" logging . basicConfig ( level = logging . INFO ) logger = logging . getLogger () file_handler = logging . FileHandler ( \"torrentfile.log\" , mode = \"a+\" , encoding = \"utf-8\" ) console_handler = logging . StreamHandler ( stream = sys . stderr ) file_formatter = logging . Formatter ( \" %(asctime)s %(levelno)s %(message)s \" , datefmt = \"%m- %d %H:%M:%S\" , style = \"%\" , ) stream_formatter = logging . Formatter ( \" %(asctime)s %(levelno)s %(message)s \" , datefmt = \"%m- %d %H:%M:%S\" , style = \"%\" , ) file_handler . setFormatter ( file_formatter ) console_handler . setFormatter ( stream_formatter ) file_handler . setLevel ( logging . INFO ) console_handler . setLevel ( logging . INFO ) logger . setLevel ( logging . INFO ) logger . addHandler ( console_handler ) logger . addHandler ( file_handler ) logger . debug ( \"Debug: ON\" ) execute ( args = None ) Initialize Command Line Interface for torrentfile. Parameters: Name Type Description Default args list Commandline arguments. default=None None Source code in torrentfile\\cli.py def execute ( args = None ): \"\"\" Initialize Command Line Interface for torrentfile. Parameters ---------- args : list Commandline arguments. default=None \"\"\" if not args : if sys . argv [ 1 :]: args = sys . argv [ 1 :] else : args = [ \"-h\" ] parser = ArgumentParser ( \"torrentfile\" , description = ( \"Command line tools for creating, editing, checking and \" \"interacting with Bittorrent metainfo files\" ), prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , conflict_handler = \"resolve\" , ) parser . add_argument ( \"-i\" , \"--interactive\" , action = \"store_true\" , dest = \"interactive\" , help = \"select program options interactively\" , ) parser . add_argument ( \"-V\" , \"--version\" , action = \"version\" , version = f \"torrentfile v { version } \" , help = \"show program version and exit\" , ) parser . add_argument ( \"-v\" , \"--verbose\" , action = \"store_true\" , dest = \"debug\" , help = \"output debug information\" , ) parser . set_defaults ( func = parser . print_help ) subparsers = parser . add_subparsers ( title = \"Actions\" , dest = \"command\" , metavar = \"create, edit, magnet, recheck\" , ) create_parser = subparsers . add_parser ( \"create\" , help = \"\"\"Generate a new torrent meta file.\"\"\" , prefix_chars = \"-\" , aliases = [ \"c\" , \"new\" ], formatter_class = TorrentFileHelpFormatter , ) create_parser . add_argument ( \"-a\" , \"-t\" , \"--announce\" , \"--tracker\" , action = \"store\" , dest = \"announce\" , metavar = \"\" , nargs = \"+\" , default = [], help = \"One or more space-seperated torrent tracker url(s).\" , ) create_parser . add_argument ( \"-p\" , \"--private\" , action = \"store_true\" , dest = \"private\" , help = \"Creates private torrent with multi-tracker and DHT turned off.\" , ) create_parser . add_argument ( \"-s\" , \"--source\" , action = \"store\" , dest = \"source\" , metavar = \"\" , help = \"Add a source string. Useful for cross-seeding.\" , ) create_parser . add_argument ( \"-m\" , \"--magnet\" , action = \"store_true\" , dest = \"magnet\" , ) create_parser . add_argument ( \"-c\" , \"--comment\" , action = \"store\" , dest = \"comment\" , metavar = \"\" , help = \"Include a comment in file metadata\" , ) create_parser . add_argument ( \"-o\" , \"--out\" , action = \"store\" , dest = \"outfile\" , metavar = \"\" , help = \"Output save path for created .torrent file\" , ) create_parser . add_argument ( \"--cwd\" , \"--current\" , action = \"store_true\" , dest = \"cwd\" , help = \"Save output .torrent file to current directory\" , ) create_parser . add_argument ( \"--progress\" , \"--prog\" , choices = [ \"0\" , \"1\" , \"2\" ], default = \"1\" , action = \"store\" , dest = \"progress\" , help = \"\"\" Set the progress bar level. Options = 0, 1, 2 (0) = Do not display progress bar. (1) = Display 1 progress bar for the whole torrent (default) (2) = Display a progress bar for each file going into torrent \"\"\" , ) create_parser . add_argument ( \"--meta-version\" , default = \"1\" , choices = [ \"1\" , \"2\" , \"3\" ], action = \"store\" , dest = \"meta_version\" , metavar = \"\" , help = \"\"\" Bittorrent metafile version. Options = 1, 2, 3 (1) = Bittorrent v1 (Default) (2) = Bittorrent v2 (3) = Bittorrent v1 & v2 hybrid \"\"\" , ) create_parser . add_argument ( \"--piece-length\" , action = \"store\" , dest = \"piece_length\" , metavar = \"\" , help = \"\"\" (Default: ) Number of bytes for per chunk of data transmitted by Bittorrent client. Acceptable values include integers 14-26 which will be interpreted as a perfect power of 2. e.g. 14 = 16KiB pieces. Examples:: [--piece-length 14] [--piece-length 20] \"\"\" , ) create_parser . add_argument ( \"-w\" , \"--web-seed\" , action = \"store\" , dest = \"url_list\" , metavar = \"\" , nargs = \"+\" , help = \"list of web addresses where torrent data exists (GetRight).\" , ) create_parser . add_argument ( \"--http-seed\" , action = \"store\" , dest = \"httpseeds\" , metavar = \"\" , nargs = \"+\" , help = \"list of URLs, addresses where content can be found (Hoffman).\" , ) create_parser . add_argument ( \"content\" , action = \"store\" , metavar = \"\" , nargs = \"?\" , help = \"Path to content file or directory\" , ) create_parser . set_defaults ( func = create ) edit_parser = subparsers . add_parser ( \"edit\" , help = \"\"\" Edit existing torrent meta file. \"\"\" , aliases = [ \"e\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) edit_parser . add_argument ( \"metafile\" , action = \"store\" , help = \"path to *.torrent file\" , metavar = \"<*.torrent>\" , ) edit_parser . add_argument ( \"--tracker\" , action = \"store\" , dest = \"announce\" , metavar = \"\" , nargs = \"+\" , help = \"\"\" Replace current list of tracker/announce urls with one or more space seperated Bittorrent tracker announce url(s). \"\"\" , ) edit_parser . add_argument ( \"--web-seed\" , action = \"store\" , dest = \"url_list\" , metavar = \"\" , nargs = \"+\" , help = \"Replace current list of web-seed urls with one or more url(s)\" , ) edit_parser . add_argument ( \"--http-seed\" , action = \"store\" , dest = \"httpseeds\" , metavar = \"\" , nargs = \"+\" , help = \"replace all currently listed addresses with new list (Hoffman).\" , ) edit_parser . add_argument ( \"--private\" , action = \"store_true\" , help = \"Make torrent private.\" , dest = \"private\" , ) edit_parser . add_argument ( \"--comment\" , help = \"Replaces any existing comment with \" , metavar = \"\" , dest = \"comment\" , action = \"store\" , ) edit_parser . add_argument ( \"--source\" , action = \"store\" , dest = \"source\" , metavar = \"\" , help = \"Replaces current source with \" , ) edit_parser . set_defaults ( func = edit ) magnet_parser = subparsers . add_parser ( \"magnet\" , help = \"\"\" Generate magnet url from an existing Bittorrent meta file. \"\"\" , aliases = [ \"m\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) magnet_parser . add_argument ( \"metafile\" , action = \"store\" , help = \"Path to Bittorrent meta file.\" , metavar = \"<*.torrent>\" , ) magnet_parser . set_defaults ( func = magnet ) check_parser = subparsers . add_parser ( \"recheck\" , help = \"\"\" Calculate amount of torrent meta file's content is found on disk. \"\"\" , aliases = [ \"r\" , \"check\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) check_parser . add_argument ( \"metafile\" , action = \"store\" , metavar = \"<*.torrent>\" , help = \"path to .torrent file.\" , ) check_parser . add_argument ( \"content\" , action = \"store\" , metavar = \"\" , help = \"path to content file or directory\" , ) check_parser . set_defaults ( func = recheck ) info_parser = subparsers . add_parser ( \"info\" , help = \"\"\" Show detailed information about a torrent file. \"\"\" , aliases = [ \"i\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) info_parser . add_argument ( \"metafile\" , action = \"store\" , metavar = \"<*.torrent>\" , help = \"path to pre-existing torrent file.\" , ) info_parser . set_defaults ( func = info ) args = parser . parse_args ( args ) if args . debug : activate_logger () if args . interactive : return select_action () return args . func ( args ) main () Initiate main function for CLI script. Source code in torrentfile\\cli.py def main (): \"\"\" Initiate main function for CLI script. \"\"\" execute () main_script ( args = None ) Initialize Command Line Interface for torrentfile. Parameters: Name Type Description Default args list Commandline arguments. default=None None Source code in torrentfile\\cli.py def execute ( args = None ): \"\"\" Initialize Command Line Interface for torrentfile. Parameters ---------- args : list Commandline arguments. default=None \"\"\" if not args : if sys . argv [ 1 :]: args = sys . argv [ 1 :] else : args = [ \"-h\" ] parser = ArgumentParser ( \"torrentfile\" , description = ( \"Command line tools for creating, editing, checking and \" \"interacting with Bittorrent metainfo files\" ), prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , conflict_handler = \"resolve\" , ) parser . add_argument ( \"-i\" , \"--interactive\" , action = \"store_true\" , dest = \"interactive\" , help = \"select program options interactively\" , ) parser . add_argument ( \"-V\" , \"--version\" , action = \"version\" , version = f \"torrentfile v { version } \" , help = \"show program version and exit\" , ) parser . add_argument ( \"-v\" , \"--verbose\" , action = \"store_true\" , dest = \"debug\" , help = \"output debug information\" , ) parser . set_defaults ( func = parser . print_help ) subparsers = parser . add_subparsers ( title = \"Actions\" , dest = \"command\" , metavar = \"create, edit, magnet, recheck\" , ) create_parser = subparsers . add_parser ( \"create\" , help = \"\"\"Generate a new torrent meta file.\"\"\" , prefix_chars = \"-\" , aliases = [ \"c\" , \"new\" ], formatter_class = TorrentFileHelpFormatter , ) create_parser . add_argument ( \"-a\" , \"-t\" , \"--announce\" , \"--tracker\" , action = \"store\" , dest = \"announce\" , metavar = \"\" , nargs = \"+\" , default = [], help = \"One or more space-seperated torrent tracker url(s).\" , ) create_parser . add_argument ( \"-p\" , \"--private\" , action = \"store_true\" , dest = \"private\" , help = \"Creates private torrent with multi-tracker and DHT turned off.\" , ) create_parser . add_argument ( \"-s\" , \"--source\" , action = \"store\" , dest = \"source\" , metavar = \"\" , help = \"Add a source string. Useful for cross-seeding.\" , ) create_parser . add_argument ( \"-m\" , \"--magnet\" , action = \"store_true\" , dest = \"magnet\" , ) create_parser . add_argument ( \"-c\" , \"--comment\" , action = \"store\" , dest = \"comment\" , metavar = \"\" , help = \"Include a comment in file metadata\" , ) create_parser . add_argument ( \"-o\" , \"--out\" , action = \"store\" , dest = \"outfile\" , metavar = \"\" , help = \"Output save path for created .torrent file\" , ) create_parser . add_argument ( \"--cwd\" , \"--current\" , action = \"store_true\" , dest = \"cwd\" , help = \"Save output .torrent file to current directory\" , ) create_parser . add_argument ( \"--progress\" , \"--prog\" , choices = [ \"0\" , \"1\" , \"2\" ], default = \"1\" , action = \"store\" , dest = \"progress\" , help = \"\"\" Set the progress bar level. Options = 0, 1, 2 (0) = Do not display progress bar. (1) = Display 1 progress bar for the whole torrent (default) (2) = Display a progress bar for each file going into torrent \"\"\" , ) create_parser . add_argument ( \"--meta-version\" , default = \"1\" , choices = [ \"1\" , \"2\" , \"3\" ], action = \"store\" , dest = \"meta_version\" , metavar = \"\" , help = \"\"\" Bittorrent metafile version. Options = 1, 2, 3 (1) = Bittorrent v1 (Default) (2) = Bittorrent v2 (3) = Bittorrent v1 & v2 hybrid \"\"\" , ) create_parser . add_argument ( \"--piece-length\" , action = \"store\" , dest = \"piece_length\" , metavar = \"\" , help = \"\"\" (Default: ) Number of bytes for per chunk of data transmitted by Bittorrent client. Acceptable values include integers 14-26 which will be interpreted as a perfect power of 2. e.g. 14 = 16KiB pieces. Examples:: [--piece-length 14] [--piece-length 20] \"\"\" , ) create_parser . add_argument ( \"-w\" , \"--web-seed\" , action = \"store\" , dest = \"url_list\" , metavar = \"\" , nargs = \"+\" , help = \"list of web addresses where torrent data exists (GetRight).\" , ) create_parser . add_argument ( \"--http-seed\" , action = \"store\" , dest = \"httpseeds\" , metavar = \"\" , nargs = \"+\" , help = \"list of URLs, addresses where content can be found (Hoffman).\" , ) create_parser . add_argument ( \"content\" , action = \"store\" , metavar = \"\" , nargs = \"?\" , help = \"Path to content file or directory\" , ) create_parser . set_defaults ( func = create ) edit_parser = subparsers . add_parser ( \"edit\" , help = \"\"\" Edit existing torrent meta file. \"\"\" , aliases = [ \"e\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) edit_parser . add_argument ( \"metafile\" , action = \"store\" , help = \"path to *.torrent file\" , metavar = \"<*.torrent>\" , ) edit_parser . add_argument ( \"--tracker\" , action = \"store\" , dest = \"announce\" , metavar = \"\" , nargs = \"+\" , help = \"\"\" Replace current list of tracker/announce urls with one or more space seperated Bittorrent tracker announce url(s). \"\"\" , ) edit_parser . add_argument ( \"--web-seed\" , action = \"store\" , dest = \"url_list\" , metavar = \"\" , nargs = \"+\" , help = \"Replace current list of web-seed urls with one or more url(s)\" , ) edit_parser . add_argument ( \"--http-seed\" , action = \"store\" , dest = \"httpseeds\" , metavar = \"\" , nargs = \"+\" , help = \"replace all currently listed addresses with new list (Hoffman).\" , ) edit_parser . add_argument ( \"--private\" , action = \"store_true\" , help = \"Make torrent private.\" , dest = \"private\" , ) edit_parser . add_argument ( \"--comment\" , help = \"Replaces any existing comment with \" , metavar = \"\" , dest = \"comment\" , action = \"store\" , ) edit_parser . add_argument ( \"--source\" , action = \"store\" , dest = \"source\" , metavar = \"\" , help = \"Replaces current source with \" , ) edit_parser . set_defaults ( func = edit ) magnet_parser = subparsers . add_parser ( \"magnet\" , help = \"\"\" Generate magnet url from an existing Bittorrent meta file. \"\"\" , aliases = [ \"m\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) magnet_parser . add_argument ( \"metafile\" , action = \"store\" , help = \"Path to Bittorrent meta file.\" , metavar = \"<*.torrent>\" , ) magnet_parser . set_defaults ( func = magnet ) check_parser = subparsers . add_parser ( \"recheck\" , help = \"\"\" Calculate amount of torrent meta file's content is found on disk. \"\"\" , aliases = [ \"r\" , \"check\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) check_parser . add_argument ( \"metafile\" , action = \"store\" , metavar = \"<*.torrent>\" , help = \"path to .torrent file.\" , ) check_parser . add_argument ( \"content\" , action = \"store\" , metavar = \"\" , help = \"path to content file or directory\" , ) check_parser . set_defaults ( func = recheck ) info_parser = subparsers . add_parser ( \"info\" , help = \"\"\" Show detailed information about a torrent file. \"\"\" , aliases = [ \"i\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) info_parser . add_argument ( \"metafile\" , action = \"store\" , metavar = \"<*.torrent>\" , help = \"path to pre-existing torrent file.\" , ) info_parser . set_defaults ( func = info ) args = parser . parse_args ( args ) if args . debug : activate_logger () if args . interactive : return select_action () return args . func ( args ) torrentfile.edit Edit torrent module. Provides a facility by which certain properties of a torrent meta file can be edited by the user. The various command line arguments indicate which fields should be edited, and what the new value should be. Depending on what fields are chosen to edit, this command can trigger a new info hash which means the torrent will no longer be able to participate in the same swarm as the original unedited torrent. Keywords private comment source trackers web-seeds edit_torrent ( metafile , args ) Edit the properties and values in a torrent meta file. Parameters: Name Type Description Default metafile str path to the torrent meta file. required args dict key value pairs of the properties to be edited. required Returns: Type Description dict The edited and nested Meta and info dictionaries. Source code in torrentfile\\edit.py def edit_torrent ( metafile : str , args : dict ) -> dict : \"\"\" Edit the properties and values in a torrent meta file. Parameters ---------- metafile : str path to the torrent meta file. args : dict key value pairs of the properties to be edited. Returns ------- dict The edited and nested Meta and info dictionaries. \"\"\" logger . debug ( \"editing torrent file %s \" , metafile ) meta = pyben . load ( metafile ) info = meta [ \"info\" ] filter_empty ( args , meta , info ) if \"comment\" in args : info [ \"comment\" ] = args [ \"comment\" ] if \"source\" in args : info [ \"source\" ] = args [ \"source\" ] if \"private\" in args : info [ \"private\" ] = 1 if \"announce\" in args : val = args . get ( \"announce\" , None ) if isinstance ( val , str ): vallist = val . split () meta [ \"announce\" ] = vallist [ 0 ] meta [ \"announce-list\" ] = [ vallist ] elif isinstance ( val , list ): meta [ \"announce\" ] = val [ 0 ] meta [ \"announce-list\" ] = [ val ] if \"url-list\" in args : val = args . get ( \"url-list\" ) if isinstance ( val , str ): meta [ \"url-list\" ] = val . split () elif isinstance ( val , list ): meta [ \"url-list\" ] = val if \"httpseeds\" in args : val = args . get ( \"httpseeds\" ) if isinstance ( val , str ): meta [ \"httpseeds\" ] = val . split () elif isinstance ( val , list ): meta [ \"httpseeds\" ] = val meta [ \"info\" ] = info os . remove ( metafile ) pyben . dump ( meta , metafile ) return meta filter_empty ( args , meta , info ) Remove dictionary keys with empty values. Parameters: Name Type Description Default args dict Editable metafile properties from user. required meta dict Metafile data dictionary. required info dict Metafile info dictionary. required Source code in torrentfile\\edit.py def filter_empty ( args : dict , meta : dict , info : dict ): \"\"\" Remove dictionary keys with empty values. Parameters ---------- args : dict Editable metafile properties from user. meta : dict Metafile data dictionary. info : dict Metafile info dictionary. \"\"\" for key , val in list ( args . items ()): if val is None : del args [ key ] continue if val == \"\" : if key in meta : del meta [ key ] elif key in info : del info [ key ] del args [ key ] logger . debug ( \"removeing empty fields %s \" , val ) torrentfile.hasher Piece/File Hashers for Bittorrent meta file contents. Hasher ( CbMixin , ProgMixin ) Piece hasher for Bittorrent V1 files. Takes a sorted list of all file paths, calculates sha1 hash for fixed size pieces of file data from each file seemlessly until the last piece which may be smaller than others. Parameters: Name Type Description Default paths list List of files. required piece_length int Size of chuncks to split the data into. required progress int default = None None Source code in torrentfile\\hasher.py class Hasher ( CbMixin , ProgMixin ): \"\"\" Piece hasher for Bittorrent V1 files. Takes a sorted list of all file paths, calculates sha1 hash for fixed size pieces of file data from each file seemlessly until the last piece which may be smaller than others. Parameters ---------- paths : list List of files. piece_length : int Size of chuncks to split the data into. progress : int default = None \"\"\" def __init__ ( self , paths : list , piece_length : int , progress : int = None ): \"\"\"Generate hashes of piece length data from filelist contents.\"\"\" self . piece_length = piece_length self . paths = paths self . progress = progress self . total = sum ([ os . path . getsize ( i ) for i in self . paths ]) self . index = 0 self . current = open ( self . paths [ 0 ], \"rb\" ) if self . progress and self . progress == 2 : total = os . path . getsize ( self . paths [ 0 ]) self . prog_start ( total , self . paths [ 0 ], unit = \"bytes\" ) logger . debug ( \"Hashing v1 torrent file. Size: %s Piece Length: %s \" , humanize_bytes ( self . total ), humanize_bytes ( self . piece_length ), ) def __iter__ ( self ): \"\"\" Iterate through feed pieces. Returns ------- self : iterator Iterator for leaves/hash pieces. \"\"\" return self def _handle_partial ( self , arr : bytearray ) -> bytearray : \"\"\" Define the handling partial pieces that span 2 or more files. Parameters ---------- arr : bytearray Incomplete piece containing partial data Returns ------- digest : bytearray SHA1 digest of the complete piece. \"\"\" while len ( arr ) < self . piece_length and self . next_file (): target = self . piece_length - len ( arr ) temp = bytearray ( target ) size = self . current . readinto ( temp ) arr . extend ( temp [: size ]) self . prog_update ( size ) if size == target : break return sha1 ( arr ) . digest () # nosec def next_file ( self ) -> bool : \"\"\" Seemlessly transition to next file in file list. Returns ------- bool: True if there is a next file otherwise False. \"\"\" self . index += 1 self . prog_close () if self . index < len ( self . paths ): path = self . paths [ self . index ] self . current . close () if self . progress == 2 : self . prog_start ( os . path . getsize ( path ), path , self . progress , unit = \"bytes\" ) self . current = open ( path , \"rb\" ) return True return False def __next__ ( self ) -> bytes : \"\"\" Generate piece-length pieces of data from input file list. Returns ------- bytes SHA1 hash of the piece extracted. \"\"\" while True : piece = bytearray ( self . piece_length ) size = self . current . readinto ( piece ) if size == 0 : if not self . next_file (): raise StopIteration elif size < self . piece_length : self . prog_update ( size ) return self . _handle_partial ( piece [: size ]) else : self . prog_update ( size ) return sha1 ( piece ) . digest () # nosec __init__ ( self , paths , piece_length , progress = None ) special Generate hashes of piece length data from filelist contents. Source code in torrentfile\\hasher.py def __init__ ( self , paths : list , piece_length : int , progress : int = None ): \"\"\"Generate hashes of piece length data from filelist contents.\"\"\" self . piece_length = piece_length self . paths = paths self . progress = progress self . total = sum ([ os . path . getsize ( i ) for i in self . paths ]) self . index = 0 self . current = open ( self . paths [ 0 ], \"rb\" ) if self . progress and self . progress == 2 : total = os . path . getsize ( self . paths [ 0 ]) self . prog_start ( total , self . paths [ 0 ], unit = \"bytes\" ) logger . debug ( \"Hashing v1 torrent file. Size: %s Piece Length: %s \" , humanize_bytes ( self . total ), humanize_bytes ( self . piece_length ), ) __iter__ ( self ) special Iterate through feed pieces. Returns: Type Description iterator Iterator for leaves/hash pieces. Source code in torrentfile\\hasher.py def __iter__ ( self ): \"\"\" Iterate through feed pieces. Returns ------- self : iterator Iterator for leaves/hash pieces. \"\"\" return self __next__ ( self ) special Generate piece-length pieces of data from input file list. Returns: Type Description bytes SHA1 hash of the piece extracted. Source code in torrentfile\\hasher.py def __next__ ( self ) -> bytes : \"\"\" Generate piece-length pieces of data from input file list. Returns ------- bytes SHA1 hash of the piece extracted. \"\"\" while True : piece = bytearray ( self . piece_length ) size = self . current . readinto ( piece ) if size == 0 : if not self . next_file (): raise StopIteration elif size < self . piece_length : self . prog_update ( size ) return self . _handle_partial ( piece [: size ]) else : self . prog_update ( size ) return sha1 ( piece ) . digest () # nosec next_file ( self ) Seemlessly transition to next file in file list. Returns: Type Description bool True if there is a next file otherwise False. Source code in torrentfile\\hasher.py def next_file ( self ) -> bool : \"\"\" Seemlessly transition to next file in file list. Returns ------- bool: True if there is a next file otherwise False. \"\"\" self . index += 1 self . prog_close () if self . index < len ( self . paths ): path = self . paths [ self . index ] self . current . close () if self . progress == 2 : self . prog_start ( os . path . getsize ( path ), path , self . progress , unit = \"bytes\" ) self . current = open ( path , \"rb\" ) return True return False HasherHybrid ( CbMixin , ProgMixin ) Calculate root and piece hashes for creating hybrid torrent file. Create merkle tree layers from sha256 hashed 16KiB blocks of contents. With a branching factor of 2, merge layer hashes until blocks equal piece_length bytes for the piece layer, and then the root hash. Parameters: Name Type Description Default path str path to target file. required piece_length int piece length for data chunks. required progress int default = None None Source code in torrentfile\\hasher.py class HasherHybrid ( CbMixin , ProgMixin ): \"\"\" Calculate root and piece hashes for creating hybrid torrent file. Create merkle tree layers from sha256 hashed 16KiB blocks of contents. With a branching factor of 2, merge layer hashes until blocks equal piece_length bytes for the piece layer, and then the root hash. Parameters ---------- path : str path to target file. piece_length : int piece length for data chunks. progress : int default = None \"\"\" def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Construct Hasher class instances for each file in torrent. \"\"\" self . path = path self . piece_length = piece_length self . pieces = [] self . layer_hashes = [] self . piece_layer = None self . root = None self . padding_piece = None self . padding_file = None if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . amount = piece_length // BLOCK_SIZE logger . debug ( \"Hashing: %s , Piece Size: %s \" , str ( self . path ), humanize_bytes ( self . piece_length ), ) with open ( path , \"rb\" ) as data : self . process_file ( data ) def _pad_remaining ( self , block_count : int ): \"\"\" Generate Hash sized, 0 filled bytes for padding. Parameters ---------- block_count : int current total number of blocks collected. Returns ------- padding : bytes Padding to fill remaining portion of tree. \"\"\" # when the there is only one block for file remaining = self . amount - block_count if not self . layer_hashes : power2 = next_power_2 ( block_count ) remaining = power2 - block_count self . prog_update ( HASH_SIZE * remaining ) return [ bytes ( HASH_SIZE ) for _ in range ( remaining )] def process_file ( self , data : bytearray ): \"\"\" Calculate layer hashes for contents of file. Parameters ---------- data : BytesIO File opened in read mode. \"\"\" while True : plength = self . piece_length blocks = [] piece = sha1 () # nosec total = 0 block = bytearray ( BLOCK_SIZE ) for _ in range ( self . amount ): size = data . readinto ( block ) self . prog_update ( size ) if not size : break total += size plength -= size blocks . append ( sha256 ( block [: size ]) . digest ()) piece . update ( block [: size ]) if not blocks : break if len ( blocks ) != self . amount : padding = self . _pad_remaining ( len ( blocks )) blocks . extend ( padding ) layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) if plength > 0 : self . padding_file = { \"attr\" : \"p\" , \"length\" : plength , \"path\" : [ \".pad\" , str ( plength )], } piece . update ( bytes ( plength )) self . pieces . append ( piece . digest ()) # nosec self . _calculate_root () self . prog_close () def _calculate_root ( self ): \"\"\" Calculate the root hash for opened file. \"\"\" self . piece_layer = b \"\" . join ( self . layer_hashes ) if len ( self . layer_hashes ) > 1 : pad_piece = merkle_root ([ bytes ( 32 ) for _ in range ( self . amount )]) pow2 = next_power_2 ( len ( self . layer_hashes )) remainder = pow2 - len ( self . layer_hashes ) self . layer_hashes += [ pad_piece for _ in range ( remainder )] self . root = merkle_root ( self . layer_hashes ) __init__ ( self , path , piece_length , progress = None ) special Construct Hasher class instances for each file in torrent. Source code in torrentfile\\hasher.py def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Construct Hasher class instances for each file in torrent. \"\"\" self . path = path self . piece_length = piece_length self . pieces = [] self . layer_hashes = [] self . piece_layer = None self . root = None self . padding_piece = None self . padding_file = None if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . amount = piece_length // BLOCK_SIZE logger . debug ( \"Hashing: %s , Piece Size: %s \" , str ( self . path ), humanize_bytes ( self . piece_length ), ) with open ( path , \"rb\" ) as data : self . process_file ( data ) process_file ( self , data ) Calculate layer hashes for contents of file. Parameters: Name Type Description Default data bytearray File opened in read mode. required Source code in torrentfile\\hasher.py def process_file ( self , data : bytearray ): \"\"\" Calculate layer hashes for contents of file. Parameters ---------- data : BytesIO File opened in read mode. \"\"\" while True : plength = self . piece_length blocks = [] piece = sha1 () # nosec total = 0 block = bytearray ( BLOCK_SIZE ) for _ in range ( self . amount ): size = data . readinto ( block ) self . prog_update ( size ) if not size : break total += size plength -= size blocks . append ( sha256 ( block [: size ]) . digest ()) piece . update ( block [: size ]) if not blocks : break if len ( blocks ) != self . amount : padding = self . _pad_remaining ( len ( blocks )) blocks . extend ( padding ) layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) if plength > 0 : self . padding_file = { \"attr\" : \"p\" , \"length\" : plength , \"path\" : [ \".pad\" , str ( plength )], } piece . update ( bytes ( plength )) self . pieces . append ( piece . digest ()) # nosec self . _calculate_root () self . prog_close () HasherV2 ( CbMixin , ProgMixin ) Calculate the root hash and piece layers for file contents. Iterates over 16KiB blocks of data from given file, hashes the data, then creates a hash tree from the individual block hashes until size of hashed data equals the piece-length. Then continues the hash tree until root hash is calculated. Parameters: Name Type Description Default path str Path to file. required piece_length int Size of layer hashes pieces. required progress int default = None None Source code in torrentfile\\hasher.py class HasherV2 ( CbMixin , ProgMixin ): \"\"\" Calculate the root hash and piece layers for file contents. Iterates over 16KiB blocks of data from given file, hashes the data, then creates a hash tree from the individual block hashes until size of hashed data equals the piece-length. Then continues the hash tree until root hash is calculated. Parameters ---------- path : str Path to file. piece_length : int Size of layer hashes pieces. progress : int default = None \"\"\" def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Calculate and store hash information for specific file. \"\"\" self . path = path self . root = None self . piece_layer = None self . layer_hashes = [] self . piece_length = piece_length self . num_blocks = piece_length // BLOCK_SIZE if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) logger . debug ( \"Hashing partial v2 torrent file. Piece Length: %s Path: %s \" , humanize_bytes ( self . piece_length ), str ( self . path ), ) with open ( self . path , \"rb\" ) as fd : self . process_file ( fd ) def process_file ( self , fd : str ): \"\"\" Calculate hashes over 16KiB chuncks of file content. Parameters ---------- fd : str Opened file in read mode. \"\"\" while True : blocks = [] leaf = bytearray ( BLOCK_SIZE ) # generate leaves of merkle tree for _ in range ( self . num_blocks ): size = fd . readinto ( leaf ) self . prog_update ( size ) if not size : break blocks . append ( sha256 ( leaf [: size ]) . digest ()) # blocks is empty mean eof if not blocks : break if len ( blocks ) != self . num_blocks : # when size of file doesn't fill the last block # when the file contains multiple pieces remaining = self . num_blocks - len ( blocks ) if not self . layer_hashes : # when the there is only one block for file power2 = next_power_2 ( len ( blocks )) remaining = power2 - len ( blocks ) # pad the the rest with zeroes to fill remaining space. padding = [ bytes ( 32 ) for _ in range ( remaining )] self . prog_update ( HASH_SIZE * remaining ) blocks . extend ( padding ) # calculate the root hash for the merkle tree up to piece-length layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) self . _calculate_root () self . prog_close () def _calculate_root ( self ): \"\"\" Calculate root hash for the target file. \"\"\" self . piece_layer = b \"\" . join ( self . layer_hashes ) hashes = len ( self . layer_hashes ) if hashes > 1 : pow2 = next_power_2 ( hashes ) remainder = pow2 - hashes pad_piece = [ bytes ( HASH_SIZE ) for _ in range ( self . num_blocks )] for _ in range ( remainder ): self . layer_hashes . append ( merkle_root ( pad_piece )) self . root = merkle_root ( self . layer_hashes ) __init__ ( self , path , piece_length , progress = None ) special Calculate and store hash information for specific file. Source code in torrentfile\\hasher.py def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Calculate and store hash information for specific file. \"\"\" self . path = path self . root = None self . piece_layer = None self . layer_hashes = [] self . piece_length = piece_length self . num_blocks = piece_length // BLOCK_SIZE if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) logger . debug ( \"Hashing partial v2 torrent file. Piece Length: %s Path: %s \" , humanize_bytes ( self . piece_length ), str ( self . path ), ) with open ( self . path , \"rb\" ) as fd : self . process_file ( fd ) process_file ( self , fd ) Calculate hashes over 16KiB chuncks of file content. Parameters: Name Type Description Default fd str Opened file in read mode. required Source code in torrentfile\\hasher.py def process_file ( self , fd : str ): \"\"\" Calculate hashes over 16KiB chuncks of file content. Parameters ---------- fd : str Opened file in read mode. \"\"\" while True : blocks = [] leaf = bytearray ( BLOCK_SIZE ) # generate leaves of merkle tree for _ in range ( self . num_blocks ): size = fd . readinto ( leaf ) self . prog_update ( size ) if not size : break blocks . append ( sha256 ( leaf [: size ]) . digest ()) # blocks is empty mean eof if not blocks : break if len ( blocks ) != self . num_blocks : # when size of file doesn't fill the last block # when the file contains multiple pieces remaining = self . num_blocks - len ( blocks ) if not self . layer_hashes : # when the there is only one block for file power2 = next_power_2 ( len ( blocks )) remaining = power2 - len ( blocks ) # pad the the rest with zeroes to fill remaining space. padding = [ bytes ( 32 ) for _ in range ( remaining )] self . prog_update ( HASH_SIZE * remaining ) blocks . extend ( padding ) # calculate the root hash for the merkle tree up to piece-length layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) self . _calculate_root () self . prog_close () merkle_root ( blocks ) Calculate the merkle root for a seq of sha256 hash digests. Source code in torrentfile\\hasher.py def merkle_root ( blocks : list ) -> bytes : \"\"\" Calculate the merkle root for a seq of sha256 hash digests. \"\"\" if blocks : while len ( blocks ) > 1 : blocks = [ sha256 ( x + y ) . digest () for x , y in zip ( * [ iter ( blocks )] * 2 ) ] return blocks [ 0 ] return blocks torrentfile.interactive Module contains the procedures used for Interactive Mode. InteractiveCreator Class namespace for interactive program options. Source code in torrentfile\\interactive.py class InteractiveCreator : \"\"\" Class namespace for interactive program options. \"\"\" def __init__ ( self ): \"\"\" Initialize interactive meta file creator dialog. \"\"\" self . kwargs = { \"announce\" : None , \"url_list\" : None , \"private\" : None , \"source\" : None , \"comment\" : None , \"piece_length\" : None , \"outfile\" : None , \"path\" : None , \"httpseeds\" : None , } self . outfile , self . meta = self . get_props () def get_props ( self ): \"\"\" Gather details for torrentfile from user. \"\"\" piece_length = get_input ( \"Piece Length (empty=auto): \" , lambda x : x . isdigit () ) self . kwargs [ \"piece_length\" ] = piece_length announce = get_input ( \"Tracker list (empty): \" , lambda x : isinstance ( x , str ) ) if announce : self . kwargs [ \"announce\" ] = announce . split () url_list = get_input ( \"Web Seed {GetRight} list (empty): \" , lambda x : isinstance ( x , str ) ) httpseeds = get_input ( \"Web Seed {Hoffman} list (empty): \" , lambda x : isinstance ( x , str ) ) if url_list : self . kwargs [ \"url_list\" ] = url_list . split () if httpseeds : self . kwargs [ \"httpseeds\" ] = httpseeds . split () comment = get_input ( \"Comment (empty): \" , None ) if comment : self . kwargs [ \"comment\" ] = comment source = get_input ( \"Source (empty): \" , None ) if source : self . kwargs [ \"source\" ] = source private = get_input ( \"Private Torrent? {Y/N}: (N)\" , lambda x : x in \"yYnN\" ) if private and private . lower () == \"y\" : self . kwargs [ \"private\" ] = 1 contents = get_input ( \"Content Path: \" , os . path . exists ) self . kwargs [ \"path\" ] = contents outfile = get_input ( f \"Output Path ( { contents } .torrent): \" , lambda x : os . path . exists ( os . path . dirname ( x )), ) if outfile : self . kwargs [ \"outfile\" ] = outfile meta_version = get_input ( \"Meta Version {1,2,3}: (1)\" , lambda x : x in \"123\" ) showcenter ( f \"creating { outfile } \" ) if meta_version == \"3\" : torrent = TorrentFileHybrid ( ** self . kwargs ) elif meta_version == \"2\" : torrent = TorrentFileV2 ( ** self . kwargs ) else : torrent = TorrentFile ( ** self . kwargs ) return torrent . write () __init__ ( self ) special Initialize interactive meta file creator dialog. Source code in torrentfile\\interactive.py def __init__ ( self ): \"\"\" Initialize interactive meta file creator dialog. \"\"\" self . kwargs = { \"announce\" : None , \"url_list\" : None , \"private\" : None , \"source\" : None , \"comment\" : None , \"piece_length\" : None , \"outfile\" : None , \"path\" : None , \"httpseeds\" : None , } self . outfile , self . meta = self . get_props () get_props ( self ) Gather details for torrentfile from user. Source code in torrentfile\\interactive.py def get_props ( self ): \"\"\" Gather details for torrentfile from user. \"\"\" piece_length = get_input ( \"Piece Length (empty=auto): \" , lambda x : x . isdigit () ) self . kwargs [ \"piece_length\" ] = piece_length announce = get_input ( \"Tracker list (empty): \" , lambda x : isinstance ( x , str ) ) if announce : self . kwargs [ \"announce\" ] = announce . split () url_list = get_input ( \"Web Seed {GetRight} list (empty): \" , lambda x : isinstance ( x , str ) ) httpseeds = get_input ( \"Web Seed {Hoffman} list (empty): \" , lambda x : isinstance ( x , str ) ) if url_list : self . kwargs [ \"url_list\" ] = url_list . split () if httpseeds : self . kwargs [ \"httpseeds\" ] = httpseeds . split () comment = get_input ( \"Comment (empty): \" , None ) if comment : self . kwargs [ \"comment\" ] = comment source = get_input ( \"Source (empty): \" , None ) if source : self . kwargs [ \"source\" ] = source private = get_input ( \"Private Torrent? {Y/N}: (N)\" , lambda x : x in \"yYnN\" ) if private and private . lower () == \"y\" : self . kwargs [ \"private\" ] = 1 contents = get_input ( \"Content Path: \" , os . path . exists ) self . kwargs [ \"path\" ] = contents outfile = get_input ( f \"Output Path ( { contents } .torrent): \" , lambda x : os . path . exists ( os . path . dirname ( x )), ) if outfile : self . kwargs [ \"outfile\" ] = outfile meta_version = get_input ( \"Meta Version {1,2,3}: (1)\" , lambda x : x in \"123\" ) showcenter ( f \"creating { outfile } \" ) if meta_version == \"3\" : torrent = TorrentFileHybrid ( ** self . kwargs ) elif meta_version == \"2\" : torrent = TorrentFileV2 ( ** self . kwargs ) else : torrent = TorrentFile ( ** self . kwargs ) return torrent . write () InteractiveEditor Interactive dialog class for torrent editing. Source code in torrentfile\\interactive.py class InteractiveEditor : \"\"\" Interactive dialog class for torrent editing. \"\"\" def __init__ ( self , metafile : str ): \"\"\" Initialize the Interactive torrent editor guide. Parameters ---------- metafile : str user input string identifying the path to a torrent meta file. \"\"\" self . metafile = metafile self . meta = pyben . load ( metafile ) self . info = self . meta [ \"info\" ] self . args = { \"url-list\" : self . meta . get ( \"url-list\" , None ), \"httpseeds\" : self . meta . get ( \"httpseeds\" , None ), \"announce\" : self . meta . get ( \"announce-list\" , None ), \"source\" : self . info . get ( \"source\" , None ), \"private\" : self . info . get ( \"private\" , None ), \"comment\" : self . info . get ( \"comment\" , None ), } def show_current ( self ): \"\"\" Display the current met file information to screen. \"\"\" out = \"Current properties and values: \\n \" longest = max ([ len ( label ) for label in self . args ]) + 3 for key , val in self . args . items (): txt = ( key . title () + \":\" ) . ljust ( longest ) + str ( val ) out += f \" \\t { txt } \\n \" showtext ( out ) def sanatize_response ( self , key , response ): \"\"\" Convert the input data into a form recognizable by the program. Parameters ---------- key : str name of the property and attribute being eddited. response : str User input value the property is being edited to. \"\"\" if key in [ \"announce\" , \"url-list\" , \"httpseeds\" ]: val = response . split () else : val = response self . args [ key ] = val def edit_props ( self ): \"\"\" Loop continuosly for edits until user signals DONE. \"\"\" while True : showcenter ( \"Choose the number for a propert the needs editing.\" \"Enter DONE when all editing has been completed.\" ) props = { 1 : \"comment\" , 2 : \"source\" , 3 : \"private\" , 4 : \"tracker\" , 5 : \"web-seed\" , 6 : \"httpseeds\" , } args = { 1 : \"comment\" , 2 : \"source\" , 3 : \"private\" , 4 : \"announce\" , 5 : \"url-list\" , 6 : \"httpseeds\" , } txt = \", \" . join (( str ( k ) + \": \" + v ) for k , v in props . items ()) prop = get_input ( txt ) if prop . lower () == \"done\" : break if prop . isdigit () and 0 < int ( prop ) < 6 : key = props [ int ( prop )] key2 = args [ int ( prop )] val = self . args . get ( key2 ) showtext ( \"Enter new property value or leave empty for no value.\" ) response = get_input ( f \" { key . title () } ( { val } ): \" ) self . sanatize_response ( key2 , response ) else : showtext ( \"Invalid input: Try again.\" ) edit_torrent ( self . metafile , self . args ) __init__ ( self , metafile ) special Initialize the Interactive torrent editor guide. Parameters: Name Type Description Default metafile str user input string identifying the path to a torrent meta file. required Source code in torrentfile\\interactive.py def __init__ ( self , metafile : str ): \"\"\" Initialize the Interactive torrent editor guide. Parameters ---------- metafile : str user input string identifying the path to a torrent meta file. \"\"\" self . metafile = metafile self . meta = pyben . load ( metafile ) self . info = self . meta [ \"info\" ] self . args = { \"url-list\" : self . meta . get ( \"url-list\" , None ), \"httpseeds\" : self . meta . get ( \"httpseeds\" , None ), \"announce\" : self . meta . get ( \"announce-list\" , None ), \"source\" : self . info . get ( \"source\" , None ), \"private\" : self . info . get ( \"private\" , None ), \"comment\" : self . info . get ( \"comment\" , None ), } edit_props ( self ) Loop continuosly for edits until user signals DONE. Source code in torrentfile\\interactive.py def edit_props ( self ): \"\"\" Loop continuosly for edits until user signals DONE. \"\"\" while True : showcenter ( \"Choose the number for a propert the needs editing.\" \"Enter DONE when all editing has been completed.\" ) props = { 1 : \"comment\" , 2 : \"source\" , 3 : \"private\" , 4 : \"tracker\" , 5 : \"web-seed\" , 6 : \"httpseeds\" , } args = { 1 : \"comment\" , 2 : \"source\" , 3 : \"private\" , 4 : \"announce\" , 5 : \"url-list\" , 6 : \"httpseeds\" , } txt = \", \" . join (( str ( k ) + \": \" + v ) for k , v in props . items ()) prop = get_input ( txt ) if prop . lower () == \"done\" : break if prop . isdigit () and 0 < int ( prop ) < 6 : key = props [ int ( prop )] key2 = args [ int ( prop )] val = self . args . get ( key2 ) showtext ( \"Enter new property value or leave empty for no value.\" ) response = get_input ( f \" { key . title () } ( { val } ): \" ) self . sanatize_response ( key2 , response ) else : showtext ( \"Invalid input: Try again.\" ) edit_torrent ( self . metafile , self . args ) sanatize_response ( self , key , response ) Convert the input data into a form recognizable by the program. Parameters: Name Type Description Default key str name of the property and attribute being eddited. required response str User input value the property is being edited to. required Source code in torrentfile\\interactive.py def sanatize_response ( self , key , response ): \"\"\" Convert the input data into a form recognizable by the program. Parameters ---------- key : str name of the property and attribute being eddited. response : str User input value the property is being edited to. \"\"\" if key in [ \"announce\" , \"url-list\" , \"httpseeds\" ]: val = response . split () else : val = response self . args [ key ] = val show_current ( self ) Display the current met file information to screen. Source code in torrentfile\\interactive.py def show_current ( self ): \"\"\" Display the current met file information to screen. \"\"\" out = \"Current properties and values: \\n \" longest = max ([ len ( label ) for label in self . args ]) + 3 for key , val in self . args . items (): txt = ( key . title () + \":\" ) . ljust ( longest ) + str ( val ) out += f \" \\t { txt } \\n \" showtext ( out ) create_torrent () Create new torrent file interactively. Source code in torrentfile\\interactive.py def create_torrent (): \"\"\" Create new torrent file interactively. \"\"\" showcenter ( \"Create Torrent\" ) showtext ( \" \\n Enter values for each of the options for the torrent creator, \" \"or leave blank for program defaults. \\n Spaces are considered item \" \"seperators for options that accept a list of values. \\n Values \" \"enclosed in () indicate the default value, while {} holds all \" \"valid choices available for the option. \\n\\n \" ) creator = InteractiveCreator () return creator edit_action () Edit the editable values of the torrent meta file. Source code in torrentfile\\interactive.py def edit_action (): \"\"\" Edit the editable values of the torrent meta file. \"\"\" showcenter ( \"Edit Torrent\" ) metafile = get_input ( \"Metafile(.torrent): \" , os . path . exists ) dialog = InteractiveEditor ( metafile ) dialog . show_current () dialog . edit_props () get_input ( * args ) Determine appropriate input function to call. Parameters: Name Type Description Default args tuple Arbitrary number of args to pass to next function () Returns: Type Description str The results of the function call. Source code in torrentfile\\interactive.py def get_input ( * args : tuple ): # pragma: no cover \"\"\" Determine appropriate input function to call. Parameters ---------- args : tuple Arbitrary number of args to pass to next function Returns ------- str The results of the function call. \"\"\" if len ( args ) == 2 : return _get_input_loop ( * args ) return _get_input ( * args ) recheck_torrent () Check torrent download completed percentage. Source code in torrentfile\\interactive.py def recheck_torrent (): \"\"\" Check torrent download completed percentage. \"\"\" showcenter ( \"Check Torrent\" ) msg = \"Enter path to torrent contents, and corresponding torrent metafile.\" showtext ( msg ) metafile = get_input ( \"Conent Path (downloads/complete/torrentname):\" , os . path . exists ) contents = get_input ( \"Metafile (*.torrent): \" , os . path . exists ) checker = Checker ( metafile , contents ) results = checker . results () showtext ( f \"Completion for { metafile } is { results } %\" ) return results select_action () Operate TorrentFile program interactively through terminal. Source code in torrentfile\\interactive.py def select_action (): \"\"\" Operate TorrentFile program interactively through terminal. \"\"\" showcenter ( \"TorrentFile: Starting Interactive Mode\" ) action = get_input ( \"Enter the action you wish to perform. \\n \" \"Action ( Create (c) | Edit (e) | Recheck (r) ): \" ) action = action . lower () if \"create\" in action or action == \"c\" : return create_torrent () if \"check\" in action or action == \"r\" : return recheck_torrent () if \"edit\" in action or action == \"e\" : return edit_action () print ( \"Unable to recognize input. Please try again.\" ) # pragma: nocover return select_action () # pragma: nocover showcenter ( txt ) Print text to screen in the center position of the terminal. Parameters: Name Type Description Default txt str the preformated message to send to stdout. required Source code in torrentfile\\interactive.py def showcenter ( txt : str ): \"\"\" Print text to screen in the center position of the terminal. Parameters ---------- txt : str the preformated message to send to stdout. \"\"\" termlen = shutil . get_terminal_size () . columns padding = \" \" * int ((( termlen - len ( txt )) / 2 )) string = \"\" . join ([ \" \\n \" , padding , txt , \" \\n \" ]) showtext ( string ) showtext ( txt ) Print contents of txt to screen. Parameters: Name Type Description Default txt str text to print to terminal. required Source code in torrentfile\\interactive.py def showtext ( txt ): \"\"\" Print contents of txt to screen. Parameters ---------- txt : str text to print to terminal. \"\"\" sys . stdout . write ( txt ) torrentfile.recheck Module container Checker Class. The CheckerClass takes a torrentfile and tha path to it's contents. It will then iterate through every file and directory contained and compare their data to values contained within the torrent file. Completion percentages will be printed to screen for each file and at the end for the torrentfile as a whole. Checker ( ProgMixin ) Check a given file or directory to see if it matches a torrentfile. Public constructor for Checker class instance. Parameters: Name Type Description Default metafile str Path to \".torrent\" file. required path str Path where the content is located in filesystem. required Examples: metafile = \"/path/to/torrentfile/content_file_or_dir.torrent\" >> location = \"/path/to/location\" >> os.path.exists(\"/path/to/location/content_file_or_dir\") Out: True >> checker = Checker(metafile, location) Source code in torrentfile\\recheck.py class Checker ( ProgMixin ): \"\"\" Check a given file or directory to see if it matches a torrentfile. Public constructor for Checker class instance. Parameters ---------- metafile : str Path to \".torrent\" file. path : str Path where the content is located in filesystem. Example ------- >> metafile = \"/path/to/torrentfile/content_file_or_dir.torrent\" >> location = \"/path/to/location\" >> os.path.exists(\"/path/to/location/content_file_or_dir\") Out: True >> checker = Checker(metafile, location) \"\"\" _hook = None def __init__ ( self , metafile : str , path : str ): \"\"\" Validate data against hashes contained in .torrent file. Parameters ---------- metafile : str path to .torrent file path : str path to content or contents parent directory. \"\"\" if not os . path . exists ( metafile ): raise FileNotFoundError meta = [] thread = Thread ( target = pyben . loadinto , args = ( metafile , meta )) thread . start () self . last_log = None self . log_msg ( \"Checking: %s , %s \" , metafile , path ) self . metafile = metafile self . meta_version = None self . total = 0 self . paths = [] self . fileinfo = {} thread2 = Thread ( target = waiting , args = ( \"Extracting metadata\" , meta )) if not meta : # pragma: nocover thread2 . start () thread2 . join () self . meta = meta [ 0 ] self . info = self . meta [ \"info\" ] self . name = self . info [ \"name\" ] self . piece_length = self . info [ \"piece length\" ] if \"meta version\" in self . info : if \"pieces\" in self . info : self . meta_version = 3 else : self . meta_version = 2 else : self . meta_version = 1 self . root = self . find_root ( path ) self . check_paths () @classmethod def register_callback ( cls , hook ): \"\"\" Register hooks from 3rd party programs to access generated info. Parameters ---------- hook : function callback function for the logging feature. \"\"\" cls . _hook = hook def hasher ( self ): \"\"\" Return the hasher class related to torrents meta version. Returns ------- hasher.Hasher the hashing implementation for specific torrent meta version. \"\"\" if self . meta_version == 2 : return HasherV2 if self . meta_version == 3 : return HasherHybrid return None def piece_checker ( self ): \"\"\" Check individual pieces of the torrent. Returns ------- HashChecker | FeedChecker Individual piece hasher. \"\"\" if self . meta_version == 1 : return FeedChecker return HashChecker def results ( self ): \"\"\" Generate result percentage and store for future calls. \"\"\" responses = [] for response in self . iter_hashes (): responses . append ( response ) self . log_msg ( \"Final result for %s recheck: %s \" , self . metafile , self . _result ) return self . _result def log_msg ( self , * args , level = logging . INFO ): \"\"\" Log message `msg` to logger and send `msg` to callback hook. Parameters ---------- args : dict formatting args for log message level : int Log level for this message; default=`logging.INFO` \"\"\" message = args [ 0 ] if len ( args ) >= 3 : message = message % tuple ( args [ 1 :]) elif len ( args ) == 2 : message = message % args [ 1 ] # Repeat log messages should be ignored. if message != self . last_log : self . last_log = message logger . log ( level , message ) if self . _hook and level == logging . INFO : self . _hook ( message ) def find_root ( self , path : str ) -> str : \"\"\" Check path for torrent content. The path can be a relative or absolute filesystem path. In the case where the content is a single file, the path may point directly to the the file, or it may point to the parent directory. If content points to a directory. The directory will be checked to see if it matches the torrent's name, if not the directories contents will be searched. The returned value will be the absolute path that matches the torrent's name. Parameters ---------- path : str root path to torrent content Returns ------- str root path to content \"\"\" if not os . path . exists ( path ): self . log_msg ( \"Could not locate torrent content %s .\" , path ) raise FileNotFoundError ( path ) root = Path ( path ) if root . name == self . name : self . log_msg ( \"Content found: %s .\" , str ( root )) return root if self . name in os . listdir ( root ): return root / self . name self . log_msg ( \"Could not locate torrent content in: %s \" , str ( root )) raise FileNotFoundError ( root ) def check_paths ( self ): \"\"\" Gather all file paths described in the torrent file. \"\"\" finfo = self . fileinfo if \"length\" in self . info : self . log_msg ( \" %s points to a single file\" , self . root ) self . total = self . info [ \"length\" ] self . paths . append ( str ( self . root )) finfo [ 0 ] = { \"path\" : self . root , \"length\" : self . info [ \"length\" ], } if self . meta_version > 1 : root = self . info [ \"file tree\" ][ self . name ][ \"\" ][ \"pieces root\" ] finfo [ 0 ][ \"pieces root\" ] = root return # Otherwise Content is more than 1 file. self . log_msg ( \" %s points to a directory\" , self . root ) if self . meta_version == 1 : for i , item in enumerate ( self . info [ \"files\" ]): self . total += item [ \"length\" ] base = os . path . join ( * item [ \"path\" ]) self . fileinfo [ i ] = { \"path\" : str ( self . root / base ), \"length\" : item [ \"length\" ], } self . paths . append ( str ( self . root / base )) return self . walk_file_tree ( self . info [ \"file tree\" ], []) def walk_file_tree ( self , tree : dict , partials : list ): \"\"\" Traverse File Tree dictionary to get file details. Extract full pathnames, length, root hash, and layer hashes for each file included in the .torrent's file tree. Parameters ---------- tree : dict File Tree dict extracted from torrent file. partials : list list of intermediate pathnames. \"\"\" for key , val in tree . items (): # Empty string means the tree's leaf is value if \"\" in val : base = os . path . join ( * partials , key ) roothash = None length = val [ \"\" ][ \"length\" ] roothash = None if not length else val [ \"\" ][ \"pieces root\" ] full = str ( self . root / base ) self . fileinfo [ len ( self . paths )] = { \"path\" : full , \"length\" : length , \"pieces root\" : roothash , } self . paths . append ( full ) self . total += length else : self . walk_file_tree ( val , partials + [ key ]) def iter_hashes ( self ) -> tuple : \"\"\" Produce results of comparing torrent contents piece by piece. Yields ------ chunck : bytes hash of data found on disk piece : bytes hash of data when complete and correct path : str path to file being hashed size : int length of bytes hashed for piece \"\"\" matched = consumed = 0 checker = self . piece_checker () hasher = self . hasher () for chunk , piece , path , size in checker ( self , hasher ): consumed += size matching = 0 if chunk == piece : matching += size matched += size yield chunk , piece , path , size total_consumed = str ( int ( consumed / self . total * 100 )) percent_matched = str ( int ( matched / consumed * 100 )) self . log_msg ( \"Processed: %s%% , Matched: %s%% \" , total_consumed , percent_matched , level = logging . DEBUG , ) self . _result = ( matched / consumed ) * 100 if consumed > 0 else 0 __init__ ( self , metafile , path ) special Validate data against hashes contained in .torrent file. Parameters: Name Type Description Default metafile str path to .torrent file required path str path to content or contents parent directory. required Source code in torrentfile\\recheck.py def __init__ ( self , metafile : str , path : str ): \"\"\" Validate data against hashes contained in .torrent file. Parameters ---------- metafile : str path to .torrent file path : str path to content or contents parent directory. \"\"\" if not os . path . exists ( metafile ): raise FileNotFoundError meta = [] thread = Thread ( target = pyben . loadinto , args = ( metafile , meta )) thread . start () self . last_log = None self . log_msg ( \"Checking: %s , %s \" , metafile , path ) self . metafile = metafile self . meta_version = None self . total = 0 self . paths = [] self . fileinfo = {} thread2 = Thread ( target = waiting , args = ( \"Extracting metadata\" , meta )) if not meta : # pragma: nocover thread2 . start () thread2 . join () self . meta = meta [ 0 ] self . info = self . meta [ \"info\" ] self . name = self . info [ \"name\" ] self . piece_length = self . info [ \"piece length\" ] if \"meta version\" in self . info : if \"pieces\" in self . info : self . meta_version = 3 else : self . meta_version = 2 else : self . meta_version = 1 self . root = self . find_root ( path ) self . check_paths () check_paths ( self ) Gather all file paths described in the torrent file. Source code in torrentfile\\recheck.py def check_paths ( self ): \"\"\" Gather all file paths described in the torrent file. \"\"\" finfo = self . fileinfo if \"length\" in self . info : self . log_msg ( \" %s points to a single file\" , self . root ) self . total = self . info [ \"length\" ] self . paths . append ( str ( self . root )) finfo [ 0 ] = { \"path\" : self . root , \"length\" : self . info [ \"length\" ], } if self . meta_version > 1 : root = self . info [ \"file tree\" ][ self . name ][ \"\" ][ \"pieces root\" ] finfo [ 0 ][ \"pieces root\" ] = root return # Otherwise Content is more than 1 file. self . log_msg ( \" %s points to a directory\" , self . root ) if self . meta_version == 1 : for i , item in enumerate ( self . info [ \"files\" ]): self . total += item [ \"length\" ] base = os . path . join ( * item [ \"path\" ]) self . fileinfo [ i ] = { \"path\" : str ( self . root / base ), \"length\" : item [ \"length\" ], } self . paths . append ( str ( self . root / base )) return self . walk_file_tree ( self . info [ \"file tree\" ], []) find_root ( self , path ) Check path for torrent content. The path can be a relative or absolute filesystem path. In the case where the content is a single file, the path may point directly to the the file, or it may point to the parent directory. If content points to a directory. The directory will be checked to see if it matches the torrent's name, if not the directories contents will be searched. The returned value will be the absolute path that matches the torrent's name. Parameters: Name Type Description Default path str root path to torrent content required Returns: Type Description str root path to content Source code in torrentfile\\recheck.py def find_root ( self , path : str ) -> str : \"\"\" Check path for torrent content. The path can be a relative or absolute filesystem path. In the case where the content is a single file, the path may point directly to the the file, or it may point to the parent directory. If content points to a directory. The directory will be checked to see if it matches the torrent's name, if not the directories contents will be searched. The returned value will be the absolute path that matches the torrent's name. Parameters ---------- path : str root path to torrent content Returns ------- str root path to content \"\"\" if not os . path . exists ( path ): self . log_msg ( \"Could not locate torrent content %s .\" , path ) raise FileNotFoundError ( path ) root = Path ( path ) if root . name == self . name : self . log_msg ( \"Content found: %s .\" , str ( root )) return root if self . name in os . listdir ( root ): return root / self . name self . log_msg ( \"Could not locate torrent content in: %s \" , str ( root )) raise FileNotFoundError ( root ) hasher ( self ) Return the hasher class related to torrents meta version. Returns: Type Description hasher.Hasher the hashing implementation for specific torrent meta version. Source code in torrentfile\\recheck.py def hasher ( self ): \"\"\" Return the hasher class related to torrents meta version. Returns ------- hasher.Hasher the hashing implementation for specific torrent meta version. \"\"\" if self . meta_version == 2 : return HasherV2 if self . meta_version == 3 : return HasherHybrid return None iter_hashes ( self ) Produce results of comparing torrent contents piece by piece. Returns: Type Description tuple hash of data found on disk Source code in torrentfile\\recheck.py def iter_hashes ( self ) -> tuple : \"\"\" Produce results of comparing torrent contents piece by piece. Yields ------ chunck : bytes hash of data found on disk piece : bytes hash of data when complete and correct path : str path to file being hashed size : int length of bytes hashed for piece \"\"\" matched = consumed = 0 checker = self . piece_checker () hasher = self . hasher () for chunk , piece , path , size in checker ( self , hasher ): consumed += size matching = 0 if chunk == piece : matching += size matched += size yield chunk , piece , path , size total_consumed = str ( int ( consumed / self . total * 100 )) percent_matched = str ( int ( matched / consumed * 100 )) self . log_msg ( \"Processed: %s%% , Matched: %s%% \" , total_consumed , percent_matched , level = logging . DEBUG , ) self . _result = ( matched / consumed ) * 100 if consumed > 0 else 0 log_msg ( self , * args , * , level = 20 ) Log message msg to logger and send msg to callback hook. Parameters: Name Type Description Default args dict formatting args for log message () level int Log level for this message; default= logging.INFO 20 Source code in torrentfile\\recheck.py def log_msg ( self , * args , level = logging . INFO ): \"\"\" Log message `msg` to logger and send `msg` to callback hook. Parameters ---------- args : dict formatting args for log message level : int Log level for this message; default=`logging.INFO` \"\"\" message = args [ 0 ] if len ( args ) >= 3 : message = message % tuple ( args [ 1 :]) elif len ( args ) == 2 : message = message % args [ 1 ] # Repeat log messages should be ignored. if message != self . last_log : self . last_log = message logger . log ( level , message ) if self . _hook and level == logging . INFO : self . _hook ( message ) piece_checker ( self ) Check individual pieces of the torrent. Returns: Type Description HashChecker | FeedChecker Individual piece hasher. Source code in torrentfile\\recheck.py def piece_checker ( self ): \"\"\" Check individual pieces of the torrent. Returns ------- HashChecker | FeedChecker Individual piece hasher. \"\"\" if self . meta_version == 1 : return FeedChecker return HashChecker register_callback ( hook ) classmethod Register hooks from 3rd party programs to access generated info. Parameters: Name Type Description Default hook function callback function for the logging feature. required Source code in torrentfile\\recheck.py @classmethod def register_callback ( cls , hook ): \"\"\" Register hooks from 3rd party programs to access generated info. Parameters ---------- hook : function callback function for the logging feature. \"\"\" cls . _hook = hook results ( self ) Generate result percentage and store for future calls. Source code in torrentfile\\recheck.py def results ( self ): \"\"\" Generate result percentage and store for future calls. \"\"\" responses = [] for response in self . iter_hashes (): responses . append ( response ) self . log_msg ( \"Final result for %s recheck: %s \" , self . metafile , self . _result ) return self . _result walk_file_tree ( self , tree , partials ) Traverse File Tree dictionary to get file details. Extract full pathnames, length, root hash, and layer hashes for each file included in the .torrent's file tree. Parameters: Name Type Description Default tree dict File Tree dict extracted from torrent file. required partials list list of intermediate pathnames. required Source code in torrentfile\\recheck.py def walk_file_tree ( self , tree : dict , partials : list ): \"\"\" Traverse File Tree dictionary to get file details. Extract full pathnames, length, root hash, and layer hashes for each file included in the .torrent's file tree. Parameters ---------- tree : dict File Tree dict extracted from torrent file. partials : list list of intermediate pathnames. \"\"\" for key , val in tree . items (): # Empty string means the tree's leaf is value if \"\" in val : base = os . path . join ( * partials , key ) roothash = None length = val [ \"\" ][ \"length\" ] roothash = None if not length else val [ \"\" ][ \"pieces root\" ] full = str ( self . root / base ) self . fileinfo [ len ( self . paths )] = { \"path\" : full , \"length\" : length , \"pieces root\" : roothash , } self . paths . append ( full ) self . total += length else : self . walk_file_tree ( val , partials + [ key ]) FeedChecker ( ProgMixin ) Validates torrent content. Seemlesly validate torrent file contents by comparing hashes in metafile against data on disk. Parameters: Name Type Description Default checker Checker the checker class instance. required hasher Any hashing class for calculating piece hashes. default=None None Source code in torrentfile\\recheck.py class FeedChecker ( ProgMixin ): \"\"\" Validates torrent content. Seemlesly validate torrent file contents by comparing hashes in metafile against data on disk. Parameters ---------- checker : object the checker class instance. hasher : Any hashing class for calculating piece hashes. default=None \"\"\" def __init__ ( self , checker : Checker , hasher = None ): \"\"\" Generate hashes of piece length data from filelist contents. \"\"\" self . piece_length = checker . piece_length self . paths = checker . paths self . pieces = checker . info [ \"pieces\" ] self . fileinfo = checker . fileinfo self . hasher = hasher self . piece_map = {} self . index = 0 self . piece_count = 0 self . it = None def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" self . it = self . iter_pieces () return self def __next__ ( self ): \"\"\" Yield back result of comparison. \"\"\" try : partial = next ( self . it ) except StopIteration as itererror : raise StopIteration from itererror chunck = sha1 ( partial ) . digest () # nosec start = self . piece_count * SHA1 end = start + SHA1 piece = self . pieces [ start : end ] self . piece_count += 1 path = self . paths [ self . index ] return chunck , piece , path , len ( partial ) def iter_pieces ( self ): \"\"\" Iterate through, and hash pieces of torrent contents. Yields ------ piece : bytes hash digest for block of torrent data. \"\"\" partial = bytearray () for i , path in enumerate ( self . paths ): total = self . fileinfo [ i ][ \"length\" ] self . prog_start ( total , path , unit = \"bytes\" ) self . index = i if os . path . exists ( path ): for piece in self . extract ( path , partial ): if ( len ( piece ) == self . piece_length ) or ( i + 1 == len ( self . paths ) ): yield piece else : partial = piece else : length = self . fileinfo [ i ][ \"length\" ] for pad in self . _gen_padding ( partial , length ): if len ( pad ) == self . piece_length : yield pad else : partial = pad self . prog_close () def extract ( self , path : str , partial : bytearray ) -> bytearray : \"\"\" Split file paths contents into blocks of data for hash pieces. Parameters ---------- path : str path to content. partial : bytes any remaining content from last file. Returns ------- bytearray Hash digest for block of .torrent contents. \"\"\" read = 0 length = self . fileinfo [ self . index ][ \"length\" ] partial = bytearray () if len ( partial ) == self . piece_length else partial if path not in self . paths : # pragma: no cover raise MissingPathError ( path ) with open ( path , \"rb\" ) as current : while True : bitlength = self . piece_length - len ( partial ) part = bytearray ( bitlength ) amount = current . readinto ( part ) read += amount partial . extend ( part [: amount ]) if amount < bitlength : if amount > 0 and read == length : self . prog_update ( amount ) yield partial break self . prog_update ( amount ) yield partial partial = bytearray ( 0 ) if length != read : for pad in self . _gen_padding ( partial , length , read ): yield pad def _gen_padding ( self , partial : bytes , length : int , read = 0 ) -> bytes : \"\"\" Create padded pieces where file sizes do not match. Parameters ---------- partial : bytes any remaining data from last file processed. length : int size of space that needs padding read : int portion of length already padded Yields ------ bytes A piece length sized block of zeros. \"\"\" while read < length : left = self . piece_length - len ( partial ) if length - read > left : padding = bytearray ( left ) partial . extend ( padding ) yield partial read += left partial = bytearray ( 0 ) else : partial . extend ( bytearray ( length - read )) read = length yield partial __init__ ( self , checker , hasher = None ) special Generate hashes of piece length data from filelist contents. Source code in torrentfile\\recheck.py def __init__ ( self , checker : Checker , hasher = None ): \"\"\" Generate hashes of piece length data from filelist contents. \"\"\" self . piece_length = checker . piece_length self . paths = checker . paths self . pieces = checker . info [ \"pieces\" ] self . fileinfo = checker . fileinfo self . hasher = hasher self . piece_map = {} self . index = 0 self . piece_count = 0 self . it = None __iter__ ( self ) special Assign iterator and return self. Source code in torrentfile\\recheck.py def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" self . it = self . iter_pieces () return self __next__ ( self ) special Yield back result of comparison. Source code in torrentfile\\recheck.py def __next__ ( self ): \"\"\" Yield back result of comparison. \"\"\" try : partial = next ( self . it ) except StopIteration as itererror : raise StopIteration from itererror chunck = sha1 ( partial ) . digest () # nosec start = self . piece_count * SHA1 end = start + SHA1 piece = self . pieces [ start : end ] self . piece_count += 1 path = self . paths [ self . index ] return chunck , piece , path , len ( partial ) extract ( self , path , partial ) Split file paths contents into blocks of data for hash pieces. Parameters: Name Type Description Default path str path to content. required partial bytearray any remaining content from last file. required Returns: Type Description bytearray Hash digest for block of .torrent contents. Source code in torrentfile\\recheck.py def extract ( self , path : str , partial : bytearray ) -> bytearray : \"\"\" Split file paths contents into blocks of data for hash pieces. Parameters ---------- path : str path to content. partial : bytes any remaining content from last file. Returns ------- bytearray Hash digest for block of .torrent contents. \"\"\" read = 0 length = self . fileinfo [ self . index ][ \"length\" ] partial = bytearray () if len ( partial ) == self . piece_length else partial if path not in self . paths : # pragma: no cover raise MissingPathError ( path ) with open ( path , \"rb\" ) as current : while True : bitlength = self . piece_length - len ( partial ) part = bytearray ( bitlength ) amount = current . readinto ( part ) read += amount partial . extend ( part [: amount ]) if amount < bitlength : if amount > 0 and read == length : self . prog_update ( amount ) yield partial break self . prog_update ( amount ) yield partial partial = bytearray ( 0 ) if length != read : for pad in self . _gen_padding ( partial , length , read ): yield pad iter_pieces ( self ) Iterate through, and hash pieces of torrent contents. Returns: Type Description bytes hash digest for block of torrent data. Source code in torrentfile\\recheck.py def iter_pieces ( self ): \"\"\" Iterate through, and hash pieces of torrent contents. Yields ------ piece : bytes hash digest for block of torrent data. \"\"\" partial = bytearray () for i , path in enumerate ( self . paths ): total = self . fileinfo [ i ][ \"length\" ] self . prog_start ( total , path , unit = \"bytes\" ) self . index = i if os . path . exists ( path ): for piece in self . extract ( path , partial ): if ( len ( piece ) == self . piece_length ) or ( i + 1 == len ( self . paths ) ): yield piece else : partial = piece else : length = self . fileinfo [ i ][ \"length\" ] for pad in self . _gen_padding ( partial , length ): if len ( pad ) == self . piece_length : yield pad else : partial = pad self . prog_close () HashChecker ( ProgMixin ) Verify that root hashes of content files match the .torrent files. Parameters: Name Type Description Default checker Checker the checker instance that maintains variables. required hasher Object the version specific hashing class for torrent content. None Source code in torrentfile\\recheck.py class HashChecker ( ProgMixin ): \"\"\" Verify that root hashes of content files match the .torrent files. Parameters ---------- checker : Object the checker instance that maintains variables. hasher : Object the version specific hashing class for torrent content. \"\"\" def __init__ ( self , checker : Checker , hasher = None ): \"\"\" Construct a HybridChecker instance. \"\"\" self . checker = checker self . paths = checker . paths self . hasher = hasher self . piece_length = checker . piece_length self . fileinfo = checker . fileinfo self . piece_layers = checker . meta [ \"piece layers\" ] self . piece_count = 0 self . it = None def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" self . it = self . iter_paths () return self def __next__ ( self ): \"\"\" Provide the result of comparison. \"\"\" try : value = next ( self . it ) return value except StopIteration as stopiter : raise StopIteration () from stopiter def iter_paths ( self ) -> tuple : \"\"\" Iterate through and compare root file hashes to .torrent file. Yields ------ results : tuple The size of the file and result of match. \"\"\" for i , path in enumerate ( self . paths ): info = self . fileinfo [ i ] length , plength = info [ \"length\" ], self . piece_length roothash = info [ \"pieces root\" ] self . prog_start ( length , path , unit = \"bytes\" ) if roothash in self . piece_layers : pieces = self . piece_layers [ roothash ] else : pieces = roothash if roothash else bytes () amount = len ( pieces ) // SHA256 if not os . path . exists ( path ): for i in range ( amount ): start = i * SHA256 end = start + SHA256 piece = pieces [ start : end ] if length > plength : size = plength else : size = length length -= size block = sha256 ( bytearray ( size )) . digest () yield block , piece , path , size else : hashed = self . hasher ( path , plength ) if len ( hashed . layer_hashes ) == 1 : block = hashed . root piece = roothash size = length self . prog_update ( size ) yield block , piece , path , size else : for i in range ( amount ): start = i * SHA256 end = start + SHA256 piece = pieces [ start : end ] try : block = hashed . piece_layer [ start : end ] except IndexError : # pragma: nocover block = sha256 ( bytearray ( size )) . digest () size = plength if plength < length else length length -= size self . prog_update ( size ) yield block , piece , path , size __init__ ( self , checker , hasher = None ) special Construct a HybridChecker instance. Source code in torrentfile\\recheck.py def __init__ ( self , checker : Checker , hasher = None ): \"\"\" Construct a HybridChecker instance. \"\"\" self . checker = checker self . paths = checker . paths self . hasher = hasher self . piece_length = checker . piece_length self . fileinfo = checker . fileinfo self . piece_layers = checker . meta [ \"piece layers\" ] self . piece_count = 0 self . it = None __iter__ ( self ) special Assign iterator and return self. Source code in torrentfile\\recheck.py def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" self . it = self . iter_paths () return self __next__ ( self ) special Provide the result of comparison. Source code in torrentfile\\recheck.py def __next__ ( self ): \"\"\" Provide the result of comparison. \"\"\" try : value = next ( self . it ) return value except StopIteration as stopiter : raise StopIteration () from stopiter iter_paths ( self ) Iterate through and compare root file hashes to .torrent file. Returns: Type Description tuple The size of the file and result of match. Source code in torrentfile\\recheck.py def iter_paths ( self ) -> tuple : \"\"\" Iterate through and compare root file hashes to .torrent file. Yields ------ results : tuple The size of the file and result of match. \"\"\" for i , path in enumerate ( self . paths ): info = self . fileinfo [ i ] length , plength = info [ \"length\" ], self . piece_length roothash = info [ \"pieces root\" ] self . prog_start ( length , path , unit = \"bytes\" ) if roothash in self . piece_layers : pieces = self . piece_layers [ roothash ] else : pieces = roothash if roothash else bytes () amount = len ( pieces ) // SHA256 if not os . path . exists ( path ): for i in range ( amount ): start = i * SHA256 end = start + SHA256 piece = pieces [ start : end ] if length > plength : size = plength else : size = length length -= size block = sha256 ( bytearray ( size )) . digest () yield block , piece , path , size else : hashed = self . hasher ( path , plength ) if len ( hashed . layer_hashes ) == 1 : block = hashed . root piece = roothash size = length self . prog_update ( size ) yield block , piece , path , size else : for i in range ( amount ): start = i * SHA256 end = start + SHA256 piece = pieces [ start : end ] try : block = hashed . piece_layer [ start : end ] except IndexError : # pragma: nocover block = sha256 ( bytearray ( size )) . digest () size = plength if plength < length else length length -= size self . prog_update ( size ) yield block , piece , path , size torrentfile.torrent Classes and procedures pertaining to the creation of torrent meta files. Classes TorrentFile construct .torrent file. TorrentFileV2 construct .torrent v2 files using provided data. MetaFile base class for all MetaFile classes. Constants BLOCK_SIZE : int size of leaf hashes for merkle tree. HASH_SIZE : int Length of a sha256 hash. Bittorrent V2 From Bittorrent.org Documentation pages. Implementation details for Bittorrent Protocol v2. Note All strings in a .torrent file that contain text must be UTF-8 encoded. Meta Version 2 Dictionary: \"announce\": The URL of the tracker. \"info\": This maps to a dictionary, with keys described below. \"name\": A display name for the torrent. It is purely advisory. \"piece length\": The number of bytes that each logical piece in the peer protocol refers to. I.e. it sets the granularity of piece, request, bitfield and have messages. It must be a power of two and at least 6KiB. \"meta version\": An integer value, set to 2 to indicate compatibility with the current revision of this specification. Version 1 is not assigned to avoid confusion with BEP3. Future revisions will only increment this issue to indicate an incompatible change has been made, for example that hash algorithms were changed due to newly discovered vulnerabilities. Lementations must check this field first and indicate that a torrent is of a newer version than they can handle before performing other idations which may result in more general messages about invalid files. Files are mapped into this piece address space so that each non-empty \"file tree\": A tree of dictionaries where dictionary keys represent UTF-8 encoded path elements. Entries with zero-length keys describe the properties of the composed path at that point. 'UTF-8 encoded' context only means that if the native encoding is known at creation time it must be converted to UTF-8. Keys may contain invalid UTF-8 sequences or characters and names that are reserved on specific filesystems. Implementations must be prepared to sanitize them. On platforms path components exactly matching '.' and '..' must be sanitized since they could lead to directory traversal attacks and conflicting path descriptions. On platforms that require UTF-8 path components this sanitizing step must happen after normalizing overlong UTF-8 encodings. File is aligned to a piece boundary and occurs in same order as the file tree. The last piece of each file may be shorter than the specified piece length, resulting in an alignment gap. \"length\": Length of the file in bytes. Presence of this field indicates that the dictionary describes a file, not a directory. Which means it must not have any sibling entries. \"pieces root\": For non-empty files this is the the root hash of a merkle tree with a branching factor of 2, constructed from 16KiB blocks of the file. The last block may be shorter than 16KiB. The remaining leaf hashes beyond the end of the file required to construct upper layers of the merkle tree are set to zero. As of meta version 2 SHA2-256 is used as digest function for the merkle tree. The hash is stored in its binary form, not as human-readable string. \"piece layers\": A dictionary of strings. For each file in the file tree that is larger than the piece size it contains one string value. The keys are the merkle roots while the values consist of concatenated hashes of one layer within that merkle tree. The layer is chosen so that one hash covers piece length bytes. For example if the piece size is 16KiB then the leaf hashes are used. If a piece size of 128KiB is used then 3rd layer up from the leaf hashes is used. Layer hashes which exclusively cover data beyond the end of file, i.e. are only needed to balance the tree, are omitted. All hashes are stored in their binary format. A torrent is not valid if this field is absent, the contained hashes do not match the merkle roots or are not from the correct layer. Important The file tree root dictionary itself must not be a file, i.e. it must not contain a zero-length key with a dictionary containing a length key. Bittorrent V1 v1 meta-dictionary announce: The URL of the tracker. info: This maps to a dictionary, with keys described below. name : maps to a UTF-8 encoded string which is the suggested name to save the file (or directory) as. It is purely advisory. piece length : maps to the number of bytes in each piece the file is split into. For the purposes of transfer, files are split into fixed-size pieces which are all the same length except for possibly the last one which may be truncated. piece length : is almost always a power of two, most commonly 2^18 = 256 K pieces : maps to a string whose length is a multiple of 20. It is to be subdivided into strings of length 20, each of which is the SHA1 hash of the piece at the corresponding index. length : In the single file case, maps to the length of the file in bytes. files : If present then the download represents a single file, otherwise it represents a set of files which go in a directory structure. For the purposes of the other keys, the multi-file case is treated as only having a single file by concatenating the files in the order they appear in the files list. The files list is the value files maps to, and is a list of dictionaries containing the following keys: path : A list of UTF-8 encoded strings corresponding to subdirectory names, the last of which is the actual file name length : Maps to the length of the file in bytes. length : Only present if the content is a single file. Maps to the length of the file in bytes. Note In the single file case, the name key is the name of a file, in the muliple file case, it's the name of a directory. MetaFile Base Class for all TorrentFile classes. Parameters: Name Type Description Default path str target path to torrent content. Default: None None announce str One or more tracker URL's. Default: None None comment str A comment. Default: None None piece_length int Size of torrent pieces. Default: None None private bool For private trackers. Default: None False outfile str target path to write .torrent file. Default: None None source str Private tracker source. Default: None None progress str level of progress bar displayed Default: \"1\" 1 cwd bool If True change default save location to current directory False httpseeds list one or more web addresses where torrent content can be found. None url_list list one or more web addressess where torrent content exists. None content str alias for 'path' arg. None Source code in torrentfile\\torrent.py class MetaFile : \"\"\" Base Class for all TorrentFile classes. Parameters ---------- path : str target path to torrent content. Default: None announce : str One or more tracker URL's. Default: None comment : str A comment. Default: None piece_length : int Size of torrent pieces. Default: None private : bool For private trackers. Default: None outfile : str target path to write .torrent file. Default: None source : str Private tracker source. Default: None progress : str level of progress bar displayed Default: \"1\" cwd : bool If True change default save location to current directory httpseeds : list one or more web addresses where torrent content can be found. url_list : list one or more web addressess where torrent content exists. content : str alias for 'path' arg. \"\"\" hasher = None @classmethod def set_callback ( cls , func ): \"\"\" Assign a callback function for the Hashing class to call for each hash. Parameters ---------- func : function The callback function which accepts a single paramter. \"\"\" if \"hasher\" in vars ( cls ) and vars ( cls )[ \"hasher\" ]: cls . hasher . set_callback ( func ) def __init__ ( self , path = None , announce = None , comment = None , piece_length = None , private = False , outfile = None , source = None , progress = 1 , cwd = False , httpseeds = None , url_list = None , content = None , ** _ , ): \"\"\" Construct MetaFile superclass and assign local attributes. \"\"\" self . private = private self . cwd = cwd self . outfile = outfile self . progress = int ( progress ) self . comment = comment self . source = source if content : path = content if not path : if announce and len ( announce ) > 1 and os . path . exists ( announce [ - 1 ]): path = announce [ - 1 ] announce = announce [: - 1 ] elif url_list and os . path . exists ( url_list [ - 1 ]): path = url_list [ - 1 ] url_list = url_list [: - 1 ] elif httpseeds and os . path . exists ( httpseeds [ - 1 ]): path = httpseeds [ - 1 ] httpseeds = httpseeds [: - 1 ] else : raise utils . MissingPathError ( \"Path to content is required.\" ) # base path to torrent content. self . path = path logger . debug ( \"path parameter found %s \" , path ) # Format piece_length attribute. if piece_length : self . piece_length = utils . normalize_piece_length ( piece_length ) logger . debug ( \"piece length parameter found %s \" , piece_length ) else : self . piece_length = utils . path_piece_length ( self . path ) logger . debug ( \"piece length calculated %s \" , self . piece_length ) # Assign announce URL to empty string if none provided. if not announce : self . announce , self . announce_list = \"\" , [[ \"\" ]] # Most torrent clients have editting trackers as a feature. elif isinstance ( announce , str ): self . announce , self . announce_list = announce , [[ announce ]] elif isinstance ( announce , Sequence ): self . announce , self . announce_list = announce [ 0 ], [ announce ] self . meta = { \"announce\" : self . announce , \"announce-list\" : self . announce_list , \"created by\" : f \"TorrentFile:v { version } \" , \"creation date\" : int ( datetime . timestamp ( datetime . now ())), \"info\" : {}, } if comment : self . meta [ \"info\" ][ \"comment\" ] = comment logger . debug ( \"comment parameter found %s \" , comment ) if private : self . meta [ \"info\" ][ \"private\" ] = 1 logger . debug ( \"private parameter triggered\" ) if source : self . meta [ \"info\" ][ \"source\" ] = source logger . debug ( \"source parameter found %s \" , source ) if url_list : self . meta [ \"url-list\" ] = url_list logger . debug ( \"url list parameter found %s \" , str ( url_list )) if httpseeds : self . meta [ \"httpseeds\" ] = httpseeds logger . debug ( \"httpseeds parameter found %s \" , str ( httpseeds )) self . meta [ \"info\" ][ \"piece length\" ] = self . piece_length parent , self . name = os . path . split ( self . path ) if not self . name : self . name = os . path . basename ( parent ) self . meta [ \"info\" ][ \"name\" ] = self . name def assemble ( self ): \"\"\" Overload in subclasses. Raises ------ Exception NotImplementedError \"\"\" raise NotImplementedError def sort_meta ( self ): \"\"\"Sort the info and meta dictionaries.\"\"\" logger . debug ( \"sorting dictionary keys\" ) meta = self . meta meta [ \"info\" ] = dict ( sorted ( list ( meta [ \"info\" ] . items ()))) meta = dict ( sorted ( list ( meta . items ()))) return meta def write ( self , outfile = None ) -> tuple : \"\"\" Write meta information to .torrent file. Parameters ---------- outfile : str Destination path for .torrent file. default=None Returns ------- outfile : str Where the .torrent file was writen. meta : dict .torrent meta information. \"\"\" fallback = os . path . join ( os . getcwd (), self . name ) + \".torrent\" if not self . outfile and not outfile : if self . cwd : self . outfile = fallback else : path = str ( self . path ) . rstrip ( \" \\\\ /\" ) self . outfile = path + \".torrent\" elif outfile : self . outfile = outfile if str ( self . outfile )[ - 1 ] in \" \\\\ /\" : self . outfile = self . outfile + ( self . name + \".torrent\" ) self . meta = self . sort_meta () try : pyben . dump ( self . meta , self . outfile ) except PermissionError : self . outfile = fallback pyben . dump ( self . meta , fallback ) return self . outfile , self . meta __init__ ( self , path = None , announce = None , comment = None , piece_length = None , private = False , outfile = None , source = None , progress = 1 , cwd = False , httpseeds = None , url_list = None , content = None , ** _ ) special Construct MetaFile superclass and assign local attributes. Source code in torrentfile\\torrent.py def __init__ ( self , path = None , announce = None , comment = None , piece_length = None , private = False , outfile = None , source = None , progress = 1 , cwd = False , httpseeds = None , url_list = None , content = None , ** _ , ): \"\"\" Construct MetaFile superclass and assign local attributes. \"\"\" self . private = private self . cwd = cwd self . outfile = outfile self . progress = int ( progress ) self . comment = comment self . source = source if content : path = content if not path : if announce and len ( announce ) > 1 and os . path . exists ( announce [ - 1 ]): path = announce [ - 1 ] announce = announce [: - 1 ] elif url_list and os . path . exists ( url_list [ - 1 ]): path = url_list [ - 1 ] url_list = url_list [: - 1 ] elif httpseeds and os . path . exists ( httpseeds [ - 1 ]): path = httpseeds [ - 1 ] httpseeds = httpseeds [: - 1 ] else : raise utils . MissingPathError ( \"Path to content is required.\" ) # base path to torrent content. self . path = path logger . debug ( \"path parameter found %s \" , path ) # Format piece_length attribute. if piece_length : self . piece_length = utils . normalize_piece_length ( piece_length ) logger . debug ( \"piece length parameter found %s \" , piece_length ) else : self . piece_length = utils . path_piece_length ( self . path ) logger . debug ( \"piece length calculated %s \" , self . piece_length ) # Assign announce URL to empty string if none provided. if not announce : self . announce , self . announce_list = \"\" , [[ \"\" ]] # Most torrent clients have editting trackers as a feature. elif isinstance ( announce , str ): self . announce , self . announce_list = announce , [[ announce ]] elif isinstance ( announce , Sequence ): self . announce , self . announce_list = announce [ 0 ], [ announce ] self . meta = { \"announce\" : self . announce , \"announce-list\" : self . announce_list , \"created by\" : f \"TorrentFile:v { version } \" , \"creation date\" : int ( datetime . timestamp ( datetime . now ())), \"info\" : {}, } if comment : self . meta [ \"info\" ][ \"comment\" ] = comment logger . debug ( \"comment parameter found %s \" , comment ) if private : self . meta [ \"info\" ][ \"private\" ] = 1 logger . debug ( \"private parameter triggered\" ) if source : self . meta [ \"info\" ][ \"source\" ] = source logger . debug ( \"source parameter found %s \" , source ) if url_list : self . meta [ \"url-list\" ] = url_list logger . debug ( \"url list parameter found %s \" , str ( url_list )) if httpseeds : self . meta [ \"httpseeds\" ] = httpseeds logger . debug ( \"httpseeds parameter found %s \" , str ( httpseeds )) self . meta [ \"info\" ][ \"piece length\" ] = self . piece_length parent , self . name = os . path . split ( self . path ) if not self . name : self . name = os . path . basename ( parent ) self . meta [ \"info\" ][ \"name\" ] = self . name assemble ( self ) Overload in subclasses. Exceptions: Type Description Exception NotImplementedError Source code in torrentfile\\torrent.py def assemble ( self ): \"\"\" Overload in subclasses. Raises ------ Exception NotImplementedError \"\"\" raise NotImplementedError set_callback ( func ) classmethod Assign a callback function for the Hashing class to call for each hash. Parameters: Name Type Description Default func function The callback function which accepts a single paramter. required Source code in torrentfile\\torrent.py @classmethod def set_callback ( cls , func ): \"\"\" Assign a callback function for the Hashing class to call for each hash. Parameters ---------- func : function The callback function which accepts a single paramter. \"\"\" if \"hasher\" in vars ( cls ) and vars ( cls )[ \"hasher\" ]: cls . hasher . set_callback ( func ) sort_meta ( self ) Sort the info and meta dictionaries. Source code in torrentfile\\torrent.py def sort_meta ( self ): \"\"\"Sort the info and meta dictionaries.\"\"\" logger . debug ( \"sorting dictionary keys\" ) meta = self . meta meta [ \"info\" ] = dict ( sorted ( list ( meta [ \"info\" ] . items ()))) meta = dict ( sorted ( list ( meta . items ()))) return meta write ( self , outfile = None ) Write meta information to .torrent file. Parameters: Name Type Description Default outfile str Destination path for .torrent file. default=None None Returns: Type Description tuple Where the .torrent file was writen. Source code in torrentfile\\torrent.py def write ( self , outfile = None ) -> tuple : \"\"\" Write meta information to .torrent file. Parameters ---------- outfile : str Destination path for .torrent file. default=None Returns ------- outfile : str Where the .torrent file was writen. meta : dict .torrent meta information. \"\"\" fallback = os . path . join ( os . getcwd (), self . name ) + \".torrent\" if not self . outfile and not outfile : if self . cwd : self . outfile = fallback else : path = str ( self . path ) . rstrip ( \" \\\\ /\" ) self . outfile = path + \".torrent\" elif outfile : self . outfile = outfile if str ( self . outfile )[ - 1 ] in \" \\\\ /\" : self . outfile = self . outfile + ( self . name + \".torrent\" ) self . meta = self . sort_meta () try : pyben . dump ( self . meta , self . outfile ) except PermissionError : self . outfile = fallback pyben . dump ( self . meta , fallback ) return self . outfile , self . meta TorrentFile ( MetaFile , ProgMixin ) Class for creating Bittorrent meta files. Construct Torrentfile class instance object. Parameters: Name Type Description Default kwargs dict Dictionary containing torrent file options. {} Source code in torrentfile\\torrent.py class TorrentFile ( MetaFile , ProgMixin ): \"\"\" Class for creating Bittorrent meta files. Construct *Torrentfile* class instance object. Parameters ---------- kwargs : dict Dictionary containing torrent file options. \"\"\" hasher = Hasher def __init__ ( self , ** kwargs ): \"\"\" Construct TorrentFile instance with given keyword args. Parameters ---------- kwargs : dict dictionary of keyword args passed to superclass. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"bittorrent version 1 file assembly\" ) self . assemble () def assemble ( self ): \"\"\" Assemble components of torrent metafile. Returns ------- dict metadata dictionary for torrent file \"\"\" info = self . meta [ \"info\" ] size , filelist = utils . filelist_total ( self . path ) if os . path . isfile ( self . path ): info [ \"length\" ] = size else : info [ \"files\" ] = [ { \"length\" : os . path . getsize ( path ), \"path\" : os . path . relpath ( path , self . path ) . split ( os . sep ), } for path in filelist ] pieces = bytearray () feeder = Hasher ( filelist , self . piece_length , self . progress ) if self . progress != 1 : for piece in feeder : pieces . extend ( piece ) else : total = ( size // self . piece_length ) + 1 title = self . path unit = \"pieces\" self . prog_start ( total , title , unit = unit ) for piece in feeder : pieces . extend ( piece ) self . prog_update ( 1 ) self . prog_close () info [ \"pieces\" ] = pieces hasher ( CbMixin , ProgMixin ) Piece hasher for Bittorrent V1 files. Takes a sorted list of all file paths, calculates sha1 hash for fixed size pieces of file data from each file seemlessly until the last piece which may be smaller than others. Parameters: Name Type Description Default paths list List of files. required piece_length int Size of chuncks to split the data into. required progress int default = None None Source code in torrentfile\\torrent.py class Hasher ( CbMixin , ProgMixin ): \"\"\" Piece hasher for Bittorrent V1 files. Takes a sorted list of all file paths, calculates sha1 hash for fixed size pieces of file data from each file seemlessly until the last piece which may be smaller than others. Parameters ---------- paths : list List of files. piece_length : int Size of chuncks to split the data into. progress : int default = None \"\"\" def __init__ ( self , paths : list , piece_length : int , progress : int = None ): \"\"\"Generate hashes of piece length data from filelist contents.\"\"\" self . piece_length = piece_length self . paths = paths self . progress = progress self . total = sum ([ os . path . getsize ( i ) for i in self . paths ]) self . index = 0 self . current = open ( self . paths [ 0 ], \"rb\" ) if self . progress and self . progress == 2 : total = os . path . getsize ( self . paths [ 0 ]) self . prog_start ( total , self . paths [ 0 ], unit = \"bytes\" ) logger . debug ( \"Hashing v1 torrent file. Size: %s Piece Length: %s \" , humanize_bytes ( self . total ), humanize_bytes ( self . piece_length ), ) def __iter__ ( self ): \"\"\" Iterate through feed pieces. Returns ------- self : iterator Iterator for leaves/hash pieces. \"\"\" return self def _handle_partial ( self , arr : bytearray ) -> bytearray : \"\"\" Define the handling partial pieces that span 2 or more files. Parameters ---------- arr : bytearray Incomplete piece containing partial data Returns ------- digest : bytearray SHA1 digest of the complete piece. \"\"\" while len ( arr ) < self . piece_length and self . next_file (): target = self . piece_length - len ( arr ) temp = bytearray ( target ) size = self . current . readinto ( temp ) arr . extend ( temp [: size ]) self . prog_update ( size ) if size == target : break return sha1 ( arr ) . digest () # nosec def next_file ( self ) -> bool : \"\"\" Seemlessly transition to next file in file list. Returns ------- bool: True if there is a next file otherwise False. \"\"\" self . index += 1 self . prog_close () if self . index < len ( self . paths ): path = self . paths [ self . index ] self . current . close () if self . progress == 2 : self . prog_start ( os . path . getsize ( path ), path , self . progress , unit = \"bytes\" ) self . current = open ( path , \"rb\" ) return True return False def __next__ ( self ) -> bytes : \"\"\" Generate piece-length pieces of data from input file list. Returns ------- bytes SHA1 hash of the piece extracted. \"\"\" while True : piece = bytearray ( self . piece_length ) size = self . current . readinto ( piece ) if size == 0 : if not self . next_file (): raise StopIteration elif size < self . piece_length : self . prog_update ( size ) return self . _handle_partial ( piece [: size ]) else : self . prog_update ( size ) return sha1 ( piece ) . digest () # nosec __init__ ( self , paths , piece_length , progress = None ) special Generate hashes of piece length data from filelist contents. Source code in torrentfile\\torrent.py def __init__ ( self , paths : list , piece_length : int , progress : int = None ): \"\"\"Generate hashes of piece length data from filelist contents.\"\"\" self . piece_length = piece_length self . paths = paths self . progress = progress self . total = sum ([ os . path . getsize ( i ) for i in self . paths ]) self . index = 0 self . current = open ( self . paths [ 0 ], \"rb\" ) if self . progress and self . progress == 2 : total = os . path . getsize ( self . paths [ 0 ]) self . prog_start ( total , self . paths [ 0 ], unit = \"bytes\" ) logger . debug ( \"Hashing v1 torrent file. Size: %s Piece Length: %s \" , humanize_bytes ( self . total ), humanize_bytes ( self . piece_length ), ) __iter__ ( self ) special Iterate through feed pieces. Returns: Type Description iterator Iterator for leaves/hash pieces. Source code in torrentfile\\torrent.py def __iter__ ( self ): \"\"\" Iterate through feed pieces. Returns ------- self : iterator Iterator for leaves/hash pieces. \"\"\" return self __next__ ( self ) special Generate piece-length pieces of data from input file list. Returns: Type Description bytes SHA1 hash of the piece extracted. Source code in torrentfile\\torrent.py def __next__ ( self ) -> bytes : \"\"\" Generate piece-length pieces of data from input file list. Returns ------- bytes SHA1 hash of the piece extracted. \"\"\" while True : piece = bytearray ( self . piece_length ) size = self . current . readinto ( piece ) if size == 0 : if not self . next_file (): raise StopIteration elif size < self . piece_length : self . prog_update ( size ) return self . _handle_partial ( piece [: size ]) else : self . prog_update ( size ) return sha1 ( piece ) . digest () # nosec next_file ( self ) Seemlessly transition to next file in file list. Returns: Type Description bool True if there is a next file otherwise False. Source code in torrentfile\\torrent.py def next_file ( self ) -> bool : \"\"\" Seemlessly transition to next file in file list. Returns ------- bool: True if there is a next file otherwise False. \"\"\" self . index += 1 self . prog_close () if self . index < len ( self . paths ): path = self . paths [ self . index ] self . current . close () if self . progress == 2 : self . prog_start ( os . path . getsize ( path ), path , self . progress , unit = \"bytes\" ) self . current = open ( path , \"rb\" ) return True return False __init__ ( self , ** kwargs ) special Construct TorrentFile instance with given keyword args. Parameters: Name Type Description Default kwargs dict dictionary of keyword args passed to superclass. {} Source code in torrentfile\\torrent.py def __init__ ( self , ** kwargs ): \"\"\" Construct TorrentFile instance with given keyword args. Parameters ---------- kwargs : dict dictionary of keyword args passed to superclass. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"bittorrent version 1 file assembly\" ) self . assemble () assemble ( self ) Assemble components of torrent metafile. Returns: Type Description dict metadata dictionary for torrent file Source code in torrentfile\\torrent.py def assemble ( self ): \"\"\" Assemble components of torrent metafile. Returns ------- dict metadata dictionary for torrent file \"\"\" info = self . meta [ \"info\" ] size , filelist = utils . filelist_total ( self . path ) if os . path . isfile ( self . path ): info [ \"length\" ] = size else : info [ \"files\" ] = [ { \"length\" : os . path . getsize ( path ), \"path\" : os . path . relpath ( path , self . path ) . split ( os . sep ), } for path in filelist ] pieces = bytearray () feeder = Hasher ( filelist , self . piece_length , self . progress ) if self . progress != 1 : for piece in feeder : pieces . extend ( piece ) else : total = ( size // self . piece_length ) + 1 title = self . path unit = \"pieces\" self . prog_start ( total , title , unit = unit ) for piece in feeder : pieces . extend ( piece ) self . prog_update ( 1 ) self . prog_close () info [ \"pieces\" ] = pieces TorrentFileHybrid ( MetaFile , ProgMixin ) Construct the Hybrid torrent meta file with provided parameters. Parameters: Name Type Description Default kwargs dict Keyword arguments for torrent options. {} Source code in torrentfile\\torrent.py class TorrentFileHybrid ( MetaFile , ProgMixin ): \"\"\" Construct the Hybrid torrent meta file with provided parameters. Parameters ---------- kwargs : dict Keyword arguments for torrent options. \"\"\" hasher = HasherHybrid def __init__ ( self , ** kwargs ): \"\"\" Create Bittorrent v1 v2 hybrid metafiles. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"hybrid bittorrent file detected\" ) self . name = os . path . basename ( self . path ) self . hashes = [] self . piece_layers = {} self . pieces = [] self . files = [] self . total = len ( utils . get_file_list ( self . path )) if self . progress == 1 : title = os . path . basename ( self . path ) unit = \"bytes\" self . prog_start ( self . total , title , unit = unit ) self . assemble () self . prog_close () def assemble ( self ): \"\"\" Assemble the parts of the torrentfile into meta dictionary. \"\"\" info = self . meta [ \"info\" ] info [ \"meta version\" ] = 2 if os . path . isfile ( self . path ): info [ \"file tree\" ] = { self . name : self . _traverse ( self . path )} info [ \"length\" ] = os . path . getsize ( self . path ) self . prog_update ( info [ \"length\" ]) else : info [ \"file tree\" ] = self . _traverse ( self . path ) info [ \"files\" ] = self . files info [ \"pieces\" ] = b \"\" . join ( self . pieces ) self . meta [ \"piece layers\" ] = self . piece_layers return info def _traverse ( self , path : str ) -> dict : \"\"\" Build meta dictionary while walking directory. Parameters ---------- path : str Path to target file. \"\"\" if os . path . isfile ( path ): file_size = os . path . getsize ( path ) self . files . append ( { \"length\" : file_size , \"path\" : os . path . relpath ( path , self . path ) . split ( os . sep ), } ) if file_size == 0 : return { \"\" : { \"length\" : file_size }} file_hash = HasherHybrid ( path , self . piece_length , self . progress ) self . prog_update ( file_size ) if file_size > self . piece_length : self . piece_layers [ file_hash . root ] = file_hash . piece_layer self . hashes . append ( file_hash ) self . pieces . extend ( file_hash . pieces ) if file_hash . padding_file : self . files . append ( file_hash . padding_file ) return { \"\" : { \"length\" : file_size , \"pieces root\" : file_hash . root }} tree = {} if os . path . isdir ( path ): for name in sorted ( os . listdir ( path )): tree [ name ] = self . _traverse ( os . path . join ( path , name )) return tree hasher ( CbMixin , ProgMixin ) Calculate root and piece hashes for creating hybrid torrent file. Create merkle tree layers from sha256 hashed 16KiB blocks of contents. With a branching factor of 2, merge layer hashes until blocks equal piece_length bytes for the piece layer, and then the root hash. Parameters: Name Type Description Default path str path to target file. required piece_length int piece length for data chunks. required progress int default = None None Source code in torrentfile\\torrent.py class HasherHybrid ( CbMixin , ProgMixin ): \"\"\" Calculate root and piece hashes for creating hybrid torrent file. Create merkle tree layers from sha256 hashed 16KiB blocks of contents. With a branching factor of 2, merge layer hashes until blocks equal piece_length bytes for the piece layer, and then the root hash. Parameters ---------- path : str path to target file. piece_length : int piece length for data chunks. progress : int default = None \"\"\" def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Construct Hasher class instances for each file in torrent. \"\"\" self . path = path self . piece_length = piece_length self . pieces = [] self . layer_hashes = [] self . piece_layer = None self . root = None self . padding_piece = None self . padding_file = None if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . amount = piece_length // BLOCK_SIZE logger . debug ( \"Hashing: %s , Piece Size: %s \" , str ( self . path ), humanize_bytes ( self . piece_length ), ) with open ( path , \"rb\" ) as data : self . process_file ( data ) def _pad_remaining ( self , block_count : int ): \"\"\" Generate Hash sized, 0 filled bytes for padding. Parameters ---------- block_count : int current total number of blocks collected. Returns ------- padding : bytes Padding to fill remaining portion of tree. \"\"\" # when the there is only one block for file remaining = self . amount - block_count if not self . layer_hashes : power2 = next_power_2 ( block_count ) remaining = power2 - block_count self . prog_update ( HASH_SIZE * remaining ) return [ bytes ( HASH_SIZE ) for _ in range ( remaining )] def process_file ( self , data : bytearray ): \"\"\" Calculate layer hashes for contents of file. Parameters ---------- data : BytesIO File opened in read mode. \"\"\" while True : plength = self . piece_length blocks = [] piece = sha1 () # nosec total = 0 block = bytearray ( BLOCK_SIZE ) for _ in range ( self . amount ): size = data . readinto ( block ) self . prog_update ( size ) if not size : break total += size plength -= size blocks . append ( sha256 ( block [: size ]) . digest ()) piece . update ( block [: size ]) if not blocks : break if len ( blocks ) != self . amount : padding = self . _pad_remaining ( len ( blocks )) blocks . extend ( padding ) layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) if plength > 0 : self . padding_file = { \"attr\" : \"p\" , \"length\" : plength , \"path\" : [ \".pad\" , str ( plength )], } piece . update ( bytes ( plength )) self . pieces . append ( piece . digest ()) # nosec self . _calculate_root () self . prog_close () def _calculate_root ( self ): \"\"\" Calculate the root hash for opened file. \"\"\" self . piece_layer = b \"\" . join ( self . layer_hashes ) if len ( self . layer_hashes ) > 1 : pad_piece = merkle_root ([ bytes ( 32 ) for _ in range ( self . amount )]) pow2 = next_power_2 ( len ( self . layer_hashes )) remainder = pow2 - len ( self . layer_hashes ) self . layer_hashes += [ pad_piece for _ in range ( remainder )] self . root = merkle_root ( self . layer_hashes ) __init__ ( self , path , piece_length , progress = None ) special Construct Hasher class instances for each file in torrent. Source code in torrentfile\\torrent.py def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Construct Hasher class instances for each file in torrent. \"\"\" self . path = path self . piece_length = piece_length self . pieces = [] self . layer_hashes = [] self . piece_layer = None self . root = None self . padding_piece = None self . padding_file = None if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . amount = piece_length // BLOCK_SIZE logger . debug ( \"Hashing: %s , Piece Size: %s \" , str ( self . path ), humanize_bytes ( self . piece_length ), ) with open ( path , \"rb\" ) as data : self . process_file ( data ) process_file ( self , data ) Calculate layer hashes for contents of file. Parameters: Name Type Description Default data bytearray File opened in read mode. required Source code in torrentfile\\torrent.py def process_file ( self , data : bytearray ): \"\"\" Calculate layer hashes for contents of file. Parameters ---------- data : BytesIO File opened in read mode. \"\"\" while True : plength = self . piece_length blocks = [] piece = sha1 () # nosec total = 0 block = bytearray ( BLOCK_SIZE ) for _ in range ( self . amount ): size = data . readinto ( block ) self . prog_update ( size ) if not size : break total += size plength -= size blocks . append ( sha256 ( block [: size ]) . digest ()) piece . update ( block [: size ]) if not blocks : break if len ( blocks ) != self . amount : padding = self . _pad_remaining ( len ( blocks )) blocks . extend ( padding ) layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) if plength > 0 : self . padding_file = { \"attr\" : \"p\" , \"length\" : plength , \"path\" : [ \".pad\" , str ( plength )], } piece . update ( bytes ( plength )) self . pieces . append ( piece . digest ()) # nosec self . _calculate_root () self . prog_close () __init__ ( self , ** kwargs ) special Create Bittorrent v1 v2 hybrid metafiles. Source code in torrentfile\\torrent.py def __init__ ( self , ** kwargs ): \"\"\" Create Bittorrent v1 v2 hybrid metafiles. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"hybrid bittorrent file detected\" ) self . name = os . path . basename ( self . path ) self . hashes = [] self . piece_layers = {} self . pieces = [] self . files = [] self . total = len ( utils . get_file_list ( self . path )) if self . progress == 1 : title = os . path . basename ( self . path ) unit = \"bytes\" self . prog_start ( self . total , title , unit = unit ) self . assemble () self . prog_close () assemble ( self ) Assemble the parts of the torrentfile into meta dictionary. Source code in torrentfile\\torrent.py def assemble ( self ): \"\"\" Assemble the parts of the torrentfile into meta dictionary. \"\"\" info = self . meta [ \"info\" ] info [ \"meta version\" ] = 2 if os . path . isfile ( self . path ): info [ \"file tree\" ] = { self . name : self . _traverse ( self . path )} info [ \"length\" ] = os . path . getsize ( self . path ) self . prog_update ( info [ \"length\" ]) else : info [ \"file tree\" ] = self . _traverse ( self . path ) info [ \"files\" ] = self . files info [ \"pieces\" ] = b \"\" . join ( self . pieces ) self . meta [ \"piece layers\" ] = self . piece_layers return info TorrentFileV2 ( MetaFile , ProgMixin ) Class for creating Bittorrent meta v2 files. Parameters: Name Type Description Default kwargs dict Keyword arguments for torrent file options. {} Source code in torrentfile\\torrent.py class TorrentFileV2 ( MetaFile , ProgMixin ): \"\"\" Class for creating Bittorrent meta v2 files. Parameters ---------- kwargs : dict Keyword arguments for torrent file options. \"\"\" hasher = HasherV2 def __init__ ( self , ** kwargs ): \"\"\" Construct `TorrentFileV2` Class instance from given parameters. Parameters ---------- kwargs : dict keywword arguments to pass to superclass. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"bittorrent v2 file detected\" ) self . piece_layers = {} self . hashes = [] self . total = len ( utils . get_file_list ( self . path )) if self . progress == 1 : title = os . path . basename ( self . path ) total = self . total unit = \"bytes\" self . prog_start ( total , title , unit = unit ) self . assemble () self . prog_close () def assemble ( self ): \"\"\" Assemble then return the meta dictionary for encoding. Returns ------- meta : dict Metainformation about the torrent. \"\"\" info = self . meta [ \"info\" ] if os . path . isfile ( self . path ): info [ \"file tree\" ] = { info [ \"name\" ]: self . _traverse ( self . path )} info [ \"length\" ] = os . path . getsize ( self . path ) self . prog_update ( info [ \"length\" ]) else : info [ \"file tree\" ] = self . _traverse ( self . path ) info [ \"meta version\" ] = 2 self . meta [ \"piece layers\" ] = self . piece_layers def _traverse ( self , path : str ) -> dict : \"\"\" Walk directory tree. Parameters ---------- path : str Path to file or directory. \"\"\" if os . path . isfile ( path ): # Calculate Size and hashes for each file. size = os . path . getsize ( path ) if size == 0 : return { \"\" : { \"length\" : size }} fhash = HasherV2 ( path , self . piece_length , self . progress ) self . prog_update ( size ) if size > self . piece_length : self . piece_layers [ fhash . root ] = fhash . piece_layer return { \"\" : { \"length\" : size , \"pieces root\" : fhash . root }} file_tree = {} if os . path . isdir ( path ): for name in sorted ( os . listdir ( path )): file_tree [ name ] = self . _traverse ( os . path . join ( path , name )) return file_tree hasher ( CbMixin , ProgMixin ) Calculate the root hash and piece layers for file contents. Iterates over 16KiB blocks of data from given file, hashes the data, then creates a hash tree from the individual block hashes until size of hashed data equals the piece-length. Then continues the hash tree until root hash is calculated. Parameters: Name Type Description Default path str Path to file. required piece_length int Size of layer hashes pieces. required progress int default = None None Source code in torrentfile\\torrent.py class HasherV2 ( CbMixin , ProgMixin ): \"\"\" Calculate the root hash and piece layers for file contents. Iterates over 16KiB blocks of data from given file, hashes the data, then creates a hash tree from the individual block hashes until size of hashed data equals the piece-length. Then continues the hash tree until root hash is calculated. Parameters ---------- path : str Path to file. piece_length : int Size of layer hashes pieces. progress : int default = None \"\"\" def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Calculate and store hash information for specific file. \"\"\" self . path = path self . root = None self . piece_layer = None self . layer_hashes = [] self . piece_length = piece_length self . num_blocks = piece_length // BLOCK_SIZE if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) logger . debug ( \"Hashing partial v2 torrent file. Piece Length: %s Path: %s \" , humanize_bytes ( self . piece_length ), str ( self . path ), ) with open ( self . path , \"rb\" ) as fd : self . process_file ( fd ) def process_file ( self , fd : str ): \"\"\" Calculate hashes over 16KiB chuncks of file content. Parameters ---------- fd : str Opened file in read mode. \"\"\" while True : blocks = [] leaf = bytearray ( BLOCK_SIZE ) # generate leaves of merkle tree for _ in range ( self . num_blocks ): size = fd . readinto ( leaf ) self . prog_update ( size ) if not size : break blocks . append ( sha256 ( leaf [: size ]) . digest ()) # blocks is empty mean eof if not blocks : break if len ( blocks ) != self . num_blocks : # when size of file doesn't fill the last block # when the file contains multiple pieces remaining = self . num_blocks - len ( blocks ) if not self . layer_hashes : # when the there is only one block for file power2 = next_power_2 ( len ( blocks )) remaining = power2 - len ( blocks ) # pad the the rest with zeroes to fill remaining space. padding = [ bytes ( 32 ) for _ in range ( remaining )] self . prog_update ( HASH_SIZE * remaining ) blocks . extend ( padding ) # calculate the root hash for the merkle tree up to piece-length layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) self . _calculate_root () self . prog_close () def _calculate_root ( self ): \"\"\" Calculate root hash for the target file. \"\"\" self . piece_layer = b \"\" . join ( self . layer_hashes ) hashes = len ( self . layer_hashes ) if hashes > 1 : pow2 = next_power_2 ( hashes ) remainder = pow2 - hashes pad_piece = [ bytes ( HASH_SIZE ) for _ in range ( self . num_blocks )] for _ in range ( remainder ): self . layer_hashes . append ( merkle_root ( pad_piece )) self . root = merkle_root ( self . layer_hashes ) __init__ ( self , path , piece_length , progress = None ) special Calculate and store hash information for specific file. Source code in torrentfile\\torrent.py def __init__ ( self , path : str , piece_length : int , progress : int = None ): \"\"\" Calculate and store hash information for specific file. \"\"\" self . path = path self . root = None self . piece_layer = None self . layer_hashes = [] self . piece_length = piece_length self . num_blocks = piece_length // BLOCK_SIZE if progress and progress == 2 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) logger . debug ( \"Hashing partial v2 torrent file. Piece Length: %s Path: %s \" , humanize_bytes ( self . piece_length ), str ( self . path ), ) with open ( self . path , \"rb\" ) as fd : self . process_file ( fd ) process_file ( self , fd ) Calculate hashes over 16KiB chuncks of file content. Parameters: Name Type Description Default fd str Opened file in read mode. required Source code in torrentfile\\torrent.py def process_file ( self , fd : str ): \"\"\" Calculate hashes over 16KiB chuncks of file content. Parameters ---------- fd : str Opened file in read mode. \"\"\" while True : blocks = [] leaf = bytearray ( BLOCK_SIZE ) # generate leaves of merkle tree for _ in range ( self . num_blocks ): size = fd . readinto ( leaf ) self . prog_update ( size ) if not size : break blocks . append ( sha256 ( leaf [: size ]) . digest ()) # blocks is empty mean eof if not blocks : break if len ( blocks ) != self . num_blocks : # when size of file doesn't fill the last block # when the file contains multiple pieces remaining = self . num_blocks - len ( blocks ) if not self . layer_hashes : # when the there is only one block for file power2 = next_power_2 ( len ( blocks )) remaining = power2 - len ( blocks ) # pad the the rest with zeroes to fill remaining space. padding = [ bytes ( 32 ) for _ in range ( remaining )] self . prog_update ( HASH_SIZE * remaining ) blocks . extend ( padding ) # calculate the root hash for the merkle tree up to piece-length layer_hash = merkle_root ( blocks ) if self . _cb : self . _cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) self . _calculate_root () self . prog_close () __init__ ( self , ** kwargs ) special Construct TorrentFileV2 Class instance from given parameters. Parameters: Name Type Description Default kwargs dict keywword arguments to pass to superclass. {} Source code in torrentfile\\torrent.py def __init__ ( self , ** kwargs ): \"\"\" Construct `TorrentFileV2` Class instance from given parameters. Parameters ---------- kwargs : dict keywword arguments to pass to superclass. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"bittorrent v2 file detected\" ) self . piece_layers = {} self . hashes = [] self . total = len ( utils . get_file_list ( self . path )) if self . progress == 1 : title = os . path . basename ( self . path ) total = self . total unit = \"bytes\" self . prog_start ( total , title , unit = unit ) self . assemble () self . prog_close () assemble ( self ) Assemble then return the meta dictionary for encoding. Returns: Type Description dict Metainformation about the torrent. Source code in torrentfile\\torrent.py def assemble ( self ): \"\"\" Assemble then return the meta dictionary for encoding. Returns ------- meta : dict Metainformation about the torrent. \"\"\" info = self . meta [ \"info\" ] if os . path . isfile ( self . path ): info [ \"file tree\" ] = { info [ \"name\" ]: self . _traverse ( self . path )} info [ \"length\" ] = os . path . getsize ( self . path ) self . prog_update ( info [ \"length\" ]) else : info [ \"file tree\" ] = self . _traverse ( self . path ) info [ \"meta version\" ] = 2 self . meta [ \"piece layers\" ] = self . piece_layers torrentfile.utils Utility functions and classes used throughout package. Functions: get_piece_length: calculate ideal piece length for torrent file. sortfiles: traverse directory in sorted order yielding paths encountered. path_size: Sum the sizes of each file in path. get_file_list: Return list of all files contained in directory. path_stat: Get ideal piece length, total size, and file list for directory. path_piece_length: Get ideal piece length based on size of directory. Classes: MissingPathError: Custom exception raised when no path was provided to CLI. PieceLengthValueError: Custom exception raised when incorrect input value used for piece length field. Memo Memoice chache object. Parameters: Name Type Description Default func function The function that is being memoized. required Source code in torrentfile\\utils.py class Memo : \"\"\" Memoice chache object. Parameters ---------- func : function The function that is being memoized. \"\"\" def __init__ ( self , func ): \"\"\" Construct for memoization. \"\"\" self . func = func self . counter = 0 self . cache = {} def __call__ ( self , path ): \"\"\" Invoke each time memo function is called. Parameters ---------- path : str The relative or absolute path being used as key in cache dict. Returns ------- Any : The results of calling the function with path. \"\"\" if path in self . cache and os . path . exists ( path ): self . counter += 1 return self . cache [ path ] result = self . func ( path ) self . cache [ path ] = result return result __call__ ( self , path ) special Invoke each time memo function is called. Parameters: Name Type Description Default path str The relative or absolute path being used as key in cache dict. required Source code in torrentfile\\utils.py def __call__ ( self , path ): \"\"\" Invoke each time memo function is called. Parameters ---------- path : str The relative or absolute path being used as key in cache dict. Returns ------- Any : The results of calling the function with path. \"\"\" if path in self . cache and os . path . exists ( path ): self . counter += 1 return self . cache [ path ] result = self . func ( path ) self . cache [ path ] = result return result __init__ ( self , func ) special Construct for memoization. Source code in torrentfile\\utils.py def __init__ ( self , func ): \"\"\" Construct for memoization. \"\"\" self . func = func self . counter = 0 self . cache = {} MissingPathError ( Exception ) Path parameter is required to specify target content. Creating a .torrent file with no contents seems rather silly. Parameters: Name Type Description Default message str Message for user (optional). None Source code in torrentfile\\utils.py class MissingPathError ( Exception ): \"\"\" Path parameter is required to specify target content. Creating a .torrent file with no contents seems rather silly. Parameters ---------- message : str Message for user (optional). \"\"\" def __init__ ( self , message : str = None ): \"\"\" Raise when creating a meta file without specifying target content. The `message` argument is a message to pass to Exception base class. \"\"\" self . message = f \"Path arguement is missing and required { str ( message ) } \" super () . __init__ ( message ) __init__ ( self , message = None ) special Raise when creating a meta file without specifying target content. The message argument is a message to pass to Exception base class. Source code in torrentfile\\utils.py def __init__ ( self , message : str = None ): \"\"\" Raise when creating a meta file without specifying target content. The `message` argument is a message to pass to Exception base class. \"\"\" self . message = f \"Path arguement is missing and required { str ( message ) } \" super () . __init__ ( message ) PieceLengthValueError ( Exception ) Piece Length parameter must equal a perfect power of 2. Parameters: Name Type Description Default message str Message for user (optional). None Source code in torrentfile\\utils.py class PieceLengthValueError ( Exception ): \"\"\" Piece Length parameter must equal a perfect power of 2. Parameters ---------- message : str Message for user (optional). \"\"\" def __init__ ( self , message : str = None ): \"\"\" Raise when creating a meta file with incorrect piece length value. The `message` argument is a message to pass to Exception base class. \"\"\" self . message = f \"Incorrect value for piece length: { str ( message ) } \" super () . __init__ ( message ) __init__ ( self , message = None ) special Raise when creating a meta file with incorrect piece length value. The message argument is a message to pass to Exception base class. Source code in torrentfile\\utils.py def __init__ ( self , message : str = None ): \"\"\" Raise when creating a meta file with incorrect piece length value. The `message` argument is a message to pass to Exception base class. \"\"\" self . message = f \"Incorrect value for piece length: { str ( message ) } \" super () . __init__ ( message ) get_file_list ( path ) Return a sorted list of file paths contained in directory. Parameters: Name Type Description Default path str target file or directory. required Returns: Type Description list sorted list of file paths. Source code in torrentfile\\utils.py def get_file_list ( path : str ) -> list : \"\"\" Return a sorted list of file paths contained in directory. Parameters ---------- path : str target file or directory. Returns ------- list sorted list of file paths. \"\"\" _ , filelist = filelist_total ( path ) return filelist get_piece_length ( size ) Calculate the ideal piece length for bittorrent data. Parameters: Name Type Description Default size int Total bits of all files incluided in .torrent file. required Returns: Type Description int Ideal piece length. Source code in torrentfile\\utils.py def get_piece_length ( size : int ) -> int : \"\"\" Calculate the ideal piece length for bittorrent data. Parameters ---------- size : int Total bits of all files incluided in .torrent file. Returns ------- int Ideal piece length. \"\"\" exp = 14 while size / ( 2 ** exp ) > 200 and exp < 25 : exp += 1 return 2 ** exp humanize_bytes ( amount ) Convert integer into human readable memory sized denomination. Parameters: Name Type Description Default amount int total number of bytes. required Returns: Type Description str human readable representation of the given amount of bytes. Source code in torrentfile\\utils.py def humanize_bytes ( amount : int ) -> str : \"\"\" Convert integer into human readable memory sized denomination. Parameters ---------- amount : int total number of bytes. Returns ------- str human readable representation of the given amount of bytes. \"\"\" if amount < 1024 : return str ( amount ) if 1024 <= amount < 1_048_576 : return f \" { amount // 1024 } KiB\" if 1_048_576 <= amount < 1_073_741_824 : return f \" { amount // 1_048_576 } MiB\" return f \" { amount // 1073741824 } GiB\" next_power_2 ( value ) Calculate the next perfect power of 2 equal to or greater than value. Parameters: Name Type Description Default value int integer value that is less than some perfect power of 2. required Returns: Type Description int The next power of 2 greater than value, or value if already power of 2. Source code in torrentfile\\utils.py def next_power_2 ( value : int ) -> int : \"\"\" Calculate the next perfect power of 2 equal to or greater than value. Parameters ---------- value : int integer value that is less than some perfect power of 2. Returns ------- int The next power of 2 greater than value, or value if already power of 2. \"\"\" if not value & ( value - 1 ) and value : return value start = 1 while start < value : start <<= 1 return start normalize_piece_length ( piece_length ) Verify input piece_length is valid and convert accordingly. Parameters: Name Type Description Default piece_length int The piece length provided by user. required Exceptions: Type Description PieceLengthValueError : If piece length is improper value. Returns: Type Description int normalized piece length. Source code in torrentfile\\utils.py def normalize_piece_length ( piece_length : int ) -> int : \"\"\" Verify input piece_length is valid and convert accordingly. Parameters ---------- piece_length : int | str The piece length provided by user. Returns ------- int normalized piece length. Raises ------ PieceLengthValueError : If piece length is improper value. \"\"\" if isinstance ( piece_length , str ): if piece_length . isnumeric (): piece_length = int ( piece_length ) else : raise PieceLengthValueError ( piece_length ) if 13 < piece_length < 26 : return 2 ** piece_length if piece_length <= 13 : raise PieceLengthValueError ( piece_length ) log = int ( math . log2 ( piece_length )) if 2 ** log == piece_length : return piece_length raise PieceLengthValueError path_piece_length ( path ) Calculate piece length for input path and contents. Parameters: Name Type Description Default path str The absolute path to directory and contents. required Returns: Type Description int The size of pieces of torrent content. Source code in torrentfile\\utils.py def path_piece_length ( path : str ) -> int : \"\"\" Calculate piece length for input path and contents. Parameters ---------- path : str The absolute path to directory and contents. Returns ------- int The size of pieces of torrent content. \"\"\" psize = path_size ( path ) return get_piece_length ( psize ) path_size ( path ) Return the total size of all files in path recursively. Parameters: Name Type Description Default path str path to target file or directory. required Returns: Type Description int total size of files. Source code in torrentfile\\utils.py def path_size ( path : str ) -> int : \"\"\" Return the total size of all files in path recursively. Parameters ---------- path : str path to target file or directory. Returns ------- int total size of files. \"\"\" total_size , _ = filelist_total ( path ) return total_size path_stat ( path ) Calculate directory statistics. Parameters: Name Type Description Default path str The path to start calculating from. required Returns: Type Description tuple List of all files contained in Directory Source code in torrentfile\\utils.py def path_stat ( path : str ) -> tuple : \"\"\" Calculate directory statistics. Parameters ---------- path : str The path to start calculating from. Returns ------- list List of all files contained in Directory int Total sum of bytes from all contents of dir int The size of pieces of the torrent contents. \"\"\" total_size , filelist = filelist_total ( path ) piece_length = get_piece_length ( total_size ) return ( filelist , total_size , piece_length ) torrentfile.version Holds the release version number. tests special Unittest package init module. dir1 () Create a specific temporary structured directory. Returns: Type Description str path to root of temporary directory Source code in tests\\__init__.py @pytest . fixture ( scope = \"package\" ) def dir1 (): \"\"\"Create a specific temporary structured directory. Yields ------ str path to root of temporary directory \"\"\" root = tempdir () yield root rmpath ( root ) dir2 () Create a specific temporary structured directory. Returns: Type Description str path to root of temporary directory Source code in tests\\__init__.py @pytest . fixture () def dir2 (): \"\"\"Create a specific temporary structured directory. Yields ------ str path to root of temporary directory \"\"\" root = tempdir ( ext = \"2\" ) yield Path ( root ) rmpath ( root ) file1 () Return the path to a temporary file package scope. Source code in tests\\__init__.py @pytest . fixture ( scope = \"package\" ) def file1 (): \"\"\" Return the path to a temporary file package scope. \"\"\" path = tempfile () yield path rmpath ( path ) file2 () Return the path to a temporary file no scope. Source code in tests\\__init__.py @pytest . fixture () def file2 (): \"\"\" Return the path to a temporary file no scope. \"\"\" path = tempfile () yield path rmpath ( path ) filemeta1 ( file1 , request ) Test fixture for generating metafile for all versions of torrents. Source code in tests\\__init__.py @pytest . fixture ( scope = \"package\" , params = torrents ()) def filemeta1 ( file1 , request ): \"\"\" Test fixture for generating metafile for all versions of torrents. \"\"\" args = { \"path\" : file1 , \"announce\" : [ \"url1\" , \"url4\" ], \"url_list\" : [ \"url6\" , \"url7\" ], \"httpseeds\" : [ \"url6\" , \"url7\" ], \"comment\" : \"this is a comment\" , \"source\" : \"SomeSource\" , \"private\" : 1 , } versions = torrents () version = versions . index ( request . param ) name = str ( file1 ) + \"file\" + str ( version ) + \".torrent\" torrent = request . param ( ** args ) outfile , _ = torrent . write ( outfile = name ) yield outfile rmpath ( outfile ) filemeta2 ( file2 , request ) Test fixture for generating a meta file no scope. Source code in tests\\__init__.py @pytest . fixture ( params = torrents ()) def filemeta2 ( file2 , request ): \"\"\" Test fixture for generating a meta file no scope. \"\"\" args = { \"path\" : file2 , \"announce\" : [ \"url1\" , \"url4\" ], \"url_list\" : [ \"url6\" , \"url7\" ], \"httpseeds\" : [ \"url7\" , \"url8\" ], \"comment\" : \"this is a comment\" , \"source\" : \"SomeSource\" , \"private\" : 1 , } versions = torrents () version = versions . index ( request . param ) name = str ( file2 ) + \"file\" + str ( version ) + \".torrent\" torrent = request . param ( ** args ) outfile , _ = torrent . write ( outfile = name ) yield outfile rmpath ( outfile ) metafile1 ( dir1 , request ) Create a standard metafile for testing. Source code in tests\\__init__.py @pytest . fixture ( scope = \"package\" , params = torrents ()) def metafile1 ( dir1 , request ): \"\"\" Create a standard metafile for testing. \"\"\" versions = torrents () args = { \"path\" : dir1 , \"announce\" : [ \"url1\" , \"url2\" , \"url4\" ], \"url_list\" : [ \"url5\" , \"url6\" , \"url7\" ], \"httpseeds\" : [ \"url5\" , \"url6\" , \"url7\" ], \"comment\" : \"this is a comment\" , \"source\" : \"SomeSource\" , \"private\" : 1 , } torrent_class = request . param outfile = str ( dir1 ) + str ( versions . index ( torrent_class )) + \".torrent\" torrent = torrent_class ( ** args ) outfile , _ = torrent . write ( outfile = outfile ) yield outfile rmpath ( outfile ) metafile2 ( dir2 , request ) Create a standard metafile for testing. Source code in tests\\__init__.py @pytest . fixture ( params = torrents ()) def metafile2 ( dir2 , request ): \"\"\" Create a standard metafile for testing. \"\"\" args = { \"path\" : dir2 , \"announce\" : [ \"url1\" , \"url4\" ], \"url_list\" : [ \"url6\" , \"url7\" ], \"comment\" : \"this is a comment\" , \"httpseeds\" : [ \"url6\" , \"url7\" ], \"source\" : \"SomeSource\" , \"private\" : 1 , } torrent_class = request . param outfile = str ( dir2 ) + \".torrent\" torrent = torrent_class ( ** args ) outfile , _ = torrent . write ( outfile = outfile ) yield outfile rmpath ( outfile ) rmpath ( * args ) Remove file or directory path. Parameters: Name Type Description Default args list Filesystem locations for removing. () Source code in tests\\__init__.py def rmpath ( * args ): \"\"\"Remove file or directory path. Parameters ---------- args : list Filesystem locations for removing. \"\"\" for arg in args : if not os . path . exists ( arg ): continue if os . path . isdir ( arg ): try : shutil . rmtree ( arg ) except PermissionError : # pragma: nocover pass elif os . path . isfile ( arg ): try : os . remove ( arg ) except PermissionError : # pragma: nocover pass sizedfiles ( dir2 , sizes , request ) Generate variable sized meta files for testing, no scope. Source code in tests\\__init__.py @pytest . fixture ( params = torrents ()) def sizedfiles ( dir2 , sizes , request ): \"\"\" Generate variable sized meta files for testing, no scope. \"\"\" versions = torrents () args = { \"content\" : dir2 , \"announce\" : [ \"url1\" , \"url2\" , \"url4\" ], \"url_list\" : [ \"url5\" , \"url6\" , \"url7\" ], \"comment\" : \"this is a comment\" , \"source\" : \"SomeSource\" , \"private\" : 1 , \"piece_length\" : sizes , } torrent_class = request . param version = str ( versions . index ( torrent_class )) outfile = str ( dir2 ) + version + str ( sizes ) + \".torrent\" torrent = torrent_class ( ** args ) outfile , _ = torrent . write ( outfile = outfile ) yield outfile rmpath ( outfile ) sizes ( request ) Generate powers of 2 for file creation package scope. Source code in tests\\__init__.py @pytest . fixture ( scope = \"package\" , params = [ 2 ** i for i in range ( 15 , 20 )]) def sizes ( request ): \"\"\" Generate powers of 2 for file creation package scope. \"\"\" size = request . param yield size teardown () Remove all temporary directories and files. Source code in tests\\__init__.py @atexit . register def teardown (): # pragma: nocover \"\"\" Remove all temporary directories and files. \"\"\" root = Path ( __file__ ) . parent / \"TESTDIR\" for path in [ root , \"./torrentfile.log\" ]: if os . path . exists ( path ): rmpath ( path ) tempdir ( ext = '1' ) Create temporary directory. Parameters: Name Type Description Default ext str extension to file names, by default \"1\" '1' Returns: Type Description str path to common root for directory. Source code in tests\\__init__.py def tempdir ( ext = \"1\" ): \"\"\"Create temporary directory. Parameters ---------- ext : str, optional extension to file names, by default \"1\" Returns ------- str path to common root for directory. \"\"\" layouts = { \"1\" : [ f \"dir { ext } /file1.png\" , f \"dir { ext } /file2.mp4\" , f \"dir { ext } /file3.mp3\" , f \"dir { ext } /file4.zip\" , f \"dir { ext } /file5.txt\" , f \"dir { ext } /subdir1/subdir2/file.7z\" , f \"dir { ext } /subdir/subdir/file4.rar\" , f \"dir { ext } /subdir/subdir/file4.r01\" , ], \"2\" : [ f \"dir { ext } /file1.png\" , f \"dir { ext } /file2.jpg\" , f \"dir { ext } /subdir/file2.mp4\" , f \"dir { ext } /subdir/file3.mp3\" , ], } paths = [] for path in layouts [ ext ]: temps = tempfile ( path = path , exp = 18 ) paths . append ( temps ) return os . path . commonpath ( paths ) tempfile ( path = None , exp = 18 ) Create temporary file. Creates a temporary file for unittesting purposes.py Parameters: Name Type Description Default path str relative path to temporary files, by default None None exp int Exponent used to determine size of file., by default 18 18 Returns: Type Description str absolute path to file. Source code in tests\\__init__.py def tempfile ( path = None , exp = 18 ): \"\"\"Create temporary file. Creates a temporary file for unittesting purposes.py Parameters ---------- path : str, optional relative path to temporary files, by default None exp : int, optional Exponent used to determine size of file., by default 18 Returns ------- str absolute path to file. \"\"\" seq = ( string . printable + string . whitespace ) . encode ( \"utf-8\" ) root = Path ( __file__ ) . parent / \"TESTDIR\" if not os . path . exists ( root ): os . mkdir ( root ) if not path : path = root / ( str ( datetime . timestamp ( datetime . now ())) + \".file\" ) parts = Path ( path ) . parts partial = root for i , part in enumerate ( parts ): partial = partial / part if i == len ( parts ) - 1 : with open ( partial , \"wb\" ) as binfile : size = 2 ** exp while size > 0 : if len ( seq ) < size : binfile . write ( seq ) size -= len ( seq ) seq += seq else : binfile . write ( seq [: size ]) size -= size else : if not os . path . exists ( partial ): os . mkdir ( partial ) return partial torrents () Return seq of torrentfile objects. Source code in tests\\__init__.py def torrents (): \"\"\" Return seq of torrentfile objects. \"\"\" return [ TorrentFile , TorrentFileV2 , TorrentFileHybrid ] test_cli Testing functions for the command line interface. folder ( dir1 ) Yield a folder object as fixture. Source code in tests\\test_cli.py @pytest . fixture ( scope = \"module\" ) def folder ( dir1 ): \"\"\" Yield a folder object as fixture. \"\"\" sfolder = str ( dir1 ) torrent = sfolder + \".torrent\" yield ( sfolder , torrent ) rmpath ( torrent ) test_cli_announce ( folder , piece_length , version ) Test announce cli flag. Source code in tests\\test_cli.py @pytest . mark . parametrize ( \"piece_length\" , [ 2 ** exp for exp in range ( 14 , 21 )]) @pytest . mark . parametrize ( \"version\" , [ \"1\" , \"2\" , \"3\" ]) def test_cli_announce ( folder , piece_length , version ): \"\"\" Test announce cli flag. \"\"\" folder , torrent = folder args = [ \"torrentfile\" , \"create\" , folder , \"--piece-length\" , str ( piece_length ), \"--meta-version\" , version , \"--tracker\" , \"https://announce.org/tracker\" , ] sys . argv = args execute () meta = pyben . load ( torrent ) assert meta [ \"announce\" ] == \"https://announce.org/tracker\" test_cli_announce_list ( folder , version ) Test announce-list cli flag. Source code in tests\\test_cli.py @pytest . mark . parametrize ( \"version\" , [ \"1\" , \"2\" , \"3\" ]) def test_cli_announce_list ( folder , version ): \"\"\" Test announce-list cli flag. \"\"\" folder , torrent = folder trackers = [ \"https://announce.org/tracker\" , \"https://announce.net/tracker\" , \"https://tracker.net/announce\" , ] args = [ \"torrentfile\" , \"create\" , folder , \"--meta-version\" , version , \"--tracker\" , ] + trackers sys . argv = args execute () meta = pyben . load ( torrent ) for url in trackers : assert url in [ j for i in meta [ \"announce-list\" ] for j in i ] test_cli_announce_path ( dir1 , flag ) Test CLI when path is placed after the trackers flag. Source code in tests\\test_cli.py @pytest . mark . parametrize ( \"flag\" , [ \"-t\" , \"-w\" , \"--announce\" , \"--web-seed\" , \"--http-seed\" ] ) def test_cli_announce_path ( dir1 , flag ): \"\"\" Test CLI when path is placed after the trackers flag. \"\"\" args = [ \"torrentfile\" , \"create\" , flag , \"https://announce1.org\" , str ( dir1 )] sys . argv = args execute () outfile = str ( dir1 ) + \".torrent\" assert os . path . exists ( outfile ) rmpath ( outfile ) test_cli_comment ( folder , piece_length , version ) Test comment cli flag. Source code in tests\\test_cli.py @pytest . mark . parametrize ( \"piece_length\" , [ 2 ** exp for exp in range ( 14 , 21 )]) @pytest . mark . parametrize ( \"version\" , [ \"1\" , \"2\" , \"3\" ]) def test_cli_comment ( folder , piece_length , version ): \"\"\" Test comment cli flag. \"\"\" folder , torrent = folder args = [ \"torrentfile\" , \"create\" , folder , \"--piece-length\" , str ( piece_length ), \"--meta-version\" , version , \"--magnet\" , \"--comment\" , \"this is a comment\" , \"--progress\" , \"2\" , ] sys . argv = args execute () meta = pyben . load ( torrent ) assert meta [ \"info\" ][ \"comment\" ] == \"this is a comment\" test_cli_created_by ( folder , piece_length , version ) Test if created torrents recieve a created by field in meta info. Source code in tests\\test_cli.py @pytest . mark . parametrize ( \"piece_length\" , [ 2 ** exp for exp in range ( 14 , 21 )]) @pytest . mark . parametrize ( \"version\" , [ \"1\" , \"2\" , \"3\" ]) def test_cli_created_by ( folder , piece_length , version ): \"\"\" Test if created torrents recieve a created by field in meta info. \"\"\" folder , torrent = folder args = [ \"torrentfile\" , \"create\" , folder , \"--piece-length\" , str ( piece_length ), \"--meta-version\" , version , \"--comment\" , \"this is a comment\" , ] sys . argv = args execute () meta = pyben . load ( torrent ) assert \"TorrentFile\" in meta [ \"created by\" ] test_cli_creation_date ( folder , piece_length , version ) Test if torrents created get an accurate timestamp. Source code in tests\\test_cli.py @pytest . mark . parametrize ( \"piece_length\" , [ 2 ** exp for exp in range ( 14 , 21 )]) @pytest . mark . parametrize ( \"version\" , [ \"1\" , \"2\" , \"3\" ]) def test_cli_creation_date ( folder , piece_length , version ): \"\"\" Test if torrents created get an accurate timestamp. \"\"\" folder , torrent = folder args = [ \"torrentfile\" , \"create\" , folder , \"--piece-length\" , str ( piece_length ), \"--meta-version\" , version , \"--comment\" , \"this is a comment\" , ] sys . argv = args execute () meta = pyben . load ( torrent ) num = float ( meta [ \"creation date\" ]) date = datetime . datetime . fromtimestamp ( num ) now = datetime . datetime . now () assert date . day == now . day assert date . year == now . year assert date . month == now . month test_cli_cwd ( folder ) Test outfile cli flag. Source code in tests\\test_cli.py def test_cli_cwd ( folder ): \"\"\" Test outfile cli flag. \"\"\" folder , _ = folder args = [ \"torrentfile\" , \"create\" , \"--cwd\" , folder , ] sys . argv = args current = os . getcwd () name = os . path . basename ( folder ) outfile = os . path . join ( current , name ) + \".torrent\" execute () assert os . path . exists ( outfile ) rmpath ( outfile ) test_cli_empty_files ( dir2 , version , progress ) Test creating torrent with empty files. Source code in tests\\test_cli.py @pytest . mark . parametrize ( \"version\" , [ \"1\" , \"2\" , \"3\" ]) @pytest . mark . parametrize ( \"progress\" , [ \"0\" , \"1\" , \"2\" ]) def test_cli_empty_files ( dir2 , version , progress ): \"\"\" Test creating torrent with empty files. \"\"\" args = [ \"torrentfile\" , \"create\" , str ( dir2 ), \"--meta-version\" , version , \"--source\" , \"somesource\" , \"--prog\" , progress , ] sys . argv = args def walk ( root , count ): \"\"\" Traverse directory to edit files. \"\"\" if root . is_file (): with open ( root , \"wb\" ) as _ : return 1 elif root . is_dir (): for item in root . iterdir (): if count >= 2 : break count += walk ( item , count ) return count walk ( dir2 , 0 ) execute () outfile = str ( dir2 ) + \".torrent\" assert os . path . exists ( outfile ) rmpath ( outfile ) test_cli_help () Test showing help notice cli flag. Source code in tests\\test_cli.py def test_cli_help (): \"\"\" Test showing help notice cli flag. \"\"\" args = [ \"-h\" ] sys . argv = args try : assert execute () except SystemExit : assert True test_cli_outfile ( dir1 , piece_length , version ) Test outfile cli flag. Source code in tests\\test_cli.py @pytest . mark . parametrize ( \"piece_length\" , [ 2 ** exp for exp in range ( 14 , 21 )]) @pytest . mark . parametrize ( \"version\" , [ \"1\" , \"2\" , \"3\" ]) def test_cli_outfile ( dir1 , piece_length , version ): \"\"\" Test outfile cli flag. \"\"\" outfile = dir1 + \"test.torrent\" args = [ \"torrentfile\" , \"create\" , dir1 , \"--piece-length\" , str ( piece_length ), \"--meta-version\" , version , \"-o\" , outfile , \"--progress\" , \"1\" , ] sys . argv = args execute () assert os . path . exists ( outfile ) rmpath ( outfile ) test_cli_piece_length ( folder , piece_length , version ) Test piece length cli flag. Source code in tests\\test_cli.py @pytest . mark . parametrize ( \"piece_length\" , [ 2 ** exp for exp in range ( 14 , 21 )]) @pytest . mark . parametrize ( \"version\" , [ \"1\" , \"2\" , \"3\" ]) def test_cli_piece_length ( folder , piece_length , version ): \"\"\" Test piece length cli flag. \"\"\" folder , torrent = folder args = [ \"torrentfile\" , \"-v\" , \"create\" , folder , \"--piece-length\" , str ( piece_length ), \"--meta-version\" , version , \"--progress\" , \"0\" , ] sys . argv = args execute () meta = pyben . load ( torrent ) assert meta [ \"info\" ][ \"piece length\" ] == piece_length test_cli_private ( folder ) Test private cli flag. Source code in tests\\test_cli.py def test_cli_private ( folder ): \"\"\" Test private cli flag. \"\"\" folder , torrent = folder args = [ \"torrentfile\" , \"create\" , folder , \"--private\" ] sys . argv = args execute () meta = pyben . load ( torrent ) assert \"private\" in meta [ \"info\" ] test_cli_slash_outpath ( dir1 , sep ) Test if output when outpath ends with a /. Source code in tests\\test_cli.py @pytest . mark . parametrize ( \"sep\" , [ \"/\" , \" \\\\ \" ]) def test_cli_slash_outpath ( dir1 , sep ): \"\"\" Test if output when outpath ends with a /. \"\"\" if sys . platform != \"win32\" : sep = \"/\" # pragma: nocover parent = os . path . dirname ( dir1 ) + sep args = [ \"torrentfile\" , \"create\" , \"-t\" , \"https://announce1.org\" , \"--private\" , \"-o\" , parent , str ( dir1 ), ] sys . argv = args execute () outfile = str ( dir1 ) + \".torrent\" assert os . path . exists ( outfile ) rmpath ( outfile ) test_cli_slash_path ( dir1 , ending ) Test if output when path ends with a /. Source code in tests\\test_cli.py @pytest . mark . parametrize ( \"ending\" , [ \"/\" , \" \\\\ \" ]) def test_cli_slash_path ( dir1 , ending ): \"\"\" Test if output when path ends with a /. \"\"\" if sys . platform != \"win32\" and ending == \" \\\\ \" : # pragma: nocover ending = \"/\" args = [ \"torrentfile\" , \"create\" , \"-t\" , \"https://announce1.org\" , \"--private\" , str ( dir1 ) + ending , ] sys . argv = args execute () outfile = str ( dir1 ) + \".torrent\" assert os . path . exists ( outfile ) rmpath ( outfile ) test_cli_v1 ( folder ) Basic create torrent cli command. Source code in tests\\test_cli.py def test_cli_v1 ( folder ): \"\"\" Basic create torrent cli command. \"\"\" folder , torrent = folder args = [ \"torrentfile\" , \"create\" , folder ] sys . argv = args execute () assert os . path . exists ( torrent ) test_cli_v2 ( folder ) Create torrent v2 cli command. Source code in tests\\test_cli.py def test_cli_v2 ( folder ): \"\"\" Create torrent v2 cli command. \"\"\" folder , torrent = folder args = [ \"torrentfile\" , \"create\" , folder , \"--meta-version\" , \"2\" ] sys . argv = args execute () assert os . path . exists ( torrent ) test_cli_v3 ( folder ) Create hybrid torrent cli command. Source code in tests\\test_cli.py def test_cli_v3 ( folder ): \"\"\" Create hybrid torrent cli command. \"\"\" folder , torrent = folder args = [ \"torrentfile\" , \"create\" , folder , \"--meta-version\" , \"3\" ] sys . argv = args execute () assert os . path . exists ( torrent ) test_cli_web_seeds ( folder , piece_length , version ) Test if created torrents recieve a web seeds field in meta info. Source code in tests\\test_cli.py @pytest . mark . parametrize ( \"piece_length\" , [ 2 ** exp for exp in range ( 14 , 21 )]) @pytest . mark . parametrize ( \"version\" , [ \"1\" , \"2\" , \"3\" ]) def test_cli_web_seeds ( folder , piece_length , version ): \"\"\" Test if created torrents recieve a web seeds field in meta info. \"\"\" folder , torrent = folder args = [ \"torrentfile\" , \"create\" , folder , \"--piece-length\" , str ( piece_length ), \"--meta-version\" , version , \"-w\" , \"https://webseed.url/1\" , \"https://webseed.url/2\" , \"https://webseed.url/3\" , ] sys . argv = args execute () meta = pyben . load ( torrent ) assert \"https://webseed.url/1\" in meta [ \"url-list\" ] test_cli_with_debug ( folder , piece_length , version ) Test debug mode cli flag. Source code in tests\\test_cli.py @pytest . mark . parametrize ( \"piece_length\" , [ 2 ** exp for exp in range ( 14 , 21 )]) @pytest . mark . parametrize ( \"version\" , [ \"1\" , \"2\" , \"3\" ]) def test_cli_with_debug ( folder , piece_length , version ): \"\"\" Test debug mode cli flag. \"\"\" folder , torrent = folder args = [ \"torrentfile\" , \"-v\" , \"create\" , folder , \"--piece-length\" , str ( piece_length ), \"--meta-version\" , version , \"--comment\" , \"this is a comment\" , ] sys . argv = args execute () assert os . path . exists ( torrent ) test_cli_with_source ( folder , piece_length , version ) Test source cli flag. Source code in tests\\test_cli.py @pytest . mark . parametrize ( \"piece_length\" , [ 2 ** exp for exp in range ( 14 , 21 )]) @pytest . mark . parametrize ( \"version\" , [ \"1\" , \"2\" , \"3\" ]) def test_cli_with_source ( folder , piece_length , version ): \"\"\" Test source cli flag. \"\"\" folder , torrent = folder args = [ \"torrentfile\" , \"create\" , folder , \"--piece-length\" , str ( piece_length ), \"--meta-version\" , version , \"--source\" , \"somesource\" , ] sys . argv = args execute () meta = pyben . load ( torrent ) assert meta [ \"info\" ][ \"source\" ] == \"somesource\" test_fix () Test dir1 fixture is not None. Source code in tests\\test_cli.py def test_fix (): \"\"\" Test dir1 fixture is not None. \"\"\" assert dir1 and dir2 test_commands Testing functions for the sub-action commands from command line args. test_create_unicode_name ( file1 ) Test Unicode information in CLI args. Source code in tests\\test_commands.py def test_create_unicode_name ( file1 ): \"\"\" Test Unicode information in CLI args. \"\"\" parent = os . path . dirname ( file1 ) filename = os . path . join ( parent , \"\u4e02\u4e03\u4e07\u4e08\u4e09\u4e0e\u4e0f\u4e11\u4e12\u4e13\u4e14\u4e15\u4e16\u4e17\u4e21\u4e22\u4e23\u4e24\u4e25\u4e29\u4e2a\u4e2b\u4e2c\u4e2d\u4e2e\u4e2f.torrent\" ) args = [ \"torrentfile\" , \"-v\" , \"create\" , \"-a\" , \"tracker_url.com/announce_3456\" , \"tracker_url.net/announce_3456\" , \"--source\" , \"sourcetext\" , \"--comment\" , \"filename is \u4e02\u4e03\u4e07\u4e08\u4e09\u4e0e\u4e0f\u4e11\u4e12\u4e13\u4e14\u4e15\u4e16\u4e17\u4e21\u4e22\u4e23\u4e24\u4e25\u4e29\u4e2a\u4e2b\u4e2c\u4e2d\u4e2e\u4e2f.torrent\" , \"-o\" , str ( filename ), str ( file1 ), ] sys . argv = args execute () assert os . path . exists ( filename ) test_fix () Test dir1 fixture is not None. Source code in tests\\test_commands.py def test_fix (): \"\"\" Test dir1 fixture is not None. \"\"\" assert dir1 and metafile1 and file1 and metafile2 and dir2 test_info ( field , file1 ) Test the info_command action from the Command Line Interface. Source code in tests\\test_commands.py @pytest . mark . parametrize ( \"field\" , [ \"name\" , \"announce\" , \"source\" , \"comment\" , \"private\" , \"announce-list\" ], ) def test_info ( field , file1 ): \"\"\" Test the info_command action from the Command Line Interface. \"\"\" args = [ \"torrentfile\" , \"create\" , \"-t\" , \"url1\" , \"url2\" , \"url3\" , \"--web-seed\" , \"url4\" , \"url5\" , \"--http-seed\" , \"url6\" , \"url7\" , \"--private\" , \"--comment\" , \"ExampleComment\" , \"--source\" , \"examplesource\" , str ( file1 ), ] sys . argv = args execute () class Space : \"\"\" Stand in substitution for argparse.Namespace object. \"\"\" metafile = str ( file1 ) + \".torrent\" output = info ( Space ) assert field in output test_magnet ( metafile1 ) Test create magnet function scheme. Source code in tests\\test_commands.py def test_magnet ( metafile1 ): \"\"\" Test create magnet function scheme. \"\"\" magnet_link = magnet ( metafile1 ) assert magnet_link . startswith ( \"magnet\" ) test_magnet_cli ( metafile1 ) Test magnet creation through CLI interface. Source code in tests\\test_commands.py def test_magnet_cli ( metafile1 ): \"\"\" Test magnet creation through CLI interface. \"\"\" sys . argv [ 1 :] = [ \"m\" , str ( metafile1 )] uri = execute () assert \"magnet\" in uri test_magnet_empty () Test create magnet function scheme. Source code in tests\\test_commands.py def test_magnet_empty (): \"\"\" Test create magnet function scheme. \"\"\" try : magnet ( \"file_that_does_not_exist\" ) except FileNotFoundError : assert True test_magnet_hex ( metafile1 ) Test create magnet function digest. Source code in tests\\test_commands.py def test_magnet_hex ( metafile1 ): \"\"\" Test create magnet function digest. \"\"\" magnet_link = magnet ( metafile1 ) meta = pyben . load ( metafile1 ) info = meta [ \"info\" ] binfo = sha1 ( pyben . dumps ( info )) . hexdigest () . upper () assert binfo in magnet_link test_magnet_no_announce_list ( metafile2 ) Test create magnet function scheme. Source code in tests\\test_commands.py def test_magnet_no_announce_list ( metafile2 ): \"\"\" Test create magnet function scheme. \"\"\" meta = pyben . load ( metafile2 ) del meta [ \"announce-list\" ] pyben . dump ( meta , metafile2 ) magnet_link = magnet ( metafile2 ) assert magnet_link . startswith ( \"magnet\" ) test_magnet_uri ( metafile1 ) Test create magnet function digest. Source code in tests\\test_commands.py def test_magnet_uri ( metafile1 ): \"\"\" Test create magnet function digest. \"\"\" magnet_link = magnet ( metafile1 ) meta = pyben . load ( metafile1 ) announce = meta [ \"announce\" ] assert quote_plus ( announce ) in magnet_link test_edit Testing the edit torrent feature. test_edit_cli ( metafile2 , comment , source , announce , webseed , httpseed ) Test edit torrent with all params on cli. Source code in tests\\test_edit.py @pytest . mark . parametrize ( \"comment\" , [ \"commenta\" , \"commentb\" , \"commentc\" ]) @pytest . mark . parametrize ( \"source\" , [ \"sourcea\" , \"sourceb\" , \"sourcec\" ]) @pytest . mark . parametrize ( \"announce\" , [[ \"url1\" , \"url2\" , \"url3\" ], [ \"url1\" ]]) @pytest . mark . parametrize ( \"webseed\" , [[ \"ftp1\" ], [ \"ftpa\" , \"ftpb\" ]]) @pytest . mark . parametrize ( \"httpseed\" , [[ \"ftp1\" ], [ \"ftpa\" , \"ftpb\" ]]) def test_edit_cli ( metafile2 , comment , source , announce , webseed , httpseed ): \"\"\" Test edit torrent with all params on cli. \"\"\" sys . argv = [ \"torrentfile\" , \"edit\" , metafile2 , \"--comment\" , comment , \"--source\" , source , \"--web-seed\" , webseed , \"--http-seed\" , httpseed , \"--tracker\" , announce , \"--private\" , ] main () meta = pyben . load ( metafile2 ) info = meta [ \"info\" ] assert comment == info . get ( \"comment\" ) assert source == info . get ( \"source\" ) assert info . get ( \"private\" ) == 1 assert meta [ \"announce-list\" ] == [[ announce ]] assert meta [ \"url-list\" ] == [ webseed ] test_edit_comment ( metafile2 , comment ) Test edit torrent with comment param. Source code in tests\\test_edit.py @pytest . mark . parametrize ( \"comment\" , [ \"COMMENT\" , \"COMIT\" , \"MITCO\" ]) def test_edit_comment ( metafile2 , comment ): \"\"\" Test edit torrent with comment param. \"\"\" edits = { \"comment\" : comment } data = edit_torrent ( metafile2 , edits ) meta = pyben . load ( metafile2 ) assert data == meta assert data [ \"info\" ][ \"comment\" ] == comment test_edit_httpseeds ( metafile2 , httpseed ) Test edit torrent with webseed param as string. Source code in tests\\test_edit.py @pytest . mark . parametrize ( \"httpseed\" , [[ \"urla\" ], [ \"urlb\" , \"urlc\" ], [ \"urla\" , \"urlb\" , \"urlc\" ]] ) def test_edit_httpseeds ( metafile2 , httpseed ): \"\"\" Test edit torrent with webseed param as string. \"\"\" edits = { \"httpseeds\" : httpseed } data = edit_torrent ( metafile2 , edits ) meta = pyben . load ( metafile2 ) assert data == meta assert data [ \"httpseeds\" ] == httpseed test_edit_httpseeds_str ( metafile2 , httpseeds ) Test edit torrent with webseed param. Source code in tests\\test_edit.py @pytest . mark . parametrize ( \"httpseeds\" , [ \"urla\" , \"urlb urlc\" , \"urla urlb urlc\" ]) def test_edit_httpseeds_str ( metafile2 , httpseeds ): \"\"\" Test edit torrent with webseed param. \"\"\" edits = { \"httpseeds\" : httpseeds } data = edit_torrent ( metafile2 , edits ) meta = pyben . load ( metafile2 ) assert data == meta assert data [ \"httpseeds\" ] == httpseeds . split () test_edit_none ( metafile2 ) Test edit torrent with None for all params. Source code in tests\\test_edit.py def test_edit_none ( metafile2 ): \"\"\" Test edit torrent with None for all params. \"\"\" edits = { \"announce\" : None , \"url-list\" : None , \"comment\" : None , \"source\" : None , \"private\" : None , } data = pyben . load ( metafile2 ) edited = edit_torrent ( metafile2 , edits ) meta = pyben . load ( metafile2 ) assert data == meta == edited test_edit_private_false ( metafile2 ) Test edit torrent with private param False. Source code in tests\\test_edit.py def test_edit_private_false ( metafile2 ): \"\"\" Test edit torrent with private param False. \"\"\" edits = { \"private\" : \"\" } data = edit_torrent ( metafile2 , edits ) meta = pyben . load ( metafile2 ) assert data == meta assert \"private\" not in data [ \"info\" ] test_edit_private_true ( metafile2 ) Test edit torrent with private param. Source code in tests\\test_edit.py def test_edit_private_true ( metafile2 ): \"\"\" Test edit torrent with private param. \"\"\" edits = { \"private\" : \"1\" } data = edit_torrent ( metafile2 , edits ) meta = pyben . load ( metafile2 ) assert data == meta assert data [ \"info\" ][ \"private\" ] == 1 test_edit_removal ( metafile2 ) Test edit torrent with empty for all params. Source code in tests\\test_edit.py def test_edit_removal ( metafile2 ): \"\"\" Test edit torrent with empty for all params. \"\"\" edits = { \"announce\" : \"\" , \"url-list\" : \"\" , \"httpseeds\" : \"\" , \"comment\" : \"\" , \"source\" : \"\" , \"private\" : \"\" , } data = edit_torrent ( metafile2 , edits ) meta = pyben . load ( metafile2 ) assert data == meta test_edit_source ( metafile2 , source ) Test edit torrent with source param. Source code in tests\\test_edit.py @pytest . mark . parametrize ( \"source\" , [ \"SomeSource\" , \"NoSouce\" , \"MidSource\" ]) def test_edit_source ( metafile2 , source ): \"\"\" Test edit torrent with source param. \"\"\" edits = { \"source\" : source } data = edit_torrent ( metafile2 , edits ) meta = pyben . load ( metafile2 ) assert data == meta assert data [ \"info\" ][ \"source\" ] == source test_edit_torrent ( metafile2 , announce ) Test edit torrent with announce param. Source code in tests\\test_edit.py @pytest . mark . parametrize ( \"announce\" , [[ \"urla\" ], [ \"urlb\" , \"urlc\" ], [ \"urla\" , \"urlb\" , \"urlc\" ]] ) def test_edit_torrent ( metafile2 , announce ): \"\"\" Test edit torrent with announce param. \"\"\" edits = { \"announce\" : announce } data = edit_torrent ( metafile2 , edits ) meta = pyben . load ( metafile2 ) assert data == meta assert data [ \"announce-list\" ] == [ announce ] test_edit_torrent_str ( metafile2 , announce ) Test edit torrent with announce param as string. Source code in tests\\test_edit.py @pytest . mark . parametrize ( \"announce\" , [ \"urla\" , \"urlb urlc\" , \"urla urlb urlc\" ]) def test_edit_torrent_str ( metafile2 , announce ): \"\"\" Test edit torrent with announce param as string. \"\"\" edits = { \"announce\" : announce } data = edit_torrent ( metafile2 , edits ) meta = pyben . load ( metafile2 ) assert data == meta assert data [ \"announce-list\" ] == [ announce . split ()] test_edit_urllist ( metafile2 , url_list ) Test edit torrent with webseed param as string. Source code in tests\\test_edit.py @pytest . mark . parametrize ( \"url_list\" , [[ \"urla\" ], [ \"urlb\" , \"urlc\" ], [ \"urla\" , \"urlb\" , \"urlc\" ]] ) def test_edit_urllist ( metafile2 , url_list ): \"\"\" Test edit torrent with webseed param as string. \"\"\" edits = { \"url-list\" : url_list } data = edit_torrent ( metafile2 , edits ) meta = pyben . load ( metafile2 ) assert data == meta assert data [ \"url-list\" ] == url_list test_edit_urllist_str ( metafile2 , url_list ) Test edit torrent with webseed param. Source code in tests\\test_edit.py @pytest . mark . parametrize ( \"url_list\" , [ \"urla\" , \"urlb urlc\" , \"urla urlb urlc\" ]) def test_edit_urllist_str ( metafile2 , url_list ): \"\"\" Test edit torrent with webseed param. \"\"\" edits = { \"url-list\" : url_list } data = edit_torrent ( metafile2 , edits ) meta = pyben . load ( metafile2 ) assert data == meta assert data [ \"url-list\" ] == url_list . split () test_fix () Testing dir fixtures. Source code in tests\\test_edit.py def test_fix (): \"\"\" Testing dir fixtures. \"\"\" assert dir2 and metafile2 and dir1 test_metafile_edit_with_unicode ( metafile2 ) Test if editing full unicode works as it should. Source code in tests\\test_edit.py def test_metafile_edit_with_unicode ( metafile2 ): \"\"\" Test if editing full unicode works as it should. \"\"\" edits = { \"comment\" : \"\u4e02\u4e03\u4e07\u4e08\u4e09\u4e0e\u4e0f\u4e11\u4e12\u4e13\u4e14\u4e15\u4e16\u4e17\u4e21\u4e22\u4e23\u4e24\u4e25\u4e29\u4e2a\u4e2b\u4e2c\u4e2d\u4e2e\u4e2f.torrent\" , \"source\" : \"\u4e02\u4e03\u4e07\u4e0f\u4e11\u4e25\u4e29\u4e2a\u4e2b\u4e2c\u4e2d\u4e2e\u4e2f\" , } data = edit_torrent ( metafile2 , edits ) meta = pyben . load ( metafile2 ) com1 = data [ \"info\" ][ \"comment\" ] com2 = meta [ \"info\" ][ \"comment\" ] msg = edits [ \"comment\" ] assert com1 == com2 == msg test_interactive Testing functions for the command line interface. test_fixtures () Test the fixtures used in module. Source code in tests\\test_interactive.py def test_fixtures (): \"\"\" Test the fixtures used in module. \"\"\" assert filemeta2 and file1 and file2 test_inter_create_full ( file1 , piece_length , announce , comment , source , url_list , version , monkeypatch ) Test creating torrent interactively with many parameters. Source code in tests\\test_interactive.py @pytest . mark . parametrize ( \"version\" , [ \"1\" , \"2\" , \"3\" ]) @pytest . mark . parametrize ( \"piece_length\" , [ \"23\" , \"18\" , \"131072\" ]) @pytest . mark . parametrize ( \"announce\" , [ \"url1\" , \"urla urlb urlc\" ]) @pytest . mark . parametrize ( \"url_list\" , [ \"ftp url2\" , \"ftp1 ftp2 ftp3\" ]) @pytest . mark . parametrize ( \"comment\" , [ \"Some Comment\" , \"No Comment\" ]) @pytest . mark . parametrize ( \"source\" , [ \"Do\" , \"Ra\" , \"Me\" ]) def test_inter_create_full ( file1 , piece_length , announce , comment , source , url_list , version , monkeypatch , ): \"\"\" Test creating torrent interactively with many parameters. \"\"\" mapping = [ \"create\" , piece_length , announce , url_list , url_list , comment , source , \"Y\" , file1 , str ( file1 ) + \".torrent\" , version , ] it = iter ( mapping ) monkeypatch . setattr ( MOCK , lambda * _ : next ( it )) select_action () meta = pyben . load ( str ( file1 ) + \".torrent\" ) assert meta [ \"info\" ][ \"source\" ] == source assert meta [ \"info\" ][ \"piece length\" ] == normalize_piece_length ( piece_length ) assert meta [ \"info\" ][ \"comment\" ] == comment assert meta [ \"url-list\" ] == url_list . split () test_inter_edit_cli ( filemeta2 , announce , cmnt , srce , urllist , monkeypatch ) Test editing torrent interactively from CLI. Source code in tests\\test_interactive.py @pytest . mark . parametrize ( \"announce\" , [ \"urla urlb urlc\" , \"urld url2\" ]) @pytest . mark . parametrize ( \"urllist\" , [ \"ftp url2\" , \"ftp1 ftp2 ftp3\" ]) @pytest . mark . parametrize ( \"cmnt\" , [ \"Some Comment\" ]) @pytest . mark . parametrize ( \"srce\" , [ \"Do\" , \"Ra\" ]) def test_inter_edit_cli ( filemeta2 , announce , cmnt , srce , urllist , monkeypatch ): \"\"\" Test editing torrent interactively from CLI. \"\"\" seq = [ \"edit\" , filemeta2 , \"4\" , announce , \"1\" , cmnt , \"2\" , srce , \"5\" , urllist , urllist , \"6\" , \"Y\" , \"DONE\" , ] it = iter ( seq ) monkeypatch . setattr ( MOCK , lambda * _ : next ( it )) sys . argv = [ \"torrentfile\" , \"-i\" ] main () meta2 = pyben . load ( filemeta2 ) assert meta2 [ \"info\" ][ \"source\" ] == srce assert meta2 [ \"info\" ][ \"comment\" ] == cmnt assert meta2 [ \"url-list\" ] == urllist . split () assert meta2 [ \"info\" ][ \"private\" ] == 1 test_inter_edit_full ( filemeta2 , announce , comment , source , url_list , monkeypatch ) Test editing torrent file interactively. Source code in tests\\test_interactive.py @pytest . mark . parametrize ( \"announce\" , [ \"url1\" ]) @pytest . mark . parametrize ( \"url_list\" , [ \"ftp url2\" , \"ftp1 ftp2 ftp3\" ]) @pytest . mark . parametrize ( \"comment\" , [ \"Some Comment\" , \"No Comment\" ]) @pytest . mark . parametrize ( \"source\" , [ \"Fa\" , \"So\" , \"La\" ]) def test_inter_edit_full ( filemeta2 , announce , comment , source , url_list , monkeypatch ): \"\"\" Test editing torrent file interactively. \"\"\" seq = [ \"edit\" , filemeta2 , \"4\" , announce , \"1\" , comment , \"2\" , source , \"5\" , url_list , \"\" , \"6\" , \"Y\" , \"DONE\" , ] it = iter ( seq ) monkeypatch . setattr ( MOCK , lambda * _ : next ( it )) select_action () meta1 = pyben . load ( filemeta2 ) assert meta1 [ \"info\" ][ \"source\" ] == source assert meta1 [ \"info\" ][ \"comment\" ] == comment assert meta1 [ \"url-list\" ] == url_list . split () assert meta1 [ \"info\" ][ \"private\" ] == 1 test_inter_recheck ( torrentclass , monkeypatch , file1 ) Test interactive recheck function. Source code in tests\\test_interactive.py @pytest . mark . parametrize ( \"torrentclass\" , torrents ()) def test_inter_recheck ( torrentclass , monkeypatch , file1 ): \"\"\" Test interactive recheck function. \"\"\" torrent = torrentclass ( path = file1 ) filemeta , _ = torrent . write () seq = [ \"recheck\" , filemeta , str ( file1 )] it = iter ( seq ) monkeypatch . setattr ( MOCK , lambda * _ : next ( it )) result = select_action () assert result == 100 test_interactive_create ( monkeypatch , file1 ) Test creating torrent interactively. Source code in tests\\test_interactive.py def test_interactive_create ( monkeypatch , file1 ): \"\"\" Test creating torrent interactively. \"\"\" mapping = [ \"create\" , \"\" , \"\" , \"\" , \"\" , \"\" , \"\" , \"\" , file1 , str ( file1 ) + \".torrent\" , \"\" , ] it = iter ( mapping ) monkeypatch . setattr ( MOCK , lambda * _ : next ( it )) select_action () assert os . path . exists ( str ( file1 ) + \".torrent\" ) test_recheck Testing functions for the progress module. test_checker_callback ( dir1 , metafile1 ) Test Checker class with directory that points to nothing. Source code in tests\\test_recheck.py def test_checker_callback ( dir1 , metafile1 ): \"\"\" Test Checker class with directory that points to nothing. \"\"\" Checker . register_callback ( lambda * x : print ( x )) checker = Checker ( metafile1 , str ( dir1 )) assert checker . results () == 100 test_checker_class ( dir1 , metafile1 ) Test Checker Class against meta files. Source code in tests\\test_recheck.py def test_checker_class ( dir1 , metafile1 ): \"\"\" Test Checker Class against meta files. \"\"\" checker = Checker ( metafile1 , dir1 ) assert checker . results () == 100 test_checker_class_allfiles ( sizedfiles , dir2 ) Test Checker class when all files are missing from contents. Source code in tests\\test_recheck.py def test_checker_class_allfiles ( sizedfiles , dir2 ): \"\"\" Test Checker class when all files are missing from contents. \"\"\" def traverse ( path ): \"\"\" Traverse internal subdirectories. \"\"\" if path . is_file (): rmpath ( path ) elif path . is_dir (): for item in path . iterdir (): traverse ( item ) traverse ( dir2 ) checker = Checker ( sizedfiles , dir2 ) assert int ( checker . results ()) < 100 test_checker_class_allpaths ( sizedfiles , dir2 ) Test Checker class when all files are missing from contents. Source code in tests\\test_recheck.py def test_checker_class_allpaths ( sizedfiles , dir2 ): \"\"\" Test Checker class when all files are missing from contents. \"\"\" for item in Path ( str ( dir2 )) . iterdir (): rmpath ( item ) checker = Checker ( sizedfiles , dir2 ) assert int ( checker . results ()) < 100 test_checker_class_half_file ( filemeta2 , file2 ) Test Checker class with half size single file. Source code in tests\\test_recheck.py def test_checker_class_half_file ( filemeta2 , file2 ): \"\"\" Test Checker class with half size single file. \"\"\" half = int ( os . path . getsize ( file2 ) / 2 ) barr = bytearray ( half ) with open ( file2 , \"rb\" ) as content : content . readinto ( barr ) with open ( file2 , \"wb\" ) as content : content . write ( barr ) checker = Checker ( filemeta2 , file2 ) assert int ( checker . results ()) != 10 test_checker_cli_args ( dir1 , metafile1 ) Test exclusive Checker Mode CLI. Source code in tests\\test_recheck.py def test_checker_cli_args ( dir1 , metafile1 ): \"\"\" Test exclusive Checker Mode CLI. \"\"\" sys . argv = [ \"torrentfile\" , \"check\" , str ( metafile1 ), str ( dir1 )] output = main () assert output == 100 test_checker_empty_files ( dir2 , sizedfiles ) Test Checker when directory contains 0 length files. Source code in tests\\test_recheck.py def test_checker_empty_files ( dir2 , sizedfiles ): \"\"\" Test Checker when directory contains 0 length files. \"\"\" def empty_files ( root ): \"\"\" Dump contents of files. \"\"\" if os . path . isfile ( root ): with open ( root , \"wb\" ) as _ : pass assert os . path . getsize ( root ) == 0 elif os . path . isdir ( root ): for item in os . listdir ( root ): return empty_files ( os . path . join ( root , item )) return root empty_files ( dir2 ) checker = Checker ( sizedfiles , dir2 ) assert checker . results () != 100 test_checker_first_piece ( dir2 , sizedfiles ) Test Checker Class when first piece is slightly alterred. Source code in tests\\test_recheck.py def test_checker_first_piece ( dir2 , sizedfiles ): \"\"\" Test Checker Class when first piece is slightly alterred. \"\"\" def change ( path ): \"\"\" Change some bytes in file. \"\"\" if path . is_file (): new = b \"Something other than what was there before.\" with open ( path , \"rb\" ) as bfile : data = bfile . read () new_len = len ( new ) content = b \"\" . join ([ new , data [ new_len :]]) with open ( path , \"wb\" ) as bdoc : bdoc . write ( content ) elif path . is_dir (): for item in path . iterdir (): change ( item ) change ( Path ( dir2 )) checker = Checker ( sizedfiles , dir2 ) assert checker . results () != 100 test_checker_first_piece_alt ( dir2 , sizedfiles ) Test Checker Class when first piece is slightly alterred. Source code in tests\\test_recheck.py def test_checker_first_piece_alt ( dir2 , sizedfiles ): \"\"\" Test Checker Class when first piece is slightly alterred. \"\"\" def change ( path ): \"\"\" Change some bytes in file. \"\"\" if os . path . isfile ( path ): with open ( path , \"rb\" ) as bfile : data = bfile . read () new = b \"some_other_bytes_to_use\" new_len = len ( new ) with open ( path , \"wb\" ) as wfile : wfile . write ( new + data [ new_len :]) elif os . path . isdir ( path ): for item in os . listdir ( path ): change ( os . path . join ( path , item )) change ( dir2 ) checker = Checker ( sizedfiles , dir2 ) assert checker . results () != 100 test_checker_missing ( sizedfiles , dir2 ) Test Checker class when files are missing from contents. Source code in tests\\test_recheck.py def test_checker_missing ( sizedfiles , dir2 ): \"\"\" Test Checker class when files are missing from contents. \"\"\" count = 0 for fd in Path ( dir2 ) . iterdir (): if fd . is_file () and count < 2 : rmpath ( fd ) checker = Checker ( sizedfiles , dir2 ) assert int ( checker . results ()) < 100 test_checker_missing_singles ( dir2 , sizedfiles ) Test Checker class with half size single file. Source code in tests\\test_recheck.py def test_checker_missing_singles ( dir2 , sizedfiles ): \"\"\" Test Checker class with half size single file. \"\"\" def walk ( root ): \"\"\" Remove first file found. \"\"\" if root . is_file (): rmpath ( root ) return True if root . is_dir (): for item in root . iterdir (): walk ( item ) return False walk ( Path ( dir2 )) checker = Checker ( sizedfiles , dir2 ) assert int ( checker . results ()) < 100 test_checker_no_meta_file () Test Checker when incorrect metafile is provided. Source code in tests\\test_recheck.py def test_checker_no_meta_file (): \"\"\" Test Checker when incorrect metafile is provided. \"\"\" try : Checker ( \"peaches\" , \"$\" ) except FileNotFoundError : assert True test_checker_parent_dir ( dir1 , metafile1 ) Test providing the parent directory for torrent checking feature. Source code in tests\\test_recheck.py def test_checker_parent_dir ( dir1 , metafile1 ): \"\"\" Test providing the parent directory for torrent checking feature. \"\"\" checker = Checker ( metafile1 , os . path . dirname ( dir1 )) assert checker . results () == 100 test_checker_result_property ( dir1 , metafile1 ) Test Checker class with half size single file. Source code in tests\\test_recheck.py def test_checker_result_property ( dir1 , metafile1 ): \"\"\" Test Checker class with half size single file. \"\"\" checker = Checker ( metafile1 , dir1 ) result = checker . results () assert checker . results () == result test_checker_simplest ( dir1 , metafile1 ) Test the simplest example. Source code in tests\\test_recheck.py def test_checker_simplest ( dir1 , metafile1 ): \"\"\" Test the simplest example. \"\"\" checker = Checker ( metafile1 , dir1 ) assert checker . results () == 100 test_checker_with_file ( file1 , filemeta1 ) Test checker with single file torrent. Source code in tests\\test_recheck.py def test_checker_with_file ( file1 , filemeta1 ): \"\"\" Test checker with single file torrent. \"\"\" checker = Checker ( filemeta1 , file1 ) assert checker . results () == 100 test_checker_wrong_root_dir ( metafile1 ) Test Checker when incorrect root directory is provided. Source code in tests\\test_recheck.py def test_checker_wrong_root_dir ( metafile1 ): \"\"\" Test Checker when incorrect root directory is provided. \"\"\" try : Checker ( metafile1 , \"fake\" ) except FileNotFoundError : assert True test_fixtures () Test fixtures exist. Source code in tests\\test_recheck.py def test_fixtures (): \"\"\" Test fixtures exist. \"\"\" assert dir1 and dir2 and file1 and file2 assert filemeta1 and filemeta2 and metafile1 assert metafile2 and sizes and sizedfiles test_partial_metafiles ( dir2 , sizedfiles ) Test Checker with data that is expected to be incomplete. Source code in tests\\test_recheck.py def test_partial_metafiles ( dir2 , sizedfiles ): \"\"\" Test Checker with data that is expected to be incomplete. \"\"\" def shortenfile ( path ): \"\"\" Shorten a few files for testing purposes. \"\"\" with open ( path , \"rb\" ) as bfile : data = bfile . read () with open ( path , \"wb\" ) as bfile : bfile . write ( data [: - ( 2 ** 10 )]) for item in os . listdir ( dir2 ): full = os . path . join ( dir2 , item ) if os . path . isfile ( full ): shortenfile ( full ) testdir = os . path . dirname ( dir2 ) checker = Checker ( sizedfiles , testdir ) assert checker . results () != 100 test_recheck_wrong_dir ( metafile1 ) Test recheck function with directory that doesn't contain the contents. Source code in tests\\test_recheck.py def test_recheck_wrong_dir ( metafile1 ): \"\"\" Test recheck function with directory that doesn't contain the contents. \"\"\" grandparent = os . path . dirname ( os . path . dirname ( metafile1 )) try : _ = Checker ( metafile1 , grandparent ) except FileNotFoundError : assert True test_torrent Testing functions for the torrent module. test_create_cwd_fail () Test cwd argument with create command failure. Source code in tests\\test_torrent.py def test_create_cwd_fail (): \"\"\"Test cwd argument with create command failure.\"\"\" class SuFile : \"\"\"A mock admin file.\"\"\" @staticmethod def __fspath__ (): raise PermissionError def __str__ ( self ): return \"SuFile\" tfile = tempfile () name = os . path . basename ( tfile ) + \".torrent\" torrent = MetaFile ( path = tfile ) sufile = SuFile () torrent . write ( outfile = sufile ) current = os . path . join ( \".\" , name ) assert os . path . exists ( current ) rmpath ( tfile , current ) test_fixtures () Test pytest fixtures. Source code in tests\\test_torrent.py def test_fixtures (): \"\"\" Test pytest fixtures. \"\"\" assert dir1 and dir2 test_mbtorrent ( version , progress ) Test torrent creation for file size larger than 10MB. Source code in tests\\test_torrent.py @pytest . mark . parametrize ( \"version\" , torrents ()) @pytest . mark . parametrize ( \"progress\" , [ 0 , 1 , 2 ]) def test_mbtorrent ( version , progress ): \"\"\" Test torrent creation for file size larger than 10MB. \"\"\" tfile = tempfile ( exp = 26 ) args = { \"path\" : tfile , \"progress\" : progress , \"piece_length\" : \"14\" , } torrent = version ( ** args ) outfile , _ = torrent . write () assert os . path . exists ( outfile ) rmpath ( tfile , outfile ) test_metafile_assemble ( dir1 ) Test assembling base metafile exception. Source code in tests\\test_torrent.py def test_metafile_assemble ( dir1 ): \"\"\" Test assembling base metafile exception. \"\"\" metafile = MetaFile ( path = dir1 ) try : metafile . assemble () except NotImplementedError : assert True test_torrentfile_extra ( dir2 , version ) Test creating a torrent meta file with given directory plus extra. Source code in tests\\test_torrent.py @pytest . mark . parametrize ( \"version\" , torrents ()) def test_torrentfile_extra ( dir2 , version ): \"\"\" Test creating a torrent meta file with given directory plus extra. \"\"\" def walk ( item ): \"\"\" Edit files in directory structure. \"\"\" if item . is_file (): with open ( item , \"ab\" ) as binfile : binfile . write ( bytes ( 1000 )) elif item . is_dir (): for sub in item . iterdir (): walk ( sub ) walk ( dir2 ) args = { \"path\" : dir2 , \"comment\" : \"somecomment\" , \"announce\" : \"announce\" , } torrent = version ( ** args ) assert torrent . meta [ \"announce\" ] == \"announce\" test_torrentfile_missing_path ( version ) Test missing path error exception. Source code in tests\\test_torrent.py @pytest . mark . parametrize ( \"version\" , torrents ()) def test_torrentfile_missing_path ( version ): \"\"\" Test missing path error exception. \"\"\" try : version () except MissingPathError : assert True test_torrentfile_single ( version , num , piece_length , capsys ) Test creating a torrent file from a single file contents. Source code in tests\\test_torrent.py @pytest . mark . parametrize ( \"num\" , list ( range ( 17 , 25 ))) @pytest . mark . parametrize ( \"piece_length\" , [ 2 ** i for i in range ( 14 , 18 )]) @pytest . mark . parametrize ( \"version\" , torrents ()) def test_torrentfile_single ( version , num , piece_length , capsys ): \"\"\" Test creating a torrent file from a single file contents. \"\"\" tfile = tempfile ( exp = num ) with capsys . disabled (): version . set_callback ( print ) args = { \"path\" : tfile , \"comment\" : \"somecomment\" , \"announce\" : \"announce\" , \"piece_length\" : piece_length , } trent = version ( ** args ) trent . write () assert os . path . exists ( str ( tfile ) + \".torrent\" ) rmpath ( tfile , str ( tfile ) + \".torrent\" ) test_torrentfile_single_extra ( version , size , piece_length ) Test creating a torrent file from a single file contents plus extra. Source code in tests\\test_torrent.py @pytest . mark . parametrize ( \"size\" , list ( range ( 17 , 25 ))) @pytest . mark . parametrize ( \"piece_length\" , [ 2 ** i for i in range ( 14 , 18 )]) @pytest . mark . parametrize ( \"version\" , torrents ()) def test_torrentfile_single_extra ( version , size , piece_length ): \"\"\" Test creating a torrent file from a single file contents plus extra. \"\"\" tfile = tempfile ( exp = size ) with open ( tfile , \"ab\" ) as binfile : binfile . write ( bytes ( str ( tfile ) . encode ( \"utf-8\" ))) args = { \"path\" : tfile , \"comment\" : \"somecomment\" , \"announce\" : \"announce\" , \"piece_length\" : piece_length , } torrent = version ( ** args ) torrent . write () outfile = str ( tfile ) + \".torrent\" assert os . path . exists ( outfile ) rmpath ( tfile , outfile ) test_torrentfile_single_under ( ver , sze , piecelength ) Test creating a torrent file from less than a single file contents. Source code in tests\\test_torrent.py @pytest . mark . parametrize ( \"sze\" , list ( range ( 17 , 25 ))) @pytest . mark . parametrize ( \"piecelength\" , [ 2 ** i for i in range ( 14 , 18 )]) @pytest . mark . parametrize ( \"ver\" , torrents ()) def test_torrentfile_single_under ( ver , sze , piecelength ): \"\"\" Test creating a torrent file from less than a single file contents. \"\"\" tfile = tempfile ( exp = sze ) with open ( tfile , \"rb\" ) as binfile : data = binfile . read () with open ( tfile , \"wb\" ) as binfile : binfile . write ( data [: - ( 2 ** 9 )]) kwargs = { \"path\" : tfile , \"comment\" : \"somecomment\" , \"announce\" : \"announce\" , \"piece_length\" : piecelength , } torrent = ver ( ** kwargs ) outfile , _ = torrent . write () assert os . path . exists ( outfile ) rmpath ( tfile , outfile ) test_waiting_mixin () Test waiting function. Source code in tests\\test_torrent.py def test_waiting_mixin (): \"\"\" Test waiting function. \"\"\" msg = \"Testing message\" lst = [] timeout = 3 waiting ( msg , lst , timeout = timeout ) assert len ( lst ) == 0 test_utils Unittest functions for testing torrentfile utils module. test_filelist_total ( dir1 ) Test function for acquiring a filelist for directory. Source code in tests\\test_utils.py def test_filelist_total ( dir1 ): \"\"\" Test function for acquiring a filelist for directory. \"\"\" total , _ = utils . filelist_total ( dir1 ) assert total == ( 2 ** 18 ) * 8 test_filelisttotal_missing ( dir2 ) Test function filelist total with missing path. Parameters: Name Type Description Default dir2 pytest.fixture fixture containing a temporary directory required Source code in tests\\test_utils.py def test_filelisttotal_missing ( dir2 ): \"\"\"Test function filelist total with missing path. Parameters ---------- dir2 : pytest.fixture fixture containing a temporary directory \"\"\" rmpath ( dir2 ) try : utils . filelist_total ( dir2 ) except utils . MissingPathError : assert True test_get_filelist ( dir1 ) Test function for get a list of files in a directory. Source code in tests\\test_utils.py def test_get_filelist ( dir1 ): \"\"\" Test function for get a list of files in a directory. \"\"\" filelist = utils . get_file_list ( dir1 ) assert len ( filelist ) == 8 test_get_path_length_max ( dir1 ) Test function for getting piece length for folders max. Source code in tests\\test_utils.py def test_get_path_length_max ( dir1 ): \"\"\" Test function for getting piece length for folders max. \"\"\" assert utils . path_piece_length ( dir1 ) <= ( 2 ** 27 ) test_get_path_length_min ( dir1 ) Test function for getting piece length for folders min. Source code in tests\\test_utils.py def test_get_path_length_min ( dir1 ): \"\"\" Test function for getting piece length for folders min. \"\"\" assert utils . path_piece_length ( dir1 ) >= ( 2 ** 14 ) test_get_path_length_mod ( dir1 ) Test function for the best piece length for provided path. Source code in tests\\test_utils.py def test_get_path_length_mod ( dir1 ): \"\"\" Test function for the best piece length for provided path. \"\"\" assert utils . path_piece_length ( dir1 ) % ( 2 ** 14 ) == 0 test_get_path_size ( dir1 ) Test function for getting total size of directory. Source code in tests\\test_utils.py def test_get_path_size ( dir1 ): \"\"\" Test function for getting total size of directory. \"\"\" pathsize = utils . path_size ( dir1 ) assert pathsize == ( 2 ** 18 ) * 8 test_get_piece_length ( size ) Test function for best piece length for given size. Source code in tests\\test_utils.py @pytest . mark . parametrize ( \"size\" , [ 156634528 , 2 ** 30 , 67987 , 16384 , 8563945 ]) def test_get_piece_length ( size ): \"\"\" Test function for best piece length for given size. \"\"\" value = utils . get_piece_length ( size ) assert value % 1024 == 0 test_get_piece_length_max ( size ) Test function for best piece length for given size maximum. Source code in tests\\test_utils.py @pytest . mark . parametrize ( \"size\" , [ 156634528 , 2 ** 30 , 67987 , 16384 , 8563945 ]) def test_get_piece_length_max ( size ): \"\"\" Test function for best piece length for given size maximum. \"\"\" value = utils . get_piece_length ( size ) assert value < 2 ** 27 test_get_piece_length_min ( size ) Test function for best piece length for given size minimum. Source code in tests\\test_utils.py @pytest . mark . parametrize ( \"size\" , [ 156634528 , 2 ** 30 , 67987 , 16384 , 8563945 ]) def test_get_piece_length_min ( size ): \"\"\" Test function for best piece length for given size minimum. \"\"\" value = utils . get_piece_length ( size ) assert value >= 2 ** 14 test_humanize_bytes ( amount , result ) Test humanize bytes function. Source code in tests\\test_utils.py @pytest . mark . parametrize ( \"amount, result\" , [ ( 100 , \"100\" ), ( 1100 , \"1 KiB\" ), ( 1_100_000 , \"1 MiB\" ), ( 1_100_000_000 , \"1 GiB\" ), ( 4_400_120_000 , \"4 GiB\" ), ( 4_000_120_000 , \"3 GiB\" ), ], ) def test_humanize_bytes ( amount , result ): \"\"\" Test humanize bytes function. \"\"\" assert utils . humanize_bytes ( amount ) == result test_main () Test main module in torrentfile package. Source code in tests\\test_utils.py def test_main (): \"\"\" Test main module in torrentfile package. \"\"\" assert vars ( main ) test_missing_path_error () Test exception for missing path parameter. Source code in tests\\test_utils.py def test_missing_path_error (): \"\"\" Test exception for missing path parameter. \"\"\" try : raise utils . MissingPathError ( \"message\" ) except utils . MissingPathError : assert True assert dir2 test_next_power_2 ( value ) Test next power of 2 function in utils module. Source code in tests\\test_utils.py @pytest . mark . parametrize ( \"value\" , [ 5 , 32 , 18 , 225 , 16384 , 256000 ]) def test_next_power_2 ( value ): \"\"\" Test next power of 2 function in utils module. \"\"\" result = utils . next_power_2 ( value ) log = math . log2 ( result ) assert log == int ( log ) assert result % 2 == 0 assert result >= value test_norm_plength_errors ( amount ) Test function to normalize piece length errors. Parameters: Name Type Description Default amount any arguments intended to raise an exception. required Source code in tests\\test_utils.py @pytest . mark . parametrize ( \"amount\" , [ \"hello\" , 11 , 0 , 100000 , 28 , \"zero\" , \"fifteen\" ] ) def test_norm_plength_errors ( amount ): \"\"\"Test function to normalize piece length errors. Parameters ---------- amount : any arguments intended to raise an exception. \"\"\" try : assert utils . normalize_piece_length ( amount ) except utils . PieceLengthValueError : assert True test_normalize_piece_length_int ( amount , result ) Test normalize piece length function. Parameters: Name Type Description Default amount `str` or `int` piece length or representation required result int expected output. required Source code in tests\\test_utils.py @pytest . mark . parametrize ( \"amount, result\" , [( i , 2 ** i ) for i in range ( 14 , 25 )]) def test_normalize_piece_length_int ( amount , result ): \"\"\"Test normalize piece length function. Parameters ---------- amount : `str` or `int` piece length or representation result : int expected output. \"\"\" assert utils . normalize_piece_length ( amount ) == result test_normalize_piece_length_str ( amount , result ) Test normalize piece length function. Parameters: Name Type Description Default amount `str` or `int` piece length or representation required result int expected output. required Source code in tests\\test_utils.py @pytest . mark . parametrize ( \"amount, result\" , [( str ( i ), 2 ** i ) for i in range ( 14 , 21 )] ) def test_normalize_piece_length_str ( amount , result ): \"\"\"Test normalize piece length function. Parameters ---------- amount : `str` or `int` piece length or representation result : int expected output. \"\"\" assert utils . normalize_piece_length ( amount ) == result test_path_stat ( dir1 ) Test function for acquiring piece length information on folder. Source code in tests\\test_utils.py def test_path_stat ( dir1 ): \"\"\" Test function for acquiring piece length information on folder. \"\"\" _ , _ , piece_length = utils . path_stat ( dir1 ) assert piece_length % ( 2 ** 14 ) == 0 test_path_stat_filelist_size ( dir1 ) Test function for acquiring file list information on folder. Source code in tests\\test_utils.py def test_path_stat_filelist_size ( dir1 ): \"\"\" Test function for acquiring file list information on folder. \"\"\" filelist , _ , _ = utils . path_stat ( dir1 ) assert len ( filelist ) == 8 test_path_stat_size ( dir1 ) Test function for acquiring total size information on folder. Source code in tests\\test_utils.py def test_path_stat_size ( dir1 ): \"\"\" Test function for acquiring total size information on folder. \"\"\" _ , totalsize , _ = utils . path_stat ( dir1 ) assert totalsize == ( 2 ** 18 ) * 8 test_piecelength_error_fixtures () Test exception for uninterpretable piece length value. Source code in tests\\test_utils.py def test_piecelength_error_fixtures (): \"\"\" Test exception for uninterpretable piece length value. \"\"\" try : raise utils . PieceLengthValueError ( \"message\" ) except utils . PieceLengthValueError : assert True assert dir1","title":"Source Code"},{"location":"source/#torrentfile_1","text":"","title":"TorrentFile"},{"location":"source/#api-and-source","text":"","title":"API and Source"},{"location":"source/#torrent-module","text":"module torrentfile. torrent Classes and procedures pertaining to the creation of torrent meta files.","title":"Torrent Module"},{"location":"source/#classes","text":"TorrentFile construct .torrent file. TorrentFileV2 construct .torrent v2 files using provided data. MetaFile base class for all MetaFile classes.","title":"Classes"},{"location":"source/#constants","text":"BLOCK_SIZE : int size of leaf hashes for merkle tree. HASH_SIZE : int Length of a sha256 hash.","title":"Constants"},{"location":"source/#bittorrent-v2","text":"From Bittorrent.org Documentation pages. Implementation details for Bittorrent Protocol v2. Note All strings in a .torrent file that contain text must be UTF-8 encoded.","title":"Bittorrent V2"},{"location":"source/#meta-version-2-dictionary","text":"\"announce\": The URL of the tracker. \"info\": This maps to a dictionary, with keys described below. \"name\": A display name for the torrent. It is purely advisory. \"piece length\": The number of bytes that each logical piece in the peer protocol refers to. I.e. it sets the granularity of piece, request, bitfield and have messages. It must be a power of two and at least 6KiB. \"meta version\": An integer value, set to 2 to indicate compatibility with the current revision of this specification. Version 1 is not assigned to avoid confusion with BEP3. Future revisions will only increment this issue to indicate an incompatible change has been made, for example that hash algorithms were changed due to newly discovered vulnerabilities. Lementations must check this field first and indicate that a torrent is of a newer version than they can handle before performing other idations which may result in more general messages about invalid files. Files are mapped into this piece address space so that each non-empty \"file tree\": A tree of dictionaries where dictionary keys represent UTF-8 encoded path elements. Entries with zero-length keys describe the properties of the composed path at that point. 'UTF-8 encoded' context only means that if the native encoding is known at creation time it must be converted to UTF-8. Keys may contain invalid UTF-8 sequences or characters and names that are reserved on specific filesystems. Implementations must be prepared to sanitize them. On platforms path components exactly matching '.' and '..' must be sanitized since they could lead to directory traversal attacks and conflicting path descriptions. On platforms that require UTF-8 path components this sanitizing step must happen after normalizing overlong UTF-8 encodings. File is aligned to a piece boundary and occurs in same order as the file tree. The last piece of each file may be shorter than the specified piece length, resulting in an alignment gap. \"length\": Length of the file in bytes. Presence of this field indicates that the dictionary describes a file, not a directory. Which means it must not have any sibling entries. \"pieces root\": For non-empty files this is the the root hash of a merkle tree with a branching factor of 2, constructed from 16KiB blocks of the file. The last block may be shorter than 16KiB. The remaining leaf hashes beyond the end of the file required to construct upper layers of the merkle tree are set to zero. As of meta version 2 SHA2-256 is used as digest function for the merkle tree. The hash is stored in its binary form, not as human-readable string. \"piece layers\": A dictionary of strings. For each file in the file tree that is larger than the piece size it contains one string value. The keys are the merkle roots while the values consist of concatenated hashes of one layer within that merkle tree. The layer is chosen so that one hash covers piece length bytes. For example if the piece size is 16KiB then the leaf hashes are used. If a piece size of 128KiB is used then 3rd layer up from the leaf hashes is used. Layer hashes which exclusively cover data beyond the end of file, i.e. are only needed to balance the tree, are omitted. All hashes are stored in their binary format. A torrent is not valid if this field is absent, the contained hashes do not match the merkle roots or are not from the correct layer. Important The file tree root dictionary itself must not be a file, i.e. it must not contain a zero-length key with a dictionary containing a length key.","title":"Meta Version 2 Dictionary:"},{"location":"source/#bittorrent-v1","text":"","title":"Bittorrent V1"},{"location":"source/#v1-meta-dictionary","text":"announce: The URL of the tracker. info: This maps to a dictionary, with keys described below. name : maps to a UTF-8 encoded string which is the suggested name to save the file (or directory) as. It is purely advisory. piece length : maps to the number of bytes in each piece the file is split into. For the purposes of transfer, files are split into fixed-size pieces which are all the same length except for possibly the last one which may be truncated. piece length : is almost always a power of two, most commonly 2^18 = 256 K pieces : maps to a string whose length is a multiple of 20. It is to be subdivided into strings of length 20, each of which is the SHA1 hash of the piece at the corresponding index. length : In the single file case, maps to the length of the file in bytes. files : If present then the download represents a single file, otherwise it represents a set of files which go in a directory structure. For the purposes of the other keys, the multi-file case is treated as only having a single file by concatenating the files in the order they appear in the files list. The files list is the value files maps to, and is a list of dictionaries containing the following keys: path : A list of UTF-8 encoded strings corresponding to subdirectory names, the last of which is the actual file name length : Maps to the length of the file in bytes. length : Only present if the content is a single file. Maps to the length of the file in bytes. Note In the single file case, the name key is the name of a file, in the muliple file case, it's the name of a directory. Classes MetaFile \u2014 Base Class for all TorrentFile classes. TorrentFile \u2014 Class for creating Bittorrent meta files. TorrentFileV2 \u2014 Class for creating Bittorrent meta v2 files. TorrentFileHybrid \u2014 Construct the Hybrid torrent meta file with provided parameters.","title":"v1 meta-dictionary"},{"location":"source/#-","text":"","title":"------"},{"location":"source/#utils-module","text":"module torrentfile. utils Utility functions and classes used throughout package. Functions: get_piece_length: calculate ideal piece length for torrent file. sortfiles: traverse directory in sorted order yielding paths encountered. path_size: Sum the sizes of each file in path. get_file_list: Return list of all files contained in directory. path_stat: Get ideal piece length, total size, and file list for directory. path_piece_length: Get ideal piece length based on size of directory. Classes: MissingPathError: Custom exception raised when no path was provided to CLI. PieceLengthValueError: Custom exception raised when incorrect input value used for piece length field. Classes Memo \u2014 Memoice chache object. MissingPathError \u2014 Path parameter is required to specify target content. PieceLengthValueError \u2014 Piece Length parameter must equal a perfect power of 2. Functions get_file_list ( path ) (list) \u2014 Return a sorted list of file paths contained in directory. get_piece_length ( size ) (int) \u2014 Calculate the ideal piece length for bittorrent data. humanize_bytes ( amount ) (str) \u2014 Convert integer into human readable memory sized denomination. next_power_2 ( value ) (int) \u2014 Calculate the next perfect power of 2 equal to or greater than value. normalize_piece_length ( piece_length ) (int) \u2014 Verify input piece_length is valid and convert accordingly. path_piece_length ( path ) (int) \u2014 Calculate piece length for input path and contents. path_size ( path ) (int) \u2014 Return the total size of all files in path recursively. path_stat ( path ) (list) \u2014 Calculate directory statistics.","title":"Utils Module"},{"location":"source/#-_1","text":"","title":"------"},{"location":"source/#edit-module","text":"module torrentfile. edit Edit torrent module. Provides a facility by which certain properties of a torrent meta file can be edited by the user. The various command line arguments indicate which fields should be edited, and what the new value should be. Depending on what fields are chosen to edit, this command can trigger a new info hash which means the torrent will no longer be able to participate in the same swarm as the original unedited torrent.","title":"Edit Module"},{"location":"source/#keywords","text":"private comment source trackers web-seeds Functions edit_torrent ( metafile , args ) (dict) \u2014 Edit the properties and values in a torrent meta file. filter_empty ( args , meta , info ) \u2014 Remove dictionary keys with empty values.","title":"Keywords"},{"location":"source/#-_2","text":"","title":"------"},{"location":"source/#interactive-module","text":"module torrentfile. interactive Module contains the procedures used for Interactive Mode. Classes InteractiveEditor \u2014 Interactive dialog class for torrent editing. InteractiveCreator \u2014 Class namespace for interactive program options. Functions create_torrent ( ) \u2014 Create new torrent file interactively. edit_action ( ) \u2014 Edit the editable values of the torrent meta file. get_input ( *args ) (str) \u2014 Determine appropriate input function to call. recheck_torrent ( ) \u2014 Check torrent download completed percentage. select_action ( ) \u2014 Operate TorrentFile program interactively through terminal. showcenter ( txt ) \u2014 Print text to screen in the center position of the terminal. showtext ( txt ) \u2014 Print contents of txt to screen.","title":"Interactive Module"},{"location":"source/#-_3","text":"","title":"------"},{"location":"source/#cli-module","text":"module torrentfile. cli Command Line Interface for TorrentFile project. This module provides the primary command line argument parser for the torrentfile package. The main_script function is automatically invoked when called from command line, and parses accompanying arguments. Functions: main_script: process command line arguments and run program. activate_logger: turns on debug mode and logging facility. Classes TorrentFileHelpFormatter \u2014 Formatting class for help tips provided by the CLI. Functions activate_logger ( ) \u2014 Activate the builtin logging mechanism when passed debug flag from CLI. execute ( args ) \u2014 Initialize Command Line Interface for torrentfile. execute ( args ) \u2014 Initialize Command Line Interface for torrentfile. main ( ) \u2014 Initiate main function for CLI script.","title":"CLI Module"},{"location":"source/#-_4","text":"","title":"------"},{"location":"source/#recheck-module","text":"module torrentfile. recheck Module container Checker Class. The CheckerClass takes a torrentfile and tha path to it's contents. It will then iterate through every file and directory contained and compare their data to values contained within the torrent file. Completion percentages will be printed to screen for each file and at the end for the torrentfile as a whole. Classes Checker \u2014 Check a given file or directory to see if it matches a torrentfile. FeedChecker \u2014 Validates torrent content. HashChecker \u2014 Verify that root hashes of content files match the .torrent files.","title":"Recheck Module"},{"location":"source/#-_5","text":"","title":"------"},{"location":"source/#hasher-module","text":"module torrentfile. hasher Piece/File Hashers for Bittorrent meta file contents. Classes Hasher \u2014 Piece hasher for Bittorrent V1 files. HasherV2 \u2014 Calculate the root hash and piece layers for file contents. HasherHybrid \u2014 Calculate root and piece hashes for creating hybrid torrent file. Functions merkle_root ( blocks ) (bytes) \u2014 Calculate the merkle root for a seq of sha256 hash digests.","title":"Hasher Module"},{"location":"source/#-_6","text":"","title":"------"},{"location":"source/#torrentfile","text":"Torrentfile can create Bittorrent metafiles for any content. Both Bittorrent v1 and v2 are fully supported. Also included is a torrent torrent file checker, which can verify a .torrent file is formated correctly as well as validate files and folders against metadata. Modules: metafile: Creation/Validation of v1 .torrent files. metafile2: Creation/Validation of v2 .torrent files. torrentfile: torrentfiles Command Line Interface implementation. exceptions: Custom Exceptions used in package. utils: Utilities used throughout package.","title":"torrentfile"},{"location":"source/#torrentfile.__main__","text":"Enable calling the package directly with python from the command line.","title":"__main__"},{"location":"source/#torrentfile.cli","text":"Command Line Interface for TorrentFile project. This module provides the primary command line argument parser for the torrentfile package. The main_script function is automatically invoked when called from command line, and parses accompanying arguments. Functions: main_script: process command line arguments and run program. activate_logger: turns on debug mode and logging facility.","title":"cli"},{"location":"source/#torrentfile.cli.TorrentFileHelpFormatter","text":"Formatting class for help tips provided by the CLI. Subclasses Argparse.HelpFormatter. Source code in torrentfile\\cli.py class TorrentFileHelpFormatter ( HelpFormatter ): \"\"\" Formatting class for help tips provided by the CLI. Subclasses Argparse.HelpFormatter. \"\"\" def __init__ ( self , prog , width = 40 , max_help_positions = 30 ): \"\"\" Construct HelpFormat class for usage output. Parameters ---------- prog : str Name of the program. width : int Max width of help message output. max_help_positions : int max length until line wrap. \"\"\" super () . __init__ ( prog , width = width , max_help_position = max_help_positions ) def _split_lines ( self , text , _ ): \"\"\" Split multiline help messages and remove indentation. Parameters ---------- text : str text that needs to be split _ : int max width for line. \"\"\" lines = text . split ( \" \\n \" ) return [ line . strip () for line in lines if line ] def _format_text ( self , text ): \"\"\" Format text for cli usage messages. Parameters ---------- text : str Pre-formatted text. Returns ------- str Formatted text from input. \"\"\" text = text % dict ( prog = self . _prog ) if \"%(prog)\" in text else text text = self . _whitespace_matcher . sub ( \" \" , text ) . strip () return text + \" \\n\\n \" def _join_parts ( self , part_strings ): \"\"\" Combine different sections of the help message. Parameters ---------- part_strings : list List of argument help messages and headers. Returns ------- str Fully formatted help message for CLI. \"\"\" parts = self . format_headers ( part_strings ) return super () . _join_parts ( parts ) @staticmethod def format_headers ( parts ): \"\"\" Format help message section headers. Parameters ---------- parts : list List of individual lines for help message. Returns ------- list Input list with formatted section headers. \"\"\" if parts and parts [ 0 ] . startswith ( \"usage:\" ): parts [ 0 ] = \"Usage \\n ===== \\n \" + parts [ 0 ][ 6 :] headings = [ i for i in range ( len ( parts )) if parts [ i ] . endswith ( \": \\n \" )] for i in headings [:: - 1 ]: parts [ i ] = parts [ i ][: - 2 ] . title () underline = \"\" . join ([ \" \\n \" , \"-\" * len ( parts [ i ]), \" \\n \" ]) parts . insert ( i + 1 , underline ) return parts","title":"TorrentFileHelpFormatter"},{"location":"source/#torrentfile.cli.TorrentFileHelpFormatter.__init__","text":"Construct HelpFormat class for usage output. Parameters: Name Type Description Default prog str Name of the program. required width int Max width of help message output. 40 max_help_positions int max length until line wrap. 30 Source code in torrentfile\\cli.py def __init__ ( self , prog , width = 40 , max_help_positions = 30 ): \"\"\" Construct HelpFormat class for usage output. Parameters ---------- prog : str Name of the program. width : int Max width of help message output. max_help_positions : int max length until line wrap. \"\"\" super () . __init__ ( prog , width = width , max_help_position = max_help_positions )","title":"__init__()"},{"location":"source/#torrentfile.cli.TorrentFileHelpFormatter.format_headers","text":"Format help message section headers. Parameters: Name Type Description Default parts list List of individual lines for help message. required Returns: Type Description list Input list with formatted section headers. Source code in torrentfile\\cli.py @staticmethod def format_headers ( parts ): \"\"\" Format help message section headers. Parameters ---------- parts : list List of individual lines for help message. Returns ------- list Input list with formatted section headers. \"\"\" if parts and parts [ 0 ] . startswith ( \"usage:\" ): parts [ 0 ] = \"Usage \\n ===== \\n \" + parts [ 0 ][ 6 :] headings = [ i for i in range ( len ( parts )) if parts [ i ] . endswith ( \": \\n \" )] for i in headings [:: - 1 ]: parts [ i ] = parts [ i ][: - 2 ] . title () underline = \"\" . join ([ \" \\n \" , \"-\" * len ( parts [ i ]), \" \\n \" ]) parts . insert ( i + 1 , underline ) return parts","title":"format_headers()"},{"location":"source/#torrentfile.cli.activate_logger","text":"Activate the builtin logging mechanism when passed debug flag from CLI. Source code in torrentfile\\cli.py def activate_logger (): \"\"\" Activate the builtin logging mechanism when passed debug flag from CLI. \"\"\" logging . basicConfig ( level = logging . INFO ) logger = logging . getLogger () file_handler = logging . FileHandler ( \"torrentfile.log\" , mode = \"a+\" , encoding = \"utf-8\" ) console_handler = logging . StreamHandler ( stream = sys . stderr ) file_formatter = logging . Formatter ( \" %(asctime)s %(levelno)s %(message)s \" , datefmt = \"%m- %d %H:%M:%S\" , style = \"%\" , ) stream_formatter = logging . Formatter ( \" %(asctime)s %(levelno)s %(message)s \" , datefmt = \"%m- %d %H:%M:%S\" , style = \"%\" , ) file_handler . setFormatter ( file_formatter ) console_handler . setFormatter ( stream_formatter ) file_handler . setLevel ( logging . INFO ) console_handler . setLevel ( logging . INFO ) logger . setLevel ( logging . INFO ) logger . addHandler ( console_handler ) logger . addHandler ( file_handler ) logger . debug ( \"Debug: ON\" )","title":"activate_logger()"},{"location":"source/#torrentfile.cli.execute","text":"Initialize Command Line Interface for torrentfile. Parameters: Name Type Description Default args list Commandline arguments. default=None None Source code in torrentfile\\cli.py def execute ( args = None ): \"\"\" Initialize Command Line Interface for torrentfile. Parameters ---------- args : list Commandline arguments. default=None \"\"\" if not args : if sys . argv [ 1 :]: args = sys . argv [ 1 :] else : args = [ \"-h\" ] parser = ArgumentParser ( \"torrentfile\" , description = ( \"Command line tools for creating, editing, checking and \" \"interacting with Bittorrent metainfo files\" ), prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , conflict_handler = \"resolve\" , ) parser . add_argument ( \"-i\" , \"--interactive\" , action = \"store_true\" , dest = \"interactive\" , help = \"select program options interactively\" , ) parser . add_argument ( \"-V\" , \"--version\" , action = \"version\" , version = f \"torrentfile v { version } \" , help = \"show program version and exit\" , ) parser . add_argument ( \"-v\" , \"--verbose\" , action = \"store_true\" , dest = \"debug\" , help = \"output debug information\" , ) parser . set_defaults ( func = parser . print_help ) subparsers = parser . add_subparsers ( title = \"Actions\" , dest = \"command\" , metavar = \"create, edit, magnet, recheck\" , ) create_parser = subparsers . add_parser ( \"create\" , help = \"\"\"Generate a new torrent meta file.\"\"\" , prefix_chars = \"-\" , aliases = [ \"c\" , \"new\" ], formatter_class = TorrentFileHelpFormatter , ) create_parser . add_argument ( \"-a\" , \"-t\" , \"--announce\" , \"--tracker\" , action = \"store\" , dest = \"announce\" , metavar = \"\" , nargs = \"+\" , default = [], help = \"One or more space-seperated torrent tracker url(s).\" , ) create_parser . add_argument ( \"-p\" , \"--private\" , action = \"store_true\" , dest = \"private\" , help = \"Creates private torrent with multi-tracker and DHT turned off.\" , ) create_parser . add_argument ( \"-s\" , \"--source\" , action = \"store\" , dest = \"source\" , metavar = \"\" , help = \"Add a source string. Useful for cross-seeding.\" , ) create_parser . add_argument ( \"-m\" , \"--magnet\" , action = \"store_true\" , dest = \"magnet\" , ) create_parser . add_argument ( \"-c\" , \"--comment\" , action = \"store\" , dest = \"comment\" , metavar = \"\" , help = \"Include a comment in file metadata\" , ) create_parser . add_argument ( \"-o\" , \"--out\" , action = \"store\" , dest = \"outfile\" , metavar = \"\" , help = \"Output save path for created .torrent file\" , ) create_parser . add_argument ( \"--cwd\" , \"--current\" , action = \"store_true\" , dest = \"cwd\" , help = \"Save output .torrent file to current directory\" , ) create_parser . add_argument ( \"--progress\" , \"--prog\" , choices = [ \"0\" , \"1\" , \"2\" ], default = \"1\" , action = \"store\" , dest = \"progress\" , help = \"\"\" Set the progress bar level. Options = 0, 1, 2 (0) = Do not display progress bar. (1) = Display 1 progress bar for the whole torrent (default) (2) = Display a progress bar for each file going into torrent \"\"\" , ) create_parser . add_argument ( \"--meta-version\" , default = \"1\" , choices = [ \"1\" , \"2\" , \"3\" ], action = \"store\" , dest = \"meta_version\" , metavar = \"\" , help = \"\"\" Bittorrent metafile version. Options = 1, 2, 3 (1) = Bittorrent v1 (Default) (2) = Bittorrent v2 (3) = Bittorrent v1 & v2 hybrid \"\"\" , ) create_parser . add_argument ( \"--piece-length\" , action = \"store\" , dest = \"piece_length\" , metavar = \"\" , help = \"\"\" (Default: ) Number of bytes for per chunk of data transmitted by Bittorrent client. Acceptable values include integers 14-26 which will be interpreted as a perfect power of 2. e.g. 14 = 16KiB pieces. Examples:: [--piece-length 14] [--piece-length 20] \"\"\" , ) create_parser . add_argument ( \"-w\" , \"--web-seed\" , action = \"store\" , dest = \"url_list\" , metavar = \"\" , nargs = \"+\" , help = \"list of web addresses where torrent data exists (GetRight).\" , ) create_parser . add_argument ( \"--http-seed\" , action = \"store\" , dest = \"httpseeds\" , metavar = \"\" , nargs = \"+\" , help = \"list of URLs, addresses where content can be found (Hoffman).\" , ) create_parser . add_argument ( \"content\" , action = \"store\" , metavar = \"\" , nargs = \"?\" , help = \"Path to content file or directory\" , ) create_parser . set_defaults ( func = create ) edit_parser = subparsers . add_parser ( \"edit\" , help = \"\"\" Edit existing torrent meta file. \"\"\" , aliases = [ \"e\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) edit_parser . add_argument ( \"metafile\" , action = \"store\" , help = \"path to *.torrent file\" , metavar = \"<*.torrent>\" , ) edit_parser . add_argument ( \"--tracker\" , action = \"store\" , dest = \"announce\" , metavar = \"\" , nargs = \"+\" , help = \"\"\" Replace current list of tracker/announce urls with one or more space seperated Bittorrent tracker announce url(s). \"\"\" , ) edit_parser . add_argument ( \"--web-seed\" , action = \"store\" , dest = \"url_list\" , metavar = \"\" , nargs = \"+\" , help = \"Replace current list of web-seed urls with one or more url(s)\" , ) edit_parser . add_argument ( \"--http-seed\" , action = \"store\" , dest = \"httpseeds\" , metavar = \"\" , nargs = \"+\" , help = \"replace all currently listed addresses with new list (Hoffman).\" , ) edit_parser . add_argument ( \"--private\" , action = \"store_true\" , help = \"Make torrent private.\" , dest = \"private\" , ) edit_parser . add_argument ( \"--comment\" , help = \"Replaces any existing comment with \" , metavar = \"\" , dest = \"comment\" , action = \"store\" , ) edit_parser . add_argument ( \"--source\" , action = \"store\" , dest = \"source\" , metavar = \"\" , help = \"Replaces current source with \" , ) edit_parser . set_defaults ( func = edit ) magnet_parser = subparsers . add_parser ( \"magnet\" , help = \"\"\" Generate magnet url from an existing Bittorrent meta file. \"\"\" , aliases = [ \"m\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) magnet_parser . add_argument ( \"metafile\" , action = \"store\" , help = \"Path to Bittorrent meta file.\" , metavar = \"<*.torrent>\" , ) magnet_parser . set_defaults ( func = magnet ) check_parser = subparsers . add_parser ( \"recheck\" , help = \"\"\" Calculate amount of torrent meta file's content is found on disk. \"\"\" , aliases = [ \"r\" , \"check\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) check_parser . add_argument ( \"metafile\" , action = \"store\" , metavar = \"<*.torrent>\" , help = \"path to .torrent file.\" , ) check_parser . add_argument ( \"content\" , action = \"store\" , metavar = \"\" , help = \"path to content file or directory\" , ) check_parser . set_defaults ( func = recheck ) info_parser = subparsers . add_parser ( \"info\" , help = \"\"\" Show detailed information about a torrent file. \"\"\" , aliases = [ \"i\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) info_parser . add_argument ( \"metafile\" , action = \"store\" , metavar = \"<*.torrent>\" , help = \"path to pre-existing torrent file.\" , ) info_parser . set_defaults ( func = info ) args = parser . parse_args ( args ) if args . debug : activate_logger () if args . interactive : return select_action () return args . func ( args )","title":"execute()"},{"location":"source/#torrentfile.cli.main","text":"Initiate main function for CLI script. Source code in torrentfile\\cli.py def main (): \"\"\" Initiate main function for CLI script. \"\"\" execute ()","title":"main()"},{"location":"source/#torrentfile.cli.main_script","text":"Initialize Command Line Interface for torrentfile. Parameters: Name Type Description Default args list Commandline arguments. default=None None Source code in torrentfile\\cli.py def execute ( args = None ): \"\"\" Initialize Command Line Interface for torrentfile. Parameters ---------- args : list Commandline arguments. default=None \"\"\" if not args : if sys . argv [ 1 :]: args = sys . argv [ 1 :] else : args = [ \"-h\" ] parser = ArgumentParser ( \"torrentfile\" , description = ( \"Command line tools for creating, editing, checking and \" \"interacting with Bittorrent metainfo files\" ), prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , conflict_handler = \"resolve\" , ) parser . add_argument ( \"-i\" , \"--interactive\" , action = \"store_true\" , dest = \"interactive\" , help = \"select program options interactively\" , ) parser . add_argument ( \"-V\" , \"--version\" , action = \"version\" , version = f \"torrentfile v { version } \" , help = \"show program version and exit\" , ) parser . add_argument ( \"-v\" , \"--verbose\" , action = \"store_true\" , dest = \"debug\" , help = \"output debug information\" , ) parser . set_defaults ( func = parser . print_help ) subparsers = parser . add_subparsers ( title = \"Actions\" , dest = \"command\" , metavar = \"create, edit, magnet, recheck\" , ) create_parser = subparsers . add_parser ( \"create\" , help = \"\"\"Generate a new torrent meta file.\"\"\" , prefix_chars = \"-\" , aliases = [ \"c\" , \"new\" ], formatter_class = TorrentFileHelpFormatter , ) create_parser . add_argument ( \"-a\" , \"-t\" , \"--announce\" , \"--tracker\" , action = \"store\" , dest = \"announce\" , metavar = \"\" , nargs = \"+\" , default = [], help = \"One or more space-seperated torrent tracker url(s).\" , ) create_parser . add_argument ( \"-p\" , \"--private\" , action = \"store_true\" , dest = \"private\" , help = \"Creates private torrent with multi-tracker and DHT turned off.\" , ) create_parser . add_argument ( \"-s\" , \"--source\" , action = \"store\" , dest = \"source\" , metavar = \"\" , help = \"Add a source string. Useful for cross-seeding.\" , ) create_parser . add_argument ( \"-m\" , \"--magnet\" , action = \"store_true\" , dest = \"magnet\" , ) create_parser . add_argument ( \"-c\" , \"--comment\" , action = \"store\" , dest = \"comment\" , metavar = \"\" , help = \"Include a comment in file metadata\" , ) create_parser . add_argument ( \"-o\" , \"--out\" , action = \"store\" , dest = \"outfile\" , metavar = \"\" , help = \"Output save path for created .torrent file\" , ) create_parser . add_argument ( \"--cwd\" , \"--current\" , action = \"store_true\" , dest = \"cwd\" , help = \"Save output .torrent file to current directory\" , ) create_parser . add_argument ( \"--progress\" , \"--prog\" , choices = [ \"0\" , \"1\" , \"2\" ], default = \"1\" , action = \"store\" , dest = \"progress\" , help = \"\"\" Set the progress bar level. Options = 0, 1, 2 (0) = Do not display progress bar. (1) = Display 1 progress bar for the whole torrent (default) (2) = Display a progress bar for each file going into torrent \"\"\" , ) create_parser . add_argument ( \"--meta-version\" , default = \"1\" , choices = [ \"1\" , \"2\" , \"3\" ], action = \"store\" , dest = \"meta_version\" , metavar = \"\" , help = \"\"\" Bittorrent metafile version. Options = 1, 2, 3 (1) = Bittorrent v1 (Default) (2) = Bittorrent v2 (3) = Bittorrent v1 & v2 hybrid \"\"\" , ) create_parser . add_argument ( \"--piece-length\" , action = \"store\" , dest = \"piece_length\" , metavar = \"\" , help = \"\"\" (Default: ) Number of bytes for per chunk of data transmitted by Bittorrent client. Acceptable values include integers 14-26 which will be interpreted as a perfect power of 2. e.g. 14 = 16KiB pieces. Examples:: [--piece-length 14] [--piece-length 20] \"\"\" , ) create_parser . add_argument ( \"-w\" , \"--web-seed\" , action = \"store\" , dest = \"url_list\" , metavar = \"\" , nargs = \"+\" , help = \"list of web addresses where torrent data exists (GetRight).\" , ) create_parser . add_argument ( \"--http-seed\" , action = \"store\" , dest = \"httpseeds\" , metavar = \"\" , nargs = \"+\" , help = \"list of URLs, addresses where content can be found (Hoffman).\" , ) create_parser . add_argument ( \"content\" , action = \"store\" , metavar = \"\" , nargs = \"?\" , help = \"Path to content file or directory\" , ) create_parser . set_defaults ( func = create ) edit_parser = subparsers . add_parser ( \"edit\" , help = \"\"\" Edit existing torrent meta file. \"\"\" , aliases = [ \"e\" ], prefix_chars = \"-\" , formatter_class = TorrentFileHelpFormatter , ) edit_parser . add_argument ( \"metafile\" , action = \"store\" , help = \"path to *.torrent file\" , metavar = \"<*.torrent>\" , ) edit_parser . add_argument ( \"--tracker\" , action = \"store\" , dest = \"announce\" , metavar = \"\" , nargs = \"+\" , help = \"\"\" Replace current list of tracker/announce urls with one or more space seperated Bittorrent tracker announce url(s). \"\"\" , ) edit_parser . add_argument ( \"--web-seed\" , action = \"store\" , dest = \"url_list\" , metavar = \"\" , nargs = \"+\" , help = \"Replace current list of web-seed urls with one or more url(s)\" , ) edit_parser . add_argument ( \"--http-seed\" , action = \"store\" , dest = \"httpseeds\" , metavar = \"