From 8abefd5e41e85c766b13789ee29f0defee7fb400 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 20 Oct 2022 23:00:11 -0700 Subject: [PATCH] =?UTF-8?q?Updated=20documentation=20and=20fixed=20issue?= =?UTF-8?q?=20with=20install=20not=20registering=20a=20=E2=80=A6=20(#157)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated documentation and fixed issue with install not registering a binary command on linux platforms * Bump Version 0.8.5 * Bump Version 0.8.5 * Bump Version 0.8.5 --- CHANGELOG.md | 19 + Makefile | 1 + README.md | 10 - docs/.coverage-tmp.html | 669 ++++++++++++++ docs/Source/cli/index.html | 48 +- docs/Source/interactive/index.html | 180 ++-- docs/Source/mixins/index.html | 72 +- docs/Source/rebuild/index.html | 28 +- docs/Source/torrent/index.html | 145 ++- docs/Source/utils/index.html | 140 +++ docs/api/index.html | 10 + docs/changelog/index.html | 31 + docs/htmlcover/coverage_html.js | 604 +++++++++++++ .../d_1bc82e0ab2fcb2ec___init___py.html | 137 +++ .../d_1bc82e0ab2fcb2ec___main___py.html | 129 +++ docs/htmlcover/d_1bc82e0ab2fcb2ec_cli_py.html | 705 +++++++++++++++ .../d_1bc82e0ab2fcb2ec_commands_py.html | 388 ++++++++ .../htmlcover/d_1bc82e0ab2fcb2ec_edit_py.html | 227 +++++ .../d_1bc82e0ab2fcb2ec_hasher_py.html | 631 ++++++++++++++ .../d_1bc82e0ab2fcb2ec_interactive_py.html | 486 +++++++++++ .../d_1bc82e0ab2fcb2ec_mixins_py.html | 353 ++++++++ .../d_1bc82e0ab2fcb2ec_rebuild_py.html | 713 +++++++++++++++ .../d_1bc82e0ab2fcb2ec_recheck_py.html | 746 ++++++++++++++++ .../d_1bc82e0ab2fcb2ec_torrent_py.html | 825 ++++++++++++++++++ .../d_1bc82e0ab2fcb2ec_utils_py.html | 523 +++++++++++ .../d_1bc82e0ab2fcb2ec_version_py.html | 120 +++ .../d_a44f0ac069e85531___init___py.html | 431 +++++++++ .../d_a44f0ac069e85531_test_cli_py.html | 671 ++++++++++++++ .../d_a44f0ac069e85531_test_commands_py.html | 351 ++++++++ .../d_a44f0ac069e85531_test_edit_py.html | 343 ++++++++ ..._a44f0ac069e85531_test_interactive_py.html | 289 ++++++ .../d_a44f0ac069e85531_test_rebuild_py.html | 282 ++++++ .../d_a44f0ac069e85531_test_recheck_py.html | 425 +++++++++ .../d_a44f0ac069e85531_test_torrent_py.html | 338 +++++++ .../d_a44f0ac069e85531_test_utils_py.html | 359 ++++++++ docs/htmlcover/favicon_32.png | Bin 0 -> 1732 bytes docs/htmlcover/index.html | 249 ++++++ docs/htmlcover/keybd_closed.png | Bin 0 -> 9004 bytes docs/htmlcover/keybd_open.png | Bin 0 -> 9003 bytes docs/htmlcover/status.json | 1 + docs/htmlcover/style.css | 311 +++++++ docs/index.html | 19 - docs/objects.inv | Bin 1138 -> 1161 bytes docs/overview/index.html | 232 +++-- docs/search/search_index.json | 2 +- docs/sitemap.xml | 38 +- docs/sitemap.xml.gz | Bin 334 -> 334 bytes docs/usage/index.html | 165 ++-- mkdocs.yml | 4 +- pyproject.toml | 4 +- setup.cfg | 6 +- site/api.md | 3 + site/changelog.md | 19 + site/index.md | 10 - site/overview.md | 137 +-- site/usage.md | 121 +-- tests/__init__.py | 2 - torrentfile/__init__.py | 7 +- torrentfile/cli.py | 16 +- torrentfile/interactive.py | 7 + torrentfile/mixins.py | 4 +- torrentfile/rebuild.py | 16 +- torrentfile/torrent.py | 14 - torrentfile/utils.py | 24 + torrentfile/version.py | 2 +- 65 files changed, 12234 insertions(+), 608 deletions(-) create mode 100644 docs/.coverage-tmp.html create mode 100644 docs/htmlcover/coverage_html.js create mode 100644 docs/htmlcover/d_1bc82e0ab2fcb2ec___init___py.html create mode 100644 docs/htmlcover/d_1bc82e0ab2fcb2ec___main___py.html create mode 100644 docs/htmlcover/d_1bc82e0ab2fcb2ec_cli_py.html create mode 100644 docs/htmlcover/d_1bc82e0ab2fcb2ec_commands_py.html create mode 100644 docs/htmlcover/d_1bc82e0ab2fcb2ec_edit_py.html create mode 100644 docs/htmlcover/d_1bc82e0ab2fcb2ec_hasher_py.html create mode 100644 docs/htmlcover/d_1bc82e0ab2fcb2ec_interactive_py.html create mode 100644 docs/htmlcover/d_1bc82e0ab2fcb2ec_mixins_py.html create mode 100644 docs/htmlcover/d_1bc82e0ab2fcb2ec_rebuild_py.html create mode 100644 docs/htmlcover/d_1bc82e0ab2fcb2ec_recheck_py.html create mode 100644 docs/htmlcover/d_1bc82e0ab2fcb2ec_torrent_py.html create mode 100644 docs/htmlcover/d_1bc82e0ab2fcb2ec_utils_py.html create mode 100644 docs/htmlcover/d_1bc82e0ab2fcb2ec_version_py.html create mode 100644 docs/htmlcover/d_a44f0ac069e85531___init___py.html create mode 100644 docs/htmlcover/d_a44f0ac069e85531_test_cli_py.html create mode 100644 docs/htmlcover/d_a44f0ac069e85531_test_commands_py.html create mode 100644 docs/htmlcover/d_a44f0ac069e85531_test_edit_py.html create mode 100644 docs/htmlcover/d_a44f0ac069e85531_test_interactive_py.html create mode 100644 docs/htmlcover/d_a44f0ac069e85531_test_rebuild_py.html create mode 100644 docs/htmlcover/d_a44f0ac069e85531_test_recheck_py.html create mode 100644 docs/htmlcover/d_a44f0ac069e85531_test_torrent_py.html create mode 100644 docs/htmlcover/d_a44f0ac069e85531_test_utils_py.html create mode 100644 docs/htmlcover/favicon_32.png create mode 100644 docs/htmlcover/index.html create mode 100644 docs/htmlcover/keybd_closed.png create mode 100644 docs/htmlcover/keybd_open.png create mode 100644 docs/htmlcover/status.json create mode 100644 docs/htmlcover/style.css diff --git a/CHANGELOG.md b/CHANGELOG.md index 409214fa..8766bb5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # TorrentFile +## Version 0.8.5 + +- Fixed bug with linux platforms not installing a binary cli command +- Fixed debug logging errors with the rebuild command +- Improved log message readability +- Compatability upgrades for torrentfileQt synchronization +- Added coverage details to documentation folder + +* * * + +## Version 0.8.4 + +- Documentation Updates +- Fixed logging issues with rebuild module. +- Improved algorithm for rebuild module. +- Improved testing for rebuild module. + +* * * + ## Version 0.8.3 diff --git a/Makefile b/Makefile index b9ab940f..ea62f0c9 100644 --- a/Makefile +++ b/Makefile @@ -83,6 +83,7 @@ docs: ## Regenerate docs from changes rm -rfv site/index.md cp -rfv README.md site/index.md cp -rfv CHANGELOG.md site/changelog.md + mv -fv htmlcov site/htmlcover mkdocs build touch docs/.nojekyll diff --git a/README.md b/README.md index 985b93fa..85b23aa7 100644 --- a/README.md +++ b/README.md @@ -185,16 +185,6 @@ To create a magnet URI for a pre-existing torrent meta file, use the sub-command 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. Use the -`-i` or `--interactive` flags to activate interactive mode. - -```bash -torrentfile -i -``` - ### GUI If you prefer a windowed GUI please check out the official GUI frontend [here](https://github.com/alexpdev/TorrentFileQt) diff --git a/docs/.coverage-tmp.html b/docs/.coverage-tmp.html new file mode 100644 index 00000000..bdc1eb2b --- /dev/null +++ b/docs/.coverage-tmp.html @@ -0,0 +1,669 @@ + + + + + + + + + + + + + + + + + + + + Coverage - TorrentFile Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+ + + + + + + + +

Coverage

+ + + +

+

+ + + +
+ +
+
+ +
+ + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/docs/Source/cli/index.html b/docs/Source/cli/index.html index 27bf3b51..ee7de799 100644 --- a/docs/Source/cli/index.html +++ b/docs/Source/cli/index.html @@ -750,8 +750,7 @@

Source code in torrentfile\cli.py -
71
-72
+        
72
 73
 74
 75
@@ -768,25 +767,26 @@ 

86 87 88 -89

@staticmethod
+89
+90
@staticmethod
 def activate_logger():
     """
     Activate the builtin logging mechanism when passed debug flag from CLI.
     """
-    logging.basicConfig(level=logging.INFO)
+    logging.basicConfig(level=logging.WARNING)
     logger = logging.getLogger()
     console_handler = logging.StreamHandler(stream=sys.stderr)
     stream_formatter = logging.Formatter(
-        "%(asctime)s %(levelno)s %(message)s",
-        datefmt="%m-%d %H:%M:%S",
+        "[%(asctime)s] [%(levelno)s] %(message)s",
+        datefmt="%H:%M:%S",
         style="%",
     )
     console_handler.setFormatter(stream_formatter)
     console_handler.setLevel(logging.DEBUG)
-
     logger.setLevel(logging.DEBUG)
     logger.addHandler(console_handler)
     logger.debug("Debug: ON")
+    toggle_debug_mode(True)
 
@@ -815,8 +815,7 @@

Source code in torrentfile\cli.py -
59
-60
+        
60
 61
 62
 63
@@ -825,7 +824,8 @@ 

66 67 68 -69

@staticmethod
+69
+70
@staticmethod
 def activate_quiet():
     """
     Activate quiet mode for the duration of the programs life.
@@ -928,8 +928,7 @@ 

Source code in torrentfile\cli.py -
 99
-100
+            
100
 101
 102
 103
@@ -943,7 +942,8 @@ 

111 112 113 -114

def __init__(self, prog, width=45, max_help_positions=45):
+114
+115
def __init__(self, prog, width=45, max_help_positions=45):
     """
     Construct HelpFormat class for usage output.
 
@@ -1050,8 +1050,7 @@ 

Source code in torrentfile\cli.py -
195
-196
+        
196
 197
 198
 199
@@ -1451,7 +1450,9 @@ 

593 594 595 -596

def execute(args: Optional[list] = None) -> list:
+596
+597
+598
def execute(args: Optional[list] = None) -> list:
     """
     Execute program with provided list of arguments.
 
@@ -1469,6 +1470,7 @@ 

list Depends on what the command line args were. """ + toggle_debug_mode(False) if not args: if sys.argv[1:]: args = sys.argv[1:] @@ -1477,7 +1479,7 @@

parser = ArgumentParser( "torrentfile", - usage="torrentfile [options] command [command options]", + usage="torrentfile <options>", description=( "Command line tools for creating, editing, checking, building " "and interacting with Bittorrent metainfo files" @@ -1531,7 +1533,7 @@

"create", help="Create a new Bittorrent file.", prefix_chars="-", - aliases=["c", "new"], + aliases=["c", "new", "build"], formatter_class=TorrentFileHelpFormatter, ) @@ -1777,7 +1779,7 @@

check_parser = subparsers.add_parser( "recheck", help="Gives a detailed look at how much of the torrent is available.", - aliases=["check"], + aliases=["check", "r"], prefix_chars="-", formatter_class=TorrentFileHelpFormatter, ) @@ -1875,11 +1877,11 @@

Source code in torrentfile\cli.py -
602
-603
-604
+        
604
 605
-606
def main():
+606
+607
+608
def main():
     """
     Initiate main function for CLI script.
     """
diff --git a/docs/Source/interactive/index.html b/docs/Source/interactive/index.html
index 2c383a14..db7dc719 100644
--- a/docs/Source/interactive/index.html
+++ b/docs/Source/interactive/index.html
@@ -747,14 +747,7 @@ 

Source code in torrentfile\interactive.py -
298
-299
-300
-301
-302
-303
-304
-305
+            
305
 306
 307
 308
@@ -762,7 +755,14 @@ 

310 311 312 -313

def __init__(self):
+313
+314
+315
+316
+317
+318
+319
+320
def __init__(self):
     """
     Initialize interactive meta file creator dialog.
     """
@@ -809,14 +809,7 @@ 

Source code in torrentfile\interactive.py -
315
-316
-317
-318
-319
-320
-321
-322
+        
322
 323
 324
 325
@@ -876,7 +869,14 @@ 

379 380 381 -382

def get_props(self):
+382
+383
+384
+385
+386
+387
+388
+389
def get_props(self):
     """
     Gather details for torrentfile from user.
     """
@@ -1001,14 +1001,7 @@ 

Source code in torrentfile\interactive.py -
195
-196
-197
-198
-199
-200
-201
-202
+            
202
 203
 204
 205
@@ -1021,7 +1014,14 @@ 

212 213 214 -215

def __init__(self, metafile: str):
+215
+216
+217
+218
+219
+220
+221
+222
def __init__(self, metafile: str):
     """
     Initialize the Interactive torrent editor guide.
 
@@ -1073,14 +1073,7 @@ 

Source code in torrentfile\interactive.py -
245
-246
-247
-248
-249
-250
-251
-252
+        
252
 253
 254
 255
@@ -1118,7 +1111,14 @@ 

287 288 289 -290

def edit_props(self):
+290
+291
+292
+293
+294
+295
+296
+297
def edit_props(self):
     """
     Loop continuosly for edits until user signals DONE.
     """
@@ -1221,14 +1221,7 @@ 

Source code in torrentfile\interactive.py -
228
-229
-230
-231
-232
-233
-234
-235
+        
235
 236
 237
 238
@@ -1236,7 +1229,14 @@ 

240 241 242 -243

def sanatize_response(self, key, response):
+243
+244
+245
+246
+247
+248
+249
+250
def sanatize_response(self, key, response):
     """
     Convert the input data into a form recognizable by the program.
 
@@ -1274,16 +1274,16 @@ 

Source code in torrentfile\interactive.py -
217
-218
-219
-220
-221
-222
-223
-224
+        
224
 225
-226
def show_current(self):
+226
+227
+228
+229
+230
+231
+232
+233
def show_current(self):
     """
     Display the current met file information to screen.
     """
@@ -1324,20 +1324,20 @@ 

Source code in torrentfile\interactive.py -
163
-164
-165
-166
-167
-168
-169
-170
+        
170
 171
 172
 173
 174
 175
-176
def create_torrent():
+176
+177
+178
+179
+180
+181
+182
+183
def create_torrent():
     """
     Create new torrent file interactively.
     """
@@ -1373,15 +1373,15 @@ 

Source code in torrentfile\interactive.py -
179
-180
-181
-182
-183
-184
-185
-186
-187
def edit_action():
+        
186
+187
+188
+189
+190
+191
+192
+193
+194
def edit_action():
     """
     Edit the editable values of the torrent meta file.
     """
@@ -1515,21 +1515,21 @@ 

Source code in torrentfile\interactive.py -
146
-147
-148
-149
-150
-151
-152
-153
+        
153
 154
 155
 156
 157
 158
 159
-160
def recheck_torrent():
+160
+161
+162
+163
+164
+165
+166
+167
def recheck_torrent():
     """
     Check torrent download completed percentage.
     """
@@ -1563,6 +1563,8 @@ 

Operate TorrentFile program interactively through terminal.

+

DEPRECATION WARNING: The interactive CLI feature will be deprecated +in the future.

Source code in torrentfile\interactive.py @@ -1586,11 +1588,25 @@

140 141 142 -143

def select_action():
+143
+144
+145
+146
+147
+148
+149
+150
def select_action():
     """
     Operate TorrentFile program interactively through terminal.
+
+    DEPRECATION WARNING: The interactive CLI feature will be deprecated
+    in the future.
     """
     showcenter("TorrentFile: Starting Interactive Mode")
+    showcenter(
+        "DEPRECATION WARNING: The interactive feature will be"
+        "deprecated in the near future."
+    )
     action = get_input(
         "Enter the action you wish to perform.\n"
         "Action ( Create (c) | Edit (e) | Recheck (r) ): "
diff --git a/docs/Source/mixins/index.html b/docs/Source/mixins/index.html
index bb5f9c3c..c78a79a1 100644
--- a/docs/Source/mixins/index.html
+++ b/docs/Source/mixins/index.html
@@ -771,9 +771,9 @@ 

Source code in torrentfile\mixins.py -
38
-39
-40
@classmethod
+        
40
+41
+42
@classmethod
 def cb(cls, *args, **kwargs):
     """Do nothing."""
 
@@ -825,9 +825,7 @@

Source code in torrentfile\mixins.py -
42
-43
-44
+        
44
 45
 46
 47
@@ -835,7 +833,9 @@ 

49 50 51 -52

@classmethod
+52
+53
+54
@classmethod
 def set_callback(cls, func):
     """
     Assign a callback to the Hashing class.
@@ -933,9 +933,7 @@ 

Source code in torrentfile\mixins.py -
200
-201
-202
+        
202
 203
 204
 205
@@ -944,7 +942,9 @@ 

208 209 210 -211

def is_active(self) -> bool:
+211
+212
+213
def is_active(self) -> bool:
     """
     Test to see if there is an active progress bar for object.
 
@@ -953,7 +953,7 @@ 

bool : True if there is, otherwise False. """ - if hasattr(self, "prog"): + if not debug_is_on() and hasattr(self, "prog"): return True return False

@@ -980,9 +980,7 @@

Source code in torrentfile\mixins.py -
188
-189
-190
+        
190
 191
 192
 193
@@ -990,7 +988,9 @@ 

195 196 197 -198

def prog_close(self):
+198
+199
+200
def prog_close(self):
     """
     Finalize the last bits of progress bar.
 
@@ -1090,9 +1090,7 @@ 

Source code in torrentfile\mixins.py -
139
-140
-141
+        
141
 142
 143
 144
@@ -1119,7 +1117,9 @@ 

165 166 167 -168

def prog_start(
+168
+169
+170
def prog_start(
     self, total: int, path: str, length: int = 50, unit: str = None
 ):
     """
@@ -1195,9 +1195,7 @@ 

Source code in torrentfile\mixins.py -
170
-171
-172
+        
172
 173
 174
 175
@@ -1211,7 +1209,9 @@ 

183 184 185 -186

def prog_update(self, val: int):
+186
+187
+188
def prog_update(self, val: int):
     """
     Update progress bar.
 
@@ -1333,9 +1333,7 @@ 

Source code in torrentfile\mixins.py -
 73
- 74
- 75
+            
 75
  76
  77
  78
@@ -1363,7 +1361,9 @@ 

100 101 102 -103

def __init__(
+103
+104
+105
def __init__(
     self, total: int, title: str, length: int, unit: str, start: int
 ):
     """
@@ -1450,9 +1450,7 @@ 

Source code in torrentfile\mixins.py -
105
-106
-107
+        
107
 108
 109
 110
@@ -1473,7 +1471,9 @@ 

125 126 127 -128

def get_progress(self) -> str:
+128
+129
+130
def get_progress(self) -> str:
     """
     Return the size of the filled portion of the progress bar.
 
@@ -1579,9 +1579,7 @@ 

Source code in torrentfile\mixins.py -
214
-215
-216
+        
216
 217
 218
 219
@@ -1619,7 +1617,9 @@ 

251 252 253 -254

def waiting(msg: str, flag: list, timeout: int = 20):
+254
+255
+256
def waiting(msg: str, flag: list, timeout: int = 20):
     """
     Show loading message while thread completes processing.
 
diff --git a/docs/Source/rebuild/index.html b/docs/Source/rebuild/index.html
index 295ca70a..e2c3ae52 100644
--- a/docs/Source/rebuild/index.html
+++ b/docs/Source/rebuild/index.html
@@ -757,8 +757,10 @@ 

- directory where torrents will be re-assembled

-

Construct the assembler object.

-

Takes two paths as parameters, +

Reassemble given torrent file from given cli arguments.

+

Rebuild metafiles and contents into their original directory +structure as much as possible in the destination directory. +Takes two paths as parameters, - file or directory containing 1 or more torrent meta files - path to where the contents are belived to be located.

@@ -840,10 +842,14 @@

463 464 465 -466

def __init__(self, metafiles: list, contents: list, dest: str):
+466
+467
+468
def __init__(self, metafiles: list, contents: list, dest: str):
     """
-    Construct the assembler object.
+    Reassemble given torrent file from given cli arguments.
 
+    Rebuild metafiles and contents into their original directory
+    structure as much as possible in the destination directory.
     Takes two paths as parameters,
     - file or directory containing 1 or more torrent meta files
     - path to where the contents are belived to be located.
@@ -921,8 +927,7 @@ 

Source code in torrentfile\rebuild.py -
488
-489
+        
489
 490
 491
 492
@@ -935,7 +940,8 @@ 

499 500 501 -502

def assemble_torrents(self):
+502
+503
def assemble_torrents(self):
     """
     Assemble collection of torrent files into original structure.
 
@@ -976,8 +982,7 @@ 

Source code in torrentfile\rebuild.py -
504
-505
+        
505
 506
 507
 508
@@ -985,7 +990,8 @@ 

510 511 512 -513

def rebuild(self, metafile: Metadata) -> None:
+513
+514
def rebuild(self, metafile: Metadata) -> None:
     """
     Build the torrent file structure from contents of directory.
 
@@ -1688,7 +1694,7 @@ 

""" with open(path, "rb") as fd: if self.start: - fd.read(self.start) + fd.seek(self.start) if self.stop != -1: partial = fd.read(self.stop - self.start) else: diff --git a/docs/Source/torrent/index.html b/docs/Source/torrent/index.html index 9e132fd3..c103a90b 100644 --- a/docs/Source/torrent/index.html +++ b/docs/Source/torrent/index.html @@ -1832,20 +1832,20 @@

Source code in torrentfile\torrent.py -
661
-662
-663
-664
-665
-666
-667
-668
-669
-670
-671
-672
-673
-674
def __init__(self, **kwargs):
+            
647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
def __init__(self, **kwargs):
     """
     Create Bittorrent v1 v2 hybrid metafiles.
     """
@@ -1890,26 +1890,26 @@ 

Source code in torrentfile\torrent.py -
676
+        
662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
 677
 678
 679
 680
-681
-682
-683
-684
-685
-686
-687
-688
-689
-690
-691
-692
-693
-694
-695
def assemble(self):
+681
def assemble(self):
     """
     Assemble the parts of the torrentfile into meta dictionary.
     """
@@ -2180,7 +2180,6 @@ 

Construct the Hybrid torrent meta file with provided parameters.

-

DEPRECATED

@@ -2214,19 +2213,19 @@

Source code in torrentfile\torrent.py -

565
+            
555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
 566
-567
-568
-569
-570
-571
-572
-573
-574
-575
-576
-577
def __init__(self, **kwargs):
+567
def __init__(self, **kwargs):
     """
     Create Bittorrent v1 v2 hybrid metafiles.
     """
@@ -2267,34 +2266,29 @@ 

Assemble the parts of the torrentfile into meta dictionary.

-

DEPRECATED

Source code in torrentfile\torrent.py -
579
+        
569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
 580
 581
 582
 583
 584
 585
-586
-587
-588
-589
-590
-591
-592
-593
-594
-595
-596
-597
-598
def assemble(self):
+586
def assemble(self):
     """
     Assemble the parts of the torrentfile into meta dictionary.
-
-    **DEPRECATED**
     """
     info = self.meta["info"]
     info["meta version"] = 2
@@ -2340,7 +2334,6 @@ 

Class for creating Bittorrent meta v2 files.

-

DEPRECATED

@@ -2371,7 +2364,6 @@

Construct TorrentFileV2 Class instance from given parameters.

-

DEPRECATED

@@ -2402,7 +2394,9 @@

Source code in torrentfile\torrent.py -

479
+            
477
+478
+479
 480
 481
 482
@@ -2414,16 +2408,10 @@ 

488 489 490 -491 -492 -493 -494 -495

def __init__(self, **kwargs):
+491
def __init__(self, **kwargs):
     """
     Construct `TorrentFileV2` Class instance from given parameters.
 
-    **DEPRECATED**
-
     Parameters
     ----------
     **kwargs : dict
@@ -2463,7 +2451,6 @@ 

Assemble then return the meta dictionary for encoding.

-

DEPRECATED

@@ -2492,7 +2479,11 @@

Source code in torrentfile\torrent.py -

497
+        
493
+494
+495
+496
+497
 498
 499
 500
@@ -2506,18 +2497,10 @@ 

508 509 510 -511 -512 -513 -514 -515 -516 -517

def assemble(self):
+511
def assemble(self):
     """
     Assemble then return the meta dictionary for encoding.
 
-    **DEPRECATED**
-
     Returns
     -------
     meta : dict
diff --git a/docs/Source/utils/index.html b/docs/Source/utils/index.html
index 7fce81b2..5d5d5737 100644
--- a/docs/Source/utils/index.html
+++ b/docs/Source/utils/index.html
@@ -591,6 +591,13 @@
     copypath()
   
   
+
+        
+          
  • + + debug_is_on() + +
  • @@ -654,6 +661,13 @@ path_stat() +
  • + +
  • + + toggle_debug_mode() + +
  • @@ -1263,6 +1277,68 @@

    +

    +debug_is_on() -> bool + +#

    + + +
    + +

    Return True if debug mode is on in environment variables.

    + + + + + + + + + + + + + + +
    RETURNSDESCRIPTION
    + + bool + + +

    is debug mode on

    +
    + +
    + Source code in torrentfile\utils.py +
    417
    +418
    +419
    +420
    +421
    +422
    +423
    +424
    +425
    +426
    def debug_is_on() -> bool:
    +    """
    +    Return True if debug mode is on in environment variables.
    +
    +    Returns
    +    -------
    +    bool
    +        is debug mode on
    +    """
    +    return os.environ["TORRENTFILE_DEBUG"] == "ON"
    +
    +
    +
    + +
    + +
    + + +

    filelist_total(pathstring: str) -> os.PathLike @@ -2292,6 +2368,70 @@

    +
    + + + +

    +toggle_debug_mode(switch_on: bool) + +#

    + + +
    + +

    Switch the environment variable debug indicator on or off.

    + + + + + + + + + + + + + + +
    PARAMETERDESCRIPTION
    switch_on +

    if true turn debug mode on otherwise off

    +

    + + TYPE: + bool + +

    +
    + +
    + Source code in torrentfile\utils.py +
    405
    +406
    +407
    +408
    +409
    +410
    +411
    +412
    +413
    +414
    def toggle_debug_mode(switch_on: bool):
    +    """
    +    Switch the environment variable debug indicator on or off.
    +
    +    Parameters
    +    ----------
    +    switch_on : bool
    +        if true turn debug mode on otherwise off
    +    """
    +    os.environ["TORRENTFILE_DEBUG"] = "ON" if switch_on else "OFF"
    +
    +
    +
    + +
    + diff --git a/docs/api/index.html b/docs/api/index.html index 1512ca86..83c8b552 100644 --- a/docs/api/index.html +++ b/docs/api/index.html @@ -1017,6 +1017,10 @@

    Utils ModuleCopy the file located at source to dest. +
  • debug_is_on() +(bool) + Return True if debug mode is on in environment variables.
  • +
  • get_file_list(path) (list :) Return a sorted list of file paths contained in directory.
  • @@ -1049,6 +1053,10 @@

    Utils Module(list) Calculate directory statistics. +
  • toggle_debug_mode(switch_on) + + Switch the environment variable debug indicator on or off.
  • + @@ -1331,6 +1339,8 @@

    Hasher ModuleCoverage

    + diff --git a/docs/changelog/index.html b/docs/changelog/index.html index 3e1a2aee..f37c5ff9 100644 --- a/docs/changelog/index.html +++ b/docs/changelog/index.html @@ -321,6 +321,20 @@
      +
    • + + Version 0.8.5 + + +
    • + +
    • + + Version 0.8.4 + + +
    • +
    • Version 0.8.3 @@ -895,6 +909,23 @@

      TorrentFile#

      +

      Version 0.8.5#

      +
        +
      • Fixed bug with linux platforms not installing a binary cli command
      • +
      • Fixed debug logging errors with the rebuild command
      • +
      • Improved log message readability
      • +
      • Compatability upgrades for torrentfileQt synchronization
      • +
      • Added coverage details to documentation folder
      • +
      +
      +

      Version 0.8.4#

      +
        +
      • Documentation Updates
      • +
      • Fixed logging issues with rebuild module.
      • +
      • Improved algorithm for rebuild module.
      • +
      • Improved testing for rebuild module.
      • +
      +

      Version 0.8.3#

      • Added the callback mixin to the rebuild module
      • diff --git a/docs/htmlcover/coverage_html.js b/docs/htmlcover/coverage_html.js new file mode 100644 index 00000000..1c4eb988 --- /dev/null +++ b/docs/htmlcover/coverage_html.js @@ -0,0 +1,604 @@ +// Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +// For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt + +// Coverage.py HTML report browser code. +/*jslint browser: true, sloppy: true, vars: true, plusplus: true, maxerr: 50, indent: 4 */ +/*global coverage: true, document, window, $ */ + +coverage = {}; + +// General helpers +function debounce(callback, wait) { + let timeoutId = null; + return function(...args) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + callback.apply(this, args); + }, wait); + }; +}; + +function checkVisible(element) { + const rect = element.getBoundingClientRect(); + const viewBottom = Math.max(document.documentElement.clientHeight, window.innerHeight); + const viewTop = 30; + return !(rect.bottom < viewTop || rect.top >= viewBottom); +} + +function on_click(sel, fn) { + const elt = document.querySelector(sel); + if (elt) { + elt.addEventListener("click", fn); + } +} + +// Helpers for table sorting +function getCellValue(row, column = 0) { + const cell = row.cells[column] + if (cell.childElementCount == 1) { + const child = cell.firstElementChild + if (child instanceof HTMLTimeElement && child.dateTime) { + return child.dateTime + } else if (child instanceof HTMLDataElement && child.value) { + return child.value + } + } + return cell.innerText || cell.textContent; +} + +function rowComparator(rowA, rowB, column = 0) { + let valueA = getCellValue(rowA, column); + let valueB = getCellValue(rowB, column); + if (!isNaN(valueA) && !isNaN(valueB)) { + return valueA - valueB + } + return valueA.localeCompare(valueB, undefined, {numeric: true}); +} + +function sortColumn(th) { + // Get the current sorting direction of the selected header, + // clear state on other headers and then set the new sorting direction + const currentSortOrder = th.getAttribute("aria-sort"); + [...th.parentElement.cells].forEach(header => header.setAttribute("aria-sort", "none")); + if (currentSortOrder === "none") { + th.setAttribute("aria-sort", th.dataset.defaultSortOrder || "ascending"); + } else { + th.setAttribute("aria-sort", currentSortOrder === "ascending" ? "descending" : "ascending"); + } + + const column = [...th.parentElement.cells].indexOf(th) + + // Sort all rows and afterwards append them in order to move them in the DOM + Array.from(th.closest("table").querySelectorAll("tbody tr")) + .sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (th.getAttribute("aria-sort") === "ascending" ? 1 : -1)) + .forEach(tr => tr.parentElement.appendChild(tr) ); +} + +// Find all the elements with data-shortcut attribute, and use them to assign a shortcut key. +coverage.assign_shortkeys = function () { + document.querySelectorAll("[data-shortcut]").forEach(element => { + document.addEventListener("keypress", event => { + if (event.target.tagName.toLowerCase() === "input") { + return; // ignore keypress from search filter + } + if (event.key === element.dataset.shortcut) { + element.click(); + } + }); + }); +}; + +// Create the events for the filter box. +coverage.wire_up_filter = function () { + // Cache elements. + const table = document.querySelector("table.index"); + const table_body_rows = table.querySelectorAll("tbody tr"); + const no_rows = document.getElementById("no_rows"); + + // Observe filter keyevents. + document.getElementById("filter").addEventListener("input", debounce(event => { + // Keep running total of each metric, first index contains number of shown rows + const totals = new Array(table.rows[0].cells.length).fill(0); + // Accumulate the percentage as fraction + totals[totals.length - 1] = { "numer": 0, "denom": 0 }; + + // Hide / show elements. + table_body_rows.forEach(row => { + if (!row.cells[0].textContent.includes(event.target.value)) { + // hide + row.classList.add("hidden"); + return; + } + + // show + row.classList.remove("hidden"); + totals[0]++; + + for (let column = 1; column < totals.length; column++) { + // Accumulate dynamic totals + cell = row.cells[column] + if (column === totals.length - 1) { + // Last column contains percentage + const [numer, denom] = cell.dataset.ratio.split(" "); + totals[column]["numer"] += parseInt(numer, 10); + totals[column]["denom"] += parseInt(denom, 10); + } else { + totals[column] += parseInt(cell.textContent, 10); + } + } + }); + + // Show placeholder if no rows will be displayed. + if (!totals[0]) { + // Show placeholder, hide table. + no_rows.style.display = "block"; + table.style.display = "none"; + return; + } + + // Hide placeholder, show table. + no_rows.style.display = null; + table.style.display = null; + + const footer = table.tFoot.rows[0]; + // Calculate new dynamic sum values based on visible rows. + for (let column = 1; column < totals.length; column++) { + // Get footer cell element. + const cell = footer.cells[column]; + + // Set value into dynamic footer cell element. + if (column === totals.length - 1) { + // Percentage column uses the numerator and denominator, + // and adapts to the number of decimal places. + const match = /\.([0-9]+)/.exec(cell.textContent); + const places = match ? match[1].length : 0; + const { numer, denom } = totals[column]; + cell.dataset.ratio = `${numer} ${denom}`; + // Check denom to prevent NaN if filtered files contain no statements + cell.textContent = denom + ? `${(numer * 100 / denom).toFixed(places)}%` + : `${(100).toFixed(places)}%`; + } else { + cell.textContent = totals[column]; + } + } + })); + + // Trigger change event on setup, to force filter on page refresh + // (filter value may still be present). + document.getElementById("filter").dispatchEvent(new Event("input")); +}; + +coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2"; + +// Loaded on index.html +coverage.index_ready = function () { + coverage.assign_shortkeys(); + coverage.wire_up_filter(); + document.querySelectorAll("[data-sortable] th[aria-sort]").forEach( + th => th.addEventListener("click", e => sortColumn(e.target)) + ); + + // Look for a localStorage item containing previous sort settings: + const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE); + + if (stored_list) { + const {column, direction} = JSON.parse(stored_list); + const th = document.querySelector("[data-sortable]").tHead.rows[0].cells[column]; + th.setAttribute("aria-sort", direction === "ascending" ? "descending" : "ascending"); + th.click() + } + + // Watch for page unload events so we can save the final sort settings: + window.addEventListener("unload", function () { + const th = document.querySelector('[data-sortable] th[aria-sort="ascending"], [data-sortable] [aria-sort="descending"]'); + if (!th) { + return; + } + localStorage.setItem(coverage.INDEX_SORT_STORAGE, JSON.stringify({ + column: [...th.parentElement.cells].indexOf(th), + direction: th.getAttribute("aria-sort"), + })); + }); + + on_click(".button_prev_file", coverage.to_prev_file); + on_click(".button_next_file", coverage.to_next_file); + + on_click(".button_show_hide_help", coverage.show_hide_help); +}; + +// -- pyfile stuff -- + +coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS"; + +coverage.pyfile_ready = function () { + // If we're directed to a particular line number, highlight the line. + var frag = location.hash; + if (frag.length > 2 && frag[1] === 't') { + document.querySelector(frag).closest(".n").classList.add("highlight"); + coverage.set_sel(parseInt(frag.substr(2), 10)); + } else { + coverage.set_sel(0); + } + + on_click(".button_toggle_run", coverage.toggle_lines); + on_click(".button_toggle_mis", coverage.toggle_lines); + on_click(".button_toggle_exc", coverage.toggle_lines); + on_click(".button_toggle_par", coverage.toggle_lines); + + on_click(".button_next_chunk", coverage.to_next_chunk_nicely); + on_click(".button_prev_chunk", coverage.to_prev_chunk_nicely); + on_click(".button_top_of_page", coverage.to_top); + on_click(".button_first_chunk", coverage.to_first_chunk); + + on_click(".button_prev_file", coverage.to_prev_file); + on_click(".button_next_file", coverage.to_next_file); + on_click(".button_to_index", coverage.to_index); + + on_click(".button_show_hide_help", coverage.show_hide_help); + + coverage.filters = undefined; + try { + coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE); + } catch(err) {} + + if (coverage.filters) { + coverage.filters = JSON.parse(coverage.filters); + } + else { + coverage.filters = {run: false, exc: true, mis: true, par: true}; + } + + for (cls in coverage.filters) { + coverage.set_line_visibilty(cls, coverage.filters[cls]); + } + + coverage.assign_shortkeys(); + coverage.init_scroll_markers(); + coverage.wire_up_sticky_header(); + + // Rebuild scroll markers when the window height changes. + window.addEventListener("resize", coverage.build_scroll_markers); +}; + +coverage.toggle_lines = function (event) { + const btn = event.target.closest("button"); + const category = btn.value + const show = !btn.classList.contains("show_" + category); + coverage.set_line_visibilty(category, show); + coverage.build_scroll_markers(); + coverage.filters[category] = show; + try { + localStorage.setItem(coverage.LINE_FILTERS_STORAGE, JSON.stringify(coverage.filters)); + } catch(err) {} +}; + +coverage.set_line_visibilty = function (category, should_show) { + const cls = "show_" + category; + const btn = document.querySelector(".button_toggle_" + category); + if (btn) { + if (should_show) { + document.querySelectorAll("#source ." + category).forEach(e => e.classList.add(cls)); + btn.classList.add(cls); + } + else { + document.querySelectorAll("#source ." + category).forEach(e => e.classList.remove(cls)); + btn.classList.remove(cls); + } + } +}; + +// Return the nth line div. +coverage.line_elt = function (n) { + return document.getElementById("t" + n)?.closest("p"); +}; + +// Set the selection. b and e are line numbers. +coverage.set_sel = function (b, e) { + // The first line selected. + coverage.sel_begin = b; + // The next line not selected. + coverage.sel_end = (e === undefined) ? b+1 : e; +}; + +coverage.to_top = function () { + coverage.set_sel(0, 1); + coverage.scroll_window(0); +}; + +coverage.to_first_chunk = function () { + coverage.set_sel(0, 1); + coverage.to_next_chunk(); +}; + +coverage.to_prev_file = function () { + window.location = document.getElementById("prevFileLink").href; +} + +coverage.to_next_file = function () { + window.location = document.getElementById("nextFileLink").href; +} + +coverage.to_index = function () { + location.href = document.getElementById("indexLink").href; +} + +coverage.show_hide_help = function () { + const helpCheck = document.getElementById("help_panel_state") + helpCheck.checked = !helpCheck.checked; +} + +// Return a string indicating what kind of chunk this line belongs to, +// or null if not a chunk. +coverage.chunk_indicator = function (line_elt) { + const classes = line_elt?.className; + if (!classes) { + return null; + } + const match = classes.match(/\bshow_\w+\b/); + if (!match) { + return null; + } + return match[0]; +}; + +coverage.to_next_chunk = function () { + const c = coverage; + + // Find the start of the next colored chunk. + var probe = c.sel_end; + var chunk_indicator, probe_line; + while (true) { + probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + chunk_indicator = c.chunk_indicator(probe_line); + if (chunk_indicator) { + break; + } + probe++; + } + + // There's a next chunk, `probe` points to it. + var begin = probe; + + // Find the end of this chunk. + var next_indicator = chunk_indicator; + while (next_indicator === chunk_indicator) { + probe++; + probe_line = c.line_elt(probe); + next_indicator = c.chunk_indicator(probe_line); + } + c.set_sel(begin, probe); + c.show_selection(); +}; + +coverage.to_prev_chunk = function () { + const c = coverage; + + // Find the end of the prev colored chunk. + var probe = c.sel_begin-1; + var probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + var chunk_indicator = c.chunk_indicator(probe_line); + while (probe > 1 && !chunk_indicator) { + probe--; + probe_line = c.line_elt(probe); + if (!probe_line) { + return; + } + chunk_indicator = c.chunk_indicator(probe_line); + } + + // There's a prev chunk, `probe` points to its last line. + var end = probe+1; + + // Find the beginning of this chunk. + var prev_indicator = chunk_indicator; + while (prev_indicator === chunk_indicator) { + probe--; + if (probe <= 0) { + return; + } + probe_line = c.line_elt(probe); + prev_indicator = c.chunk_indicator(probe_line); + } + c.set_sel(probe+1, end); + c.show_selection(); +}; + +// Returns 0, 1, or 2: how many of the two ends of the selection are on +// the screen right now? +coverage.selection_ends_on_screen = function () { + if (coverage.sel_begin === 0) { + return 0; + } + + const begin = coverage.line_elt(coverage.sel_begin); + const end = coverage.line_elt(coverage.sel_end-1); + + return ( + (checkVisible(begin) ? 1 : 0) + + (checkVisible(end) ? 1 : 0) + ); +}; + +coverage.to_next_chunk_nicely = function () { + if (coverage.selection_ends_on_screen() === 0) { + // The selection is entirely off the screen: + // Set the top line on the screen as selection. + + // This will select the top-left of the viewport + // As this is most likely the span with the line number we take the parent + const line = document.elementFromPoint(0, 0).parentElement; + if (line.parentElement !== document.getElementById("source")) { + // The element is not a source line but the header or similar + coverage.select_line_or_chunk(1); + } else { + // We extract the line number from the id + coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); + } + } + coverage.to_next_chunk(); +}; + +coverage.to_prev_chunk_nicely = function () { + if (coverage.selection_ends_on_screen() === 0) { + // The selection is entirely off the screen: + // Set the lowest line on the screen as selection. + + // This will select the bottom-left of the viewport + // As this is most likely the span with the line number we take the parent + const line = document.elementFromPoint(document.documentElement.clientHeight-1, 0).parentElement; + if (line.parentElement !== document.getElementById("source")) { + // The element is not a source line but the header or similar + coverage.select_line_or_chunk(coverage.lines_len); + } else { + // We extract the line number from the id + coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10)); + } + } + coverage.to_prev_chunk(); +}; + +// Select line number lineno, or if it is in a colored chunk, select the +// entire chunk +coverage.select_line_or_chunk = function (lineno) { + var c = coverage; + var probe_line = c.line_elt(lineno); + if (!probe_line) { + return; + } + var the_indicator = c.chunk_indicator(probe_line); + if (the_indicator) { + // The line is in a highlighted chunk. + // Search backward for the first line. + var probe = lineno; + var indicator = the_indicator; + while (probe > 0 && indicator === the_indicator) { + probe--; + probe_line = c.line_elt(probe); + if (!probe_line) { + break; + } + indicator = c.chunk_indicator(probe_line); + } + var begin = probe + 1; + + // Search forward for the last line. + probe = lineno; + indicator = the_indicator; + while (indicator === the_indicator) { + probe++; + probe_line = c.line_elt(probe); + indicator = c.chunk_indicator(probe_line); + } + + coverage.set_sel(begin, probe); + } + else { + coverage.set_sel(lineno); + } +}; + +coverage.show_selection = function () { + // Highlight the lines in the chunk + document.querySelectorAll("#source .highlight").forEach(e => e.classList.remove("highlight")); + for (let probe = coverage.sel_begin; probe < coverage.sel_end; probe++) { + coverage.line_elt(probe).querySelector(".n").classList.add("highlight"); + } + + coverage.scroll_to_selection(); +}; + +coverage.scroll_to_selection = function () { + // Scroll the page if the chunk isn't fully visible. + if (coverage.selection_ends_on_screen() < 2) { + const element = coverage.line_elt(coverage.sel_begin); + coverage.scroll_window(element.offsetTop - 60); + } +}; + +coverage.scroll_window = function (to_pos) { + window.scroll({top: to_pos, behavior: "smooth"}); +}; + +coverage.init_scroll_markers = function () { + // Init some variables + coverage.lines_len = document.querySelectorAll('#source > p').length; + + // Build html + coverage.build_scroll_markers(); +}; + +coverage.build_scroll_markers = function () { + const temp_scroll_marker = document.getElementById('scroll_marker') + if (temp_scroll_marker) temp_scroll_marker.remove(); + // Don't build markers if the window has no scroll bar. + if (document.body.scrollHeight <= window.innerHeight) { + return; + } + + const marker_scale = window.innerHeight / document.body.scrollHeight; + const line_height = Math.min(Math.max(3, window.innerHeight / coverage.lines_len), 10); + + let previous_line = -99, last_mark, last_top; + + const scroll_marker = document.createElement("div"); + scroll_marker.id = "scroll_marker"; + document.getElementById('source').querySelectorAll( + 'p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par' + ).forEach(element => { + const line_top = Math.floor(element.offsetTop * marker_scale); + const line_number = parseInt(element.querySelector(".n a").id.substr(1)); + + if (line_number === previous_line + 1) { + // If this solid missed block just make previous mark higher. + last_mark.style.height = `${line_top + line_height - last_top}px`; + } else { + // Add colored line in scroll_marker block. + last_mark = document.createElement("div"); + last_mark.id = `m${line_number}`; + last_mark.classList.add("marker"); + last_mark.style.height = `${line_height}px`; + last_mark.style.top = `${line_top}px`; + scroll_marker.append(last_mark); + last_top = line_top; + } + + previous_line = line_number; + }); + + // Append last to prevent layout calculation + document.body.append(scroll_marker); +}; + +coverage.wire_up_sticky_header = function () { + const header = document.querySelector('header'); + const header_bottom = ( + header.querySelector('.content h2').getBoundingClientRect().top - + header.getBoundingClientRect().top + ); + + function updateHeader() { + if (window.scrollY > header_bottom) { + header.classList.add('sticky'); + } else { + header.classList.remove('sticky'); + } + } + + window.addEventListener('scroll', updateHeader); + updateHeader(); +}; + +document.addEventListener("DOMContentLoaded", () => { + if (document.body.classList.contains("indexfile")) { + coverage.index_ready(); + } else { + coverage.pyfile_ready(); + } +}); diff --git a/docs/htmlcover/d_1bc82e0ab2fcb2ec___init___py.html b/docs/htmlcover/d_1bc82e0ab2fcb2ec___init___py.html new file mode 100644 index 00000000..1864f3ec --- /dev/null +++ b/docs/htmlcover/d_1bc82e0ab2fcb2ec___init___py.html @@ -0,0 +1,137 @@ + + + + + Coverage for torrentfile\__init__.py: 100% + + + + + +
        +
        +

        + Coverage for torrentfile\__init__.py: + 100% +

        + +

        + 7 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Torrentfile can create Bittorrent metafiles for any content. 

        +

        21 

        +

        22Both Bittorrent v1 and v2 are fully supported. Also included is a torrent 

        +

        23torrent file checker, which can verify a .torrent file is formated correctly 

        +

        24as well as validate files and folders against metadata. 

        +

        25 

        +

        26Modules: 

        +

        27 metafile: Creation/Validation of v1 .torrent files. 

        +

        28 metafile2: Creation/Validation of v2 .torrent files. 

        +

        29 torrentfile: torrentfiles Command Line Interface implementation. 

        +

        30 exceptions: Custom Exceptions used in package. 

        +

        31 utils: Utilities used throughout package. 

        +

        32""" 

        +

        33from torrentfile.cli import execute, main 

        +

        34from torrentfile.commands import create, edit, info, magnet, recheck 

        +

        35from torrentfile.utils import toggle_debug_mode 

        +

        36from torrentfile.version import __version__ 

        +

        37 

        +

        38toggle_debug_mode(False) 

        +

        39 

        +

        40__all__ = ["execute", "create", "edit", "info", "magnet", "recheck", "main"] 

        +
        + + + diff --git a/docs/htmlcover/d_1bc82e0ab2fcb2ec___main___py.html b/docs/htmlcover/d_1bc82e0ab2fcb2ec___main___py.html new file mode 100644 index 00000000..b1c67cb3 --- /dev/null +++ b/docs/htmlcover/d_1bc82e0ab2fcb2ec___main___py.html @@ -0,0 +1,129 @@ + + + + + Coverage for torrentfile\__main__.py: 100% + + + + + +
        +
        +

        + Coverage for torrentfile\__main__.py: + 100% +

        + +

        + 5 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Enable calling the package directly with python from the command line. 

        +

        21""" 

        +

        22 

        +

        23from torrentfile import execute 

        +

        24 

        +

        25 

        +

        26def main(): 

        +

        27 """Program entry point.""" 

        +

        28 execute() 

        +

        29 

        +

        30 

        +

        31if __name__ == "__main__": 

        +

        32 main() # pragma: nocover 

        +
        + + + diff --git a/docs/htmlcover/d_1bc82e0ab2fcb2ec_cli_py.html b/docs/htmlcover/d_1bc82e0ab2fcb2ec_cli_py.html new file mode 100644 index 00000000..6f029cf2 --- /dev/null +++ b/docs/htmlcover/d_1bc82e0ab2fcb2ec_cli_py.html @@ -0,0 +1,705 @@ + + + + + Coverage for torrentfile\cli.py: 100% + + + + + +
        +
        +

        + Coverage for torrentfile\cli.py: + 100% +

        + +

        + 115 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Command Line Interface for TorrentFile project. 

        +

        21 

        +

        22This module provides the primary command line argument parser for 

        +

        23the torrentfile package. The main_script function is automatically 

        +

        24invoked when called from command line, and parses accompanying arguments. 

        +

        25 

        +

        26Functions 

        +

        27--------- 

        +

        28main_script : 

        +

        29 process command line arguments and run program. 

        +

        30activate_logger : 

        +

        31 turns on debug mode and logging facility. 

        +

        32 

        +

        33Classes 

        +

        34------- 

        +

        35Config : class 

        +

        36 controls logging configuration 

        +

        37TorrentFileHelpFormatter : HelpFormatter 

        +

        38 the command line help message formatter 

        +

        39""" 

        +

        40 

        +

        41import io 

        +

        42import logging 

        +

        43import sys 

        +

        44from argparse import ArgumentParser, HelpFormatter 

        +

        45from typing import Optional 

        +

        46 

        +

        47from torrentfile.commands import create, edit, info, magnet, rebuild, recheck 

        +

        48from torrentfile.interactive import select_action 

        +

        49from torrentfile.utils import toggle_debug_mode 

        +

        50from torrentfile.version import __version__ as version 

        +

        51 

        +

        52 

        +

        53class Config: 

        +

        54 """ 

        +

        55 Class the controls the logging configuration and output settings. 

        +

        56 

        +

        57 Controls the logging level, or whether to app should operate in quiet mode. 

        +

        58 """ 

        +

        59 

        +

        60 @staticmethod 

        +

        61 def activate_quiet(): 

        +

        62 """ 

        +

        63 Activate quiet mode for the duration of the programs life. 

        +

        64 

        +

        65 When quiet mode is enabled, no logging, progress or state information 

        +

        66 is output to the terminal 

        +

        67 """ 

        +

        68 if sys.stdout or sys.stderr: 

        +

        69 sys.stdout = io.StringIO() 

        +

        70 sys.stderr = io.StringIO() 

        +

        71 

        +

        72 @staticmethod 

        +

        73 def activate_logger(): 

        +

        74 """ 

        +

        75 Activate the builtin logging mechanism when passed debug flag from CLI. 

        +

        76 """ 

        +

        77 logging.basicConfig(level=logging.WARNING) 

        +

        78 logger = logging.getLogger() 

        +

        79 console_handler = logging.StreamHandler(stream=sys.stderr) 

        +

        80 stream_formatter = logging.Formatter( 

        +

        81 "[%(asctime)s] [%(levelno)s] %(message)s", 

        +

        82 datefmt="%H:%M:%S", 

        +

        83 style="%", 

        +

        84 ) 

        +

        85 console_handler.setFormatter(stream_formatter) 

        +

        86 console_handler.setLevel(logging.DEBUG) 

        +

        87 logger.setLevel(logging.DEBUG) 

        +

        88 logger.addHandler(console_handler) 

        +

        89 logger.debug("Debug: ON") 

        +

        90 toggle_debug_mode(True) 

        +

        91 

        +

        92 

        +

        93class TorrentFileHelpFormatter(HelpFormatter): 

        +

        94 """ 

        +

        95 Formatting class for help tips provided by the CLI. 

        +

        96 

        +

        97 Subclasses Argparse.HelpFormatter. 

        +

        98 """ 

        +

        99 

        +

        100 def __init__(self, prog, width=45, max_help_positions=45): 

        +

        101 """ 

        +

        102 Construct HelpFormat class for usage output. 

        +

        103 

        +

        104 Parameters 

        +

        105 ---------- 

        +

        106 prog : str 

        +

        107 Name of the program. 

        +

        108 width : int 

        +

        109 Max width of help message output. 

        +

        110 max_help_positions : int 

        +

        111 max length until line wrap. 

        +

        112 """ 

        +

        113 super().__init__( 

        +

        114 prog, width=width, max_help_position=max_help_positions 

        +

        115 ) 

        +

        116 

        +

        117 def _split_lines(self, text: str, _: int) -> list: 

        +

        118 """ 

        +

        119 Split multiline help messages and remove indentation. 

        +

        120 

        +

        121 Parameters 

        +

        122 ---------- 

        +

        123 text : str 

        +

        124 text that needs to be split 

        +

        125 _ : int 

        +

        126 max width for line. 

        +

        127 

        +

        128 Returns 

        +

        129 ------- 

        +

        130 list : 

        +

        131 split into multiline text list 

        +

        132 """ 

        +

        133 lines = text.split("\n") 

        +

        134 return [line.strip() for line in lines if line] 

        +

        135 

        +

        136 def _format_text(self, text: str) -> str: 

        +

        137 """ 

        +

        138 Format text for cli usage messages. 

        +

        139 

        +

        140 Parameters 

        +

        141 ---------- 

        +

        142 text : str 

        +

        143 Pre-formatted text. 

        +

        144 

        +

        145 Returns 

        +

        146 ------- 

        +

        147 str 

        +

        148 Formatted text from input. 

        +

        149 """ 

        +

        150 text = text % dict(prog=self._prog) if "%(prog)" in text else text 

        +

        151 text = self._whitespace_matcher.sub(" ", text).strip() 

        +

        152 return text + "\n\n" 

        +

        153 

        +

        154 def _join_parts(self, part_strings: list) -> str: 

        +

        155 """ 

        +

        156 Combine different sections of the help message. 

        +

        157 

        +

        158 Parameters 

        +

        159 ---------- 

        +

        160 part_strings : list 

        +

        161 List of argument help messages and headers. 

        +

        162 

        +

        163 Returns 

        +

        164 ------- 

        +

        165 str 

        +

        166 Fully formatted help message for CLI. 

        +

        167 """ 

        +

        168 parts = self._format_headers(part_strings) 

        +

        169 return super()._join_parts(parts) 

        +

        170 

        +

        171 @staticmethod 

        +

        172 def _format_headers(parts: list) -> list: 

        +

        173 """ 

        +

        174 Format help message section headers. 

        +

        175 

        +

        176 Parameters 

        +

        177 ---------- 

        +

        178 parts : list 

        +

        179 List of individual lines for help message. 

        +

        180 

        +

        181 Returns 

        +

        182 ------- 

        +

        183 list 

        +

        184 Input list with formatted section headers. 

        +

        185 """ 

        +

        186 if parts and parts[0].startswith("usage:"): 

        +

        187 parts[0] = "Usage\n=====\n " + parts[0][6:] 

        +

        188 headings = [i for i in range(len(parts)) if parts[i].endswith(":\n")] 

        +

        189 for i in headings[::-1]: 

        +

        190 parts[i] = parts[i][:-2].title() 

        +

        191 underline = "".join(["\n", "-" * len(parts[i]), "\n"]) 

        +

        192 parts.insert(i + 1, underline) 

        +

        193 return parts 

        +

        194 

        +

        195 

        +

        196def execute(args: Optional[list] = None) -> list: 

        +

        197 """ 

        +

        198 Execute program with provided list of arguments. 

        +

        199 

        +

        200 If no arguments are given then it defaults to using 

        +

        201 sys.argv. This is the main entrypoint for the program 

        +

        202 and command line interface. 

        +

        203 

        +

        204 Parameters 

        +

        205 ---------- 

        +

        206 args : list 

        +

        207 Commandline arguments. default=None 

        +

        208 

        +

        209 Returns 

        +

        210 ------- 

        +

        211 list 

        +

        212 Depends on what the command line args were. 

        +

        213 """ 

        +

        214 toggle_debug_mode(False) 

        +

        215 if not args: 

        +

        216 if sys.argv[1:]: 

        +

        217 args = sys.argv[1:] 

        +

        218 else: 

        +

        219 args = ["-h"] 

        +

        220 

        +

        221 parser = ArgumentParser( 

        +

        222 "torrentfile", 

        +

        223 usage="torrentfile <options>", 

        +

        224 description=( 

        +

        225 "Command line tools for creating, editing, checking, building " 

        +

        226 "and interacting with Bittorrent metainfo files" 

        +

        227 ), 

        +

        228 prefix_chars="-", 

        +

        229 formatter_class=TorrentFileHelpFormatter, 

        +

        230 conflict_handler="resolve", 

        +

        231 ) 

        +

        232 

        +

        233 parser.add_argument( 

        +

        234 "-i", 

        +

        235 "--interactive", 

        +

        236 action="store_true", 

        +

        237 dest="interactive", 

        +

        238 help="select program options interactively", 

        +

        239 ) 

        +

        240 

        +

        241 parser.add_argument( 

        +

        242 "-q", 

        +

        243 "--quiet", 

        +

        244 help="Turn off all text output.", 

        +

        245 dest="quiet", 

        +

        246 action="store_true", 

        +

        247 ) 

        +

        248 

        +

        249 parser.add_argument( 

        +

        250 "-V", 

        +

        251 "--version", 

        +

        252 action="version", 

        +

        253 version=f"torrentfile v{version}", 

        +

        254 help="show program version and exit", 

        +

        255 ) 

        +

        256 

        +

        257 parser.add_argument( 

        +

        258 "-v", 

        +

        259 "--verbose", 

        +

        260 action="store_true", 

        +

        261 dest="debug", 

        +

        262 help="output debug information", 

        +

        263 ) 

        +

        264 

        +

        265 parser.set_defaults(func=parser.print_help) 

        +

        266 

        +

        267 subparsers = parser.add_subparsers( 

        +

        268 title="Commands", 

        +

        269 dest="command", 

        +

        270 metavar="create, edit, info, magnet, recheck, rebuild\n", 

        +

        271 ) 

        +

        272 

        +

        273 create_parser = subparsers.add_parser( 

        +

        274 "create", 

        +

        275 help="Create a new Bittorrent file.", 

        +

        276 prefix_chars="-", 

        +

        277 aliases=["c", "new", "build"], 

        +

        278 formatter_class=TorrentFileHelpFormatter, 

        +

        279 ) 

        +

        280 

        +

        281 create_parser.add_argument( 

        +

        282 "-a", 

        +

        283 "-t", 

        +

        284 "--announce", 

        +

        285 "--tracker", 

        +

        286 action="store", 

        +

        287 dest="announce", 

        +

        288 metavar="<url>", 

        +

        289 nargs="+", 

        +

        290 default=[], 

        +

        291 help="One or more space-seperated torrent tracker url(s).", 

        +

        292 ) 

        +

        293 

        +

        294 create_parser.add_argument( 

        +

        295 "-p", 

        +

        296 "--private", 

        +

        297 action="store_true", 

        +

        298 dest="private", 

        +

        299 help="Creates private torrent with multi-tracker and DHT turned off.", 

        +

        300 ) 

        +

        301 

        +

        302 create_parser.add_argument( 

        +

        303 "-s", 

        +

        304 "--source", 

        +

        305 action="store", 

        +

        306 dest="source", 

        +

        307 metavar="<source>", 

        +

        308 help="Add a source string. Useful for cross-seeding.", 

        +

        309 ) 

        +

        310 

        +

        311 create_parser.add_argument( 

        +

        312 "-m", 

        +

        313 "--magnet", 

        +

        314 action="store_true", 

        +

        315 dest="magnet", 

        +

        316 ) 

        +

        317 

        +

        318 create_parser.add_argument( 

        +

        319 "-c", 

        +

        320 "--comment", 

        +

        321 action="store", 

        +

        322 dest="comment", 

        +

        323 metavar="<comment>", 

        +

        324 help="Include a comment in file metadata", 

        +

        325 ) 

        +

        326 

        +

        327 create_parser.add_argument( 

        +

        328 "-o", 

        +

        329 "--out", 

        +

        330 action="store", 

        +

        331 dest="outfile", 

        +

        332 metavar="<path>", 

        +

        333 help="Explicitly specify the path to write the file.", 

        +

        334 ) 

        +

        335 

        +

        336 create_parser.add_argument( 

        +

        337 "--cwd", 

        +

        338 "--current", 

        +

        339 action="store_true", 

        +

        340 dest="cwd", 

        +

        341 help="*deprecated* Saving to current directory is default behaviour", 

        +

        342 ) 

        +

        343 

        +

        344 create_parser.add_argument( 

        +

        345 "--prog", 

        +

        346 "--progress", 

        +

        347 default="1", 

        +

        348 action="store", 

        +

        349 dest="progress", 

        +

        350 help=""" 

        +

        351 Set the progress bar level. 

        +

        352 Options = 0, 1 

        +

        353 (0) = Do not display progress bar. 

        +

        354 (1) = Display progress bar.(default) 

        +

        355 """, 

        +

        356 ) 

        +

        357 

        +

        358 create_parser.add_argument( 

        +

        359 "--meta-version", 

        +

        360 default="1", 

        +

        361 choices=["1", "2", "3"], 

        +

        362 action="store", 

        +

        363 dest="meta_version", 

        +

        364 metavar="<int>", 

        +

        365 help=""" 

        +

        366 Bittorrent metafile version. 

        +

        367 Options = 1, 2, 3 

        +

        368 (1) = Bittorrent v1 (Default) 

        +

        369 (2) = Bittorrent v2 

        +

        370 (3) = Bittorrent v1 & v2 hybrid 

        +

        371 """, 

        +

        372 ) 

        +

        373 

        +

        374 create_parser.add_argument( 

        +

        375 "--piece-length", 

        +

        376 action="store", 

        +

        377 dest="piece_length", 

        +

        378 metavar="<int>", 

        +

        379 help=""" 

        +

        380 (Default: <blank>) Number of bytes for per chunk of data transmitted 

        +

        381 by Bittorrent client. Acceptable values include integers 14-26 which 

        +

        382 will be interpreted as a perfect power of 2. e.g. 14 = 16KiB pieces. 

        +

        383 Examples:: [--piece-length 14] [--piece-length 20] 

        +

        384 """, 

        +

        385 ) 

        +

        386 

        +

        387 create_parser.add_argument( 

        +

        388 "-w", 

        +

        389 "--web-seed", 

        +

        390 action="store", 

        +

        391 dest="url_list", 

        +

        392 metavar="<url>", 

        +

        393 nargs="+", 

        +

        394 help="list of web addresses where torrent data exists (GetRight).", 

        +

        395 ) 

        +

        396 

        +

        397 create_parser.add_argument( 

        +

        398 "--http-seed", 

        +

        399 action="store", 

        +

        400 dest="httpseeds", 

        +

        401 metavar="<url>", 

        +

        402 nargs="+", 

        +

        403 help="list of URLs, addresses where content can be found (Hoffman).", 

        +

        404 ) 

        +

        405 

        +

        406 create_parser.add_argument( 

        +

        407 "content", 

        +

        408 action="store", 

        +

        409 metavar="<content>", 

        +

        410 nargs="?", 

        +

        411 help="Path to content file or directory", 

        +

        412 ) 

        +

        413 

        +

        414 create_parser.set_defaults(func=create) 

        +

        415 

        +

        416 edit_parser = subparsers.add_parser( 

        +

        417 "edit", 

        +

        418 help="""Edit existing torrent meta file.""", 

        +

        419 aliases=["e"], 

        +

        420 prefix_chars="-", 

        +

        421 formatter_class=TorrentFileHelpFormatter, 

        +

        422 ) 

        +

        423 

        +

        424 edit_parser.add_argument( 

        +

        425 "metafile", 

        +

        426 action="store", 

        +

        427 help="path to *.torrent file", 

        +

        428 metavar="<*.torrent>", 

        +

        429 ) 

        +

        430 

        +

        431 edit_parser.add_argument( 

        +

        432 "--tracker", 

        +

        433 action="store", 

        +

        434 dest="announce", 

        +

        435 metavar="<url>", 

        +

        436 nargs="+", 

        +

        437 help=""" 

        +

        438 Replace current list of tracker/announce urls with one or more space 

        +

        439 seperated Bittorrent tracker announce url(s). 

        +

        440 """, 

        +

        441 ) 

        +

        442 

        +

        443 edit_parser.add_argument( 

        +

        444 "--web-seed", 

        +

        445 action="store", 

        +

        446 dest="url_list", 

        +

        447 metavar="<url>", 

        +

        448 nargs="+", 

        +

        449 help="Replace current list of web-seed urls with one or more url(s)", 

        +

        450 ) 

        +

        451 

        +

        452 edit_parser.add_argument( 

        +

        453 "--http-seed", 

        +

        454 action="store", 

        +

        455 dest="httpseeds", 

        +

        456 metavar="<url>", 

        +

        457 nargs="+", 

        +

        458 help="replace all currently listed addresses with new list (Hoffman).", 

        +

        459 ) 

        +

        460 

        +

        461 edit_parser.add_argument( 

        +

        462 "--private", 

        +

        463 action="store_true", 

        +

        464 help="Make torrent private.", 

        +

        465 dest="private", 

        +

        466 ) 

        +

        467 

        +

        468 edit_parser.add_argument( 

        +

        469 "--comment", 

        +

        470 help="Replaces any existing comment with <comment>", 

        +

        471 metavar="<comment>", 

        +

        472 dest="comment", 

        +

        473 action="store", 

        +

        474 ) 

        +

        475 

        +

        476 edit_parser.add_argument( 

        +

        477 "--source", 

        +

        478 action="store", 

        +

        479 dest="source", 

        +

        480 metavar="<source>", 

        +

        481 help="Replaces current source with <source>", 

        +

        482 ) 

        +

        483 

        +

        484 edit_parser.set_defaults(func=edit) 

        +

        485 

        +

        486 info_parser = subparsers.add_parser( 

        +

        487 "info", 

        +

        488 help="Show detailed information about a torrent file.", 

        +

        489 aliases=["i"], 

        +

        490 prefix_chars="-", 

        +

        491 formatter_class=TorrentFileHelpFormatter, 

        +

        492 ) 

        +

        493 

        +

        494 info_parser.add_argument( 

        +

        495 "metafile", 

        +

        496 action="store", 

        +

        497 metavar="<*.torrent>", 

        +

        498 help="path to pre-existing torrent file.", 

        +

        499 ) 

        +

        500 

        +

        501 info_parser.set_defaults(func=info) 

        +

        502 

        +

        503 magnet_parser = subparsers.add_parser( 

        +

        504 "magnet", 

        +

        505 help="Generate magnet url from an existing Bittorrent meta file.", 

        +

        506 aliases=["m"], 

        +

        507 prefix_chars="-", 

        +

        508 formatter_class=TorrentFileHelpFormatter, 

        +

        509 ) 

        +

        510 

        +

        511 magnet_parser.add_argument( 

        +

        512 "metafile", 

        +

        513 action="store", 

        +

        514 help="Path to Bittorrent meta file.", 

        +

        515 metavar="<*.torrent>", 

        +

        516 ) 

        +

        517 

        +

        518 magnet_parser.set_defaults(func=magnet) 

        +

        519 

        +

        520 check_parser = subparsers.add_parser( 

        +

        521 "recheck", 

        +

        522 help="Gives a detailed look at how much of the torrent is available.", 

        +

        523 aliases=["check", "r"], 

        +

        524 prefix_chars="-", 

        +

        525 formatter_class=TorrentFileHelpFormatter, 

        +

        526 ) 

        +

        527 

        +

        528 check_parser.add_argument( 

        +

        529 "metafile", 

        +

        530 action="store", 

        +

        531 metavar="<*.torrent>", 

        +

        532 help="path to .torrent file.", 

        +

        533 ) 

        +

        534 

        +

        535 check_parser.add_argument( 

        +

        536 "content", 

        +

        537 action="store", 

        +

        538 metavar="<content>", 

        +

        539 help="path to content file or directory", 

        +

        540 ) 

        +

        541 

        +

        542 check_parser.set_defaults(func=recheck) 

        +

        543 

        +

        544 rebuild_parser = subparsers.add_parser( 

        +

        545 "rebuild", 

        +

        546 aliases=["build"], 

        +

        547 help="""Re-assemble files obtained from a bittorrent file into the 

        +

        548 appropriate file structure for re-seeding. Read documentation 

        +

        549 for more information, or use cases.""", 

        +

        550 formatter_class=TorrentFileHelpFormatter, 

        +

        551 ) 

        +

        552 

        +

        553 rebuild_parser.add_argument( 

        +

        554 "-m", 

        +

        555 "--metafiles", 

        +

        556 action="store", 

        +

        557 metavar="<*.torrent>", 

        +

        558 nargs="+", 

        +

        559 dest="metafiles", 

        +

        560 required=True, 

        +

        561 help="path(s) to .torrent file(s)/folder(s) containing .torrent files", 

        +

        562 ) 

        +

        563 

        +

        564 rebuild_parser.add_argument( 

        +

        565 "-c" "--contents", 

        +

        566 action="store", 

        +

        567 dest="contents", 

        +

        568 nargs="+", 

        +

        569 required=True, 

        +

        570 metavar="<contents>", 

        +

        571 help="folders that might contain the source contents needed to rebuld", 

        +

        572 ) 

        +

        573 

        +

        574 rebuild_parser.add_argument( 

        +

        575 "-d", 

        +

        576 "--destination", 

        +

        577 action="store", 

        +

        578 dest="destination", 

        +

        579 required=True, 

        +

        580 metavar="<destination>", 

        +

        581 help="path to where torrents will be re-assembled", 

        +

        582 ) 

        +

        583 

        +

        584 rebuild_parser.set_defaults(func=rebuild) 

        +

        585 args = parser.parse_args(args) 

        +

        586 

        +

        587 if args.quiet: 

        +

        588 Config.activate_quiet() 

        +

        589 

        +

        590 elif args.debug: 

        +

        591 Config.activate_logger() 

        +

        592 

        +

        593 if args.interactive: 

        +

        594 return select_action() 

        +

        595 

        +

        596 if hasattr(args, "func"): 

        +

        597 return args.func(args) 

        +

        598 return args # pragma: nocover 

        +

        599 

        +

        600 

        +

        601main_script = execute 

        +

        602 

        +

        603 

        +

        604def main(): 

        +

        605 """ 

        +

        606 Initiate main function for CLI script. 

        +

        607 """ 

        +

        608 execute() 

        +
        + + + diff --git a/docs/htmlcover/d_1bc82e0ab2fcb2ec_commands_py.html b/docs/htmlcover/d_1bc82e0ab2fcb2ec_commands_py.html new file mode 100644 index 00000000..3cab91c1 --- /dev/null +++ b/docs/htmlcover/d_1bc82e0ab2fcb2ec_commands_py.html @@ -0,0 +1,388 @@ + + + + + Coverage for torrentfile\commands.py: 100% + + + + + +
        +
        +

        + Coverage for torrentfile\commands.py: + 100% +

        + +

        + 112 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20The commands module contains the Action Commands executed by the CLI script. 

        +

        21 

        +

        22Each function pertains to a command line action/subcommand and drives specific 

        +

        23features of the application. 

        +

        24 

        +

        25Functions 

        +

        26--------- 

        +

        27- create_command 

        +

        28- info_command 

        +

        29- edit_command 

        +

        30- recheck_command 

        +

        31- magnet_command 

        +

        32""" 

        +

        33import logging 

        +

        34import os 

        +

        35import shutil 

        +

        36import sys 

        +

        37from argparse import Namespace 

        +

        38from hashlib import sha1 # nosec 

        +

        39from urllib.parse import quote_plus 

        +

        40 

        +

        41import pyben 

        +

        42 

        +

        43from torrentfile.edit import edit_torrent 

        +

        44from torrentfile.interactive import select_action 

        +

        45from torrentfile.rebuild import Assembler 

        +

        46from torrentfile.recheck import Checker 

        +

        47from torrentfile.torrent import TorrentAssembler, TorrentFile 

        +

        48from torrentfile.utils import ArgumentError 

        +

        49 

        +

        50logger = logging.getLogger(__name__) 

        +

        51 

        +

        52 

        +

        53def create(args: Namespace) -> Namespace: 

        +

        54 """ 

        +

        55 Execute the create CLI sub-command to create a new torrent metafile. 

        +

        56 

        +

        57 Parameters 

        +

        58 ---------- 

        +

        59 args : Namespace 

        +

        60 positional and optional CLI arguments. 

        +

        61 

        +

        62 Returns 

        +

        63 ------- 

        +

        64 torrentfile.MetaFile 

        +

        65 object containing the path to created metafile and its contents. 

        +

        66 """ 

        +

        67 kwargs = vars(args) 

        +

        68 logger.debug("Creating torrent from %s", args.content) 

        +

        69 if args.meta_version == "1": 

        +

        70 torrent = TorrentFile(**kwargs) 

        +

        71 else: 

        +

        72 torrent = TorrentAssembler(**kwargs) 

        +

        73 outfile, meta = torrent.write() 

        +

        74 

        +

        75 if args.magnet: 

        +

        76 magnet(outfile) 

        +

        77 

        +

        78 args.torrent = torrent 

        +

        79 args.kwargs = kwargs 

        +

        80 args.outfile = outfile 

        +

        81 args.meta = meta 

        +

        82 

        +

        83 print("\nTorrent Save Path: ", os.path.abspath(str(outfile))) 

        +

        84 logger.debug("Output path: %s", str(outfile)) 

        +

        85 return args 

        +

        86 

        +

        87 

        +

        88def info(args: Namespace) -> str: 

        +

        89 """ 

        +

        90 Show torrent metafile details to user via stdout. 

        +

        91 

        +

        92 Prints full details of torrent file contents to the terminal in 

        +

        93 a clean and readable format. 

        +

        94 

        +

        95 Parameters 

        +

        96 ---------- 

        +

        97 args : dict 

        +

        98 command line arguements provided by the user. 

        +

        99 

        +

        100 Returns 

        +

        101 ------- 

        +

        102 str 

        +

        103 The output printed to the terminal. 

        +

        104 """ 

        +

        105 metafile = args.metafile 

        +

        106 meta = pyben.load(metafile) 

        +

        107 data = meta["info"] 

        +

        108 del meta["info"] 

        +

        109 

        +

        110 meta.update(data) 

        +

        111 if "private" in meta and meta["private"] == 1: 

        +

        112 meta["private"] = "True" 

        +

        113 if "announce-list" in meta: 

        +

        114 lst = meta["announce-list"] 

        +

        115 meta["announce-list"] = ", ".join([j for i in lst for j in i]) 

        +

        116 if "url-list" in meta: 

        +

        117 meta["url-list"] = ", ".join(meta["url-list"]) 

        +

        118 if "httpseeds" in meta: 

        +

        119 meta["httpseeds"] = ", ".join(meta["httpseeds"]) 

        +

        120 

        +

        121 text = [] 

        +

        122 longest = max(len(i) for i in meta.keys()) 

        +

        123 

        +

        124 for key, val in meta.items(): 

        +

        125 if key not in ["pieces", "piece layers", "files", "file tree"]: 

        +

        126 prefix = longest - len(key) + 1 

        +

        127 string = key + (" " * prefix) + str(val) 

        +

        128 text.append(string) 

        +

        129 

        +

        130 most = max(len(i) for i in text) 

        +

        131 text = ["-" * most, "\n"] + text + ["\n", "-" * most] 

        +

        132 output = "\n".join(text) 

        +

        133 print(output) 

        +

        134 return output 

        +

        135 

        +

        136 

        +

        137def edit(args: Namespace) -> str: 

        +

        138 """ 

        +

        139 Execute the edit CLI sub-command with provided arguments. 

        +

        140 

        +

        141 Provides functionality that can change the details of a torrentfile 

        +

        142 that preserves all of the hash piece information so as not to break 

        +

        143 the torrentfile. 

        +

        144 

        +

        145 Parameters 

        +

        146 ---------- 

        +

        147 args : Namespace 

        +

        148 positional and optional CLI arguments. 

        +

        149 

        +

        150 Returns 

        +

        151 ------- 

        +

        152 str 

        +

        153 path to edited torrent file. 

        +

        154 """ 

        +

        155 metafile = args.metafile 

        +

        156 logger.info("Editing %s Meta File", str(args.metafile)) 

        +

        157 editargs = { 

        +

        158 "url-list": args.url_list, 

        +

        159 "httpseeds": args.httpseeds, 

        +

        160 "announce": args.announce, 

        +

        161 "source": args.source, 

        +

        162 "private": args.private, 

        +

        163 "comment": args.comment, 

        +

        164 } 

        +

        165 return edit_torrent(metafile, editargs) 

        +

        166 

        +

        167 

        +

        168def recheck(args: Namespace) -> str: 

        +

        169 """ 

        +

        170 Execute recheck CLI sub-command. 

        +

        171 

        +

        172 Checks the piece hashes within a pre-existing torrent file 

        +

        173 and does a piece by piece check with the contents of a file 

        +

        174 or directory for completeness and validation. 

        +

        175 

        +

        176 Parameters 

        +

        177 ---------- 

        +

        178 args : Namespace 

        +

        179 positional and optional arguments. 

        +

        180 

        +

        181 Returns 

        +

        182 ------- 

        +

        183 str 

        +

        184 The percentage of content currently saved to disk. 

        +

        185 """ 

        +

        186 metafile = args.metafile 

        +

        187 content = args.content 

        +

        188 

        +

        189 if os.path.isdir(metafile): 

        +

        190 raise ArgumentError( 

        +

        191 f"Error: Unable to parse directory {metafile}. " 

        +

        192 "Check the order of the parameters." 

        +

        193 ) 

        +

        194 

        +

        195 logger.debug( 

        +

        196 "Validating %s <---------------> %s contents", metafile, content 

        +

        197 ) 

        +

        198 

        +

        199 msg = f"Rechecking {metafile} ...\n" 

        +

        200 halfterm = shutil.get_terminal_size().columns / 2 

        +

        201 padding = int(halfterm - (len(msg) / 2)) * " " 

        +

        202 sys.stdout.write(padding + msg) 

        +

        203 

        +

        204 checker = Checker(metafile, content) 

        +

        205 logger.debug("Completed initialization of the Checker class") 

        +

        206 result = checker.results() 

        +

        207 

        +

        208 message = f"{content} <- {result}% -> {metafile}" 

        +

        209 padding = int(halfterm - (len(message) / 2)) * " " 

        +

        210 sys.stdout.write(padding + message + "\n") 

        +

        211 sys.stdout.flush() 

        +

        212 return result 

        +

        213 

        +

        214 

        +

        215def magnet(metafile: Namespace) -> str: 

        +

        216 """ 

        +

        217 Create a magnet URI from a Bittorrent meta file. 

        +

        218 

        +

        219 Parameters 

        +

        220 ---------- 

        +

        221 metafile : Namespace 

        +

        222 Namespace class for CLI arguments. 

        +

        223 

        +

        224 Returns 

        +

        225 ------- 

        +

        226 str 

        +

        227 created magnet URI. 

        +

        228 """ 

        +

        229 if hasattr(metafile, "metafile"): 

        +

        230 metafile = metafile.metafile 

        +

        231 if not os.path.exists(metafile): 

        +

        232 raise FileNotFoundError 

        +

        233 

        +

        234 meta = pyben.load(metafile) 

        +

        235 data = meta["info"] 

        +

        236 binfo = pyben.dumps(data) 

        +

        237 infohash = sha1(binfo).hexdigest().upper() # nosec 

        +

        238 

        +

        239 logger.info("Magnet Info Hash: %s", infohash) 

        +

        240 scheme = "magnet:" 

        +

        241 hasharg = "?xt=urn:btih:" + infohash 

        +

        242 namearg = "&dn=" + quote_plus(data["name"]) 

        +

        243 

        +

        244 if "announce-list" in meta: 

        +

        245 announce_args = [ 

        +

        246 "&tr=" + quote_plus(url) 

        +

        247 for urllist in meta["announce-list"] 

        +

        248 for url in urllist 

        +

        249 ] 

        +

        250 else: 

        +

        251 announce_args = ["&tr=" + quote_plus(meta["announce"])] 

        +

        252 

        +

        253 full_uri = "".join([scheme, hasharg, namearg] + announce_args) 

        +

        254 logger.info("Created Magnet URI %s", full_uri) 

        +

        255 sys.stdout.write("\n" + full_uri + "\n") 

        +

        256 return full_uri 

        +

        257 

        +

        258 

        +

        259def rebuild(args: Namespace) -> int: 

        +

        260 """ 

        +

        261 Attempt to rebuild a torrent based on the a torrent file. 

        +

        262 

        +

        263 Recursively look through a directory for files that belong in 

        +

        264 a given torrent file, and rebuild as much of the torrent file 

        +

        265 as possible. Currently only checks if the filename and file 

        +

        266 size are a match. 

        +

        267 

        +

        268 #### TODO 

        +

        269 1. Check file hashes to improve accuracy 

        +

        270 

        +

        271 Parameters 

        +

        272 ---------- 

        +

        273 args : Namespace 

        +

        274 command line arguments including the paths neccessary 

        +

        275 

        +

        276 Returns 

        +

        277 ------- 

        +

        278 int 

        +

        279 total number of content files copied to the rebuild directory 

        +

        280 """ 

        +

        281 metafiles = args.metafiles 

        +

        282 dest = args.destination 

        +

        283 contents = args.contents 

        +

        284 for path in [*metafiles, *contents]: 

        +

        285 if not os.path.exists(path): 

        +

        286 raise FileNotFoundError(path) 

        +

        287 assembler = Assembler(metafiles, contents, dest) 

        +

        288 return assembler.assemble_torrents() 

        +

        289 

        +

        290 

        +

        291interactive = select_action # for clean import system 

        +
        + + + diff --git a/docs/htmlcover/d_1bc82e0ab2fcb2ec_edit_py.html b/docs/htmlcover/d_1bc82e0ab2fcb2ec_edit_py.html new file mode 100644 index 00000000..6a5c34cc --- /dev/null +++ b/docs/htmlcover/d_1bc82e0ab2fcb2ec_edit_py.html @@ -0,0 +1,227 @@ + + + + + Coverage for torrentfile\edit.py: 100% + + + + + +
        +
        +

        + Coverage for torrentfile\edit.py: + 100% +

        + +

        + 53 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Edit torrent module. 

        +

        21 

        +

        22Provides a facility by which certain properties of a torrent meta file can be 

        +

        23edited by the user. The various command line arguments indicate which fields 

        +

        24should be edited, and what the new value should be. Depending on what fields 

        +

        25are chosen to edit, this command can trigger a new info hash which means the 

        +

        26torrent will no longer be able to participate in the same swarm as the original 

        +

        27unedited torrent. 

        +

        28 

        +

        29Keywords 

        +

        30-------- 

        +

        31private 

        +

        32comment 

        +

        33source 

        +

        34trackers 

        +

        35web-seeds 

        +

        36""" 

        +

        37 

        +

        38import logging 

        +

        39import os 

        +

        40 

        +

        41import pyben 

        +

        42 

        +

        43logger = logging.getLogger(__name__) 

        +

        44 

        +

        45 

        +

        46def filter_empty(args: dict, meta: dict, info: dict): 

        +

        47 """ 

        +

        48 Remove the fields that were not used by the original file creator. 

        +

        49 

        +

        50 Parameters 

        +

        51 ---------- 

        +

        52 args : dict 

        +

        53 Editable metafile properties from user. 

        +

        54 meta : dict 

        +

        55 Metafile data dictionary. 

        +

        56 info : dict 

        +

        57 Metafile info dictionary. 

        +

        58 """ 

        +

        59 for key, val in list(args.items()): 

        +

        60 if val is None: 

        +

        61 del args[key] 

        +

        62 continue 

        +

        63 

        +

        64 if val == "": 

        +

        65 if key in meta: 

        +

        66 del meta[key] 

        +

        67 elif key in info: 

        +

        68 del info[key] 

        +

        69 del args[key] 

        +

        70 logger.debug("removeing empty fields %s", val) 

        +

        71 

        +

        72 

        +

        73def edit_torrent(metafile: str, args: dict) -> dict: 

        +

        74 """ 

        +

        75 Edit the properties and values in a torrent meta file. 

        +

        76 

        +

        77 Parameters 

        +

        78 ---------- 

        +

        79 metafile : str 

        +

        80 path to the torrent meta file. 

        +

        81 args : dict 

        +

        82 key value pairs of the properties to be edited. 

        +

        83 

        +

        84 Returns 

        +

        85 ------- 

        +

        86 dict 

        +

        87 The edited and nested Meta and info dictionaries. 

        +

        88 """ 

        +

        89 logger.debug("editing torrent file %s", metafile) 

        +

        90 meta = pyben.load(metafile) 

        +

        91 info = meta["info"] 

        +

        92 filter_empty(args, meta, info) 

        +

        93 

        +

        94 if "comment" in args: 

        +

        95 info["comment"] = args["comment"] 

        +

        96 

        +

        97 if "source" in args: 

        +

        98 info["source"] = args["source"] 

        +

        99 

        +

        100 if "private" in args: 

        +

        101 info["private"] = 1 

        +

        102 

        +

        103 if "announce" in args: 

        +

        104 val = args.get("announce", None) 

        +

        105 if isinstance(val, str): 

        +

        106 vallist = val.split() 

        +

        107 meta["announce"] = vallist[0] 

        +

        108 meta["announce-list"] = [vallist] 

        +

        109 elif isinstance(val, list): 

        +

        110 meta["announce"] = val[0] 

        +

        111 meta["announce-list"] = [val] 

        +

        112 

        +

        113 if "url-list" in args: 

        +

        114 val = args.get("url-list") 

        +

        115 if isinstance(val, str): 

        +

        116 meta["url-list"] = val.split() 

        +

        117 elif isinstance(val, list): 

        +

        118 meta["url-list"] = val 

        +

        119 

        +

        120 if "httpseeds" in args: 

        +

        121 val = args.get("httpseeds") 

        +

        122 if isinstance(val, str): 

        +

        123 meta["httpseeds"] = val.split() 

        +

        124 elif isinstance(val, list): 

        +

        125 meta["httpseeds"] = val 

        +

        126 

        +

        127 meta["info"] = info 

        +

        128 os.remove(metafile) 

        +

        129 pyben.dump(meta, metafile) 

        +

        130 return meta 

        +
        + + + diff --git a/docs/htmlcover/d_1bc82e0ab2fcb2ec_hasher_py.html b/docs/htmlcover/d_1bc82e0ab2fcb2ec_hasher_py.html new file mode 100644 index 00000000..cfbbe64a --- /dev/null +++ b/docs/htmlcover/d_1bc82e0ab2fcb2ec_hasher_py.html @@ -0,0 +1,631 @@ + + + + + Coverage for torrentfile\hasher.py: 100% + + + + + +
        +
        +

        + Coverage for torrentfile\hasher.py: + 100% +

        + +

        + 244 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Piece/File Hashers for Bittorrent meta file contents. 

        +

        21""" 

        +

        22 

        +

        23import logging 

        +

        24import os 

        +

        25from hashlib import sha1, sha256 # nosec 

        +

        26 

        +

        27from torrentfile.mixins import CbMixin, ProgMixin 

        +

        28from torrentfile.utils import next_power_2 

        +

        29 

        +

        30BLOCK_SIZE = 2**14 # 16KiB 

        +

        31HASH_SIZE = 32 

        +

        32 

        +

        33logger = logging.getLogger(__name__) 

        +

        34 

        +

        35 

        +

        36class Hasher(CbMixin, ProgMixin): 

        +

        37 """ 

        +

        38 Piece hasher for Bittorrent V1 files. 

        +

        39 

        +

        40 Takes a sorted list of all file paths, calculates sha1 hash 

        +

        41 for fixed size pieces of file data from each file 

        +

        42 seemlessly until the last piece which may be smaller than others. 

        +

        43 

        +

        44 Parameters 

        +

        45 ---------- 

        +

        46 paths : list 

        +

        47 List of files. 

        +

        48 piece_length : int 

        +

        49 Size of chuncks to split the data into. 

        +

        50 progress : int 

        +

        51 default = None 

        +

        52 """ 

        +

        53 

        +

        54 def __init__(self, paths: list, piece_length: int, progress: bool = True): 

        +

        55 """Generate hashes of piece length data from filelist contents.""" 

        +

        56 self.piece_length = piece_length 

        +

        57 self.paths = paths 

        +

        58 self.progress = progress 

        +

        59 self.total = sum(os.path.getsize(i) for i in self.paths) 

        +

        60 self.index = 0 

        +

        61 self.current = open(self.paths[0], "rb") 

        +

        62 if self.progress: 

        +

        63 total = os.path.getsize(self.paths[0]) 

        +

        64 self.prog_start(total, self.paths[0], unit="bytes") 

        +

        65 logger.debug("Hashing %s", str(self.paths[0])) 

        +

        66 

        +

        67 def __iter__(self): 

        +

        68 """ 

        +

        69 Iterate through feed pieces. 

        +

        70 

        +

        71 Returns 

        +

        72 ------- 

        +

        73 self : iterator 

        +

        74 Iterator for leaves/hash pieces. 

        +

        75 """ 

        +

        76 return self 

        +

        77 

        +

        78 def _handle_partial(self, arr: bytearray) -> bytearray: 

        +

        79 """ 

        +

        80 Define the handling partial pieces that span 2 or more files. 

        +

        81 

        +

        82 Parameters 

        +

        83 ---------- 

        +

        84 arr : bytearray 

        +

        85 Incomplete piece containing partial data 

        +

        86 

        +

        87 Returns 

        +

        88 ------- 

        +

        89 digest : bytearray 

        +

        90 SHA1 digest of the complete piece. 

        +

        91 """ 

        +

        92 while len(arr) < self.piece_length and self.next_file(): 

        +

        93 target = self.piece_length - len(arr) 

        +

        94 temp = bytearray(target) 

        +

        95 size = self.current.readinto(temp) 

        +

        96 arr.extend(temp[:size]) 

        +

        97 self.prog_update(size) 

        +

        98 if size == target: 

        +

        99 break 

        +

        100 return sha1(arr).digest() # nosec 

        +

        101 

        +

        102 def next_file(self) -> bool: 

        +

        103 """ 

        +

        104 Seemlessly transition to next file in file list. 

        +

        105 

        +

        106 Returns 

        +

        107 ------- 

        +

        108 bool: 

        +

        109 True if there is a next file otherwise False. 

        +

        110 """ 

        +

        111 self.index += 1 

        +

        112 self.prog_close() 

        +

        113 if self.index < len(self.paths): 

        +

        114 path = self.paths[self.index] 

        +

        115 logger.debug("Hashing %s", str(path)) 

        +

        116 self.current.close() 

        +

        117 if self.progress: 

        +

        118 self.prog_start(os.path.getsize(path), path, unit="bytes") 

        +

        119 self.current = open(path, "rb") 

        +

        120 return True 

        +

        121 return False 

        +

        122 

        +

        123 def __next__(self) -> bytes: 

        +

        124 """ 

        +

        125 Generate piece-length pieces of data from input file list. 

        +

        126 

        +

        127 Returns 

        +

        128 ------- 

        +

        129 bytes 

        +

        130 SHA1 hash of the piece extracted. 

        +

        131 """ 

        +

        132 while True: 

        +

        133 piece = bytearray(self.piece_length) 

        +

        134 size = self.current.readinto(piece) 

        +

        135 if size == 0: 

        +

        136 if not self.next_file(): 

        +

        137 raise StopIteration 

        +

        138 elif size < self.piece_length: 

        +

        139 self.prog_update(size) 

        +

        140 return self._handle_partial(piece[:size]) 

        +

        141 else: 

        +

        142 self.prog_update(size) 

        +

        143 return sha1(piece).digest() # nosec 

        +

        144 

        +

        145 

        +

        146def merkle_root(blocks: list) -> bytes: 

        +

        147 """ 

        +

        148 Calculate the merkle root for a seq of sha256 hash digests. 

        +

        149 

        +

        150 Parameters 

        +

        151 ---------- 

        +

        152 blocks : list 

        +

        153 a sequence of sha256 layer hashes. 

        +

        154 

        +

        155 Returns 

        +

        156 ------- 

        +

        157 bytes 

        +

        158 the sha256 root hash of the merkle tree. 

        +

        159 """ 

        +

        160 if blocks: 

        +

        161 while len(blocks) > 1: 

        +

        162 blocks = [ 

        +

        163 sha256(x + y).digest() for x, y in zip(*[iter(blocks)] * 2) 

        +

        164 ] 

        +

        165 return blocks[0] 

        +

        166 return blocks 

        +

        167 

        +

        168 

        +

        169class HasherV2(CbMixin, ProgMixin): 

        +

        170 """ 

        +

        171 Calculate the root hash and piece layers for file contents. 

        +

        172 

        +

        173 **DEPRECATED** 

        +

        174 

        +

        175 Iterates over 16KiB blocks of data from given file, hashes the data, 

        +

        176 then creates a hash tree from the individual block hashes until size of 

        +

        177 hashed data equals the piece-length. Then continues the hash tree until 

        +

        178 root hash is calculated. 

        +

        179 

        +

        180 Parameters 

        +

        181 ---------- 

        +

        182 path : str 

        +

        183 Path to file. 

        +

        184 piece_length : int 

        +

        185 Size of layer hashes pieces. 

        +

        186 progress : int 

        +

        187 default = None 

        +

        188 """ 

        +

        189 

        +

        190 def __init__(self, path: str, piece_length: int, progress: bool = True): 

        +

        191 """ 

        +

        192 Calculate and store hash information for specific file. 

        +

        193 

        +

        194 **DEPRECATED** 

        +

        195 """ 

        +

        196 self.path = path 

        +

        197 self.root = None 

        +

        198 self.piece_layer = None 

        +

        199 self.layer_hashes = [] 

        +

        200 self.piece_length = piece_length 

        +

        201 self.num_blocks = piece_length // BLOCK_SIZE 

        +

        202 if progress: 

        +

        203 self.prog_start(os.path.getsize(path), path, unit="bytes") 

        +

        204 with open(self.path, "rb") as fd: 

        +

        205 self.process_file(fd) 

        +

        206 

        +

        207 def process_file(self, fd: str): 

        +

        208 """ 

        +

        209 Calculate hashes over 16KiB chuncks of file content. 

        +

        210 

        +

        211 **DEPRECATED** 

        +

        212 

        +

        213 Parameters 

        +

        214 ---------- 

        +

        215 fd : TextIOWrapper 

        +

        216 Opened file in read mode. 

        +

        217 """ 

        +

        218 while True: 

        +

        219 blocks = [] 

        +

        220 leaf = bytearray(BLOCK_SIZE) 

        +

        221 # generate leaves of merkle tree 

        +

        222 

        +

        223 for _ in range(self.num_blocks): 

        +

        224 size = fd.readinto(leaf) 

        +

        225 self.prog_update(size) 

        +

        226 if not size: 

        +

        227 break 

        +

        228 blocks.append(sha256(leaf[:size]).digest()) 

        +

        229 

        +

        230 # blocks is empty mean eof 

        +

        231 if not blocks: 

        +

        232 break 

        +

        233 if len(blocks) != self.num_blocks: 

        +

        234 # when size of file doesn't fill the last block 

        +

        235 # when the file contains multiple pieces 

        +

        236 remaining = self.num_blocks - len(blocks) 

        +

        237 if not self.layer_hashes: 

        +

        238 # when the there is only one block for file 

        +

        239 power2 = next_power_2(len(blocks)) 

        +

        240 remaining = power2 - len(blocks) 

        +

        241 

        +

        242 # pad the the rest with zeroes to fill remaining space. 

        +

        243 padding = [bytes(32) for _ in range(remaining)] 

        +

        244 self.prog_update(HASH_SIZE * remaining) 

        +

        245 blocks.extend(padding) 

        +

        246 # calculate the root hash for the merkle tree up to piece-length 

        +

        247 

        +

        248 layer_hash = merkle_root(blocks) 

        +

        249 self.cb(layer_hash) 

        +

        250 self.layer_hashes.append(layer_hash) 

        +

        251 self._calculate_root() 

        +

        252 self.prog_close() 

        +

        253 

        +

        254 def _calculate_root(self): 

        +

        255 """ 

        +

        256 Calculate root hash for the target file. 

        +

        257 

        +

        258 **DEPRECATED** 

        +

        259 """ 

        +

        260 self.piece_layer = b"".join(self.layer_hashes) 

        +

        261 hashes = len(self.layer_hashes) 

        +

        262 if hashes > 1: 

        +

        263 pow2 = next_power_2(hashes) 

        +

        264 remainder = pow2 - hashes 

        +

        265 pad_piece = [bytes(HASH_SIZE) for _ in range(self.num_blocks)] 

        +

        266 for _ in range(remainder): 

        +

        267 self.layer_hashes.append(merkle_root(pad_piece)) 

        +

        268 self.root = merkle_root(self.layer_hashes) 

        +

        269 

        +

        270 

        +

        271class HasherHybrid(CbMixin, ProgMixin): 

        +

        272 """ 

        +

        273 Calculate root and piece hashes for creating hybrid torrent file. 

        +

        274 

        +

        275 **DEPRECATED** 

        +

        276 

        +

        277 Create merkle tree layers from sha256 hashed 16KiB blocks of contents. 

        +

        278 With a branching factor of 2, merge layer hashes until blocks equal 

        +

        279 piece_length bytes for the piece layer, and then the root hash. 

        +

        280 

        +

        281 Parameters 

        +

        282 ---------- 

        +

        283 path : str 

        +

        284 path to target file. 

        +

        285 piece_length : int 

        +

        286 piece length for data chunks. 

        +

        287 progress : int 

        +

        288 default = None 

        +

        289 """ 

        +

        290 

        +

        291 def __init__(self, path: str, piece_length: int, progress: bool = True): 

        +

        292 """ 

        +

        293 Construct Hasher class instances for each file in torrent. 

        +

        294 

        +

        295 **DEPRECATED** 

        +

        296 """ 

        +

        297 self.path = path 

        +

        298 self.piece_length = piece_length 

        +

        299 self.pieces = [] 

        +

        300 self.layer_hashes = [] 

        +

        301 self.piece_layer = None 

        +

        302 self.root = None 

        +

        303 self.padding_piece = None 

        +

        304 self.padding_file = None 

        +

        305 if progress: 

        +

        306 self.prog_start(os.path.getsize(path), path, unit="bytes") 

        +

        307 self.amount = piece_length // BLOCK_SIZE 

        +

        308 with open(path, "rb") as data: 

        +

        309 self.process_file(data) 

        +

        310 

        +

        311 def _pad_remaining(self, block_count: int): 

        +

        312 """ 

        +

        313 Generate Hash sized, 0 filled bytes for padding. 

        +

        314 

        +

        315 **DEPRECATED** 

        +

        316 

        +

        317 Parameters 

        +

        318 ---------- 

        +

        319 block_count : int 

        +

        320 current total number of blocks collected. 

        +

        321 

        +

        322 Returns 

        +

        323 ------- 

        +

        324 padding : bytes 

        +

        325 Padding to fill remaining portion of tree. 

        +

        326 """ 

        +

        327 # when the there is only one block for file 

        +

        328 remaining = self.amount - block_count 

        +

        329 if not self.layer_hashes: 

        +

        330 power2 = next_power_2(block_count) 

        +

        331 remaining = power2 - block_count 

        +

        332 self.prog_update(HASH_SIZE * remaining) 

        +

        333 return [bytes(HASH_SIZE) for _ in range(remaining)] 

        +

        334 

        +

        335 def process_file(self, data: bytearray): 

        +

        336 """ 

        +

        337 Calculate layer hashes for contents of file. 

        +

        338 

        +

        339 **DEPRECATED** 

        +

        340 

        +

        341 Parameters 

        +

        342 ---------- 

        +

        343 data : BytesIO 

        +

        344 File opened in read mode. 

        +

        345 """ 

        +

        346 while True: 

        +

        347 plength = self.piece_length 

        +

        348 blocks = [] 

        +

        349 piece = sha1() # nosec 

        +

        350 total = 0 

        +

        351 block = bytearray(BLOCK_SIZE) 

        +

        352 for _ in range(self.amount): 

        +

        353 size = data.readinto(block) 

        +

        354 self.prog_update(size) 

        +

        355 if not size: 

        +

        356 break 

        +

        357 total += size 

        +

        358 plength -= size 

        +

        359 blocks.append(sha256(block[:size]).digest()) 

        +

        360 piece.update(block[:size]) 

        +

        361 if not blocks: 

        +

        362 break 

        +

        363 if len(blocks) != self.amount: 

        +

        364 padding = self._pad_remaining(len(blocks)) 

        +

        365 blocks.extend(padding) 

        +

        366 layer_hash = merkle_root(blocks) 

        +

        367 self.cb(layer_hash) 

        +

        368 self.layer_hashes.append(layer_hash) 

        +

        369 if plength > 0: 

        +

        370 self.padding_file = { 

        +

        371 "attr": "p", 

        +

        372 "length": plength, 

        +

        373 "path": [".pad", str(plength)], 

        +

        374 } 

        +

        375 piece.update(bytes(plength)) 

        +

        376 self.pieces.append(piece.digest()) # nosec 

        +

        377 self._calculate_root() 

        +

        378 self.prog_close() 

        +

        379 

        +

        380 def _calculate_root(self): 

        +

        381 """ 

        +

        382 Calculate the root hash for opened file. 

        +

        383 

        +

        384 **DEPRECATED** 

        +

        385 """ 

        +

        386 self.piece_layer = b"".join(self.layer_hashes) 

        +

        387 

        +

        388 if len(self.layer_hashes) > 1: 

        +

        389 pad_piece = merkle_root([bytes(32) for _ in range(self.amount)]) 

        +

        390 

        +

        391 pow2 = next_power_2(len(self.layer_hashes)) 

        +

        392 remainder = pow2 - len(self.layer_hashes) 

        +

        393 

        +

        394 self.layer_hashes += [pad_piece for _ in range(remainder)] 

        +

        395 self.root = merkle_root(self.layer_hashes) 

        +

        396 

        +

        397 

        +

        398class FileHasher(CbMixin, ProgMixin): 

        +

        399 """ 

        +

        400 Calculate root and piece hashes for creating hybrid torrent file. 

        +

        401 

        +

        402 Create merkle tree layers from sha256 hashed 16KiB blocks of contents. 

        +

        403 With a branching factor of 2, merge layer hashes until blocks equal 

        +

        404 piece_length bytes for the piece layer, and then the root hash. 

        +

        405 

        +

        406 Parameters 

        +

        407 ---------- 

        +

        408 path : str 

        +

        409 path to target file. 

        +

        410 piece_length : int 

        +

        411 piece length for data chunks. 

        +

        412 progress : int 

        +

        413 default = None 

        +

        414 """ 

        +

        415 

        +

        416 def __init__( 

        +

        417 self, 

        +

        418 path: str, 

        +

        419 piece_length: int, 

        +

        420 progress: bool = True, 

        +

        421 hybrid: bool = False, 

        +

        422 ): 

        +

        423 """ 

        +

        424 Construct Hasher class instances for each file in torrent. 

        +

        425 """ 

        +

        426 self.path = path 

        +

        427 self.piece_length = piece_length 

        +

        428 self.pieces = [] 

        +

        429 self.layer_hashes = [] 

        +

        430 self.piece_layer = None 

        +

        431 self.root = None 

        +

        432 self.padding_piece = None 

        +

        433 self.padding_file = None 

        +

        434 self.amount = piece_length // BLOCK_SIZE 

        +

        435 self.end = False 

        +

        436 self.current = open(path, "rb") 

        +

        437 self.hybrid = hybrid 

        +

        438 if progress: 

        +

        439 self.progressbar = True 

        +

        440 self.prog_start(os.path.getsize(path), path, unit="bytes") 

        +

        441 

        +

        442 def __iter__(self): 

        +

        443 """Return `self`: needed to implement iterator implementation.""" 

        +

        444 return self 

        +

        445 

        +

        446 def _pad_remaining(self, block_count: int): 

        +

        447 """ 

        +

        448 Generate Hash sized, 0 filled bytes for padding. 

        +

        449 

        +

        450 Parameters 

        +

        451 ---------- 

        +

        452 block_count : int 

        +

        453 current total number of blocks collected. 

        +

        454 

        +

        455 Returns 

        +

        456 ------- 

        +

        457 padding : bytes 

        +

        458 Padding to fill remaining portion of tree. 

        +

        459 """ 

        +

        460 # when the there is only one block for file 

        +

        461 remaining = self.amount - block_count 

        +

        462 if not self.layer_hashes: 

        +

        463 power2 = next_power_2(block_count) 

        +

        464 remaining = power2 - block_count 

        +

        465 return [bytes(HASH_SIZE) for _ in range(remaining)] 

        +

        466 

        +

        467 def __next__(self) -> bytes: 

        +

        468 """ 

        +

        469 Calculate layer hashes for contents of file. 

        +

        470 

        +

        471 Returns 

        +

        472 ------- 

        +

        473 bytes 

        +

        474 The layer merckle root hash. 

        +

        475 """ 

        +

        476 if self.end: 

        +

        477 self.end = False 

        +

        478 raise StopIteration 

        +

        479 plength = self.piece_length 

        +

        480 blocks = [] 

        +

        481 piece = sha1() # nosec 

        +

        482 total = 0 

        +

        483 block = bytearray(BLOCK_SIZE) 

        +

        484 for _ in range(self.amount): 

        +

        485 size = self.current.readinto(block) 

        +

        486 if not size: 

        +

        487 self.end = True 

        +

        488 break 

        +

        489 total += size 

        +

        490 plength -= size 

        +

        491 blocks.append(sha256(block[:size]).digest()) 

        +

        492 if self.hybrid: 

        +

        493 piece.update(block[:size]) 

        +

        494 if not blocks: 

        +

        495 self._calculate_root() 

        +

        496 raise StopIteration 

        +

        497 if len(blocks) != self.amount: 

        +

        498 padding = self._pad_remaining(len(blocks)) 

        +

        499 blocks.extend(padding) 

        +

        500 self.prog_update(total) 

        +

        501 layer_hash = merkle_root(blocks) 

        +

        502 self.layer_hashes.append(layer_hash) 

        +

        503 self.cb(layer_hash) 

        +

        504 if self.end: 

        +

        505 self._calculate_root() 

        +

        506 self.prog_close() 

        +

        507 if self.hybrid: 

        +

        508 if plength > 0: 

        +

        509 self.padding_file = { 

        +

        510 "attr": "p", 

        +

        511 "length": plength, 

        +

        512 "path": [".pad", str(plength)], 

        +

        513 } 

        +

        514 piece.update(bytes(plength)) 

        +

        515 piece = piece.digest() 

        +

        516 self.pieces.append(piece) 

        +

        517 return layer_hash, piece 

        +

        518 return layer_hash 

        +

        519 

        +

        520 def _calculate_root(self): 

        +

        521 """ 

        +

        522 Calculate the root hash for opened file. 

        +

        523 """ 

        +

        524 self.piece_layer = b"".join(self.layer_hashes) 

        +

        525 

        +

        526 if len(self.layer_hashes) > 1: 

        +

        527 pad_piece = merkle_root([bytes(32) for _ in range(self.amount)]) 

        +

        528 

        +

        529 pow2 = next_power_2(len(self.layer_hashes)) 

        +

        530 remainder = pow2 - len(self.layer_hashes) 

        +

        531 

        +

        532 self.layer_hashes += [pad_piece for _ in range(remainder)] 

        +

        533 self.root = merkle_root(self.layer_hashes) 

        +

        534 self.current.close() 

        +
        + + + diff --git a/docs/htmlcover/d_1bc82e0ab2fcb2ec_interactive_py.html b/docs/htmlcover/d_1bc82e0ab2fcb2ec_interactive_py.html new file mode 100644 index 00000000..1bdbade0 --- /dev/null +++ b/docs/htmlcover/d_1bc82e0ab2fcb2ec_interactive_py.html @@ -0,0 +1,486 @@ + + + + + Coverage for torrentfile\interactive.py: 100% + + + + + +
        +
        +

        + Coverage for torrentfile\interactive.py: + 100% +

        + +

        + 121 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Module contains the procedures used for Interactive Mode. 

        +

        21""" 

        +

        22 

        +

        23import os 

        +

        24import shutil 

        +

        25import sys 

        +

        26 

        +

        27import pyben 

        +

        28 

        +

        29from torrentfile.edit import edit_torrent 

        +

        30from torrentfile.recheck import Checker 

        +

        31from torrentfile.torrent import TorrentFile, TorrentFileHybrid, TorrentFileV2 

        +

        32 

        +

        33 

        +

        34def get_input(*args: tuple): # pragma: no cover 

        +

        35 """ 

        +

        36 Determine appropriate input function to call. 

        +

        37 

        +

        38 Parameters 

        +

        39 ---------- 

        +

        40 *args : tuple 

        +

        41 Arbitrary number of args to pass to next function 

        +

        42 

        +

        43 Returns 

        +

        44 ------- 

        +

        45 str 

        +

        46 The results of the function call. 

        +

        47 """ 

        +

        48 if len(args) == 2: 

        +

        49 return _get_input_loop(*args) 

        +

        50 return _get_input(*args) 

        +

        51 

        +

        52 

        +

        53def _get_input(txt: str): # pragma: no cover 

        +

        54 """ 

        +

        55 Gather information needed from user. 

        +

        56 

        +

        57 Parameters 

        +

        58 ---------- 

        +

        59 txt : str 

        +

        60 The message usually containing instructions for the user. 

        +

        61 

        +

        62 Returns 

        +

        63 ------- 

        +

        64 str 

        +

        65 The text input received from the user. 

        +

        66 """ 

        +

        67 value = input(txt) 

        +

        68 return value 

        +

        69 

        +

        70 

        +

        71def _get_input_loop(txt: str, func): # pragma: no cover 

        +

        72 """ 

        +

        73 Gather information needed from user. 

        +

        74 

        +

        75 Parameters 

        +

        76 ---------- 

        +

        77 txt : str 

        +

        78 The message usually containing instructions for the user. 

        +

        79 func : function 

        +

        80 Validate/Check user input data, failure = retry, success = continue. 

        +

        81 

        +

        82 Returns 

        +

        83 ------- 

        +

        84 str 

        +

        85 The text input received from the user. 

        +

        86 """ 

        +

        87 while True: 

        +

        88 value = input(txt) 

        +

        89 if func and func(value): 

        +

        90 return value 

        +

        91 if not func or value == "": 

        +

        92 return value 

        +

        93 showtext(f"Invalid input {value}: try again") 

        +

        94 

        +

        95 

        +

        96def showtext(txt): 

        +

        97 """ 

        +

        98 Print contents of txt to screen. 

        +

        99 

        +

        100 Parameters 

        +

        101 ---------- 

        +

        102 txt : str 

        +

        103 text to print to terminal. 

        +

        104 """ 

        +

        105 sys.stdout.write(txt) 

        +

        106 

        +

        107 

        +

        108def showcenter(txt: str): 

        +

        109 """ 

        +

        110 Print text to screen in the center position of the terminal. 

        +

        111 

        +

        112 Parameters 

        +

        113 ---------- 

        +

        114 txt : str 

        +

        115 the preformated message to send to stdout. 

        +

        116 """ 

        +

        117 termlen = shutil.get_terminal_size().columns 

        +

        118 padding = " " * int(((termlen - len(txt)) / 2)) 

        +

        119 string = "".join(["\n", padding, txt, "\n"]) 

        +

        120 showtext(string) 

        +

        121 

        +

        122 

        +

        123def select_action(): 

        +

        124 """ 

        +

        125 Operate TorrentFile program interactively through terminal. 

        +

        126 

        +

        127 DEPRECATION WARNING: The interactive CLI feature will be deprecated 

        +

        128 in the future. 

        +

        129 """ 

        +

        130 showcenter("TorrentFile: Starting Interactive Mode") 

        +

        131 showcenter( 

        +

        132 "DEPRECATION WARNING: The interactive feature will be" 

        +

        133 "deprecated in the near future." 

        +

        134 ) 

        +

        135 action = get_input( 

        +

        136 "Enter the action you wish to perform.\n" 

        +

        137 "Action ( Create (c) | Edit (e) | Recheck (r) ): " 

        +

        138 ) 

        +

        139 action = action.lower() 

        +

        140 

        +

        141 if "create" in action or action == "c": 

        +

        142 return create_torrent() 

        +

        143 

        +

        144 if "check" in action or action == "r": 

        +

        145 return recheck_torrent() 

        +

        146 

        +

        147 if "edit" in action or action == "e": 

        +

        148 return edit_action() 

        +

        149 print("Unable to recognize input. Please try again.") # pragma: nocover 

        +

        150 return select_action() # pragma: nocover 

        +

        151 

        +

        152 

        +

        153def recheck_torrent(): 

        +

        154 """ 

        +

        155 Check torrent download completed percentage. 

        +

        156 """ 

        +

        157 showcenter("Check Torrent") 

        +

        158 msg = "Enter path to torrent contents, and corresponding torrent metafile." 

        +

        159 showtext(msg) 

        +

        160 metafile = get_input( 

        +

        161 "Conent Path (downloads/complete/torrentname):", os.path.exists 

        +

        162 ) 

        +

        163 contents = get_input("Metafile (*.torrent): ", os.path.exists) 

        +

        164 checker = Checker(metafile, contents) 

        +

        165 results = checker.results() 

        +

        166 showtext(f"Completion for {metafile} is {results}%") 

        +

        167 return results 

        +

        168 

        +

        169 

        +

        170def create_torrent(): 

        +

        171 """ 

        +

        172 Create new torrent file interactively. 

        +

        173 """ 

        +

        174 showcenter("Create Torrent") 

        +

        175 showtext( 

        +

        176 "\nEnter values for each of the options for the torrent creator, " 

        +

        177 "or leave blank for program defaults.\nSpaces are considered item " 

        +

        178 "seperators for options that accept a list of values.\nValues " 

        +

        179 "enclosed in () indicate the default value, while {} holds all " 

        +

        180 "valid choices available for the option.\n\n" 

        +

        181 ) 

        +

        182 creator = InteractiveCreator() 

        +

        183 return creator 

        +

        184 

        +

        185 

        +

        186def edit_action(): 

        +

        187 """ 

        +

        188 Edit the editable values of the torrent meta file. 

        +

        189 """ 

        +

        190 showcenter("Edit Torrent") 

        +

        191 metafile = get_input("Metafile(.torrent): ", os.path.exists) 

        +

        192 dialog = InteractiveEditor(metafile) 

        +

        193 dialog.show_current() 

        +

        194 dialog.edit_props() 

        +

        195 

        +

        196 

        +

        197class InteractiveEditor: 

        +

        198 """ 

        +

        199 Interactive dialog class for torrent editing. 

        +

        200 """ 

        +

        201 

        +

        202 def __init__(self, metafile: str): 

        +

        203 """ 

        +

        204 Initialize the Interactive torrent editor guide. 

        +

        205 

        +

        206 Parameters 

        +

        207 ---------- 

        +

        208 metafile : str 

        +

        209 user input string identifying the path to a torrent meta file. 

        +

        210 """ 

        +

        211 self.metafile = metafile 

        +

        212 self.meta = pyben.load(metafile) 

        +

        213 self.info = self.meta["info"] 

        +

        214 

        +

        215 self.args = { 

        +

        216 "url-list": self.meta.get("url-list", None), 

        +

        217 "httpseeds": self.meta.get("httpseeds", None), 

        +

        218 "announce": self.meta.get("announce-list", None), 

        +

        219 "source": self.info.get("source", None), 

        +

        220 "private": self.info.get("private", None), 

        +

        221 "comment": self.info.get("comment", None), 

        +

        222 } 

        +

        223 

        +

        224 def show_current(self): 

        +

        225 """ 

        +

        226 Display the current met file information to screen. 

        +

        227 """ 

        +

        228 out = "Current properties and values:\n" 

        +

        229 longest = max(len(label) for label in self.args) + 3 

        +

        230 for key, val in self.args.items(): 

        +

        231 txt = (key.title() + ":").ljust(longest) + str(val) 

        +

        232 out += f"\t{txt}\n" 

        +

        233 showtext(out) 

        +

        234 

        +

        235 def sanatize_response(self, key, response): 

        +

        236 """ 

        +

        237 Convert the input data into a form recognizable by the program. 

        +

        238 

        +

        239 Parameters 

        +

        240 ---------- 

        +

        241 key : str 

        +

        242 name of the property and attribute being eddited. 

        +

        243 response : str 

        +

        244 User input value the property is being edited to. 

        +

        245 """ 

        +

        246 if key in ["announce", "url-list", "httpseeds"]: 

        +

        247 val = response.split() 

        +

        248 else: 

        +

        249 val = response 

        +

        250 self.args[key] = val 

        +

        251 

        +

        252 def edit_props(self): 

        +

        253 """ 

        +

        254 Loop continuosly for edits until user signals DONE. 

        +

        255 """ 

        +

        256 while True: 

        +

        257 showcenter( 

        +

        258 "Choose the number for a propert the needs editing." 

        +

        259 "Enter DONE when all editing has been completed." 

        +

        260 ) 

        +

        261 

        +

        262 props = { 

        +

        263 1: "comment", 

        +

        264 2: "source", 

        +

        265 3: "private", 

        +

        266 4: "tracker", 

        +

        267 5: "web-seed", 

        +

        268 6: "httpseeds", 

        +

        269 } 

        +

        270 

        +

        271 args = { 

        +

        272 1: "comment", 

        +

        273 2: "source", 

        +

        274 3: "private", 

        +

        275 4: "announce", 

        +

        276 5: "url-list", 

        +

        277 6: "httpseeds", 

        +

        278 } 

        +

        279 

        +

        280 txt = ", ".join((str(k) + ": " + v) for k, v in props.items()) 

        +

        281 prop = get_input(txt) 

        +

        282 if prop.lower() == "done": 

        +

        283 break 

        +

        284 

        +

        285 if prop.isdigit() and 0 < int(prop) < 6: 

        +

        286 key = props[int(prop)] 

        +

        287 key2 = args[int(prop)] 

        +

        288 val = self.args.get(key2) 

        +

        289 showtext( 

        +

        290 "Enter new property value or leave empty for no value." 

        +

        291 ) 

        +

        292 response = get_input(f"{key.title()} ({val}): ") 

        +

        293 self.sanatize_response(key2, response) 

        +

        294 

        +

        295 else: 

        +

        296 showtext("Invalid input: Try again.") 

        +

        297 edit_torrent(self.metafile, self.args) 

        +

        298 

        +

        299 

        +

        300class InteractiveCreator: 

        +

        301 """ 

        +

        302 Class namespace for interactive program options. 

        +

        303 """ 

        +

        304 

        +

        305 def __init__(self): 

        +

        306 """ 

        +

        307 Initialize interactive meta file creator dialog. 

        +

        308 """ 

        +

        309 self.kwargs = { 

        +

        310 "announce": None, 

        +

        311 "url_list": None, 

        +

        312 "private": None, 

        +

        313 "source": None, 

        +

        314 "comment": None, 

        +

        315 "piece_length": None, 

        +

        316 "outfile": None, 

        +

        317 "path": None, 

        +

        318 "httpseeds": None, 

        +

        319 } 

        +

        320 self.outfile, self.meta = self.get_props() 

        +

        321 

        +

        322 def get_props(self): 

        +

        323 """ 

        +

        324 Gather details for torrentfile from user. 

        +

        325 """ 

        +

        326 piece_length = get_input( 

        +

        327 "Piece Length (empty=auto): ", lambda x: x.isdigit() 

        +

        328 ) 

        +

        329 

        +

        330 self.kwargs["piece_length"] = piece_length 

        +

        331 announce = get_input( 

        +

        332 "Tracker list (empty): ", lambda x: isinstance(x, str) 

        +

        333 ) 

        +

        334 

        +

        335 if announce: 

        +

        336 self.kwargs["announce"] = announce.split() 

        +

        337 

        +

        338 url_list = get_input( 

        +

        339 "Web Seed {GetRight} list (empty): ", lambda x: isinstance(x, str) 

        +

        340 ) 

        +

        341 

        +

        342 httpseeds = get_input( 

        +

        343 "Web Seed {Hoffman} list (empty): ", lambda x: isinstance(x, str) 

        +

        344 ) 

        +

        345 

        +

        346 if url_list: 

        +

        347 self.kwargs["url_list"] = url_list.split() 

        +

        348 if httpseeds: 

        +

        349 self.kwargs["httpseeds"] = httpseeds.split() 

        +

        350 comment = get_input("Comment (empty): ", None) 

        +

        351 

        +

        352 if comment: 

        +

        353 self.kwargs["comment"] = comment 

        +

        354 source = get_input("Source (empty): ", None) 

        +

        355 

        +

        356 if source: 

        +

        357 self.kwargs["source"] = source 

        +

        358 

        +

        359 private = get_input( 

        +

        360 "Private Torrent? {Y/N}: (N)", lambda x: x in "yYnN" 

        +

        361 ) 

        +

        362 

        +

        363 if private and private.lower() == "y": 

        +

        364 self.kwargs["private"] = 1 

        +

        365 

        +

        366 contents = get_input("Content Path: ", os.path.exists) 

        +

        367 self.kwargs["path"] = contents 

        +

        368 

        +

        369 outfile = get_input( 

        +

        370 f"Output Path ({contents}.torrent): ", 

        +

        371 lambda x: os.path.exists(os.path.dirname(x)), 

        +

        372 ) 

        +

        373 

        +

        374 if outfile: 

        +

        375 self.kwargs["outfile"] = outfile 

        +

        376 

        +

        377 meta_version = get_input( 

        +

        378 "Meta Version {1,2,3}: (1)", lambda x: x in "123" 

        +

        379 ) 

        +

        380 

        +

        381 showcenter(f"creating {outfile}") 

        +

        382 

        +

        383 if meta_version == "3": 

        +

        384 torrent = TorrentFileHybrid(**self.kwargs) 

        +

        385 elif meta_version == "2": 

        +

        386 torrent = TorrentFileV2(**self.kwargs) 

        +

        387 else: 

        +

        388 torrent = TorrentFile(**self.kwargs) 

        +

        389 return torrent.write() 

        +
        + + + diff --git a/docs/htmlcover/d_1bc82e0ab2fcb2ec_mixins_py.html b/docs/htmlcover/d_1bc82e0ab2fcb2ec_mixins_py.html new file mode 100644 index 00000000..b061fa26 --- /dev/null +++ b/docs/htmlcover/d_1bc82e0ab2fcb2ec_mixins_py.html @@ -0,0 +1,353 @@ + + + + + Coverage for torrentfile\mixins.py: 100% + + + + + +
        +
        +

        + Coverage for torrentfile\mixins.py: + 100% +

        + +

        + 101 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Collection of classes that can be used as Mixins with other base classes. 

        +

        21 

        +

        22Classes such as TorrentFile, TorrentFilev2, and all Hasher classes can use the 

        +

        23progress bar mixin. And any class is eligible to use the callback mixin. 

        +

        24""" 

        +

        25import math 

        +

        26import os 

        +

        27import shutil 

        +

        28import sys 

        +

        29import time 

        +

        30from pathlib import Path 

        +

        31 

        +

        32from torrentfile.utils import debug_is_on 

        +

        33 

        +

        34 

        +

        35class CbMixin: 

        +

        36 """ 

        +

        37 Mixin class to set a callback during hashing procedure. 

        +

        38 """ 

        +

        39 

        +

        40 @classmethod 

        +

        41 def cb(cls, *args, **kwargs): 

        +

        42 """Do nothing.""" 

        +

        43 

        +

        44 @classmethod 

        +

        45 def set_callback(cls, func): 

        +

        46 """ 

        +

        47 Assign a callback to the Hashing class. 

        +

        48 

        +

        49 Parameters 

        +

        50 ---------- 

        +

        51 func : Callable 

        +

        52 the callback function 

        +

        53 """ 

        +

        54 cls.cb = func # pragma: nocover 

        +

        55 

        +

        56 

        +

        57class ProgressBar: 

        +

        58 """ 

        +

        59 Holds the state and details of the terminal progress bars. 

        +

        60 

        +

        61 Parameters 

        +

        62 ---------- 

        +

        63 total : int 

        +

        64 the total amount to be accumulated. 

        +

        65 title : str 

        +

        66 the subject of the progress tracker 

        +

        67 length : int 

        +

        68 the width of the progress bar 

        +

        69 unit : str 

        +

        70 the text representation incremented 

        +

        71 start : int 

        +

        72 column where the progress bar should be drawn 

        +

        73 """ 

        +

        74 

        +

        75 def __init__( 

        +

        76 self, total: int, title: str, length: int, unit: str, start: int 

        +

        77 ): 

        +

        78 """ 

        +

        79 Construct the progress bar object and store state of it's properties. 

        +

        80 """ 

        +

        81 self.total = total 

        +

        82 self.start = start 

        +

        83 self.length = length 

        +

        84 self.fill = chr(9608) 

        +

        85 self.empty = chr(9617) 

        +

        86 self.state = 0 

        +

        87 self.unit = unit 

        +

        88 self.show_total = total 

        +

        89 if not unit: 

        +

        90 self.unit = "" # pragma: nocover 

        +

        91 elif unit == "bytes": 

        +

        92 if self.total > 1_000_000_000: 

        +

        93 self.show_total = math.floor(self.total / (2**30)) 

        +

        94 self.unit = "GiB" 

        +

        95 elif self.total > 1_000_000: 

        +

        96 self.show_total = math.floor(self.total / 1048576) 

        +

        97 self.unit = "MiB" 

        +

        98 elif self.total > 10000: 

        +

        99 self.show_total = math.floor(self.total / 1024) 

        +

        100 self.unit = "KiB" 

        +

        101 self.suffix = f"/{self.show_total} {self.unit}" 

        +

        102 if len(title) > start: 

        +

        103 title = title[: start - 1] 

        +

        104 padding = (start - len(title)) * " " 

        +

        105 self.prefix = "".join([title, padding]) 

        +

        106 

        +

        107 def get_progress(self) -> str: 

        +

        108 """ 

        +

        109 Return the size of the filled portion of the progress bar. 

        +

        110 

        +

        111 Returns 

        +

        112 ------- 

        +

        113 str : 

        +

        114 the progress bar characters 

        +

        115 """ 

        +

        116 if self.state >= self.total: 

        +

        117 fill = self.length 

        +

        118 else: 

        +

        119 fill = math.ceil((self.state / self.total) * self.length) 

        +

        120 empt = self.length - fill 

        +

        121 if self.unit == "GiB": 

        +

        122 state = math.floor(self.state / (2**30)) 

        +

        123 elif self.unit == "MiB": 

        +

        124 state = math.floor(self.state / 1048576) 

        +

        125 elif self.unit == "KiB": 

        +

        126 state = math.floor(self.state / 1024) 

        +

        127 else: 

        +

        128 state = self.state 

        +

        129 progbar = ["|", self.fill * fill, self.empty * empt, "| ", str(state)] 

        +

        130 return "".join(progbar) 

        +

        131 

        +

        132 

        +

        133class ProgMixin: 

        +

        134 """ 

        +

        135 Progress bar mixin class. 

        +

        136 

        +

        137 Displays progress of hashing individual files, usefull when hashing 

        +

        138 really big files. 

        +

        139 """ 

        +

        140 

        +

        141 def prog_start( 

        +

        142 self, total: int, path: str, length: int = 50, unit: str = None 

        +

        143 ): 

        +

        144 """ 

        +

        145 Generate a new progress bar for the given file path. 

        +

        146 

        +

        147 Parameters 

        +

        148 ---------- 

        +

        149 total : int 

        +

        150 the total amount of units accumulating towards. 

        +

        151 path : str 

        +

        152 path to file being hashed. 

        +

        153 length : int 

        +

        154 the number of characters of the actual progress bar. 

        +

        155 unit : str 

        +

        156 the text representation of the value being measured. 

        +

        157 """ 

        +

        158 title = path 

        +

        159 width = shutil.get_terminal_size().columns 

        +

        160 if len(str(title)) >= width // 2: 

        +

        161 parts = list(Path(title).parts) 

        +

        162 while (len("//".join(parts)) > (width // 2)) and (len(parts) > 0): 

        +

        163 del parts[0] 

        +

        164 if parts: 

        +

        165 title = os.path.join(*parts) 

        +

        166 else: 

        +

        167 title = os.path.basename(path) # pragma: nocover 

        +

        168 length = min(length, width // 2) 

        +

        169 start = width - int(length * 1.5) 

        +

        170 self.prog = ProgressBar(total, title, length, unit, start) 

        +

        171 

        +

        172 def prog_update(self, val: int): 

        +

        173 """ 

        +

        174 Update progress bar. 

        +

        175 

        +

        176 Using the value provided, increment the progress bar by that value. 

        +

        177 

        +

        178 Parameters 

        +

        179 ---------- 

        +

        180 val : int 

        +

        181 the number of bytes count the progress bar should increase. 

        +

        182 """ 

        +

        183 if self.is_active(): 

        +

        184 self.prog.state += val 

        +

        185 pbar = self.prog.get_progress() 

        +

        186 output = f"{self.prog.prefix}{pbar}{self.prog.suffix}\r" 

        +

        187 sys.stdout.write(output) 

        +

        188 sys.stdout.flush() 

        +

        189 

        +

        190 def prog_close(self): 

        +

        191 """ 

        +

        192 Finalize the last bits of progress bar. 

        +

        193 

        +

        194 Increment the terminal by one line leaving the progress bar in place, 

        +

        195 and deleting the progress bar object to clear a space for the next one. 

        +

        196 """ 

        +

        197 if self.is_active(): 

        +

        198 sys.stdout.flush() 

        +

        199 sys.stdout.write("\n") 

        +

        200 del self.prog 

        +

        201 

        +

        202 def is_active(self) -> bool: 

        +

        203 """ 

        +

        204 Test to see if there is an active progress bar for object. 

        +

        205 

        +

        206 Returns 

        +

        207 ------- 

        +

        208 bool : 

        +

        209 True if there is, otherwise False. 

        +

        210 """ 

        +

        211 if not debug_is_on() and hasattr(self, "prog"): 

        +

        212 return True 

        +

        213 return False 

        +

        214 

        +

        215 

        +

        216def waiting(msg: str, flag: list, timeout: int = 20): 

        +

        217 """ 

        +

        218 Show loading message while thread completes processing. 

        +

        219 

        +

        220 Parameters 

        +

        221 ---------- 

        +

        222 msg : str 

        +

        223 Message string printed before the progress bar 

        +

        224 flag : list 

        +

        225 Once flag is filled exit loop 

        +

        226 timeout : int 

        +

        227 max amount of time to run the function. 

        +

        228 """ 

        +

        229 then = time.time() 

        +

        230 codes, fill = list(range(9617, 9620)), chr(9619) 

        +

        231 size = idx = 0 

        +

        232 total = shutil.get_terminal_size().columns - len(msg) - 20 

        +

        233 

        +

        234 def output(text: str): 

        +

        235 """ 

        +

        236 Print parameter message to the console. 

        +

        237 

        +

        238 Parameters 

        +

        239 ---------- 

        +

        240 text : str 

        +

        241 output message 

        +

        242 """ 

        +

        243 sys.stdout.write(text) 

        +

        244 sys.stdout.flush() 

        +

        245 

        +

        246 output("\n") 

        +

        247 time.sleep(0.16) 

        +

        248 while len(flag) == 0: 

        +

        249 time.sleep(0.16) 

        +

        250 filled = (fill * size) + chr(codes[idx]) + (" " * (total - size)) 

        +

        251 output(f"{msg}: {filled}\r") 

        +

        252 idx = idx + 1 if idx + 1 < len(codes) else 0 

        +

        253 size = size + 1 if size < total else 0 

        +

        254 if time.time() - then > timeout: 

        +

        255 break 

        +

        256 output("\n") 

        +
        + + + diff --git a/docs/htmlcover/d_1bc82e0ab2fcb2ec_rebuild_py.html b/docs/htmlcover/d_1bc82e0ab2fcb2ec_rebuild_py.html new file mode 100644 index 00000000..dc143499 --- /dev/null +++ b/docs/htmlcover/d_1bc82e0ab2fcb2ec_rebuild_py.html @@ -0,0 +1,713 @@ + + + + + Coverage for torrentfile\rebuild.py: 100% + + + + + +
        +
        +

        + Coverage for torrentfile\rebuild.py: + 100% +

        + +

        + 251 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Clases and functions for the rebuild or reassemble subcommand. 

        +

        21 

        +

        22Re-assemble a torrent into the propper directory structure as indicated by a 

        +

        23torrent meta file, and validate the contents of each file allong the 

        +

        24way. Displays a progress bar for each torrent. 

        +

        25""" 

        +

        26import logging 

        +

        27import math 

        +

        28import os 

        +

        29from hashlib import sha1 

        +

        30from pathlib import Path 

        +

        31 

        +

        32import pyben 

        +

        33 

        +

        34from torrentfile.hasher import HasherV2 

        +

        35from torrentfile.mixins import CbMixin, ProgMixin 

        +

        36from torrentfile.utils import copypath 

        +

        37 

        +

        38logger = logging.getLogger(__name__) 

        +

        39SHA1 = 20 

        +

        40 

        +

        41 

        +

        42class PathNode: 

        +

        43 """ 

        +

        44 Base class representing information regarding a file included in torrent. 

        +

        45 """ 

        +

        46 

        +

        47 def __init__( 

        +

        48 self, 

        +

        49 start: int = None, 

        +

        50 stop: int = None, 

        +

        51 full: str = None, 

        +

        52 filename: str = None, 

        +

        53 path: str = None, 

        +

        54 length: int = None, 

        +

        55 ): 

        +

        56 """ 

        +

        57 Hold file information that contributes to the contents of torrent. 

        +

        58 

        +

        59 Parameters 

        +

        60 ---------- 

        +

        61 start : int, optional 

        +

        62 where the piece starts, by default None 

        +

        63 stop : int, optional 

        +

        64 where the piece ends, by default None 

        +

        65 full : str, optional 

        +

        66 full path, by default None 

        +

        67 filename : str, optional 

        +

        68 filename, by default None 

        +

        69 path : str, optional 

        +

        70 parent path, by default None 

        +

        71 length : int, optional 

        +

        72 size, by default None 

        +

        73 """ 

        +

        74 self.path = path 

        +

        75 self.start = start 

        +

        76 self.stop = stop 

        +

        77 self.length = length 

        +

        78 self.filename = filename 

        +

        79 self.full = full 

        +

        80 

        +

        81 def get_part(self, path: str) -> bytes: 

        +

        82 """ 

        +

        83 Extract the part of the file needed to complete the hash. 

        +

        84 

        +

        85 Parameters 

        +

        86 ---------- 

        +

        87 path : str 

        +

        88 filesystem path location of file. 

        +

        89 

        +

        90 Returns 

        +

        91 ------- 

        +

        92 bytes 

        +

        93 part of the file's contents 

        +

        94 """ 

        +

        95 with open(path, "rb") as fd: 

        +

        96 if self.start: 

        +

        97 fd.seek(self.start) 

        +

        98 if self.stop != -1: 

        +

        99 partial = fd.read(self.stop - self.start) 

        +

        100 else: 

        +

        101 partial = fd.read() 

        +

        102 return partial 

        +

        103 

        +

        104 def __len__(self) -> int: 

        +

        105 """ 

        +

        106 Return size of the file. 

        +

        107 

        +

        108 Returns 

        +

        109 ------- 

        +

        110 int 

        +

        111 total size 

        +

        112 """ 

        +

        113 return self.length 

        +

        114 

        +

        115 

        +

        116class PieceNode: 

        +

        117 """ 

        +

        118 Base class representing a single SHA1 hash block of data from a torrent. 

        +

        119 """ 

        +

        120 

        +

        121 def __init__(self, piece: bytes): 

        +

        122 """ 

        +

        123 Store information about an individual SHA1 hash for a torrent file. 

        +

        124 

        +

        125 _extended_summary_ 

        +

        126 

        +

        127 Parameters 

        +

        128 ---------- 

        +

        129 piece : bytes 

        +

        130 SHA1 hash bytes 

        +

        131 """ 

        +

        132 self.piece = piece 

        +

        133 self.paths = [] 

        +

        134 self.result = None 

        +

        135 self.dest = None 

        +

        136 

        +

        137 def append(self, pathnode: PathNode): 

        +

        138 """ 

        +

        139 Append the path argument to the paths list attribute. 

        +

        140 

        +

        141 Parameters 

        +

        142 ---------- 

        +

        143 pathnode : PathNode 

        +

        144 the pathnode 

        +

        145 """ 

        +

        146 self.paths.append(pathnode) 

        +

        147 

        +

        148 def _find_matches(self, filemap: dict, paths: list, data: bytes) -> bool: 

        +

        149 """ 

        +

        150 Gather relavent sections of the files in the list and check the hash. 

        +

        151 

        +

        152 Parameters 

        +

        153 ---------- 

        +

        154 filemap : dict 

        +

        155 dictionary containing filename and path details 

        +

        156 paths : list 

        +

        157 list of pathnodes 

        +

        158 data : bytes 

        +

        159 raw file contents 

        +

        160 

        +

        161 Returns 

        +

        162 ------- 

        +

        163 bool 

        +

        164 success state 

        +

        165 """ 

        +

        166 if not paths: 

        +

        167 piece_hash = sha1(data).digest() # nosec 

        +

        168 return piece_hash == self.piece 

        +

        169 pathnode = paths[0] 

        +

        170 filename = pathnode.filename 

        +

        171 if filename not in filemap: 

        +

        172 return False # pragma: nocover 

        +

        173 for loc, size in filemap[filename]: 

        +

        174 if size != len(pathnode): 

        +

        175 continue 

        +

        176 partial = pathnode.get_part(loc) 

        +

        177 val = self._find_matches(filemap, paths[1:], data + partial) 

        +

        178 if val: 

        +

        179 dest_path = os.path.join(self.dest, pathnode.full) 

        +

        180 copypath(loc, dest_path) 

        +

        181 return val 

        +

        182 return False 

        +

        183 

        +

        184 def find_matches(self, filemap: dict, dest: str) -> bool: 

        +

        185 """ 

        +

        186 Find the matching files for each path in the node. 

        +

        187 

        +

        188 Parameters 

        +

        189 ---------- 

        +

        190 filemap : dict 

        +

        191 filename and details 

        +

        192 dest : str 

        +

        193 target destination path 

        +

        194 

        +

        195 Returns 

        +

        196 ------- 

        +

        197 bool 

        +

        198 success status 

        +

        199 """ 

        +

        200 self.dest = dest 

        +

        201 self.result = self._find_matches(filemap, self.paths[:], bytes()) 

        +

        202 return self.result 

        +

        203 

        +

        204 

        +

        205class Metadata(CbMixin, ProgMixin): 

        +

        206 """ 

        +

        207 Class containing the metadata contents of a torrent file. 

        +

        208 """ 

        +

        209 

        +

        210 def __init__(self, path: str): 

        +

        211 """ 

        +

        212 Construct metadata object for torrent info. 

        +

        213 

        +

        214 Parameters 

        +

        215 ---------- 

        +

        216 path : str 

        +

        217 path to the .torrent file. 

        +

        218 """ 

        +

        219 self.path = os.path.abspath(path) 

        +

        220 self.name = None 

        +

        221 self.piece_length = 1 

        +

        222 self.meta_version = 1 

        +

        223 self.pieces = b"" 

        +

        224 self.piece_nodes = [] 

        +

        225 self.length = 0 

        +

        226 self.files = [] 

        +

        227 self.filenames = set() 

        +

        228 self.extract() 

        +

        229 if self.meta_version == 2: 

        +

        230 self.num_pieces = len(self.filenames) 

        +

        231 else: 

        +

        232 self.num_pieces = math.ceil(len(self.pieces) / SHA1) 

        +

        233 

        +

        234 def extract(self): 

        +

        235 """ 

        +

        236 Decode and extract information for the .torrent file. 

        +

        237 """ 

        +

        238 meta = pyben.load(self.path) 

        +

        239 info = meta["info"] 

        +

        240 self.piece_length = info["piece length"] 

        +

        241 self.name = info["name"] 

        +

        242 self.meta_version = info.get("meta version", 1) 

        +

        243 self.pieces = info.get("pieces", bytes()) 

        +

        244 if self.meta_version == 2: 

        +

        245 self._parse_tree(info["file tree"], [self.name]) 

        +

        246 elif "length" in info: 

        +

        247 self.length += info["length"] 

        +

        248 self.is_file = True 

        +

        249 self.filenames.add(info["name"]) 

        +

        250 self.files.append( 

        +

        251 { 

        +

        252 "path": Path(self.name).parent, 

        +

        253 "filename": self.name, 

        +

        254 "full": self.name, 

        +

        255 "length": self.length, 

        +

        256 } 

        +

        257 ) 

        +

        258 elif "files" in info: 

        +

        259 for f in info["files"]: 

        +

        260 path = f["path"] 

        +

        261 full = os.path.join(self.name, *path) 

        +

        262 self.files.append( 

        +

        263 { 

        +

        264 "path": Path(full).parent, 

        +

        265 "filename": path[-1], 

        +

        266 "full": full, 

        +

        267 "length": f["length"], 

        +

        268 } 

        +

        269 ) 

        +

        270 self.length += f["length"] 

        +

        271 self.filenames.add(path[-1]) 

        +

        272 

        +

        273 def _map_pieces(self): 

        +

        274 """ 

        +

        275 Create PathNode and PieceNode details for each piece in the torrent. 

        +

        276 """ 

        +

        277 total_pieces = len(self.pieces) // SHA1 

        +

        278 remainder = file_index = 0 

        +

        279 current = {} 

        +

        280 for i in range(total_pieces): 

        +

        281 begin = SHA1 * i 

        +

        282 piece = PieceNode(self.pieces[begin: begin + SHA1]) 

        +

        283 target = self.piece_length 

        +

        284 if remainder: 

        +

        285 start = current["length"] - remainder 

        +

        286 if remainder < target: 

        +

        287 stop = -1 

        +

        288 target -= remainder 

        +

        289 remainder = 0 

        +

        290 file_index += 1 

        +

        291 else: 

        +

        292 stop = start + target 

        +

        293 remainder -= target 

        +

        294 target -= target 

        +

        295 pathnode = PathNode(start=start, stop=stop, **current) 

        +

        296 piece.append(pathnode) 

        +

        297 while target > 0 and file_index < len(self.files): 

        +

        298 start = 0 

        +

        299 current = self.files[file_index] 

        +

        300 size = current["length"] 

        +

        301 if size < target: 

        +

        302 stop = -1 

        +

        303 target -= size 

        +

        304 file_index += 1 

        +

        305 else: 

        +

        306 stop = target 

        +

        307 remainder = size - target 

        +

        308 target = 0 

        +

        309 pathnode = PathNode(start=start, stop=stop, **current) 

        +

        310 piece.append(pathnode) 

        +

        311 self.piece_nodes.append(piece) 

        +

        312 

        +

        313 def _parse_tree(self, tree: dict, partials: list): 

        +

        314 """ 

        +

        315 Parse the file tree dictionary of the torrent metafile. 

        +

        316 

        +

        317 Parameters 

        +

        318 ---------- 

        +

        319 tree : dict 

        +

        320 the dictionary representation of a file tree. 

        +

        321 partials : list 

        +

        322 list of paths leading up to the current key value. 

        +

        323 """ 

        +

        324 for key, val in tree.items(): 

        +

        325 if "" in val: 

        +

        326 self.filenames.add(key) 

        +

        327 path = Path(os.path.join(*partials)) 

        +

        328 full = Path(os.path.join(path, key)) 

        +

        329 length = val[""]["length"] 

        +

        330 root = val[""]["pieces root"] 

        +

        331 self.files.append( 

        +

        332 { 

        +

        333 "path": path, 

        +

        334 "full": full, 

        +

        335 "filename": key, 

        +

        336 "length": length, 

        +

        337 "root": root, 

        +

        338 } 

        +

        339 ) 

        +

        340 self.length += length 

        +

        341 else: 

        +

        342 self._parse_tree(val, partials + [key]) 

        +

        343 

        +

        344 def _match_v1(self, filemap: dict, dest: str): 

        +

        345 """ 

        +

        346 Check each of the nodes against the filemap dictionary for matches. 

        +

        347 

        +

        348 Parameters 

        +

        349 ---------- 

        +

        350 filemap : dict 

        +

        351 filenames and filesystem information 

        +

        352 dest : str 

        +

        353 target destination path 

        +

        354 """ 

        +

        355 self._map_pieces() 

        +

        356 copied = [] 

        +

        357 for piece_node in self.piece_nodes: 

        +

        358 paths = piece_node.paths 

        +

        359 if len(paths) == 1 and paths[0].path in copied: 

        +

        360 self._update() 

        +

        361 continue 

        +

        362 if piece_node.find_matches(filemap, dest): 

        +

        363 for pathnode in paths: 

        +

        364 if pathnode.path not in copied: 

        +

        365 copied.append(pathnode.path) 

        +

        366 dest_path = os.path.join(dest, pathnode.path) 

        +

        367 self._update() 

        +

        368 self.cb(pathnode.path, dest_path, self.num_pieces) 

        +

        369 

        +

        370 def _match_v2(self, filemap: dict, dest: str): 

        +

        371 """ 

        +

        372 Rebuild method for torrent v2 files. 

        +

        373 

        +

        374 Parameters 

        +

        375 ---------- 

        +

        376 filemap : dict 

        +

        377 filesystem information 

        +

        378 dest : str 

        +

        379 destiantion path 

        +

        380 """ 

        +

        381 for entry in self.files: 

        +

        382 filename = entry["filename"] 

        +

        383 length = entry["length"] 

        +

        384 if filename not in filemap: 

        +

        385 continue # pragma: nocover 

        +

        386 paths = filemap[filename] 

        +

        387 for path, size in paths: 

        +

        388 if size == length: 

        +

        389 hasher = HasherV2(path, self.piece_length, True) 

        +

        390 if entry["root"] == hasher.root: 

        +

        391 dest_path = os.path.join(dest, entry["full"]) 

        +

        392 copypath(entry["path"], dest_path) 

        +

        393 self._update() 

        +

        394 self.cb(path, dest_path, self.num_pieces) 

        +

        395 break 

        +

        396 

        +

        397 def rebuild(self, filemap: dict, dest: str): 

        +

        398 """ 

        +

        399 Rebuild torrent file contents from filemap at dest. 

        +

        400 

        +

        401 Searches through the contents of the meta file and compares filenames 

        +

        402 with those in the filemap dict, and if found checks their contents, 

        +

        403 and copies them to the destination path. 

        +

        404 

        +

        405 Parameters 

        +

        406 ---------- 

        +

        407 filemap : dict 

        +

        408 filesystem information 

        +

        409 dest : str 

        +

        410 destiantion path 

        +

        411 """ 

        +

        412 self._prog = None 

        +

        413 if self.meta_version == 2: 

        +

        414 self._match_v2(filemap, dest) 

        +

        415 else: 

        +

        416 self._match_v1(filemap, dest) 

        +

        417 if self._prog is not None: 

        +

        418 self.prog_close() 

        +

        419 

        +

        420 def _update(self): 

        +

        421 """Start and updating the progress bar.""" 

        +

        422 if self._prog is None: 

        +

        423 self._prog = True 

        +

        424 self.prog_start(self.num_pieces, self.name, unit="piece") 

        +

        425 self.prog_update(1) 

        +

        426 

        +

        427 

        +

        428class Assembler(CbMixin): 

        +

        429 """ 

        +

        430 Does most of the work in attempting the structure of torrentfiles. 

        +

        431 

        +

        432 Requires three paths as arguments. 

        +

        433 - torrent metafile or directory containing multiple meta files 

        +

        434 - directory containing the contents of meta file 

        +

        435 - directory where torrents will be re-assembled 

        +

        436 """ 

        +

        437 

        +

        438 def __init__(self, metafiles: list, contents: list, dest: str): 

        +

        439 """ 

        +

        440 Reassemble given torrent file from given cli arguments. 

        +

        441 

        +

        442 Rebuild metafiles and contents into their original directory 

        +

        443 structure as much as possible in the destination directory. 

        +

        444 Takes two paths as parameters, 

        +

        445 - file or directory containing 1 or more torrent meta files 

        +

        446 - path to where the contents are belived to be located. 

        +

        447 

        +

        448 Parameters 

        +

        449 ---------- 

        +

        450 metafiles : str 

        +

        451 path to torrent metafile or directory containing torrent metafiles. 

        +

        452 contents : str 

        +

        453 path to content or directory containing content that belongs to 

        +

        454 torrentfile. 

        +

        455 dest: str 

        +

        456 path to the directory where rebuild will take place. 

        +

        457 """ 

        +

        458 self.counter = 0 

        +

        459 self._lastlog = None 

        +

        460 self.contents = contents 

        +

        461 Metadata.set_callback(self._callback) 

        +

        462 self.dest = dest 

        +

        463 self.meta_paths = metafiles 

        +

        464 self.metafiles = self._get_metafiles() 

        +

        465 filenames = set() 

        +

        466 for meta in self.metafiles: 

        +

        467 filenames |= meta.filenames 

        +

        468 self.filemap = _index_contents(self.contents, filenames) 

        +

        469 

        +

        470 def _callback(self, filename: str, dest: str, num_pieces: int): 

        +

        471 """ 

        +

        472 Run the callback functions associated with Mixin for copied files. 

        +

        473 

        +

        474 Parameters 

        +

        475 ---------- 

        +

        476 filename : str 

        +

        477 filename 

        +

        478 dest : str 

        +

        479 destination path 

        +

        480 num_pieces : int 

        +

        481 number of hash pieces 

        +

        482 """ 

        +

        483 self.counter += 1 

        +

        484 message = f"Matched:{num_pieces} {filename} -> {dest}" 

        +

        485 if message != self._lastlog: 

        +

        486 self._lastlog = message 

        +

        487 logger.debug(message) 

        +

        488 

        +

        489 def assemble_torrents(self): 

        +

        490 """ 

        +

        491 Assemble collection of torrent files into original structure. 

        +

        492 

        +

        493 Returns 

        +

        494 ------- 

        +

        495 int 

        +

        496 number of files copied 

        +

        497 """ 

        +

        498 for metafile in self.metafiles: 

        +

        499 logger.info( 

        +

        500 "#%s Searching contents for %s", self.counter, metafile.name 

        +

        501 ) 

        +

        502 self.rebuild(metafile) 

        +

        503 return self.counter 

        +

        504 

        +

        505 def rebuild(self, metafile: Metadata) -> None: 

        +

        506 """ 

        +

        507 Build the torrent file structure from contents of directory. 

        +

        508 

        +

        509 Traverse contents dir and compare discovered files 

        +

        510 with files listed in torrent metadata and copy 

        +

        511 the matches to the destination directory respecting folder 

        +

        512 structures along the way. 

        +

        513 """ 

        +

        514 metafile.rebuild(self.filemap, self.dest) 

        +

        515 

        +

        516 def _iter_files(self, path: str) -> list: 

        +

        517 """ 

        +

        518 Iterate through metfiles directory createing Metafile objects. 

        +

        519 

        +

        520 Parameters 

        +

        521 ---------- 

        +

        522 path : str 

        +

        523 fs path 

        +

        524 

        +

        525 Returns 

        +

        526 ------- 

        +

        527 list 

        +

        528 list of Metadata Object 

        +

        529 """ 

        +

        530 metafiles = [] 

        +

        531 for filename in os.listdir(path): 

        +

        532 if filename.lower().endswith(".torrent"): 

        +

        533 try: 

        +

        534 meta = Metadata(os.path.join(path, filename)) 

        +

        535 metafiles.append(meta) 

        +

        536 except ValueError: # pragma: nocover 

        +

        537 self.counter -= 1 

        +

        538 return metafiles 

        +

        539 

        +

        540 def _get_metafiles(self) -> list: 

        +

        541 """ 

        +

        542 Collect all .torrent meta files from given directory or file. 

        +

        543 

        +

        544 Returns 

        +

        545 ------- 

        +

        546 list 

        +

        547 metafile objects 

        +

        548 """ 

        +

        549 metafiles = [] 

        +

        550 for path in self.meta_paths: 

        +

        551 if os.path.exists(path): 

        +

        552 if os.path.isdir(path): 

        +

        553 metafiles += self._iter_files(path) 

        +

        554 elif path.lower().endswith(".torrent"): 

        +

        555 meta = Metadata(path) 

        +

        556 metafiles.append(meta) 

        +

        557 return metafiles 

        +

        558 

        +

        559 

        +

        560def _index_contents(contents: list, filenames: set) -> dict: 

        +

        561 """ 

        +

        562 Collect all of the filenames and their respective paths. 

        +

        563 

        +

        564 Parameters 

        +

        565 ---------- 

        +

        566 contents : list 

        +

        567 paths to traverse looking for filenames 

        +

        568 filenames : set 

        +

        569 set of filenames to look for 

        +

        570 

        +

        571 Returns 

        +

        572 ------- 

        +

        573 dict 

        +

        574 all filenames and their respective paths. 

        +

        575 """ 

        +

        576 mapping = {} 

        +

        577 for dirpath in contents: 

        +

        578 mapped = _index_content(dirpath, filenames) 

        +

        579 for key, value in mapped.items(): 

        +

        580 mapping.setdefault(key, []) 

        +

        581 mapping[key].extend(value) 

        +

        582 return mapping 

        +

        583 

        +

        584 

        +

        585def _index_content(root: str, filenames: set) -> dict: 

        +

        586 """ 

        +

        587 Collect filenames from directory or file. 

        +

        588 

        +

        589 Parameters 

        +

        590 ---------- 

        +

        591 root : str 

        +

        592 path to search for filenames 

        +

        593 filenames : set 

        +

        594 set of filenames to search for 

        +

        595 

        +

        596 Returns 

        +

        597 ------- 

        +

        598 dict 

        +

        599 filenames and their respective paths 

        +

        600 """ 

        +

        601 filemap = {} 

        +

        602 if os.path.isfile(root): 

        +

        603 name = os.path.basename(root) 

        +

        604 if name in filenames: 

        +

        605 size = os.path.getsize(root) 

        +

        606 filemap.setdefault(name, []) 

        +

        607 filemap[name].append((root, size)) 

        +

        608 return filemap 

        +

        609 if os.path.isdir(root): 

        +

        610 for path in os.listdir(root): 

        +

        611 fullpath = os.path.join(root, path) 

        +

        612 resultmap = _index_content(fullpath, filenames) 

        +

        613 for key, value in resultmap.items(): 

        +

        614 filemap.setdefault(key, []) 

        +

        615 filemap[key].extend(value) 

        +

        616 return filemap 

        +
        + + + diff --git a/docs/htmlcover/d_1bc82e0ab2fcb2ec_recheck_py.html b/docs/htmlcover/d_1bc82e0ab2fcb2ec_recheck_py.html new file mode 100644 index 00000000..01986232 --- /dev/null +++ b/docs/htmlcover/d_1bc82e0ab2fcb2ec_recheck_py.html @@ -0,0 +1,746 @@ + + + + + Coverage for torrentfile\recheck.py: 100% + + + + + +
        +
        +

        + Coverage for torrentfile\recheck.py: + 100% +

        + +

        + 279 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Module container Checker Class. 

        +

        21 

        +

        22The CheckerClass takes a torrentfile and tha path to it's contents. 

        +

        23It will then iterate through every file and directory contained 

        +

        24and compare their data to values contained within the torrent file. 

        +

        25Completion percentages will be printed to screen for each file and 

        +

        26at the end for the torrentfile as a whole. 

        +

        27""" 

        +

        28 

        +

        29import logging 

        +

        30import os 

        +

        31from hashlib import sha1, sha256 # nosec 

        +

        32from pathlib import Path 

        +

        33 

        +

        34import pyben 

        +

        35 

        +

        36from torrentfile.hasher import FileHasher 

        +

        37from torrentfile.mixins import ProgMixin 

        +

        38from torrentfile.utils import ArgumentError, MissingPathError 

        +

        39 

        +

        40SHA1 = 20 

        +

        41SHA256 = 32 

        +

        42BLOCK_SIZE = 2**14 # 16KiB 

        +

        43 

        +

        44logger = logging.getLogger(__name__) 

        +

        45 

        +

        46 

        +

        47class Checker: 

        +

        48 """ 

        +

        49 Check a given file or directory to see if it matches a torrentfile. 

        +

        50 

        +

        51 Public constructor for Checker class instance. 

        +

        52 

        +

        53 Parameters 

        +

        54 ---------- 

        +

        55 metafile : str 

        +

        56 Path to ".torrent" file. 

        +

        57 path : str 

        +

        58 Path where the content is located in filesystem. 

        +

        59 

        +

        60 Example 

        +

        61 ------- 

        +

        62 >> metafile = "/path/to/torrentfile/content_file_or_dir.torrent" 

        +

        63 >> location = "/path/to/location" 

        +

        64 >> os.path.exists("/path/to/location/content_file_or_dir") 

        +

        65 Out: True 

        +

        66 >> checker = Checker(metafile, location) 

        +

        67 """ 

        +

        68 

        +

        69 _hook = None 

        +

        70 

        +

        71 def __init__(self, metafile: str, path: str): 

        +

        72 """ 

        +

        73 Validate data against hashes contained in .torrent file. 

        +

        74 

        +

        75 Parameters 

        +

        76 ---------- 

        +

        77 metafile : str 

        +

        78 path to .torrent file 

        +

        79 path : str 

        +

        80 path to content or contents parent directory. 

        +

        81 """ 

        +

        82 if not os.path.exists(metafile): 

        +

        83 raise FileNotFoundError 

        +

        84 if os.path.isdir(metafile): 

        +

        85 raise ArgumentError( 

        +

        86 "The <metafile> must be a .torrent file. Not a directory" 

        +

        87 ) 

        +

        88 self.last_log = None 

        +

        89 self.log_msg("Checking: %s, %s", metafile, path) 

        +

        90 self.metafile = metafile 

        +

        91 self.total = 0 

        +

        92 self.paths = [] 

        +

        93 self.fileinfo = {} 

        +

        94 print("Extracting data from torrent file...") 

        +

        95 self.meta = pyben.load(metafile) 

        +

        96 self.info = self.meta["info"] 

        +

        97 self.name = self.info["name"] 

        +

        98 self.piece_length = self.info["piece length"] 

        +

        99 

        +

        100 if "meta version" in self.info: 

        +

        101 if "pieces" in self.info: 

        +

        102 self.meta_version = 3 

        +

        103 else: 

        +

        104 self.meta_version = 2 

        +

        105 else: 

        +

        106 self.meta_version = 1 

        +

        107 

        +

        108 self.root = self.find_root(path) 

        +

        109 self.check_paths() 

        +

        110 

        +

        111 @classmethod 

        +

        112 def register_callback(cls, hook): 

        +

        113 """ 

        +

        114 Register hooks from 3rd party programs to access generated info. 

        +

        115 

        +

        116 Parameters 

        +

        117 ---------- 

        +

        118 hook : function 

        +

        119 callback function for the logging feature. 

        +

        120 """ 

        +

        121 cls._hook = hook 

        +

        122 

        +

        123 def piece_checker(self): 

        +

        124 """ 

        +

        125 Check individual pieces of the torrent. 

        +

        126 

        +

        127 Returns 

        +

        128 ------- 

        +

        129 HashChecker | FeedChecker 

        +

        130 Individual piece hasher. 

        +

        131 """ 

        +

        132 if self.meta_version == 1: 

        +

        133 return FeedChecker 

        +

        134 return HashChecker 

        +

        135 

        +

        136 def results(self): 

        +

        137 """ 

        +

        138 Generate result percentage and store for future calls. 

        +

        139 """ 

        +

        140 responses = [] 

        +

        141 for response in self.iter_hashes(): 

        +

        142 responses.append(response) 

        +

        143 

        +

        144 self.log_msg( 

        +

        145 "Final result for %s recheck: %s", self.metafile, self._result 

        +

        146 ) 

        +

        147 

        +

        148 return self._result 

        +

        149 

        +

        150 def log_msg(self, *args, level: int = logging.INFO): 

        +

        151 """ 

        +

        152 Log message `msg` to logger and send `msg` to callback hook. 

        +

        153 

        +

        154 Parameters 

        +

        155 ---------- 

        +

        156 *args : dict 

        +

        157 formatting args for log message 

        +

        158 level : int 

        +

        159 Log level for this message; default=`logging.INFO` 

        +

        160 """ 

        +

        161 message = args[0] 

        +

        162 if len(args) >= 3: 

        +

        163 message = message % tuple(args[1:]) 

        +

        164 elif len(args) == 2: 

        +

        165 message = message % args[1] 

        +

        166 

        +

        167 # Repeat log messages should be ignored. 

        +

        168 if message != self.last_log: 

        +

        169 self.last_log = message 

        +

        170 logger.log(level, message) 

        +

        171 if self._hook and level == logging.INFO: 

        +

        172 self._hook(message) 

        +

        173 

        +

        174 def find_root(self, path: str) -> str: 

        +

        175 """ 

        +

        176 Check path for torrent content. 

        +

        177 

        +

        178 The path can be a relative or absolute filesystem path. In the case 

        +

        179 where the content is a single file, the path may point directly to the 

        +

        180 the file, or it may point to the parent directory. If content points 

        +

        181 to a directory. The directory will be checked to see if it matches 

        +

        182 the torrent's name, if not the directories contents will be searched. 

        +

        183 The returned value will be the absolute path that matches the torrent's 

        +

        184 name. 

        +

        185 

        +

        186 Parameters 

        +

        187 ---------- 

        +

        188 path : str 

        +

        189 root path to torrent content 

        +

        190 

        +

        191 Returns 

        +

        192 ------- 

        +

        193 str 

        +

        194 root path to content 

        +

        195 """ 

        +

        196 if not os.path.exists(path): 

        +

        197 self.log_msg("Could not locate torrent content %s.", path) 

        +

        198 raise FileNotFoundError(path) 

        +

        199 

        +

        200 root = Path(path) 

        +

        201 if root.name == self.name: 

        +

        202 self.log_msg("Content found: %s.", str(root)) 

        +

        203 return root 

        +

        204 

        +

        205 if self.name in os.listdir(root): 

        +

        206 return root / self.name 

        +

        207 

        +

        208 self.log_msg("Could not locate torrent content in: %s", str(root)) 

        +

        209 raise FileNotFoundError(root) 

        +

        210 

        +

        211 def check_paths(self): 

        +

        212 """ 

        +

        213 Gather all file paths described in the torrent file. 

        +

        214 """ 

        +

        215 finfo = self.fileinfo 

        +

        216 

        +

        217 if "length" in self.info: 

        +

        218 self.log_msg("%s points to a single file", self.root) 

        +

        219 self.total = self.info["length"] 

        +

        220 self.paths.append(str(self.root)) 

        +

        221 

        +

        222 finfo[0] = { 

        +

        223 "path": self.root, 

        +

        224 "length": self.info["length"], 

        +

        225 } 

        +

        226 

        +

        227 if self.meta_version > 1: 

        +

        228 root = self.info["file tree"][self.name][""]["pieces root"] 

        +

        229 finfo[0]["pieces root"] = root 

        +

        230 

        +

        231 return 

        +

        232 

        +

        233 # Otherwise Content is more than 1 file. 

        +

        234 self.log_msg("%s points to a directory", self.root) 

        +

        235 if self.meta_version == 1: 

        +

        236 

        +

        237 for i, item in enumerate(self.info["files"]): 

        +

        238 self.total += item["length"] 

        +

        239 base = os.path.join(*item["path"]) 

        +

        240 

        +

        241 self.fileinfo[i] = { 

        +

        242 "path": str(self.root / base), 

        +

        243 "length": item["length"], 

        +

        244 } 

        +

        245 

        +

        246 self.paths.append(str(self.root / base)) 

        +

        247 return 

        +

        248 

        +

        249 self.walk_file_tree(self.info["file tree"], []) 

        +

        250 

        +

        251 def walk_file_tree(self, tree: dict, partials: list): 

        +

        252 """ 

        +

        253 Traverse File Tree dictionary to get file details. 

        +

        254 

        +

        255 Extract full pathnames, length, root hash, and layer hashes 

        +

        256 for each file included in the .torrent's file tree. 

        +

        257 

        +

        258 Parameters 

        +

        259 ---------- 

        +

        260 tree : dict 

        +

        261 File Tree dict extracted from torrent file. 

        +

        262 partials : list 

        +

        263 list of intermediate pathnames. 

        +

        264 """ 

        +

        265 for key, val in tree.items(): 

        +

        266 

        +

        267 # Empty string means the tree's leaf is value 

        +

        268 if "" in val: 

        +

        269 

        +

        270 base = os.path.join(*partials, key) 

        +

        271 roothash = None 

        +

        272 length = val[""]["length"] 

        +

        273 roothash = None if not length else val[""]["pieces root"] 

        +

        274 full = str(self.root / base) 

        +

        275 self.fileinfo[len(self.paths)] = { 

        +

        276 "path": full, 

        +

        277 "length": length, 

        +

        278 "pieces root": roothash, 

        +

        279 } 

        +

        280 self.paths.append(full) 

        +

        281 self.total += length 

        +

        282 else: 

        +

        283 self.walk_file_tree(val, partials + [key]) 

        +

        284 

        +

        285 def iter_hashes(self) -> tuple: 

        +

        286 """ 

        +

        287 Produce results of comparing torrent contents piece by piece. 

        +

        288 

        +

        289 Yields 

        +

        290 ------ 

        +

        291 chunck : bytes 

        +

        292 hash of data found on disk 

        +

        293 piece : bytes 

        +

        294 hash of data when complete and correct 

        +

        295 path : str 

        +

        296 path to file being hashed 

        +

        297 size : int 

        +

        298 length of bytes hashed for piece 

        +

        299 """ 

        +

        300 matched = consumed = 0 

        +

        301 checker = self.piece_checker() 

        +

        302 for chunk, piece, path, size in checker(self): 

        +

        303 consumed += size 

        +

        304 matching = 0 

        +

        305 if chunk == piece: 

        +

        306 matching += size 

        +

        307 matched += size 

        +

        308 yield chunk, piece, path, size 

        +

        309 total_consumed = str(int(consumed / self.total * 100)) 

        +

        310 percent_matched = str(int(matched / consumed * 100)) 

        +

        311 self.log_msg( 

        +

        312 "Processed: %s%%, Matched: %s%%", 

        +

        313 total_consumed, 

        +

        314 percent_matched, 

        +

        315 ) 

        +

        316 self._result = (matched / consumed) * 100 if consumed > 0 else 0 

        +

        317 

        +

        318 

        +

        319class FeedChecker(ProgMixin): 

        +

        320 """ 

        +

        321 Validates torrent content. 

        +

        322 

        +

        323 Seemlesly validate torrent file contents by comparing hashes in 

        +

        324 metafile against data on disk. 

        +

        325 

        +

        326 Parameters 

        +

        327 ---------- 

        +

        328 checker : object 

        +

        329 the checker class instance. 

        +

        330 """ 

        +

        331 

        +

        332 def __init__(self, checker: Checker): 

        +

        333 """ 

        +

        334 Generate hashes of piece length data from filelist contents. 

        +

        335 """ 

        +

        336 self.piece_length = checker.piece_length 

        +

        337 self.paths = checker.paths 

        +

        338 self.pieces = checker.info["pieces"] 

        +

        339 self.fileinfo = checker.fileinfo 

        +

        340 self.piece_map = {} 

        +

        341 self.index = 0 

        +

        342 self.piece_count = 0 

        +

        343 self.it = None 

        +

        344 

        +

        345 def __iter__(self): 

        +

        346 """ 

        +

        347 Assign iterator and return self. 

        +

        348 """ 

        +

        349 self.it = self.iter_pieces() 

        +

        350 return self 

        +

        351 

        +

        352 def __next__(self): 

        +

        353 """ 

        +

        354 Yield back result of comparison. 

        +

        355 """ 

        +

        356 try: 

        +

        357 partial = next(self.it) 

        +

        358 except StopIteration as itererror: 

        +

        359 raise StopIteration from itererror 

        +

        360 

        +

        361 chunck = sha1(partial).digest() # nosec 

        +

        362 start = self.piece_count * SHA1 

        +

        363 end = start + SHA1 

        +

        364 piece = self.pieces[start:end] 

        +

        365 self.piece_count += 1 

        +

        366 path = self.paths[self.index] 

        +

        367 return chunck, piece, path, len(partial) 

        +

        368 

        +

        369 def iter_pieces(self): 

        +

        370 """ 

        +

        371 Iterate through, and hash pieces of torrent contents. 

        +

        372 

        +

        373 Yields 

        +

        374 ------ 

        +

        375 piece : bytes 

        +

        376 hash digest for block of torrent data. 

        +

        377 """ 

        +

        378 partial = bytearray() 

        +

        379 for i, path in enumerate(self.paths): 

        +

        380 total = self.fileinfo[i]["length"] 

        +

        381 self.prog_start(total, path, unit="bytes") 

        +

        382 self.index = i 

        +

        383 if os.path.exists(path): 

        +

        384 for piece in self.extract(path, partial): 

        +

        385 if (len(piece) == self.piece_length) or ( 

        +

        386 i + 1 == len(self.paths) 

        +

        387 ): 

        +

        388 yield piece 

        +

        389 else: 

        +

        390 partial = piece 

        +

        391 

        +

        392 else: 

        +

        393 length = self.fileinfo[i]["length"] 

        +

        394 for pad in self._gen_padding(partial, length): 

        +

        395 if len(pad) == self.piece_length: 

        +

        396 yield pad 

        +

        397 else: 

        +

        398 partial = pad 

        +

        399 self.prog_close() 

        +

        400 

        +

        401 def extract(self, path: str, partial: bytearray) -> bytearray: 

        +

        402 """ 

        +

        403 Split file paths contents into blocks of data for hash pieces. 

        +

        404 

        +

        405 Parameters 

        +

        406 ---------- 

        +

        407 path : str 

        +

        408 path to content. 

        +

        409 partial : bytes 

        +

        410 any remaining content from last file. 

        +

        411 

        +

        412 Returns 

        +

        413 ------- 

        +

        414 bytearray 

        +

        415 Hash digest for block of .torrent contents. 

        +

        416 """ 

        +

        417 read = 0 

        +

        418 length = self.fileinfo[self.index]["length"] 

        +

        419 partial = bytearray() if len(partial) == self.piece_length else partial 

        +

        420 if path not in self.paths: # pragma: no cover 

        +

        421 raise MissingPathError(path) 

        +

        422 with open(path, "rb") as current: 

        +

        423 while True: 

        +

        424 bitlength = self.piece_length - len(partial) 

        +

        425 part = bytearray(bitlength) 

        +

        426 amount = current.readinto(part) 

        +

        427 read += amount 

        +

        428 partial.extend(part[:amount]) 

        +

        429 if amount < bitlength: 

        +

        430 if amount > 0 and read == length: 

        +

        431 self.prog_update(amount) 

        +

        432 yield partial 

        +

        433 break 

        +

        434 self.prog_update(amount) 

        +

        435 yield partial 

        +

        436 partial = bytearray(0) 

        +

        437 if length != read: 

        +

        438 for pad in self._gen_padding(partial, length, read): 

        +

        439 yield pad 

        +

        440 

        +

        441 def _gen_padding(self, partial: bytes, length: int, read=0) -> bytes: 

        +

        442 """ 

        +

        443 Create padded pieces where file sizes do not match. 

        +

        444 

        +

        445 Parameters 

        +

        446 ---------- 

        +

        447 partial : bytes 

        +

        448 any remaining data from last file processed. 

        +

        449 length : int 

        +

        450 size of space that needs padding 

        +

        451 read : int 

        +

        452 portion of length already padded 

        +

        453 

        +

        454 Yields 

        +

        455 ------ 

        +

        456 bytes 

        +

        457 A piece length sized block of zeros. 

        +

        458 """ 

        +

        459 while read < length: 

        +

        460 left = self.piece_length - len(partial) 

        +

        461 if length - read > left: 

        +

        462 padding = bytearray(left) 

        +

        463 partial.extend(padding) 

        +

        464 yield partial 

        +

        465 read += left 

        +

        466 partial = bytearray(0) 

        +

        467 else: 

        +

        468 partial.extend(bytearray(length - read)) 

        +

        469 read = length 

        +

        470 yield partial 

        +

        471 

        +

        472 

        +

        473class HashChecker(ProgMixin): 

        +

        474 """ 

        +

        475 Iterate through contents of meta data and verify with file contents. 

        +

        476 

        +

        477 Parameters 

        +

        478 ---------- 

        +

        479 checker : Checker 

        +

        480 the checker instance that maintains variables. 

        +

        481 """ 

        +

        482 

        +

        483 def __init__(self, checker: Checker): 

        +

        484 """ 

        +

        485 Construct a HybridChecker instance. 

        +

        486 """ 

        +

        487 self.checker = checker 

        +

        488 self.paths = checker.paths 

        +

        489 self.piece_length = checker.piece_length 

        +

        490 self.fileinfo = checker.fileinfo 

        +

        491 self.piece_layers = checker.meta["piece layers"] 

        +

        492 self.current = None 

        +

        493 self.index = -1 

        +

        494 

        +

        495 def __iter__(self): 

        +

        496 """ 

        +

        497 Assign iterator and return self. 

        +

        498 """ 

        +

        499 return self 

        +

        500 

        +

        501 def __next__(self): 

        +

        502 """ 

        +

        503 Provide the result of comparison. 

        +

        504 """ 

        +

        505 if self.current is None: 

        +

        506 self.next_file() 

        +

        507 try: 

        +

        508 return self.process_current() 

        +

        509 except StopIteration as itererr: 

        +

        510 if self.next_file(): 

        +

        511 return self.process_current() 

        +

        512 raise StopIteration from itererr 

        +

        513 

        +

        514 class Padder: 

        +

        515 """ 

        +

        516 Padding class to generate padding hashes wherever needed. 

        +

        517 

        +

        518 Parameters 

        +

        519 ---------- 

        +

        520 length: int 

        +

        521 the total size of the mock file generating padding for. 

        +

        522 piece_length : int 

        +

        523 the block size that each hash represents. 

        +

        524 """ 

        +

        525 

        +

        526 def __init__(self, length, piece_length): 

        +

        527 """ 

        +

        528 Construct padding class to Mock missing or incomplete files. 

        +

        529 

        +

        530 Parameters 

        +

        531 ---------- 

        +

        532 length : int 

        +

        533 size of the file 

        +

        534 piece_length : int 

        +

        535 the piece length for each iteration. 

        +

        536 """ 

        +

        537 self.length = length 

        +

        538 self.piece_length = piece_length 

        +

        539 self.pad = sha256(bytearray(piece_length)).digest() 

        +

        540 

        +

        541 def __iter__(self): 

        +

        542 """ 

        +

        543 Return self to correctly implement iterator type. 

        +

        544 """ 

        +

        545 return self # pragma: nocover 

        +

        546 

        +

        547 def __next__(self) -> bytes: 

        +

        548 """ 

        +

        549 Iterate through seemingly endless sha256 hashes of zeros. 

        +

        550 

        +

        551 Returns 

        +

        552 ------- 

        +

        553 tuple : 

        +

        554 returns the padding 

        +

        555 

        +

        556 Raises 

        +

        557 ------ 

        +

        558 StopIteration 

        +

        559 """ 

        +

        560 if self.length >= self.piece_length: 

        +

        561 self.length -= self.piece_length 

        +

        562 return self.pad 

        +

        563 if self.length > 0: 

        +

        564 pad = sha256(bytearray(self.length)).digest() 

        +

        565 self.length -= self.length 

        +

        566 return pad 

        +

        567 raise StopIteration 

        +

        568 

        +

        569 def next_file(self) -> bool: 

        +

        570 """ 

        +

        571 Remove all references to processed files and prepare for the next. 

        +

        572 

        +

        573 Returns 

        +

        574 ------- 

        +

        575 bool 

        +

        576 if there is a next file found 

        +

        577 """ 

        +

        578 self.index += 1 

        +

        579 self.prog_close() 

        +

        580 if self.current is None or self.index < len(self.paths): 

        +

        581 self.current = self.paths[self.index] 

        +

        582 self.length = self.fileinfo[self.index]["length"] 

        +

        583 self.root_hash = self.fileinfo[self.index]["pieces root"] 

        +

        584 if self.length > self.piece_length: 

        +

        585 self.pieces = self.piece_layers[self.root_hash] 

        +

        586 else: 

        +

        587 self.pieces = self.root_hash 

        +

        588 path = self.paths[self.index] 

        +

        589 self.prog_start(self.length, path, unit="bytes") 

        +

        590 self.count = 0 

        +

        591 if os.path.exists(self.current): 

        +

        592 self.hasher = FileHasher(path, self.piece_length, progress=0) 

        +

        593 else: 

        +

        594 self.hasher = self.Padder(self.length, self.piece_length) 

        +

        595 return True 

        +

        596 if self.index >= len(self.paths): 

        +

        597 del self.current 

        +

        598 del self.length 

        +

        599 del self.root_hash 

        +

        600 del self.pieces 

        +

        601 return False 

        +

        602 

        +

        603 def process_current(self) -> tuple: 

        +

        604 """ 

        +

        605 Gather necessary information to compare to metafile details. 

        +

        606 

        +

        607 Returns 

        +

        608 ------- 

        +

        609 tuple 

        +

        610 a tuple containing the layer, piece, current path and size 

        +

        611 

        +

        612 Raises 

        +

        613 ------ 

        +

        614 StopIteration 

        +

        615 """ 

        +

        616 try: 

        +

        617 layer = next(self.hasher) 

        +

        618 piece, size = self.advance() 

        +

        619 self.prog_update(size) 

        +

        620 return layer, piece, self.current, size 

        +

        621 except StopIteration as err: 

        +

        622 if self.length > 0 and self.count * SHA256 < len(self.pieces): 

        +

        623 self.hasher = self.Padder(self.length, self.piece_length) 

        +

        624 piece, size = self.advance() 

        +

        625 layer = next(self.hasher) 

        +

        626 self.prog_update(0) 

        +

        627 return layer, piece, self.current, size 

        +

        628 raise StopIteration from err 

        +

        629 

        +

        630 def advance(self) -> tuple: 

        +

        631 """ 

        +

        632 Increment the number of pieces processed for the current file. 

        +

        633 

        +

        634 Returns 

        +

        635 ------- 

        +

        636 tuple 

        +

        637 the piece and size 

        +

        638 """ 

        +

        639 start = self.count * SHA256 

        +

        640 end = start + SHA256 

        +

        641 piece = self.pieces[start:end] 

        +

        642 self.count += 1 

        +

        643 if self.length >= self.piece_length: 

        +

        644 self.length -= self.piece_length 

        +

        645 size = self.piece_length 

        +

        646 else: 

        +

        647 size = self.length 

        +

        648 self.length -= self.length 

        +

        649 return piece, size 

        +
        + + + diff --git a/docs/htmlcover/d_1bc82e0ab2fcb2ec_torrent_py.html b/docs/htmlcover/d_1bc82e0ab2fcb2ec_torrent_py.html new file mode 100644 index 00000000..606c0004 --- /dev/null +++ b/docs/htmlcover/d_1bc82e0ab2fcb2ec_torrent_py.html @@ -0,0 +1,825 @@ + + + + + Coverage for torrentfile\torrent.py: 100% + + + + + +
        +
        +

        + Coverage for torrentfile\torrent.py: + 100% +

        + +

        + 242 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Classes and procedures pertaining to the creation of torrent meta files. 

        +

        21 

        +

        22Classes 

        +

        23------- 

        +

        24 

        +

        25- `TorrentFile` 

        +

        26 construct .torrent file. 

        +

        27 

        +

        28- `TorrentFileV2` 

        +

        29 construct .torrent v2 files using provided data. 

        +

        30 

        +

        31- `MetaFile` 

        +

        32 base class for all MetaFile classes. 

        +

        33 

        +

        34Constants 

        +

        35--------- 

        +

        36 

        +

        37- BLOCK_SIZE : int 

        +

        38 size of leaf hashes for merkle tree. 

        +

        39 

        +

        40- HASH_SIZE : int 

        +

        41 Length of a sha256 hash. 

        +

        42 

        +

        43Bittorrent V2 

        +

        44------------- 

        +

        45 

        +

        46**From Bittorrent.org Documentation pages.** 

        +

        47 

        +

        48*Implementation details for Bittorrent Protocol v2.* 

        +

        49 

        +

        50!!!Note 

        +

        51 All strings in a .torrent file that contain text must be UTF-8 encoded. 

        +

        52 

        +

        53### Meta Version 2 Dictionary: 

        +

        54 

        +

        55- "announce": 

        +

        56 The URL of the tracker. 

        +

        57 

        +

        58- "info": 

        +

        59 This maps to a dictionary, with keys described below. 

        +

        60 

        +

        61 - "name": 

        +

        62 A display name for the torrent. It is purely advisory. 

        +

        63 

        +

        64 - "piece length": 

        +

        65 The number of bytes that each logical piece in the peer 

        +

        66 protocol refers to. I.e. it sets the granularity of piece, request, 

        +

        67 bitfield and have messages. It must be a power of two and at least 

        +

        68 6KiB. 

        +

        69 

        +

        70 - "meta version": 

        +

        71 An integer value, set to 2 to indicate compatibility 

        +

        72 with the current revision of this specification. Version 1 is not 

        +

        73 assigned to avoid confusion with BEP3. Future revisions will only 

        +

        74 increment this issue to indicate an incompatible change has been made, 

        +

        75 for example that hash algorithms were changed due to newly discovered 

        +

        76 vulnerabilities. Lementations must check this field first and indicate 

        +

        77 that a torrent is of a newer version than they can handle before 

        +

        78 performing other idations which may result in more general messages 

        +

        79 about invalid files. Files are mapped into this piece address space so 

        +

        80 that each non-empty 

        +

        81 

        +

        82 - "file tree": 

        +

        83 A tree of dictionaries where dictionary keys represent UTF-8 

        +

        84 encoded path elements. Entries with zero-length keys describe the 

        +

        85 properties of the composed path at that point. 'UTF-8 encoded' 

        +

        86 context only means that if the native encoding is known at creation 

        +

        87 time it must be converted to UTF-8. Keys may contain invalid UTF-8 

        +

        88 sequences or characters and names that are reserved on specific 

        +

        89 filesystems. Implementations must be prepared to sanitize them. On 

        +

        90 platforms path components exactly matching '.' and '..' must be 

        +

        91 sanitized since they could lead to directory traversal attacks and 

        +

        92 conflicting path descriptions. On platforms that require UTF-8 

        +

        93 path components this sanitizing step must happen after normalizing 

        +

        94 overlong UTF-8 encodings. 

        +

        95 File is aligned to a piece boundary and occurs in same order as 

        +

        96 the file tree. The last piece of each file may be shorter than the 

        +

        97 specified piece length, resulting in an alignment gap. 

        +

        98 

        +

        99 - "length": 

        +

        100 Length of the file in bytes. Presence of this field indicates 

        +

        101 that the dictionary describes a file, not a directory. Which means 

        +

        102 it must not have any sibling entries. 

        +

        103 

        +

        104 - "pieces root": 

        +

        105 For non-empty files this is the the root hash of a merkle 

        +

        106 tree with a branching factor of 2, constructed from 16KiB blocks 

        +

        107 of the file. The last block may be shorter than 16KiB. The 

        +

        108 remaining leaf hashes beyond the end of the file required to 

        +

        109 construct upper layers of the merkle tree are set to zero. As of 

        +

        110 meta version 2 SHA2-256 is used as digest function for the merkle 

        +

        111 tree. The hash is stored in its binary form, not as human-readable 

        +

        112 string. 

        +

        113 

        +

        114- "piece layers": 

        +

        115 A dictionary of strings. For each file in the file tree that 

        +

        116 is larger than the piece size it contains one string value. 

        +

        117 The keys are the merkle roots while the values consist of concatenated 

        +

        118 hashes of one layer within that merkle tree. The layer is chosen so 

        +

        119 that one hash covers piece length bytes. For example if the piece 

        +

        120 size is 16KiB then the leaf hashes are used. If a piece size of 

        +

        121 128KiB is used then 3rd layer up from the leaf hashes is used. Layer 

        +

        122 hashes which exclusively cover data beyond the end of file, i.e. 

        +

        123 are only needed to balance the tree, are omitted. All hashes are 

        +

        124 stored in their binary format. A torrent is not valid if this field is 

        +

        125 absent, the contained hashes do not match the merkle roots or are 

        +

        126 not from the correct layer. 

        +

        127 

        +

        128!!!important 

        +

        129 The file tree root dictionary itself must not be a file, 

        +

        130 i.e. it must not contain a zero-length key with a dictionary containing 

        +

        131 a length key. 

        +

        132 

        +

        133Bittorrent V1 

        +

        134------------- 

        +

        135 

        +

        136### v1 meta-dictionary 

        +

        137 

        +

        138- announce: 

        +

        139 The URL of the tracker. 

        +

        140 

        +

        141- info: 

        +

        142 This maps to a dictionary, with keys described below. 

        +

        143 

        +

        144 - `name`: 

        +

        145 maps to a UTF-8 encoded string which is the suggested name to 

        +

        146 save the file (or directory) as. It is purely advisory. 

        +

        147 

        +

        148 - `piece length`: 

        +

        149 maps to the number of bytes in each piece the file is split 

        +

        150 into. For the purposes of transfer, files are split into 

        +

        151 fixed-size pieces which are all the same length except for 

        +

        152 possibly the last one which may be truncated. 

        +

        153 

        +

        154 - `piece length`: 

        +

        155 is almost always a power of two, most commonly 2^18 = 256 K 

        +

        156 

        +

        157 - `pieces`: 

        +

        158 maps to a string whose length is a multiple of 20. It is to be 

        +

        159 subdivided into strings of length 20, each of which is the SHA1 

        +

        160 hash of the piece at the corresponding index. 

        +

        161 

        +

        162 - `length`: 

        +

        163 In the single file case, maps to the length of the file in bytes. 

        +

        164 

        +

        165 - `files`: 

        +

        166 If present then the download represents a single file, otherwise it 

        +

        167 represents a set of files which go in a directory structure. 

        +

        168 For the purposes of the other keys, the multi-file case is treated 

        +

        169 as only having a single file by concatenating the files in the order 

        +

        170 they appear in the files list. The files list is the value `files` 

        +

        171 maps to, and is a list of dictionaries containing the following keys: 

        +

        172 

        +

        173 - `path`: 

        +

        174 A list of UTF-8 encoded strings corresponding to subdirectory 

        +

        175 names, the last of which is the actual file name 

        +

        176 

        +

        177 - `length`: 

        +

        178 Maps to the length of the file in bytes. 

        +

        179 

        +

        180 - `length`: 

        +

        181 Only present if the content is a single file. Maps to the length 

        +

        182 of the file in bytes. 

        +

        183 

        +

        184!!!Note 

        +

        185 In the single file case, the name key is the name of a file, 

        +

        186 in the muliple file case, it's the name of a directory. 

        +

        187""" 

        +

        188 

        +

        189import logging 

        +

        190import os 

        +

        191from collections.abc import Sequence 

        +

        192from datetime import datetime 

        +

        193 

        +

        194import pyben 

        +

        195 

        +

        196from torrentfile import utils 

        +

        197from torrentfile.hasher import FileHasher, Hasher, HasherHybrid, HasherV2 

        +

        198from torrentfile.mixins import ProgMixin 

        +

        199from torrentfile.version import __version__ as version 

        +

        200 

        +

        201logger = logging.getLogger(__name__) 

        +

        202 

        +

        203 

        +

        204class MetaFile: 

        +

        205 """ 

        +

        206 Base Class for all TorrentFile classes. 

        +

        207 

        +

        208 Parameters 

        +

        209 ---------- 

        +

        210 path : str 

        +

        211 target path to torrent content. Default: None 

        +

        212 announce : str 

        +

        213 One or more tracker URL's. Default: None 

        +

        214 comment : str 

        +

        215 A comment. Default: None 

        +

        216 piece_length : int 

        +

        217 Size of torrent pieces. Default: None 

        +

        218 private : bool 

        +

        219 For private trackers. Default: None 

        +

        220 outfile : str 

        +

        221 target path to write .torrent file. Default: None 

        +

        222 source : str 

        +

        223 Private tracker source. Default: None 

        +

        224 progress : str 

        +

        225 level of progress bar displayed Default: "1" 

        +

        226 cwd : bool 

        +

        227 If True change default save location to current directory 

        +

        228 httpseeds : list 

        +

        229 one or more web addresses where torrent content can be found. 

        +

        230 url_list : list 

        +

        231 one or more web addressess where torrent content exists. 

        +

        232 content : str 

        +

        233 alias for 'path' arg. 

        +

        234 meta_version : int 

        +

        235 indicates which Bittorrent protocol to use for hashing content 

        +

        236 """ 

        +

        237 

        +

        238 hasher = None 

        +

        239 

        +

        240 @classmethod 

        +

        241 def set_callback(cls, func): 

        +

        242 """ 

        +

        243 Assign a callback function for the Hashing class to call for each hash. 

        +

        244 

        +

        245 Parameters 

        +

        246 ---------- 

        +

        247 func : function 

        +

        248 The callback function which accepts a single paramter. 

        +

        249 """ 

        +

        250 if "hasher" in vars(cls) and vars(cls)["hasher"]: 

        +

        251 cls.hasher.set_callback(func) 

        +

        252 

        +

        253 def __init__( 

        +

        254 self, 

        +

        255 path=None, 

        +

        256 announce=None, 

        +

        257 comment=None, 

        +

        258 piece_length=None, 

        +

        259 private=False, 

        +

        260 outfile=None, 

        +

        261 source=None, 

        +

        262 progress=1, 

        +

        263 cwd=False, 

        +

        264 httpseeds=None, 

        +

        265 url_list=None, 

        +

        266 content=None, 

        +

        267 meta_version=None, 

        +

        268 **_, 

        +

        269 ): 

        +

        270 """ 

        +

        271 Construct MetaFile superclass and assign local attributes. 

        +

        272 """ 

        +

        273 self.private = private 

        +

        274 self.cwd = cwd 

        +

        275 self.outfile = outfile 

        +

        276 self.progress = int(progress) 

        +

        277 self.comment = comment 

        +

        278 self.source = source 

        +

        279 self.meta_version = meta_version 

        +

        280 

        +

        281 if content: 

        +

        282 path = content 

        +

        283 if not path: 

        +

        284 if announce and len(announce) > 1 and os.path.exists(announce[-1]): 

        +

        285 path = announce[-1] 

        +

        286 announce = announce[:-1] 

        +

        287 elif url_list and os.path.exists(url_list[-1]): 

        +

        288 path = url_list[-1] 

        +

        289 url_list = url_list[:-1] 

        +

        290 elif httpseeds and os.path.exists(httpseeds[-1]): 

        +

        291 path = httpseeds[-1] 

        +

        292 httpseeds = httpseeds[:-1] 

        +

        293 else: 

        +

        294 raise utils.MissingPathError("Path to content is required.") 

        +

        295 

        +

        296 # base path to torrent content. 

        +

        297 self.path = path 

        +

        298 

        +

        299 logger.debug("path parameter found %s", path) 

        +

        300 

        +

        301 # Format piece_length attribute. 

        +

        302 if piece_length: 

        +

        303 self.piece_length = utils.normalize_piece_length(piece_length) 

        +

        304 logger.debug("piece length parameter found %s", piece_length) 

        +

        305 else: 

        +

        306 self.piece_length = utils.path_piece_length(self.path) 

        +

        307 logger.debug("piece length calculated %s", self.piece_length) 

        +

        308 

        +

        309 # Assign announce URL to empty string if none provided. 

        +

        310 if not announce: 

        +

        311 self.announce, self.announce_list = "", [[""]] 

        +

        312 

        +

        313 # Most torrent clients have editting trackers as a feature. 

        +

        314 elif isinstance(announce, str): 

        +

        315 self.announce, self.announce_list = announce, [[announce]] 

        +

        316 

        +

        317 elif isinstance(announce, Sequence): 

        +

        318 self.announce, self.announce_list = announce[0], [announce] 

        +

        319 

        +

        320 self.meta = { 

        +

        321 "announce": self.announce, 

        +

        322 "announce-list": self.announce_list, 

        +

        323 "created by": f"TorrentFile_v{version}", 

        +

        324 "creation date": int(datetime.timestamp(datetime.now())), 

        +

        325 "info": {}, 

        +

        326 } 

        +

        327 if comment: 

        +

        328 self.meta["info"]["comment"] = comment 

        +

        329 logger.debug("comment parameter found %s", comment) 

        +

        330 if private: 

        +

        331 self.meta["info"]["private"] = 1 

        +

        332 logger.debug("private parameter triggered") 

        +

        333 if source: 

        +

        334 self.meta["info"]["source"] = source 

        +

        335 logger.debug("source parameter found %s", source) 

        +

        336 if url_list: 

        +

        337 self.meta["url-list"] = url_list 

        +

        338 logger.debug("url list parameter found %s", str(url_list)) 

        +

        339 if httpseeds: 

        +

        340 self.meta["httpseeds"] = httpseeds 

        +

        341 logger.debug("httpseeds parameter found %s", str(httpseeds)) 

        +

        342 self.meta["info"]["piece length"] = self.piece_length 

        +

        343 

        +

        344 self.meta_version = meta_version 

        +

        345 parent, self.name = os.path.split(self.path) 

        +

        346 if not self.name: 

        +

        347 self.name = os.path.basename(parent) 

        +

        348 self.meta["info"]["name"] = self.name 

        +

        349 

        +

        350 def assemble(self): 

        +

        351 """ 

        +

        352 Overload in subclasses. 

        +

        353 

        +

        354 Raises 

        +

        355 ------ 

        +

        356 Exception 

        +

        357 NotImplementedError 

        +

        358 """ 

        +

        359 raise NotImplementedError 

        +

        360 

        +

        361 def sort_meta(self): 

        +

        362 """Sort the info and meta dictionaries.""" 

        +

        363 logger.debug("sorting dictionary keys") 

        +

        364 meta = self.meta 

        +

        365 meta["info"] = dict(sorted(list(meta["info"].items()))) 

        +

        366 meta = dict(sorted(list(meta.items()))) 

        +

        367 return meta 

        +

        368 

        +

        369 def write(self, outfile=None) -> tuple: 

        +

        370 """ 

        +

        371 Write meta information to .torrent file. 

        +

        372 

        +

        373 Final step in the torrent file creation process. 

        +

        374 After hashing and sorting every piece of content 

        +

        375 write the contents to file using the bencode encoding. 

        +

        376 

        +

        377 Parameters 

        +

        378 ---------- 

        +

        379 outfile : str 

        +

        380 Destination path for .torrent file. default=None 

        +

        381 

        +

        382 Returns 

        +

        383 ------- 

        +

        384 outfile : str 

        +

        385 Where the .torrent file was writen. 

        +

        386 meta : dict 

        +

        387 .torrent meta information. 

        +

        388 """ 

        +

        389 if outfile: 

        +

        390 self.outfile = outfile 

        +

        391 elif self.outfile: 

        +

        392 pass 

        +

        393 else: 

        +

        394 self.outfile = os.path.join(os.getcwd(), self.name) + ".torrent" 

        +

        395 if str(self.outfile)[-1] in "\\/": 

        +

        396 self.outfile = self.outfile + (self.name + ".torrent") 

        +

        397 self.meta = self.sort_meta() 

        +

        398 try: 

        +

        399 pyben.dump(self.meta, self.outfile) 

        +

        400 except PermissionError as excp: 

        +

        401 logger.error( 

        +

        402 "Permission Denied: Could not write to %s", self.outfile 

        +

        403 ) 

        +

        404 raise PermissionError from excp 

        +

        405 return self.outfile, self.meta 

        +

        406 

        +

        407 

        +

        408class TorrentFile(MetaFile, ProgMixin): 

        +

        409 """ 

        +

        410 Class for creating Bittorrent meta files. 

        +

        411 

        +

        412 Construct *Torrentfile* class instance object. 

        +

        413 

        +

        414 Parameters 

        +

        415 ---------- 

        +

        416 **kwargs : dict 

        +

        417 Dictionary containing torrent file options. 

        +

        418 """ 

        +

        419 

        +

        420 hasher = Hasher 

        +

        421 

        +

        422 def __init__(self, **kwargs): 

        +

        423 """ 

        +

        424 Construct TorrentFile instance with given keyword args. 

        +

        425 

        +

        426 Parameters 

        +

        427 ---------- 

        +

        428 **kwargs : dict 

        +

        429 dictionary of keyword args passed to superclass. 

        +

        430 """ 

        +

        431 super().__init__(**kwargs) 

        +

        432 logger.debug("Assembling bittorrent v1 torrent file") 

        +

        433 self.assemble() 

        +

        434 

        +

        435 def assemble(self): 

        +

        436 """ 

        +

        437 Assemble components of torrent metafile. 

        +

        438 

        +

        439 Returns 

        +

        440 ------- 

        +

        441 dict 

        +

        442 metadata dictionary for torrent file 

        +

        443 """ 

        +

        444 info = self.meta["info"] 

        +

        445 size, filelist = utils.filelist_total(self.path) 

        +

        446 if os.path.isfile(self.path): 

        +

        447 info["length"] = size 

        +

        448 else: 

        +

        449 info["files"] = [ 

        +

        450 { 

        +

        451 "length": os.path.getsize(path), 

        +

        452 "path": os.path.relpath(path, self.path).split(os.sep), 

        +

        453 } 

        +

        454 for path in filelist 

        +

        455 ] 

        +

        456 pieces = bytearray() 

        +

        457 

        +

        458 feeder = Hasher(filelist, self.piece_length, self.progress) 

        +

        459 for piece in feeder: 

        +

        460 pieces.extend(piece) 

        +

        461 

        +

        462 info["pieces"] = pieces 

        +

        463 

        +

        464 

        +

        465class TorrentFileV2(MetaFile, ProgMixin): 

        +

        466 """ 

        +

        467 Class for creating Bittorrent meta v2 files. 

        +

        468 

        +

        469 Parameters 

        +

        470 ---------- 

        +

        471 **kwargs : dict 

        +

        472 Keyword arguments for torrent file options. 

        +

        473 """ 

        +

        474 

        +

        475 hasher = HasherV2 

        +

        476 

        +

        477 def __init__(self, **kwargs): 

        +

        478 """ 

        +

        479 Construct `TorrentFileV2` Class instance from given parameters. 

        +

        480 

        +

        481 Parameters 

        +

        482 ---------- 

        +

        483 **kwargs : dict 

        +

        484 keywword arguments to pass to superclass. 

        +

        485 """ 

        +

        486 super().__init__(**kwargs) 

        +

        487 logger.debug("Assembling bittorrent v2 torrent file") 

        +

        488 self.piece_layers = {} 

        +

        489 self.hashes = [] 

        +

        490 self.total = len(utils.get_file_list(self.path)) 

        +

        491 self.assemble() 

        +

        492 

        +

        493 def assemble(self): 

        +

        494 """ 

        +

        495 Assemble then return the meta dictionary for encoding. 

        +

        496 

        +

        497 Returns 

        +

        498 ------- 

        +

        499 meta : dict 

        +

        500 Metainformation about the torrent. 

        +

        501 """ 

        +

        502 info = self.meta["info"] 

        +

        503 if os.path.isfile(self.path): 

        +

        504 info["file tree"] = {info["name"]: self._traverse(self.path)} 

        +

        505 info["length"] = os.path.getsize(self.path) 

        +

        506 self.prog_update(info["length"]) 

        +

        507 else: 

        +

        508 info["file tree"] = self._traverse(self.path) 

        +

        509 

        +

        510 info["meta version"] = 2 

        +

        511 self.meta["piece layers"] = self.piece_layers 

        +

        512 

        +

        513 def _traverse(self, path: str) -> dict: 

        +

        514 """ 

        +

        515 Walk directory tree. 

        +

        516 

        +

        517 Parameters 

        +

        518 ---------- 

        +

        519 path : str 

        +

        520 Path to file or directory. 

        +

        521 """ 

        +

        522 if os.path.isfile(path): 

        +

        523 # Calculate Size and hashes for each file. 

        +

        524 size = os.path.getsize(path) 

        +

        525 

        +

        526 if size == 0: 

        +

        527 return {"": {"length": size}} 

        +

        528 

        +

        529 logger.debug("Hashing %s", str(path)) 

        +

        530 fhash = HasherV2(path, self.piece_length, self.progress) 

        +

        531 

        +

        532 if size > self.piece_length: 

        +

        533 self.piece_layers[fhash.root] = fhash.piece_layer 

        +

        534 return {"": {"length": size, "pieces root": fhash.root}} 

        +

        535 

        +

        536 file_tree = {} 

        +

        537 if os.path.isdir(path): 

        +

        538 for name in sorted(os.listdir(path)): 

        +

        539 file_tree[name] = self._traverse(os.path.join(path, name)) 

        +

        540 return file_tree 

        +

        541 

        +

        542 

        +

        543class TorrentFileHybrid(MetaFile, ProgMixin): 

        +

        544 """ 

        +

        545 Construct the Hybrid torrent meta file with provided parameters. 

        +

        546 

        +

        547 Parameters 

        +

        548 ---------- 

        +

        549 **kwargs : dict 

        +

        550 Keyword arguments for torrent options. 

        +

        551 """ 

        +

        552 

        +

        553 hasher = HasherHybrid 

        +

        554 

        +

        555 def __init__(self, **kwargs): 

        +

        556 """ 

        +

        557 Create Bittorrent v1 v2 hybrid metafiles. 

        +

        558 """ 

        +

        559 super().__init__(**kwargs) 

        +

        560 logger.debug("Assembling bittorrent Hybrid file") 

        +

        561 self.name = os.path.basename(self.path) 

        +

        562 self.hashes = [] 

        +

        563 self.piece_layers = {} 

        +

        564 self.pieces = [] 

        +

        565 self.files = [] 

        +

        566 self.total = len(utils.get_file_list(self.path)) 

        +

        567 self.assemble() 

        +

        568 

        +

        569 def assemble(self): 

        +

        570 """ 

        +

        571 Assemble the parts of the torrentfile into meta dictionary. 

        +

        572 """ 

        +

        573 info = self.meta["info"] 

        +

        574 info["meta version"] = 2 

        +

        575 

        +

        576 if os.path.isfile(self.path): 

        +

        577 info["file tree"] = {self.name: self._traverse(self.path)} 

        +

        578 info["length"] = os.path.getsize(self.path) 

        +

        579 

        +

        580 else: 

        +

        581 info["file tree"] = self._traverse(self.path) 

        +

        582 info["files"] = self.files 

        +

        583 

        +

        584 info["pieces"] = b"".join(self.pieces) 

        +

        585 self.meta["piece layers"] = self.piece_layers 

        +

        586 return info 

        +

        587 

        +

        588 def _traverse(self, path: str) -> dict: 

        +

        589 """ 

        +

        590 Build meta dictionary while walking directory. 

        +

        591 

        +

        592 Parameters 

        +

        593 ---------- 

        +

        594 path : str 

        +

        595 Path to target file. 

        +

        596 """ 

        +

        597 if os.path.isfile(path): 

        +

        598 file_size = os.path.getsize(path) 

        +

        599 

        +

        600 self.files.append( 

        +

        601 { 

        +

        602 "length": file_size, 

        +

        603 "path": os.path.relpath(path, self.path).split(os.sep), 

        +

        604 } 

        +

        605 ) 

        +

        606 

        +

        607 if file_size == 0: 

        +

        608 return {"": {"length": file_size}} 

        +

        609 

        +

        610 logger.debug("Hashing %s", str(path)) 

        +

        611 file_hash = HasherHybrid(path, self.piece_length, self.progress) 

        +

        612 self.prog_update(file_size) 

        +

        613 

        +

        614 if file_size > self.piece_length: 

        +

        615 self.piece_layers[file_hash.root] = file_hash.piece_layer 

        +

        616 

        +

        617 self.hashes.append(file_hash) 

        +

        618 self.pieces.extend(file_hash.pieces) 

        +

        619 

        +

        620 if file_hash.padding_file: 

        +

        621 self.files.append(file_hash.padding_file) 

        +

        622 

        +

        623 return {"": {"length": file_size, "pieces root": file_hash.root}} 

        +

        624 

        +

        625 tree = {} 

        +

        626 if os.path.isdir(path): 

        +

        627 for name in sorted(os.listdir(path)): 

        +

        628 tree[name] = self._traverse(os.path.join(path, name)) 

        +

        629 return tree 

        +

        630 

        +

        631 

        +

        632class TorrentAssembler(MetaFile): 

        +

        633 """ 

        +

        634 Assembler class for Bittorrent version 2 and hybrid meta files. 

        +

        635 

        +

        636 This differs from the TorrentFileV2 and TorrentFileHybrid, because 

        +

        637 it can be used as an iterator and works for both versions. 

        +

        638 

        +

        639 Parameters 

        +

        640 ---------- 

        +

        641 **kwargs : dict 

        +

        642 Keyword arguments for torrent options. 

        +

        643 """ 

        +

        644 

        +

        645 hasher = FileHasher 

        +

        646 

        +

        647 def __init__(self, **kwargs): 

        +

        648 """ 

        +

        649 Create Bittorrent v1 v2 hybrid metafiles. 

        +

        650 """ 

        +

        651 super().__init__(**kwargs) 

        +

        652 logger.debug("Assembling bittorrent Hybrid file") 

        +

        653 self.name = os.path.basename(self.path) 

        +

        654 self.hashes = [] 

        +

        655 self.piece_layers = {} 

        +

        656 self.pieces = bytearray() 

        +

        657 self.files = [] 

        +

        658 self.hybrid = self.meta_version == "3" 

        +

        659 self.total = len(utils.get_file_list(self.path)) 

        +

        660 self.assemble() 

        +

        661 

        +

        662 def assemble(self): 

        +

        663 """ 

        +

        664 Assemble the parts of the torrentfile into meta dictionary. 

        +

        665 """ 

        +

        666 info = self.meta["info"] 

        +

        667 info["meta version"] = 2 

        +

        668 

        +

        669 if os.path.isfile(self.path): 

        +

        670 info["file tree"] = {self.name: self._traverse(self.path)} 

        +

        671 info["length"] = os.path.getsize(self.path) 

        +

        672 

        +

        673 else: 

        +

        674 info["file tree"] = self._traverse(self.path) 

        +

        675 if self.hybrid: 

        +

        676 info["files"] = self.files 

        +

        677 

        +

        678 if self.hybrid: 

        +

        679 info["pieces"] = self.pieces 

        +

        680 self.meta["piece layers"] = self.piece_layers 

        +

        681 return info 

        +

        682 

        +

        683 def _traverse(self, path: str) -> dict: 

        +

        684 """ 

        +

        685 Build meta dictionary while walking directory. 

        +

        686 

        +

        687 Parameters 

        +

        688 ---------- 

        +

        689 path : str 

        +

        690 Path to target file. 

        +

        691 """ 

        +

        692 if os.path.isfile(path): 

        +

        693 file_size = os.path.getsize(path) 

        +

        694 if self.hybrid: 

        +

        695 self.files.append( 

        +

        696 { 

        +

        697 "length": file_size, 

        +

        698 "path": os.path.relpath(path, self.path).split(os.sep), 

        +

        699 } 

        +

        700 ) 

        +

        701 

        +

        702 if file_size == 0: 

        +

        703 return {"": {"length": file_size}} 

        +

        704 

        +

        705 logger.debug("Hashing %s", str(path)) 

        +

        706 hasher = FileHasher( 

        +

        707 path, self.piece_length, progress=True, hybrid=self.hybrid 

        +

        708 ) 

        +

        709 layers = bytearray() 

        +

        710 for result in hasher: 

        +

        711 if self.hybrid: 

        +

        712 layer_hash, piece = result 

        +

        713 self.pieces.extend(piece) 

        +

        714 else: 

        +

        715 layer_hash = result 

        +

        716 layers.extend(layer_hash) 

        +

        717 if file_size > self.piece_length: 

        +

        718 self.piece_layers[hasher.root] = layers 

        +

        719 if self.hybrid and hasher.padding_file: 

        +

        720 self.files.append(hasher.padding_file) 

        +

        721 

        +

        722 return {"": {"length": file_size, "pieces root": hasher.root}} 

        +

        723 

        +

        724 tree = {} 

        +

        725 if os.path.isdir(path): 

        +

        726 for name in sorted(os.listdir(path)): 

        +

        727 tree[name] = self._traverse(os.path.join(path, name)) 

        +

        728 return tree 

        +
        + + + diff --git a/docs/htmlcover/d_1bc82e0ab2fcb2ec_utils_py.html b/docs/htmlcover/d_1bc82e0ab2fcb2ec_utils_py.html new file mode 100644 index 00000000..6634e96d --- /dev/null +++ b/docs/htmlcover/d_1bc82e0ab2fcb2ec_utils_py.html @@ -0,0 +1,523 @@ + + + + + Coverage for torrentfile\utils.py: 100% + + + + + +
        +
        +

        + Coverage for torrentfile\utils.py: + 100% +

        + +

        + 114 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Utility functions and classes used throughout package. 

        +

        21 

        +

        22Functions: 

        +

        23 get_piece_length: calculate ideal piece length for torrent file. 

        +

        24 sortfiles: traverse directory in sorted order yielding paths encountered. 

        +

        25 path_size: Sum the sizes of each file in path. 

        +

        26 get_file_list: Return list of all files contained in directory. 

        +

        27 path_stat: Get ideal piece length, total size, and file list for directory. 

        +

        28 path_piece_length: Get ideal piece length based on size of directory. 

        +

        29 

        +

        30Classes: 

        +

        31 MissingPathError: Custom exception raised when no path was provided to CLI. 

        +

        32 PieceLengthValueError: Custom exception raised when incorrect input value 

        +

        33 used for piece length field. 

        +

        34""" 

        +

        35 

        +

        36import math 

        +

        37import os 

        +

        38import shutil 

        +

        39from pathlib import Path 

        +

        40 

        +

        41 

        +

        42class Memo: 

        +

        43 """ 

        +

        44 Memoize cache. 

        +

        45 

        +

        46 Parameters 

        +

        47 ---------- 

        +

        48 func : Callable 

        +

        49 The results of this callable will be cached. 

        +

        50 """ 

        +

        51 

        +

        52 def __init__(self, func): 

        +

        53 """ 

        +

        54 Construct for memoization. 

        +

        55 """ 

        +

        56 self.func = func 

        +

        57 self.counter = 0 

        +

        58 self.cache = {} 

        +

        59 

        +

        60 def __call__(self, path): 

        +

        61 """ 

        +

        62 Invoke each time memo function is called. 

        +

        63 

        +

        64 Parameters 

        +

        65 ---------- 

        +

        66 path : str 

        +

        67 The relative or absolute path being used as key in cache dict. 

        +

        68 

        +

        69 Returns 

        +

        70 ------- 

        +

        71 Any : 

        +

        72 The results of calling the function with path. 

        +

        73 """ 

        +

        74 if path in self.cache and os.path.exists(path): 

        +

        75 self.counter += 1 

        +

        76 return self.cache[path] 

        +

        77 result = self.func(path) 

        +

        78 self.cache[path] = result 

        +

        79 return result 

        +

        80 

        +

        81 

        +

        82class MissingPathError(Exception): 

        +

        83 """ 

        +

        84 Path parameter is required to specify target content. 

        +

        85 

        +

        86 Creating a .torrent file with no contents seems rather silly. 

        +

        87 

        +

        88 Parameters 

        +

        89 ---------- 

        +

        90 message : str 

        +

        91 Message for user (optional). 

        +

        92 """ 

        +

        93 

        +

        94 def __init__(self, message: str = None): 

        +

        95 """ 

        +

        96 Raise when creating a meta file without specifying target content. 

        +

        97 

        +

        98 The `message` argument is a message to pass to Exception base class. 

        +

        99 """ 

        +

        100 self.message = f"Path arguement is missing and required {str(message)}" 

        +

        101 super().__init__(message) 

        +

        102 

        +

        103 

        +

        104class PieceLengthValueError(Exception): 

        +

        105 """ 

        +

        106 Piece Length parameter must equal a perfect power of 2. 

        +

        107 

        +

        108 Parameters 

        +

        109 ---------- 

        +

        110 message : str 

        +

        111 Message for user (optional). 

        +

        112 """ 

        +

        113 

        +

        114 def __init__(self, message: str = None): 

        +

        115 """ 

        +

        116 Raise when creating a meta file with incorrect piece length value. 

        +

        117 

        +

        118 The `message` argument is a message to pass to Exception base class. 

        +

        119 """ 

        +

        120 self.message = f"Incorrect value for piece length: {str(message)}" 

        +

        121 super().__init__(message) 

        +

        122 

        +

        123 

        +

        124class ArgumentError(Exception): 

        +

        125 """ 

        +

        126 Exception for mismatched or mistyped CLI arguments. 

        +

        127 """ 

        +

        128 

        +

        129 

        +

        130def humanize_bytes(amount: int) -> str: 

        +

        131 """ 

        +

        132 Convert integer into human readable memory sized denomination. 

        +

        133 

        +

        134 Parameters 

        +

        135 ---------- 

        +

        136 amount : int 

        +

        137 total number of bytes. 

        +

        138 

        +

        139 Returns 

        +

        140 ------- 

        +

        141 str 

        +

        142 human readable representation of the given amount of bytes. 

        +

        143 """ 

        +

        144 if amount < 1024: 

        +

        145 return str(amount) 

        +

        146 if 1024 <= amount < 1_048_576: 

        +

        147 return f"{amount // 1024} KiB" 

        +

        148 if 1_048_576 <= amount < 1_073_741_824: 

        +

        149 return f"{amount // 1_048_576} MiB" 

        +

        150 return f"{amount // 1073741824} GiB" 

        +

        151 

        +

        152 

        +

        153def normalize_piece_length(piece_length: int) -> int: 

        +

        154 """ 

        +

        155 Verify input piece_length is valid and convert accordingly. 

        +

        156 

        +

        157 Parameters 

        +

        158 ---------- 

        +

        159 piece_length : int | str 

        +

        160 The piece length provided by user. 

        +

        161 

        +

        162 Returns 

        +

        163 ------- 

        +

        164 int 

        +

        165 normalized piece length. 

        +

        166 

        +

        167 Raises 

        +

        168 ------ 

        +

        169 PieceLengthValueError : 

        +

        170 Piece length is improper value. 

        +

        171 """ 

        +

        172 if isinstance(piece_length, str): 

        +

        173 if piece_length.isnumeric(): 

        +

        174 piece_length = int(piece_length) 

        +

        175 else: 

        +

        176 raise PieceLengthValueError(piece_length) 

        +

        177 

        +

        178 if piece_length > (1 << 14): 

        +

        179 if 2 ** math.log2(piece_length) == piece_length: 

        +

        180 return piece_length 

        +

        181 raise PieceLengthValueError(piece_length) 

        +

        182 

        +

        183 if 13 < piece_length < 26: 

        +

        184 return 2**piece_length 

        +

        185 if piece_length <= 13: 

        +

        186 raise PieceLengthValueError(piece_length) 

        +

        187 

        +

        188 log = int(math.log2(piece_length)) 

        +

        189 if 2**log == piece_length: 

        +

        190 return piece_length 

        +

        191 raise PieceLengthValueError 

        +

        192 

        +

        193 

        +

        194def get_piece_length(size: int) -> int: 

        +

        195 """ 

        +

        196 Calculate the ideal piece length for bittorrent data. 

        +

        197 

        +

        198 Parameters 

        +

        199 ---------- 

        +

        200 size : int 

        +

        201 Total bits of all files incluided in .torrent file. 

        +

        202 

        +

        203 Returns 

        +

        204 ------- 

        +

        205 int 

        +

        206 Ideal piece length. 

        +

        207 """ 

        +

        208 exp = 14 

        +

        209 while size / (2**exp) > 1000 and exp < 24: 

        +

        210 exp += 1 

        +

        211 return 2**exp 

        +

        212 

        +

        213 

        +

        214@Memo 

        +

        215def filelist_total(pathstring: str) -> os.PathLike: 

        +

        216 """ 

        +

        217 Perform error checking and format conversion to os.PathLike. 

        +

        218 

        +

        219 Parameters 

        +

        220 ---------- 

        +

        221 pathstring : str 

        +

        222 An existing filesystem path. 

        +

        223 

        +

        224 Returns 

        +

        225 ------- 

        +

        226 os.PathLike 

        +

        227 Input path converted to bytes format. 

        +

        228 

        +

        229 Raises 

        +

        230 ------ 

        +

        231 MissingPathError 

        +

        232 File could not be found. 

        +

        233 """ 

        +

        234 if os.path.exists(pathstring): 

        +

        235 path = Path(pathstring) 

        +

        236 return _filelist_total(path) 

        +

        237 raise MissingPathError 

        +

        238 

        +

        239 

        +

        240def _filelist_total(path: str) -> tuple: 

        +

        241 """ 

        +

        242 Search directory tree for files. 

        +

        243 

        +

        244 Parameters 

        +

        245 ---------- 

        +

        246 path : str 

        +

        247 Path to file or directory base 

        +

        248 

        +

        249 Returns 

        +

        250 ------- 

        +

        251 int 

        +

        252 Sum of all filesizes in filelist. 

        +

        253 list 

        +

        254 All file paths within directory tree. 

        +

        255 """ 

        +

        256 if path.is_file(): 

        +

        257 file_size = os.path.getsize(path) 

        +

        258 return file_size, [str(path)] 

        +

        259 total = 0 

        +

        260 filelist = [] 

        +

        261 if path.is_dir(): 

        +

        262 for item in path.iterdir(): 

        +

        263 size, paths = filelist_total(item) 

        +

        264 total += size 

        +

        265 filelist.extend(paths) 

        +

        266 return total, sorted(filelist) 

        +

        267 

        +

        268 

        +

        269def path_size(path: str) -> int: 

        +

        270 """ 

        +

        271 Return the total size of all files in path recursively. 

        +

        272 

        +

        273 Parameters 

        +

        274 ---------- 

        +

        275 path : str 

        +

        276 path to target file or directory. 

        +

        277 

        +

        278 Returns 

        +

        279 ------- 

        +

        280 int 

        +

        281 total size of files. 

        +

        282 """ 

        +

        283 total_size, _ = filelist_total(path) 

        +

        284 return total_size 

        +

        285 

        +

        286 

        +

        287def get_file_list(path: str) -> list: 

        +

        288 """ 

        +

        289 Return a sorted list of file paths contained in directory. 

        +

        290 

        +

        291 Parameters 

        +

        292 ---------- 

        +

        293 path : str 

        +

        294 target file or directory. 

        +

        295 

        +

        296 Returns 

        +

        297 ------- 

        +

        298 list : 

        +

        299 sorted list of file paths. 

        +

        300 """ 

        +

        301 _, filelist = filelist_total(path) 

        +

        302 return filelist 

        +

        303 

        +

        304 

        +

        305def path_stat(path: str) -> tuple: 

        +

        306 """ 

        +

        307 Calculate directory statistics. 

        +

        308 

        +

        309 Parameters 

        +

        310 ---------- 

        +

        311 path : str 

        +

        312 The path to start calculating from. 

        +

        313 

        +

        314 Returns 

        +

        315 ------- 

        +

        316 list 

        +

        317 List of all files contained in Directory 

        +

        318 int 

        +

        319 Total sum of bytes from all contents of dir 

        +

        320 int 

        +

        321 The size of pieces of the torrent contents. 

        +

        322 """ 

        +

        323 total_size, filelist = filelist_total(path) 

        +

        324 piece_length = get_piece_length(total_size) 

        +

        325 return (filelist, total_size, piece_length) 

        +

        326 

        +

        327 

        +

        328def path_piece_length(path: str) -> int: 

        +

        329 """ 

        +

        330 Calculate piece length for input path and contents. 

        +

        331 

        +

        332 Parameters 

        +

        333 ---------- 

        +

        334 path : str 

        +

        335 The absolute path to directory and contents. 

        +

        336 

        +

        337 Returns 

        +

        338 ------- 

        +

        339 int 

        +

        340 The size of pieces of torrent content. 

        +

        341 """ 

        +

        342 psize = path_size(path) 

        +

        343 return get_piece_length(psize) 

        +

        344 

        +

        345 

        +

        346def next_power_2(value: int) -> int: 

        +

        347 """ 

        +

        348 Calculate the next perfect power of 2 equal to or greater than value. 

        +

        349 

        +

        350 Parameters 

        +

        351 ---------- 

        +

        352 value : int 

        +

        353 integer value that is less than some perfect power of 2. 

        +

        354 

        +

        355 Returns 

        +

        356 ------- 

        +

        357 int 

        +

        358 The next power of 2 greater than value, or value if already power of 2. 

        +

        359 """ 

        +

        360 if not value & (value - 1) and value: 

        +

        361 return value 

        +

        362 start = 1 

        +

        363 while start < value: 

        +

        364 start <<= 1 

        +

        365 return start 

        +

        366 

        +

        367 

        +

        368def copypath(source: str, dest: str) -> None: 

        +

        369 """ 

        +

        370 Copy the file located at source to dest. 

        +

        371 

        +

        372 If one or more directory paths don't exist in dest, they will be created. 

        +

        373 If dest already exists and dest and source are the same size, it will be 

        +

        374 ignored, however if dest is smaller than source, dest will be overwritten. 

        +

        375 

        +

        376 Parameters 

        +

        377 ---------- 

        +

        378 source : str 

        +

        379 path to source file 

        +

        380 dest : str 

        +

        381 path to target destination 

        +

        382 """ 

        +

        383 if not os.path.exists(source): 

        +

        384 return 

        +

        385 if os.path.exists(dest): 

        +

        386 if os.path.getsize(source) <= os.path.getsize(dest): 

        +

        387 return 

        +

        388 shutil.copy(source, dest) # pragma: nocover 

        +

        389 return # pragma: nocover 

        +

        390 path_parts = iter(Path(dest).parts[:-1]) 

        +

        391 try: 

        +

        392 root = next(path_parts) 

        +

        393 except StopIteration: # pragma: nocover 

        +

        394 return 

        +

        395 if not os.path.exists(root): 

        +

        396 os.mkdir(root) # pragma: nocover 

        +

        397 for part in path_parts: 

        +

        398 path = os.path.join(root, part) 

        +

        399 if not os.path.exists(path): 

        +

        400 os.mkdir(path) 

        +

        401 root = path 

        +

        402 shutil.copy(source, dest) 

        +

        403 

        +

        404 

        +

        405def toggle_debug_mode(switch_on: bool): 

        +

        406 """ 

        +

        407 Switch the environment variable debug indicator on or off. 

        +

        408 

        +

        409 Parameters 

        +

        410 ---------- 

        +

        411 switch_on : bool 

        +

        412 if true turn debug mode on otherwise off 

        +

        413 """ 

        +

        414 os.environ["TORRENTFILE_DEBUG"] = "ON" if switch_on else "OFF" 

        +

        415 

        +

        416 

        +

        417def debug_is_on() -> bool: 

        +

        418 """ 

        +

        419 Return True if debug mode is on in environment variables. 

        +

        420 

        +

        421 Returns 

        +

        422 ------- 

        +

        423 bool 

        +

        424 is debug mode on 

        +

        425 """ 

        +

        426 return os.environ["TORRENTFILE_DEBUG"] == "ON" 

        +
        + + + diff --git a/docs/htmlcover/d_1bc82e0ab2fcb2ec_version_py.html b/docs/htmlcover/d_1bc82e0ab2fcb2ec_version_py.html new file mode 100644 index 00000000..27384209 --- /dev/null +++ b/docs/htmlcover/d_1bc82e0ab2fcb2ec_version_py.html @@ -0,0 +1,120 @@ + + + + + Coverage for torrentfile\version.py: 100% + + + + + +
        +
        +

        + Coverage for torrentfile\version.py: + 100% +

        + +

        + 2 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Holds the release version number. 

        +

        21""" 

        +

        22 

        +

        23__version__ = "0.8.5" 

        +
        + + + diff --git a/docs/htmlcover/d_a44f0ac069e85531___init___py.html b/docs/htmlcover/d_a44f0ac069e85531___init___py.html new file mode 100644 index 00000000..bdb44b09 --- /dev/null +++ b/docs/htmlcover/d_a44f0ac069e85531___init___py.html @@ -0,0 +1,431 @@ + + + + + Coverage for tests\__init__.py: 100% + + + + + +
        +
        +

        + Coverage for tests\__init__.py: + 100% +

        + +

        + 127 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Unittest package init module. 

        +

        21""" 

        +

        22 

        +

        23import atexit 

        +

        24import os 

        +

        25import shutil 

        +

        26import string 

        +

        27from datetime import datetime 

        +

        28from pathlib import Path 

        +

        29 

        +

        30import pytest 

        +

        31 

        +

        32from torrentfile.torrent import ( 

        +

        33 TorrentAssembler, 

        +

        34 TorrentFile, 

        +

        35 TorrentFileHybrid, 

        +

        36 TorrentFileV2, 

        +

        37) 

        +

        38 

        +

        39 

        +

        40def tempfile(path=None, exp=18): 

        +

        41 """Create temporary file. 

        +

        42 

        +

        43 Creates a temporary file for unittesting purposes.py 

        +

        44 

        +

        45 Parameters 

        +

        46 ---------- 

        +

        47 path : str, optional 

        +

        48 relative path to temporary files, by default None 

        +

        49 exp : int, optional 

        +

        50 Exponent used to determine size of file., by default 18 

        +

        51 

        +

        52 Returns 

        +

        53 ------- 

        +

        54 str 

        +

        55 absolute path to file. 

        +

        56 """ 

        +

        57 seq = (string.printable + string.whitespace).encode("utf-8") 

        +

        58 root = Path(__file__).parent / "TESTDIR" 

        +

        59 if not os.path.exists(root): 

        +

        60 os.mkdir(root) 

        +

        61 if not path: 

        +

        62 path = root / (str(datetime.timestamp(datetime.now())) + ".file") 

        +

        63 parts = Path(path).parts 

        +

        64 partial = root 

        +

        65 for i, part in enumerate(parts): 

        +

        66 partial = partial / part 

        +

        67 if i == len(parts) - 1: 

        +

        68 with open(partial, "wb") as binfile: 

        +

        69 size = 2**exp 

        +

        70 while size > 0: 

        +

        71 if len(seq) < size: 

        +

        72 binfile.write(seq) 

        +

        73 size -= len(seq) 

        +

        74 seq += seq 

        +

        75 else: 

        +

        76 binfile.write(seq[:size]) 

        +

        77 size -= size 

        +

        78 else: 

        +

        79 if not os.path.exists(partial): 

        +

        80 os.mkdir(partial) 

        +

        81 return partial 

        +

        82 

        +

        83 

        +

        84def rmpath(*args): 

        +

        85 """Remove file or directory path. 

        +

        86 

        +

        87 Parameters 

        +

        88 ---------- 

        +

        89 *args : list 

        +

        90 Filesystem locations for removing. 

        +

        91 """ 

        +

        92 for arg in args: 

        +

        93 if not os.path.exists(arg): 

        +

        94 continue 

        +

        95 if os.path.isdir(arg): 

        +

        96 try: 

        +

        97 shutil.rmtree(arg) 

        +

        98 except PermissionError: # pragma: nocover 

        +

        99 pass 

        +

        100 elif os.path.isfile(arg): 

        +

        101 try: 

        +

        102 os.remove(arg) 

        +

        103 except PermissionError: # pragma: nocover 

        +

        104 pass 

        +

        105 

        +

        106 

        +

        107def tempdir(ext="1"): 

        +

        108 """Create temporary directory. 

        +

        109 

        +

        110 Parameters 

        +

        111 ---------- 

        +

        112 ext : str, optional 

        +

        113 extension to file names, by default "1" 

        +

        114 

        +

        115 Returns 

        +

        116 ------- 

        +

        117 str 

        +

        118 path to common root for directory. 

        +

        119 """ 

        +

        120 layouts = { 

        +

        121 "1": [ 

        +

        122 f"dir{ext}/file1.png", 

        +

        123 f"dir{ext}/file2.mp4", 

        +

        124 f"dir{ext}/file3.mp3", 

        +

        125 f"dir{ext}/file4.zip", 

        +

        126 f"dir{ext}/file5.txt", 

        +

        127 f"dir{ext}/subdir1/subdir2/file.7z", 

        +

        128 f"dir{ext}/subdir/subdir/file4.rar", 

        +

        129 f"dir{ext}/subdir/subdir/file4.r01", 

        +

        130 ], 

        +

        131 "2": [ 

        +

        132 f"dir{ext}/file1.png", 

        +

        133 f"dir{ext}/file2.jpg", 

        +

        134 f"dir{ext}/subdir/file2.mp4", 

        +

        135 f"dir{ext}/subdir/file3.mp3", 

        +

        136 ], 

        +

        137 } 

        +

        138 paths = [] 

        +

        139 for path in layouts[ext]: 

        +

        140 temps = tempfile(path=path, exp=18) 

        +

        141 paths.append(temps) 

        +

        142 return os.path.commonpath(paths) 

        +

        143 

        +

        144 

        +

        145@atexit.register 

        +

        146def teardown(): # pragma: nocover 

        +

        147 """ 

        +

        148 Remove all temporary directories and files. 

        +

        149 """ 

        +

        150 root = Path(__file__).parent / "TESTDIR" 

        +

        151 dest = Path(__file__).parent / "dest" 

        +

        152 dest2 = Path(__file__).parent / "dest2" 

        +

        153 for path in [root, "./torrentfile.log", dest, dest2]: 

        +

        154 if os.path.exists(path): 

        +

        155 rmpath(path) 

        +

        156 

        +

        157 

        +

        158def torrents(): 

        +

        159 """ 

        +

        160 Return seq of torrentfile objects. 

        +

        161 """ 

        +

        162 return [TorrentFile, TorrentFileV2, TorrentFileHybrid, TorrentAssembler] 

        +

        163 

        +

        164 

        +

        165@pytest.fixture(scope="package", params=[2**i for i in range(15, 20)]) 

        +

        166def sizes(request): 

        +

        167 """ 

        +

        168 Generate powers of 2 for file creation package scope. 

        +

        169 """ 

        +

        170 size = request.param 

        +

        171 yield size 

        +

        172 

        +

        173 

        +

        174@pytest.fixture(scope="package") 

        +

        175def dir1(): 

        +

        176 """Create a specific temporary structured directory. 

        +

        177 

        +

        178 Yields 

        +

        179 ------ 

        +

        180 str 

        +

        181 path to root of temporary directory 

        +

        182 """ 

        +

        183 root = tempdir() 

        +

        184 yield root 

        +

        185 rmpath(root) 

        +

        186 

        +

        187 

        +

        188@pytest.fixture() 

        +

        189def dir2(): 

        +

        190 """Create a specific temporary structured directory. 

        +

        191 

        +

        192 Yields 

        +

        193 ------ 

        +

        194 str 

        +

        195 path to root of temporary directory 

        +

        196 """ 

        +

        197 root = tempdir(ext="2") 

        +

        198 yield Path(root) 

        +

        199 rmpath(root) 

        +

        200 

        +

        201 

        +

        202@pytest.fixture(scope="package", params=torrents()) 

        +

        203def metafile1(dir1, request): 

        +

        204 """ 

        +

        205 Create a standard metafile for testing. 

        +

        206 """ 

        +

        207 versions = torrents() 

        +

        208 args = { 

        +

        209 "path": dir1, 

        +

        210 "announce": ["url1", "url2", "url4"], 

        +

        211 "url_list": ["url5", "url6", "url7"], 

        +

        212 "httpseeds": ["url5", "url6", "url7"], 

        +

        213 "comment": "this is a comment", 

        +

        214 "source": "SomeSource", 

        +

        215 "private": 1, 

        +

        216 } 

        +

        217 torrent_class = request.param 

        +

        218 outfile = str(dir1) + str(versions.index(torrent_class)) + ".torrent" 

        +

        219 torrent = torrent_class(**args) 

        +

        220 outfile, _ = torrent.write(outfile=outfile) 

        +

        221 yield outfile 

        +

        222 rmpath(outfile) 

        +

        223 

        +

        224 

        +

        225@pytest.fixture(params=torrents()) 

        +

        226def metafile2(dir2, request): 

        +

        227 """ 

        +

        228 Create a standard metafile for testing. 

        +

        229 """ 

        +

        230 args = { 

        +

        231 "path": dir2, 

        +

        232 "announce": ["url1", "url4"], 

        +

        233 "url_list": ["url6", "url7"], 

        +

        234 "comment": "this is a comment", 

        +

        235 "httpseeds": ["url6", "url7"], 

        +

        236 "source": "SomeSource", 

        +

        237 "private": 1, 

        +

        238 } 

        +

        239 torrent_class = request.param 

        +

        240 outfile = str(dir2) + ".torrent" 

        +

        241 torrent = torrent_class(**args) 

        +

        242 outfile, _ = torrent.write(outfile=outfile) 

        +

        243 yield outfile 

        +

        244 rmpath(outfile) 

        +

        245 

        +

        246 

        +

        247@pytest.fixture(scope="package") 

        +

        248def file1(): 

        +

        249 """ 

        +

        250 Return the path to a temporary file package scope. 

        +

        251 """ 

        +

        252 path = tempfile() 

        +

        253 yield path 

        +

        254 rmpath(path) 

        +

        255 

        +

        256 

        +

        257@pytest.fixture(scope="package", params=torrents()) 

        +

        258def filemeta1(file1, request): 

        +

        259 """ 

        +

        260 Test fixture for generating metafile for all versions of torrents. 

        +

        261 """ 

        +

        262 args = { 

        +

        263 "path": file1, 

        +

        264 "announce": ["url1", "url4"], 

        +

        265 "url_list": ["url6", "url7"], 

        +

        266 "httpseeds": ["url6", "url7"], 

        +

        267 "comment": "this is a comment", 

        +

        268 "source": "SomeSource", 

        +

        269 "private": 1, 

        +

        270 } 

        +

        271 versions = torrents() 

        +

        272 version = versions.index(request.param) 

        +

        273 name = str(file1) + "file" + str(version) + ".torrent" 

        +

        274 torrent = request.param(**args) 

        +

        275 outfile, _ = torrent.write(outfile=name) 

        +

        276 yield outfile 

        +

        277 rmpath(outfile) 

        +

        278 

        +

        279 

        +

        280@pytest.fixture(params=torrents()) 

        +

        281def filemeta2(file2, request): 

        +

        282 """ 

        +

        283 Test fixture for generating a meta file no scope. 

        +

        284 """ 

        +

        285 args = { 

        +

        286 "path": file2, 

        +

        287 "announce": ["url1", "url4"], 

        +

        288 "url_list": ["url6", "url7"], 

        +

        289 "httpseeds": ["url7", "url8"], 

        +

        290 "comment": "this is a comment", 

        +

        291 "source": "SomeSource", 

        +

        292 "private": 1, 

        +

        293 } 

        +

        294 versions = torrents() 

        +

        295 version = versions.index(request.param) 

        +

        296 name = str(file2) + "file" + str(version) + ".torrent" 

        +

        297 torrent = request.param(**args) 

        +

        298 outfile, _ = torrent.write(outfile=name) 

        +

        299 yield outfile 

        +

        300 rmpath(outfile) 

        +

        301 

        +

        302 

        +

        303@pytest.fixture() 

        +

        304def file2(): 

        +

        305 """ 

        +

        306 Return the path to a temporary file no scope. 

        +

        307 """ 

        +

        308 path = tempfile() 

        +

        309 yield path 

        +

        310 rmpath(path) 

        +

        311 

        +

        312 

        +

        313@pytest.fixture(params=torrents()) 

        +

        314def sizedfiles(dir2, sizes, request): 

        +

        315 """ 

        +

        316 Generate variable sized meta files for testing, no scope. 

        +

        317 """ 

        +

        318 versions = torrents() 

        +

        319 args = { 

        +

        320 "content": dir2, 

        +

        321 "announce": ["url1", "url2", "url4"], 

        +

        322 "url_list": ["url5", "url6", "url7"], 

        +

        323 "comment": "this is a comment", 

        +

        324 "source": "SomeSource", 

        +

        325 "private": 1, 

        +

        326 "piece_length": sizes, 

        +

        327 } 

        +

        328 torrent_class = request.param 

        +

        329 version = str(versions.index(torrent_class)) 

        +

        330 outfile = str(dir2) + version + str(sizes) + ".torrent" 

        +

        331 torrent = torrent_class(**args) 

        +

        332 outfile, _ = torrent.write(outfile=outfile) 

        +

        333 yield outfile 

        +

        334 rmpath(outfile) 

        +
        + + + diff --git a/docs/htmlcover/d_a44f0ac069e85531_test_cli_py.html b/docs/htmlcover/d_a44f0ac069e85531_test_cli_py.html new file mode 100644 index 00000000..3635bb97 --- /dev/null +++ b/docs/htmlcover/d_a44f0ac069e85531_test_cli_py.html @@ -0,0 +1,671 @@ + + + + + Coverage for tests\test_cli.py: 100% + + + + + +
        +
        +

        + Coverage for tests\test_cli.py: + 100% +

        + +

        + 224 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Testing functions for the command line interface. 

        +

        21""" 

        +

        22 

        +

        23import datetime 

        +

        24import os 

        +

        25import sys 

        +

        26 

        +

        27import pyben 

        +

        28import pytest 

        +

        29 

        +

        30from tests import dir1, dir2, file1, filemeta1, metafile1, rmpath 

        +

        31from torrentfile import execute 

        +

        32from torrentfile.__main__ import main 

        +

        33 

        +

        34 

        +

        35def test_fix(): 

        +

        36 """ 

        +

        37 Test dir1 fixture is not None. 

        +

        38 """ 

        +

        39 assert dir1 and dir2 and metafile1 and filemeta1 and file1 

        +

        40 

        +

        41 

        +

        42@pytest.fixture(scope="module") 

        +

        43def folder(dir1): 

        +

        44 """ 

        +

        45 Yield a folder object as fixture. 

        +

        46 """ 

        +

        47 sfolder = str(dir1) 

        +

        48 torrent = sfolder + ".torrent" 

        +

        49 yield (sfolder, torrent) 

        +

        50 rmpath(torrent) 

        +

        51 

        +

        52 

        +

        53def test_cli_v1(folder): 

        +

        54 """ 

        +

        55 Basic create torrent cli command. 

        +

        56 """ 

        +

        57 folder, torrent = folder 

        +

        58 args = ["torrentfile", "create", folder, "-o", torrent] 

        +

        59 sys.argv = args 

        +

        60 execute() 

        +

        61 assert os.path.exists(torrent) 

        +

        62 

        +

        63 

        +

        64def test_cli_v2(folder): 

        +

        65 """ 

        +

        66 Create torrent v2 cli command. 

        +

        67 """ 

        +

        68 folder, torrent = folder 

        +

        69 args = [ 

        +

        70 "torrentfile", 

        +

        71 "create", 

        +

        72 folder, 

        +

        73 "--meta-version", 

        +

        74 "2", 

        +

        75 "-o", 

        +

        76 torrent, 

        +

        77 ] 

        +

        78 sys.argv = args 

        +

        79 execute() 

        +

        80 assert os.path.exists(torrent) 

        +

        81 

        +

        82 

        +

        83def test_cli_v3(folder): 

        +

        84 """ 

        +

        85 Create hybrid torrent cli command. 

        +

        86 """ 

        +

        87 folder, torrent = folder 

        +

        88 args = [ 

        +

        89 "torrentfile", 

        +

        90 "create", 

        +

        91 folder, 

        +

        92 "--meta-version", 

        +

        93 "3", 

        +

        94 "-o", 

        +

        95 torrent, 

        +

        96 ] 

        +

        97 sys.argv = args 

        +

        98 execute() 

        +

        99 assert os.path.exists(torrent) 

        +

        100 

        +

        101 

        +

        102def test_cli_private(folder): 

        +

        103 """ 

        +

        104 Test private cli flag. 

        +

        105 """ 

        +

        106 folder, torrent = folder 

        +

        107 args = ["torrentfile", "create", folder, "--private", "-o", torrent] 

        +

        108 sys.argv = args 

        +

        109 main() 

        +

        110 meta = pyben.load(torrent) 

        +

        111 assert "private" in meta["info"] 

        +

        112 

        +

        113 

        +

        114@pytest.mark.parametrize("piece_length", [2**exp for exp in range(14, 21)]) 

        +

        115@pytest.mark.parametrize("version", ["1", "2", "3"]) 

        +

        116def test_cli_piece_length(folder, piece_length, version): 

        +

        117 """ 

        +

        118 Test piece length cli flag. 

        +

        119 """ 

        +

        120 folder, torrent = folder 

        +

        121 args = [ 

        +

        122 "torrentfile", 

        +

        123 "-v", 

        +

        124 "create", 

        +

        125 folder, 

        +

        126 "--piece-length", 

        +

        127 str(piece_length), 

        +

        128 "--meta-version", 

        +

        129 version, 

        +

        130 "--progress", 

        +

        131 "0", 

        +

        132 "-o", 

        +

        133 torrent, 

        +

        134 ] 

        +

        135 sys.argv = args 

        +

        136 execute() 

        +

        137 meta = pyben.load(torrent) 

        +

        138 assert meta["info"]["piece length"] == piece_length 

        +

        139 

        +

        140 

        +

        141@pytest.mark.parametrize("piece_length", [2**exp for exp in range(14, 21)]) 

        +

        142@pytest.mark.parametrize("version", ["1", "2", "3"]) 

        +

        143def test_cli_announce(folder, piece_length, version): 

        +

        144 """ 

        +

        145 Test announce cli flag. 

        +

        146 """ 

        +

        147 folder, torrent = folder 

        +

        148 args = [ 

        +

        149 "torrentfile", 

        +

        150 "create", 

        +

        151 folder, 

        +

        152 "--piece-length", 

        +

        153 str(piece_length), 

        +

        154 "--meta-version", 

        +

        155 version, 

        +

        156 "--tracker", 

        +

        157 "https://announce.org/tracker", 

        +

        158 "-o", 

        +

        159 torrent, 

        +

        160 ] 

        +

        161 sys.argv = args 

        +

        162 execute() 

        +

        163 meta = pyben.load(torrent) 

        +

        164 assert meta["announce"] == "https://announce.org/tracker" 

        +

        165 

        +

        166 

        +

        167@pytest.mark.parametrize("version", ["1", "2", "3"]) 

        +

        168def test_cli_announce_list(folder, version): 

        +

        169 """ 

        +

        170 Test announce-list cli flag. 

        +

        171 """ 

        +

        172 folder, torrent = folder 

        +

        173 trackers = [ 

        +

        174 "https://announce.org/tracker", 

        +

        175 "https://announce.net/tracker", 

        +

        176 "https://tracker.net/announce", 

        +

        177 ] 

        +

        178 args = [ 

        +

        179 "torrentfile", 

        +

        180 "create", 

        +

        181 folder, 

        +

        182 "--meta-version", 

        +

        183 version, 

        +

        184 "-o", 

        +

        185 torrent, 

        +

        186 "--tracker", 

        +

        187 ] + trackers 

        +

        188 sys.argv = args 

        +

        189 execute() 

        +

        190 meta = pyben.load(torrent) 

        +

        191 for url in trackers: 

        +

        192 assert url in [j for i in meta["announce-list"] for j in i] 

        +

        193 

        +

        194 

        +

        195@pytest.mark.parametrize("piece_length", [2**exp for exp in range(14, 21)]) 

        +

        196@pytest.mark.parametrize("version", ["1", "2", "3"]) 

        +

        197def test_cli_comment(folder, piece_length, version): 

        +

        198 """ 

        +

        199 Test comment cli flag. 

        +

        200 """ 

        +

        201 folder, torrent = folder 

        +

        202 args = [ 

        +

        203 "torrentfile", 

        +

        204 "create", 

        +

        205 folder, 

        +

        206 "--piece-length", 

        +

        207 str(piece_length), 

        +

        208 "--meta-version", 

        +

        209 version, 

        +

        210 "--magnet", 

        +

        211 "--comment", 

        +

        212 "this is a comment", 

        +

        213 "--progress", 

        +

        214 "1", 

        +

        215 "-o", 

        +

        216 torrent, 

        +

        217 ] 

        +

        218 sys.argv = args 

        +

        219 execute() 

        +

        220 meta = pyben.load(torrent) 

        +

        221 assert meta["info"]["comment"] == "this is a comment" 

        +

        222 

        +

        223 

        +

        224@pytest.mark.parametrize("piece_length", [2**exp for exp in range(14, 21)]) 

        +

        225@pytest.mark.parametrize("version", ["1", "2", "3"]) 

        +

        226def test_cli_outfile(dir1, piece_length, version): 

        +

        227 """ 

        +

        228 Test outfile cli flag. 

        +

        229 """ 

        +

        230 outfile = dir1 + "test.torrent" 

        +

        231 args = [ 

        +

        232 "torrentfile", 

        +

        233 "create", 

        +

        234 dir1, 

        +

        235 "--piece-length", 

        +

        236 str(piece_length), 

        +

        237 "--meta-version", 

        +

        238 version, 

        +

        239 "-o", 

        +

        240 outfile, 

        +

        241 "--prog", 

        +

        242 "1", 

        +

        243 ] 

        +

        244 sys.argv = args 

        +

        245 execute() 

        +

        246 assert os.path.exists(outfile) 

        +

        247 rmpath(outfile) 

        +

        248 

        +

        249 

        +

        250@pytest.mark.parametrize("piece_length", [2**exp for exp in range(14, 21)]) 

        +

        251@pytest.mark.parametrize("version", ["1", "2", "3"]) 

        +

        252def test_cli_creation_date(folder, piece_length, version): 

        +

        253 """ 

        +

        254 Test if torrents created get an accurate timestamp. 

        +

        255 """ 

        +

        256 folder, torrent = folder 

        +

        257 args = [ 

        +

        258 "torrentfile", 

        +

        259 "create", 

        +

        260 folder, 

        +

        261 "--piece-length", 

        +

        262 str(piece_length), 

        +

        263 "--meta-version", 

        +

        264 version, 

        +

        265 "--comment", 

        +

        266 "this is a comment", 

        +

        267 "-o", 

        +

        268 torrent, 

        +

        269 ] 

        +

        270 sys.argv = args 

        +

        271 execute() 

        +

        272 meta = pyben.load(torrent) 

        +

        273 num = float(meta["creation date"]) 

        +

        274 date = datetime.datetime.fromtimestamp(num) 

        +

        275 now = datetime.datetime.now() 

        +

        276 assert date.day == now.day 

        +

        277 assert date.year == now.year 

        +

        278 assert date.month == now.month 

        +

        279 

        +

        280 

        +

        281@pytest.mark.parametrize("piece_length", [2**exp for exp in range(14, 21)]) 

        +

        282@pytest.mark.parametrize("version", ["1", "2", "3"]) 

        +

        283def test_cli_created_by(folder, piece_length, version): 

        +

        284 """ 

        +

        285 Test if created torrents recieve a created by field in meta info. 

        +

        286 """ 

        +

        287 folder, torrent = folder 

        +

        288 args = [ 

        +

        289 "torrentfile", 

        +

        290 "-q", 

        +

        291 "create", 

        +

        292 folder, 

        +

        293 "--piece-length", 

        +

        294 str(piece_length), 

        +

        295 "--meta-version", 

        +

        296 version, 

        +

        297 "--comment", 

        +

        298 "this is a comment", 

        +

        299 "-o", 

        +

        300 torrent, 

        +

        301 ] 

        +

        302 sys.argv = args 

        +

        303 execute() 

        +

        304 meta = pyben.load(torrent) 

        +

        305 assert "TorrentFile" in meta["created by"] 

        +

        306 

        +

        307 

        +

        308@pytest.mark.parametrize("piece_length", [2**exp for exp in range(14, 21)]) 

        +

        309@pytest.mark.parametrize("version", ["1", "2", "3"]) 

        +

        310def test_cli_web_seeds(folder, piece_length, version): 

        +

        311 """ 

        +

        312 Test if created torrents recieve a web seeds field in meta info. 

        +

        313 """ 

        +

        314 folder, torrent = folder 

        +

        315 args = [ 

        +

        316 "torrentfile", 

        +

        317 "create", 

        +

        318 folder, 

        +

        319 "--piece-length", 

        +

        320 str(piece_length), 

        +

        321 "--meta-version", 

        +

        322 version, 

        +

        323 "-w", 

        +

        324 "https://webseed.url/1", 

        +

        325 "https://webseed.url/2", 

        +

        326 "https://webseed.url/3", 

        +

        327 "-o", 

        +

        328 torrent, 

        +

        329 ] 

        +

        330 sys.argv = args 

        +

        331 execute() 

        +

        332 meta = pyben.load(torrent) 

        +

        333 assert "https://webseed.url/1" in meta["url-list"] 

        +

        334 

        +

        335 

        +

        336@pytest.mark.parametrize("piece_length", [2**exp for exp in range(14, 21)]) 

        +

        337@pytest.mark.parametrize("version", ["1", "2", "3"]) 

        +

        338def test_cli_with_debug(folder, piece_length, version): 

        +

        339 """ 

        +

        340 Test debug mode cli flag. 

        +

        341 """ 

        +

        342 folder, torrent = folder 

        +

        343 args = [ 

        +

        344 "torrentfile", 

        +

        345 "-v", 

        +

        346 "create", 

        +

        347 folder, 

        +

        348 "--piece-length", 

        +

        349 str(piece_length), 

        +

        350 "--meta-version", 

        +

        351 version, 

        +

        352 "--comment", 

        +

        353 "this is a comment", 

        +

        354 "-o", 

        +

        355 torrent, 

        +

        356 ] 

        +

        357 sys.argv = args 

        +

        358 execute() 

        +

        359 assert os.path.exists(torrent) 

        +

        360 

        +

        361 

        +

        362@pytest.mark.parametrize("piece_length", [2**exp for exp in range(14, 21)]) 

        +

        363@pytest.mark.parametrize("version", ["1", "2", "3"]) 

        +

        364def test_cli_with_source(folder, piece_length, version): 

        +

        365 """ 

        +

        366 Test source cli flag. 

        +

        367 """ 

        +

        368 folder, torrent = folder 

        +

        369 args = [ 

        +

        370 "torrentfile", 

        +

        371 "create", 

        +

        372 folder, 

        +

        373 "--piece-length", 

        +

        374 str(piece_length), 

        +

        375 "--meta-version", 

        +

        376 version, 

        +

        377 "--source", 

        +

        378 "somesource", 

        +

        379 "-o", 

        +

        380 torrent, 

        +

        381 ] 

        +

        382 sys.argv = args 

        +

        383 execute() 

        +

        384 meta = pyben.load(torrent) 

        +

        385 assert meta["info"]["source"] == "somesource" 

        +

        386 

        +

        387 

        +

        388def test_cli_help(): 

        +

        389 """ 

        +

        390 Test showing help notice cli flag. 

        +

        391 """ 

        +

        392 args = ["-h"] 

        +

        393 sys.argv = args 

        +

        394 try: 

        +

        395 assert execute() 

        +

        396 except SystemExit: 

        +

        397 assert True 

        +

        398 

        +

        399 

        +

        400@pytest.mark.parametrize("version", ["1", "2", "3"]) 

        +

        401@pytest.mark.parametrize("progress", ["0", "1"]) 

        +

        402def test_cli_empty_files(dir2, version, progress): 

        +

        403 """ 

        +

        404 Test creating torrent with empty files. 

        +

        405 """ 

        +

        406 outfile = str(dir2) + ".torrent" 

        +

        407 args = [ 

        +

        408 "torrentfile", 

        +

        409 "create", 

        +

        410 str(dir2), 

        +

        411 "--meta-version", 

        +

        412 version, 

        +

        413 "--source", 

        +

        414 "somesource", 

        +

        415 "--prog", 

        +

        416 progress, 

        +

        417 "-o", 

        +

        418 outfile, 

        +

        419 ] 

        +

        420 sys.argv = args 

        +

        421 

        +

        422 def walk(root, count): 

        +

        423 """ 

        +

        424 Traverse directory to edit files. 

        +

        425 """ 

        +

        426 if root.is_file(): 

        +

        427 with open(root, "wb") as _: 

        +

        428 return 1 

        +

        429 elif root.is_dir(): 

        +

        430 for item in root.iterdir(): 

        +

        431 if count >= 2: 

        +

        432 break 

        +

        433 count += walk(item, count) 

        +

        434 return count 

        +

        435 

        +

        436 walk(dir2, 0) 

        +

        437 execute() 

        +

        438 assert os.path.exists(outfile) 

        +

        439 rmpath(outfile) 

        +

        440 

        +

        441 

        +

        442@pytest.mark.parametrize("ending", ["/", "\\"]) 

        +

        443def test_cli_slash_path(dir1, ending): 

        +

        444 """ 

        +

        445 Test if output when path ends with a /. 

        +

        446 """ 

        +

        447 outfile = str(dir1) + ".torrent" 

        +

        448 if sys.platform != "win32" and ending == "\\": # pragma: nocover 

        +

        449 ending = "/" 

        +

        450 args = [ 

        +

        451 "torrentfile", 

        +

        452 "create", 

        +

        453 "-o", 

        +

        454 outfile, 

        +

        455 "-t", 

        +

        456 "https://announce1.org", 

        +

        457 "--private", 

        +

        458 str(dir1) + ending, 

        +

        459 ] 

        +

        460 sys.argv = args 

        +

        461 execute() 

        +

        462 assert os.path.exists(outfile) 

        +

        463 rmpath(outfile) 

        +

        464 

        +

        465 

        +

        466@pytest.mark.parametrize("sep", ["/", "\\"]) 

        +

        467def test_cli_slash_outpath(dir1, sep): 

        +

        468 """ 

        +

        469 Test if output when outpath ends with a /. 

        +

        470 """ 

        +

        471 if sys.platform != "win32": 

        +

        472 sep = "/" # pragma: nocover 

        +

        473 parent = os.path.dirname(dir1) + sep 

        +

        474 args = [ 

        +

        475 "torrentfile", 

        +

        476 "create", 

        +

        477 "-t", 

        +

        478 "https://announce1.org", 

        +

        479 "--private", 

        +

        480 "-o", 

        +

        481 parent, 

        +

        482 str(dir1), 

        +

        483 ] 

        +

        484 sys.argv = args 

        +

        485 execute() 

        +

        486 outfile = str(dir1) + ".torrent" 

        +

        487 assert os.path.exists(outfile) 

        +

        488 rmpath(outfile) 

        +

        489 

        +

        490 

        +

        491@pytest.mark.parametrize( 

        +

        492 "flag", ["-t", "-w", "--announce", "--web-seed", "--http-seed"] 

        +

        493) 

        +

        494def test_cli_announce_path(dir1, flag): 

        +

        495 """ 

        +

        496 Test CLI when path is placed after the trackers flag. 

        +

        497 """ 

        +

        498 outfile = str(dir1) + ".torrent" 

        +

        499 args = [ 

        +

        500 "torrentfile", 

        +

        501 "create", 

        +

        502 "-o", 

        +

        503 outfile, 

        +

        504 flag, 

        +

        505 "https://announce1.org", 

        +

        506 str(dir1), 

        +

        507 ] 

        +

        508 sys.argv = args 

        +

        509 execute() 

        +

        510 assert os.path.exists(outfile) 

        +

        511 rmpath(outfile) 

        +

        512 

        +

        513 

        +

        514def test_cli_cwd(folder): 

        +

        515 """ 

        +

        516 Test outfile cli flag. 

        +

        517 """ 

        +

        518 folder, _ = folder 

        +

        519 args = [ 

        +

        520 "torrentfile", 

        +

        521 "create", 

        +

        522 "--cwd", 

        +

        523 folder, 

        +

        524 ] 

        +

        525 sys.argv = args 

        +

        526 current = os.getcwd() 

        +

        527 name = os.path.basename(folder) 

        +

        528 outfile = os.path.join(current, name) + ".torrent" 

        +

        529 execute() 

        +

        530 assert os.path.exists(outfile) 

        +

        531 rmpath(outfile) 

        +

        532 

        +

        533 

        +

        534@pytest.fixture() 

        +

        535def build(dir2): 

        +

        536 """Fixture for testing the build subcommand.""" 

        +

        537 dest = os.path.join(os.path.dirname(__file__), "dest") 

        +

        538 if os.path.exists(dest): 

        +

        539 rmpath(dest) 

        +

        540 os.makedirs(dest) 

        +

        541 return os.path.dirname(dir2), dest, dir2 

        +

        542 

        +

        543 

        +

        544@pytest.mark.parametrize("size", list(range(15, 19))) 

        +

        545@pytest.mark.parametrize("version", [1, 2]) 

        +

        546def test_rebuild_subcommand(build, version, size): 

        +

        547 """Test the rebuild CLI subcommand.""" 

        +

        548 basedir, dest, content = build 

        +

        549 args = [ 

        +

        550 "torrentfile", 

        +

        551 "create", 

        +

        552 str(content), 

        +

        553 "--meta-version", 

        +

        554 str(version), 

        +

        555 "--piece-length", 

        +

        556 str(size), 

        +

        557 "-o", 

        +

        558 str(content) + ".torrent", 

        +

        559 ] 

        +

        560 sys.argv = args 

        +

        561 execute() 

        +

        562 args = ["torrentfile", "rebuild", "-m", basedir, "-c", basedir, "-d", dest] 

        +

        563 sys.argv = args 

        +

        564 counter = execute() 

        +

        565 assert counter > 0 

        +

        566 

        +

        567 

        +

        568def test_empty_maker(dir1): 

        +

        569 """Test empty create cli.""" 

        +

        570 args = ["torrentfile", "create", dir1, "-o", dir1 + ".torrent"] 

        +

        571 sys.argv = args 

        +

        572 execute() 

        +

        573 assert os.path.exists(dir1 + ".torrent") 

        +

        574 rmpath(dir1 + ".torrent") 

        +
        + + + diff --git a/docs/htmlcover/d_a44f0ac069e85531_test_commands_py.html b/docs/htmlcover/d_a44f0ac069e85531_test_commands_py.html new file mode 100644 index 00000000..8f6fc0a8 --- /dev/null +++ b/docs/htmlcover/d_a44f0ac069e85531_test_commands_py.html @@ -0,0 +1,351 @@ + + + + + Coverage for tests\test_commands.py: 100% + + + + + +
        +
        +

        + Coverage for tests\test_commands.py: + 100% +

        + +

        + 102 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Testing functions for the sub-action commands from command line args. 

        +

        21""" 

        +

        22import os 

        +

        23import sys 

        +

        24from hashlib import sha1 # nosec 

        +

        25from urllib.parse import quote_plus 

        +

        26 

        +

        27import pyben 

        +

        28import pytest 

        +

        29 

        +

        30from tests import ( 

        +

        31 dir1, 

        +

        32 dir2, 

        +

        33 file1, 

        +

        34 metafile1, 

        +

        35 metafile2, 

        +

        36 rmpath, 

        +

        37 tempfile, 

        +

        38 torrents, 

        +

        39) 

        +

        40from torrentfile.cli import execute 

        +

        41from torrentfile.commands import info, magnet, rebuild, recheck 

        +

        42from torrentfile.hasher import merkle_root 

        +

        43from torrentfile.utils import ArgumentError 

        +

        44 

        +

        45 

        +

        46def test_fix(): 

        +

        47 """ 

        +

        48 Test dir1 fixture is not None. 

        +

        49 """ 

        +

        50 assert dir1 and metafile1 and file1 and metafile2 and dir2 

        +

        51 

        +

        52 

        +

        53def test_magnet_uri(metafile1): 

        +

        54 """ 

        +

        55 Test create magnet function digest. 

        +

        56 """ 

        +

        57 magnet_link = magnet(metafile1) 

        +

        58 meta = pyben.load(metafile1) 

        +

        59 announce = meta["announce"] 

        +

        60 assert quote_plus(announce) in magnet_link 

        +

        61 

        +

        62 

        +

        63def test_magnet_hex(metafile1): 

        +

        64 """ 

        +

        65 Test create magnet function digest. 

        +

        66 """ 

        +

        67 magnet_link = magnet(metafile1) 

        +

        68 meta = pyben.load(metafile1) 

        +

        69 info = meta["info"] 

        +

        70 binfo = sha1(pyben.dumps(info)).hexdigest().upper() 

        +

        71 assert binfo in magnet_link 

        +

        72 

        +

        73 

        +

        74def test_magnet(metafile1): 

        +

        75 """ 

        +

        76 Test create magnet function scheme. 

        +

        77 """ 

        +

        78 magnet_link = magnet(metafile1) 

        +

        79 assert magnet_link.startswith("magnet") 

        +

        80 

        +

        81 

        +

        82def test_magnet_no_announce_list(metafile2): 

        +

        83 """ 

        +

        84 Test create magnet function scheme. 

        +

        85 """ 

        +

        86 meta = pyben.load(metafile2) 

        +

        87 del meta["announce-list"] 

        +

        88 pyben.dump(meta, metafile2) 

        +

        89 magnet_link = magnet(metafile2) 

        +

        90 assert magnet_link.startswith("magnet") 

        +

        91 

        +

        92 

        +

        93def test_magnet_empty(): 

        +

        94 """ 

        +

        95 Test create magnet function scheme. 

        +

        96 """ 

        +

        97 try: 

        +

        98 magnet("file_that_does_not_exist") 

        +

        99 except FileNotFoundError: 

        +

        100 assert True 

        +

        101 

        +

        102 

        +

        103@pytest.mark.parametrize( 

        +

        104 "field", 

        +

        105 ["name", "announce", "source", "comment", "private", "announce-list"], 

        +

        106) 

        +

        107def test_info(field, file1): 

        +

        108 """ 

        +

        109 Test the info_command action from the Command Line Interface. 

        +

        110 """ 

        +

        111 outfile = str(file1) + ".torrent" 

        +

        112 args = [ 

        +

        113 "torrentfile", 

        +

        114 "create", 

        +

        115 "-t", 

        +

        116 "url1", 

        +

        117 "url2", 

        +

        118 "url3", 

        +

        119 "--web-seed", 

        +

        120 "url4", 

        +

        121 "url5", 

        +

        122 "--http-seed", 

        +

        123 "url6", 

        +

        124 "url7", 

        +

        125 "--private", 

        +

        126 "-o", 

        +

        127 outfile, 

        +

        128 "--comment", 

        +

        129 "ExampleComment", 

        +

        130 "--source", 

        +

        131 "examplesource", 

        +

        132 str(file1), 

        +

        133 ] 

        +

        134 sys.argv = args 

        +

        135 execute() 

        +

        136 

        +

        137 class Space: 

        +

        138 """ 

        +

        139 Stand in substitution for argparse.Namespace object. 

        +

        140 """ 

        +

        141 

        +

        142 metafile = str(file1) + ".torrent" 

        +

        143 

        +

        144 output = info(Space) 

        +

        145 assert field in output 

        +

        146 

        +

        147 

        +

        148def test_magnet_cli(metafile1): 

        +

        149 """ 

        +

        150 Test magnet creation through CLI interface. 

        +

        151 """ 

        +

        152 sys.argv[1:] = ["m", str(metafile1)] 

        +

        153 uri = execute() 

        +

        154 assert "magnet" in uri 

        +

        155 

        +

        156 

        +

        157def test_create_unicode_name(file1): 

        +

        158 """ 

        +

        159 Test Unicode information in CLI args. 

        +

        160 """ 

        +

        161 parent = os.path.dirname(file1) 

        +

        162 filename = os.path.join(parent, "丂七万丈三与丏丑丒专且丕世丗両丢丣两严丩个丫丬中丮丯.torrent") 

        +

        163 args = [ 

        +

        164 "torrentfile", 

        +

        165 "-v", 

        +

        166 "create", 

        +

        167 "-a", 

        +

        168 "tracker_url.com/announce_3456", 

        +

        169 "tracker_url.net/announce_3456", 

        +

        170 "--source", 

        +

        171 "sourcetext", 

        +

        172 "--comment", 

        +

        173 "filename is 丂七万丈三与丏丑丒专且丕世丗両丢丣两严丩个丫丬中丮丯.torrent", 

        +

        174 "-o", 

        +

        175 str(filename), 

        +

        176 str(file1), 

        +

        177 ] 

        +

        178 sys.argv = args 

        +

        179 execute() 

        +

        180 assert os.path.exists(filename) 

        +

        181 

        +

        182 

        +

        183@pytest.mark.parametrize("blocks", [[], [sha1(b"1010").digest()]]) # nosec 

        +

        184def test_merkle_root_no_blocks(blocks): 

        +

        185 """ 

        +

        186 Test running merkle root function with 1 and 0 len lists. 

        +

        187 """ 

        +

        188 if blocks: 

        +

        189 assert merkle_root(blocks) 

        +

        190 else: 

        +

        191 assert not merkle_root(blocks) 

        +

        192 

        +

        193 

        +

        194@pytest.mark.parametrize("torrent", torrents()) 

        +

        195def test_mixins_progbar(torrent): 

        +

        196 """ 

        +

        197 Test progbar mixins with small file. 

        +

        198 """ 

        +

        199 tfile = tempfile(exp=14) 

        +

        200 outfile = str(tfile) + ".torrent" 

        +

        201 msg = "1234abcd" * 80 

        +

        202 with open(tfile, "wb") as temp: 

        +

        203 temp.write(msg.encode("utf-8")) 

        +

        204 args = { 

        +

        205 "path": tfile, 

        +

        206 "--prog": "1", 

        +

        207 } 

        +

        208 metafile = torrent(**args) 

        +

        209 output, _ = metafile.write(outfile=outfile) 

        +

        210 assert output == outfile 

        +

        211 rmpath(tfile, outfile) 

        +

        212 

        +

        213 

        +

        214@pytest.fixture 

        +

        215def build(dir2, metafile2): 

        +

        216 """Create fixture for testing rebuild command.""" 

        +

        217 basedir = os.path.dirname(dir2) 

        +

        218 parent = os.path.dirname(basedir) 

        +

        219 dest = os.path.join(parent, "dest") 

        +

        220 if os.path.exists(dest): 

        +

        221 rmpath(dest) # pragma: nocover 

        +

        222 os.mkdir(dest) 

        +

        223 

        +

        224 class Namespace: 

        +

        225 """Command line args for rebuild command.""" 

        +

        226 

        +

        227 metafiles = [os.path.dirname(metafile2)] 

        +

        228 contents = [basedir] 

        +

        229 destination = dest 

        +

        230 

        +

        231 yield Namespace 

        +

        232 rmpath(dest) 

        +

        233 

        +

        234 

        +

        235def test_rebuild(build): 

        +

        236 """Test the rebuild function in the commands module.""" 

        +

        237 counter = rebuild(build) 

        +

        238 assert counter > 0 

        +

        239 

        +

        240 

        +

        241def test_recheck_with_dir(): 

        +

        242 """Test running the recheck command with a directory as the metafile.""" 

        +

        243 path = os.path.dirname(__file__) 

        +

        244 

        +

        245 class Namespace: 

        +

        246 """Emulates the namespace class from argparse module.""" 

        +

        247 

        +

        248 metafile = path 

        +

        249 content = path 

        +

        250 

        +

        251 try: 

        +

        252 recheck(Namespace) 

        +

        253 except ArgumentError: 

        +

        254 assert True 

        +
        + + + diff --git a/docs/htmlcover/d_a44f0ac069e85531_test_edit_py.html b/docs/htmlcover/d_a44f0ac069e85531_test_edit_py.html new file mode 100644 index 00000000..02d1ab94 --- /dev/null +++ b/docs/htmlcover/d_a44f0ac069e85531_test_edit_py.html @@ -0,0 +1,343 @@ + + + + + Coverage for tests\test_edit.py: 100% + + + + + +
        +
        +

        + Coverage for tests\test_edit.py: + 100% +

        + +

        + 111 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Testing the edit torrent feature. 

        +

        21""" 

        +

        22 

        +

        23import sys 

        +

        24 

        +

        25import pyben 

        +

        26import pytest 

        +

        27 

        +

        28from tests import dir1, dir2, metafile2 

        +

        29from torrentfile.cli import main 

        +

        30from torrentfile.edit import edit_torrent 

        +

        31 

        +

        32 

        +

        33def test_fix(): 

        +

        34 """ 

        +

        35 Testing dir fixtures. 

        +

        36 """ 

        +

        37 assert dir2 and metafile2 and dir1 

        +

        38 

        +

        39 

        +

        40@pytest.mark.parametrize( 

        +

        41 "announce", [["urla"], ["urlb", "urlc"], ["urla", "urlb", "urlc"]] 

        +

        42) 

        +

        43def test_edit_torrent(metafile2, announce): 

        +

        44 """ 

        +

        45 Test edit torrent with announce param. 

        +

        46 """ 

        +

        47 edits = {"announce": announce} 

        +

        48 data = edit_torrent(metafile2, edits) 

        +

        49 meta = pyben.load(metafile2) 

        +

        50 assert data == meta 

        +

        51 assert data["announce-list"] == [announce] 

        +

        52 

        +

        53 

        +

        54@pytest.mark.parametrize("announce", ["urla", "urlb urlc", "urla urlb urlc"]) 

        +

        55def test_edit_torrent_str(metafile2, announce): 

        +

        56 """ 

        +

        57 Test edit torrent with announce param as string. 

        +

        58 """ 

        +

        59 edits = {"announce": announce} 

        +

        60 data = edit_torrent(metafile2, edits) 

        +

        61 meta = pyben.load(metafile2) 

        +

        62 assert data == meta 

        +

        63 assert data["announce-list"] == [announce.split()] 

        +

        64 

        +

        65 

        +

        66@pytest.mark.parametrize("url_list", ["urla", "urlb urlc", "urla urlb urlc"]) 

        +

        67def test_edit_urllist_str(metafile2, url_list): 

        +

        68 """ 

        +

        69 Test edit torrent with webseed param. 

        +

        70 """ 

        +

        71 edits = {"url-list": url_list} 

        +

        72 data = edit_torrent(metafile2, edits) 

        +

        73 meta = pyben.load(metafile2) 

        +

        74 assert data == meta 

        +

        75 assert data["url-list"] == url_list.split() 

        +

        76 

        +

        77 

        +

        78@pytest.mark.parametrize("httpseeds", ["urla", "urlb urlc", "urla urlb urlc"]) 

        +

        79def test_edit_httpseeds_str(metafile2, httpseeds): 

        +

        80 """ 

        +

        81 Test edit torrent with webseed param. 

        +

        82 """ 

        +

        83 edits = {"httpseeds": httpseeds} 

        +

        84 data = edit_torrent(metafile2, edits) 

        +

        85 meta = pyben.load(metafile2) 

        +

        86 assert data == meta 

        +

        87 assert data["httpseeds"] == httpseeds.split() 

        +

        88 

        +

        89 

        +

        90@pytest.mark.parametrize( 

        +

        91 "url_list", [["urla"], ["urlb", "urlc"], ["urla", "urlb", "urlc"]] 

        +

        92) 

        +

        93def test_edit_urllist(metafile2, url_list): 

        +

        94 """ 

        +

        95 Test edit torrent with webseed param as string. 

        +

        96 """ 

        +

        97 edits = {"url-list": url_list} 

        +

        98 data = edit_torrent(metafile2, edits) 

        +

        99 meta = pyben.load(metafile2) 

        +

        100 assert data == meta 

        +

        101 assert data["url-list"] == url_list 

        +

        102 

        +

        103 

        +

        104@pytest.mark.parametrize( 

        +

        105 "httpseed", [["urla"], ["urlb", "urlc"], ["urla", "urlb", "urlc"]] 

        +

        106) 

        +

        107def test_edit_httpseeds(metafile2, httpseed): 

        +

        108 """ 

        +

        109 Test edit torrent with webseed param as string. 

        +

        110 """ 

        +

        111 edits = {"httpseeds": httpseed} 

        +

        112 data = edit_torrent(metafile2, edits) 

        +

        113 meta = pyben.load(metafile2) 

        +

        114 assert data == meta 

        +

        115 assert data["httpseeds"] == httpseed 

        +

        116 

        +

        117 

        +

        118@pytest.mark.parametrize("comment", ["COMMENT", "COMIT", "MITCO"]) 

        +

        119def test_edit_comment(metafile2, comment): 

        +

        120 """ 

        +

        121 Test edit torrent with comment param. 

        +

        122 """ 

        +

        123 edits = {"comment": comment} 

        +

        124 data = edit_torrent(metafile2, edits) 

        +

        125 meta = pyben.load(metafile2) 

        +

        126 assert data == meta 

        +

        127 assert data["info"]["comment"] == comment 

        +

        128 

        +

        129 

        +

        130@pytest.mark.parametrize("source", ["SomeSource", "NoSouce", "MidSource"]) 

        +

        131def test_edit_source(metafile2, source): 

        +

        132 """ 

        +

        133 Test edit torrent with source param. 

        +

        134 """ 

        +

        135 edits = {"source": source} 

        +

        136 data = edit_torrent(metafile2, edits) 

        +

        137 meta = pyben.load(metafile2) 

        +

        138 assert data == meta 

        +

        139 assert data["info"]["source"] == source 

        +

        140 

        +

        141 

        +

        142def test_edit_private_true(metafile2): 

        +

        143 """ 

        +

        144 Test edit torrent with private param. 

        +

        145 """ 

        +

        146 edits = {"private": "1"} 

        +

        147 data = edit_torrent(metafile2, edits) 

        +

        148 meta = pyben.load(metafile2) 

        +

        149 assert data == meta 

        +

        150 assert data["info"]["private"] == 1 

        +

        151 

        +

        152 

        +

        153def test_edit_private_false(metafile2): 

        +

        154 """ 

        +

        155 Test edit torrent with private param False. 

        +

        156 """ 

        +

        157 edits = {"private": ""} 

        +

        158 data = edit_torrent(metafile2, edits) 

        +

        159 meta = pyben.load(metafile2) 

        +

        160 assert data == meta 

        +

        161 assert "private" not in data["info"] 

        +

        162 

        +

        163 

        +

        164def test_edit_none(metafile2): 

        +

        165 """ 

        +

        166 Test edit torrent with None for all params. 

        +

        167 """ 

        +

        168 edits = { 

        +

        169 "announce": None, 

        +

        170 "url-list": None, 

        +

        171 "comment": None, 

        +

        172 "source": None, 

        +

        173 "private": None, 

        +

        174 } 

        +

        175 data = pyben.load(metafile2) 

        +

        176 edited = edit_torrent(metafile2, edits) 

        +

        177 meta = pyben.load(metafile2) 

        +

        178 assert data == meta == edited 

        +

        179 

        +

        180 

        +

        181def test_edit_removal(metafile2): 

        +

        182 """ 

        +

        183 Test edit torrent with empty for all params. 

        +

        184 """ 

        +

        185 edits = { 

        +

        186 "announce": "", 

        +

        187 "url-list": "", 

        +

        188 "httpseeds": "", 

        +

        189 "comment": "", 

        +

        190 "source": "", 

        +

        191 "private": "", 

        +

        192 } 

        +

        193 data = edit_torrent(metafile2, edits) 

        +

        194 meta = pyben.load(metafile2) 

        +

        195 assert data == meta 

        +

        196 

        +

        197 

        +

        198@pytest.mark.parametrize("comment", ["commenta", "commentb", "commentc"]) 

        +

        199@pytest.mark.parametrize("source", ["sourcea", "sourceb", "sourcec"]) 

        +

        200@pytest.mark.parametrize("announce", [["url1", "url2", "url3"], ["url1"]]) 

        +

        201@pytest.mark.parametrize("webseed", [["ftp1"], ["ftpa", "ftpb"]]) 

        +

        202@pytest.mark.parametrize("httpseed", [["ftp1"], ["ftpa", "ftpb"]]) 

        +

        203def test_edit_cli(metafile2, comment, source, announce, webseed, httpseed): 

        +

        204 """ 

        +

        205 Test edit torrent with all params on cli. 

        +

        206 """ 

        +

        207 sys.argv = [ 

        +

        208 "torrentfile", 

        +

        209 "edit", 

        +

        210 metafile2, 

        +

        211 "--comment", 

        +

        212 comment, 

        +

        213 "--source", 

        +

        214 source, 

        +

        215 "--web-seed", 

        +

        216 webseed, 

        +

        217 "--http-seed", 

        +

        218 httpseed, 

        +

        219 "--tracker", 

        +

        220 announce, 

        +

        221 "--private", 

        +

        222 ] 

        +

        223 main() 

        +

        224 meta = pyben.load(metafile2) 

        +

        225 info = meta["info"] 

        +

        226 assert comment == info.get("comment") 

        +

        227 assert source == info.get("source") 

        +

        228 assert info.get("private") == 1 

        +

        229 assert meta["announce-list"] == [[announce]] 

        +

        230 assert meta["url-list"] == [webseed] 

        +

        231 

        +

        232 

        +

        233def test_metafile_edit_with_unicode(metafile2): 

        +

        234 """ 

        +

        235 Test if editing full unicode works as it should. 

        +

        236 """ 

        +

        237 edits = { 

        +

        238 "comment": "丂七万丈三与丏丑丒专且丕世丗両丢丣两严丩个丫丬中丮丯.torrent", 

        +

        239 "source": "丂七万丏丑严丩个丫丬中丮丯", 

        +

        240 } 

        +

        241 data = edit_torrent(metafile2, edits) 

        +

        242 meta = pyben.load(metafile2) 

        +

        243 com1 = data["info"]["comment"] 

        +

        244 com2 = meta["info"]["comment"] 

        +

        245 msg = edits["comment"] 

        +

        246 assert com1 == com2 == msg 

        +
        + + + diff --git a/docs/htmlcover/d_a44f0ac069e85531_test_interactive_py.html b/docs/htmlcover/d_a44f0ac069e85531_test_interactive_py.html new file mode 100644 index 00000000..a652fcfa --- /dev/null +++ b/docs/htmlcover/d_a44f0ac069e85531_test_interactive_py.html @@ -0,0 +1,289 @@ + + + + + Coverage for tests\test_interactive.py: 100% + + + + + +
        +
        +

        + Coverage for tests\test_interactive.py: + 100% +

        + +

        + 73 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Testing functions for the command line interface. 

        +

        21""" 

        +

        22 

        +

        23import os 

        +

        24import sys 

        +

        25 

        +

        26import pyben 

        +

        27import pytest 

        +

        28 

        +

        29from tests import file1, file2, filemeta2, torrents 

        +

        30from torrentfile.cli import main 

        +

        31from torrentfile.interactive import select_action 

        +

        32from torrentfile.utils import normalize_piece_length 

        +

        33 

        +

        34MOCK = "torrentfile.interactive.get_input" 

        +

        35 

        +

        36 

        +

        37def test_fixtures(): 

        +

        38 """ 

        +

        39 Test the fixtures used in module. 

        +

        40 """ 

        +

        41 assert filemeta2 and file1 and file2 

        +

        42 

        +

        43 

        +

        44def test_interactive_create(monkeypatch, file1): 

        +

        45 """ 

        +

        46 Test creating torrent interactively. 

        +

        47 """ 

        +

        48 mapping = [ 

        +

        49 "create", 

        +

        50 "", 

        +

        51 "", 

        +

        52 "", 

        +

        53 "", 

        +

        54 "", 

        +

        55 "", 

        +

        56 "", 

        +

        57 file1, 

        +

        58 str(file1) + ".torrent", 

        +

        59 "", 

        +

        60 ] 

        +

        61 it = iter(mapping) 

        +

        62 monkeypatch.setattr(MOCK, lambda *_: next(it)) 

        +

        63 select_action() 

        +

        64 assert os.path.exists(str(file1) + ".torrent") 

        +

        65 

        +

        66 

        +

        67@pytest.mark.parametrize("version", ["1", "2", "3"]) 

        +

        68@pytest.mark.parametrize("piece_length", ["23", "18", "131072"]) 

        +

        69@pytest.mark.parametrize("announce", ["url1", "urla urlb urlc"]) 

        +

        70@pytest.mark.parametrize("url_list", ["ftp url2", "ftp1 ftp2 ftp3"]) 

        +

        71@pytest.mark.parametrize("comment", ["Some Comment", "No Comment"]) 

        +

        72@pytest.mark.parametrize("source", ["Do", "Ra", "Me"]) 

        +

        73def test_inter_create_full( 

        +

        74 file1, 

        +

        75 piece_length, 

        +

        76 announce, 

        +

        77 comment, 

        +

        78 source, 

        +

        79 url_list, 

        +

        80 version, 

        +

        81 monkeypatch, 

        +

        82): 

        +

        83 """ 

        +

        84 Test creating torrent interactively with many parameters. 

        +

        85 """ 

        +

        86 mapping = [ 

        +

        87 "create", 

        +

        88 piece_length, 

        +

        89 announce, 

        +

        90 url_list, 

        +

        91 url_list, 

        +

        92 comment, 

        +

        93 source, 

        +

        94 "Y", 

        +

        95 file1, 

        +

        96 str(file1) + ".torrent", 

        +

        97 version, 

        +

        98 ] 

        +

        99 it = iter(mapping) 

        +

        100 monkeypatch.setattr(MOCK, lambda *_: next(it)) 

        +

        101 select_action() 

        +

        102 meta = pyben.load(str(file1) + ".torrent") 

        +

        103 assert meta["info"]["source"] == source 

        +

        104 assert meta["info"]["piece length"] == normalize_piece_length(piece_length) 

        +

        105 assert meta["info"]["comment"] == comment 

        +

        106 assert meta["url-list"] == url_list.split() 

        +

        107 

        +

        108 

        +

        109@pytest.mark.parametrize("announce", ["url1"]) 

        +

        110@pytest.mark.parametrize("url_list", ["ftp url2", "ftp1 ftp2 ftp3"]) 

        +

        111@pytest.mark.parametrize("comment", ["Some Comment", "No Comment"]) 

        +

        112@pytest.mark.parametrize("source", ["Fa", "So", "La"]) 

        +

        113def test_inter_edit_full( 

        +

        114 filemeta2, announce, comment, source, url_list, monkeypatch 

        +

        115): 

        +

        116 """ 

        +

        117 Test editing torrent file interactively. 

        +

        118 """ 

        +

        119 seq = [ 

        +

        120 "edit", 

        +

        121 filemeta2, 

        +

        122 "4", 

        +

        123 announce, 

        +

        124 "1", 

        +

        125 comment, 

        +

        126 "2", 

        +

        127 source, 

        +

        128 "5", 

        +

        129 url_list, 

        +

        130 "", 

        +

        131 "6", 

        +

        132 "Y", 

        +

        133 "DONE", 

        +

        134 ] 

        +

        135 it = iter(seq) 

        +

        136 monkeypatch.setattr(MOCK, lambda *_: next(it)) 

        +

        137 select_action() 

        +

        138 meta1 = pyben.load(filemeta2) 

        +

        139 assert meta1["info"]["source"] == source 

        +

        140 assert meta1["info"]["comment"] == comment 

        +

        141 assert meta1["url-list"] == url_list.split() 

        +

        142 assert meta1["info"]["private"] == 1 

        +

        143 

        +

        144 

        +

        145@pytest.mark.parametrize("announce", ["urla urlb urlc", "urld url2"]) 

        +

        146@pytest.mark.parametrize("urllist", ["ftp url2", "ftp1 ftp2 ftp3"]) 

        +

        147@pytest.mark.parametrize("cmnt", ["Some Comment"]) 

        +

        148@pytest.mark.parametrize("srce", ["Do", "Ra"]) 

        +

        149def test_inter_edit_cli(filemeta2, announce, cmnt, srce, urllist, monkeypatch): 

        +

        150 """ 

        +

        151 Test editing torrent interactively from CLI. 

        +

        152 """ 

        +

        153 seq = [ 

        +

        154 "edit", 

        +

        155 filemeta2, 

        +

        156 "4", 

        +

        157 announce, 

        +

        158 "1", 

        +

        159 cmnt, 

        +

        160 "2", 

        +

        161 srce, 

        +

        162 "5", 

        +

        163 urllist, 

        +

        164 urllist, 

        +

        165 "6", 

        +

        166 "Y", 

        +

        167 "DONE", 

        +

        168 ] 

        +

        169 it = iter(seq) 

        +

        170 monkeypatch.setattr(MOCK, lambda *_: next(it)) 

        +

        171 sys.argv = ["torrentfile", "-i"] 

        +

        172 main() 

        +

        173 meta2 = pyben.load(filemeta2) 

        +

        174 assert meta2["info"]["source"] == srce 

        +

        175 assert meta2["info"]["comment"] == cmnt 

        +

        176 assert meta2["url-list"] == urllist.split() 

        +

        177 assert meta2["info"]["private"] == 1 

        +

        178 

        +

        179 

        +

        180@pytest.mark.parametrize("torrentclass", torrents()) 

        +

        181def test_inter_recheck(torrentclass, monkeypatch, file1): 

        +

        182 """ 

        +

        183 Test interactive recheck function. 

        +

        184 """ 

        +

        185 outpath = str(file1) + ".torrent" 

        +

        186 torrent = torrentclass(path=file1, outfile=outpath) 

        +

        187 filemeta, _ = torrent.write(outfile=outpath) 

        +

        188 seq = ["recheck", filemeta, str(file1)] 

        +

        189 it = iter(seq) 

        +

        190 monkeypatch.setattr(MOCK, lambda *_: next(it)) 

        +

        191 result = select_action() 

        +

        192 assert result == 100 

        +
        + + + diff --git a/docs/htmlcover/d_a44f0ac069e85531_test_rebuild_py.html b/docs/htmlcover/d_a44f0ac069e85531_test_rebuild_py.html new file mode 100644 index 00000000..cec83812 --- /dev/null +++ b/docs/htmlcover/d_a44f0ac069e85531_test_rebuild_py.html @@ -0,0 +1,282 @@ + + + + + Coverage for tests\test_rebuild.py: 100% + + + + + +
        +
        +

        + Coverage for tests\test_rebuild.py: + 100% +

        + +

        + 97 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Testing functions for the rebuild sub-action commands from command line args. 

        +

        21""" 

        +

        22import os 

        +

        23 

        +

        24import pyben 

        +

        25import pytest 

        +

        26 

        +

        27from tests import dir1, file1, file2, filemeta1, filemeta2, rmpath, tempfile 

        +

        28from torrentfile.commands import rebuild 

        +

        29from torrentfile.hasher import FileHasher, HasherHybrid, HasherV2 

        +

        30from torrentfile.rebuild import Assembler 

        +

        31from torrentfile.torrent import TorrentAssembler, TorrentFile 

        +

        32 

        +

        33 

        +

        34def test_fix(): 

        +

        35 """Test that the fixtures import properly.""" 

        +

        36 assert filemeta2 and dir1 and file1 and filemeta1 and file2 

        +

        37 

        +

        38 

        +

        39def create_torrentfile(path, creator, dest, piece_length): 

        +

        40 """ 

        +

        41 Create a new torrent metafile based on the provided parameters. 

        +

        42 """ 

        +

        43 torrent = creator(path=path, outfile=dest, piece_length=piece_length) 

        +

        44 data = torrent.write() 

        +

        45 return data 

        +

        46 

        +

        47 

        +

        48def create_dest(): 

        +

        49 """ 

        +

        50 Create the destination directory for testing the rebuild function. 

        +

        51 """ 

        +

        52 parent = os.path.dirname(__file__) 

        +

        53 dest = os.path.join(parent, "dest2") 

        +

        54 if os.path.exists(dest): 

        +

        55 rmpath(dest) 

        +

        56 os.mkdir(dest) 

        +

        57 return dest 

        +

        58 

        +

        59 

        +

        60@pytest.mark.parametrize("creator", [TorrentFile, TorrentAssembler]) 

        +

        61@pytest.mark.parametrize("size", list(range(15, 19))) 

        +

        62def test_rebuilder_with_dir(dir1, creator, size): 

        +

        63 """ 

        +

        64 Test that the rebuilder works with full directories. 

        +

        65 """ 

        +

        66 dest = create_dest() 

        +

        67 contents = [os.path.dirname(dir1)] 

        +

        68 outfile = dir1 + ".torrent" 

        +

        69 create_torrentfile(dir1, creator, outfile, size) 

        +

        70 assembler = Assembler(contents, contents, dest) 

        +

        71 counter = assembler.assemble_torrents() 

        +

        72 assert counter > 0 

        +

        73 

        +

        74 

        +

        75def get_params(path): 

        +

        76 """ 

        +

        77 Shortcut method for building the parameters provided by fixtures. 

        +

        78 """ 

        +

        79 base = os.path.dirname(path) 

        +

        80 out = os.path.join(os.path.dirname(base), "dest") 

        +

        81 if os.path.exists(out): 

        +

        82 rmpath(out) # pragma: nocover 

        +

        83 os.mkdir(out) 

        +

        84 return ([path], [base], out) 

        +

        85 

        +

        86 

        +

        87@pytest.fixture() 

        +

        88def single1(filemeta1): 

        +

        89 """Test fixture for testing the build subcommand.""" 

        +

        90 params = get_params(filemeta1) 

        +

        91 return params 

        +

        92 

        +

        93 

        +

        94@pytest.fixture() 

        +

        95def single2(filemeta2): 

        +

        96 """Test fixture testing build with mismatched file size.""" 

        +

        97 params = get_params(filemeta2) 

        +

        98 return params 

        +

        99 

        +

        100 

        +

        101def test_single_file(single1): 

        +

        102 """ 

        +

        103 Test functionality of single file torrent and single torrent. 

        +

        104 """ 

        +

        105 assembler = Assembler(*single1) 

        +

        106 counter = assembler.assemble_torrents() 

        +

        107 assert counter > 0 

        +

        108 

        +

        109 

        +

        110def test_single_file_smaller(single2): 

        +

        111 """ 

        +

        112 Test functionality of single file torrent and single torrent. 

        +

        113 """ 

        +

        114 name = pyben.load(single2[0][0])["info"]["name"] 

        +

        115 contents = os.path.join(single2[1][0], name) 

        +

        116 with open(contents, "rb") as content: 

        +

        117 data = content.read() 

        +

        118 with open(contents, "wb") as content: 

        +

        119 content.write(data[: len(data) // 2]) 

        +

        120 assembler = Assembler(*single2) 

        +

        121 counter = assembler.assemble_torrents() 

        +

        122 assert counter == 0 

        +

        123 

        +

        124 

        +

        125def test_wrong_path(): 

        +

        126 """Test rebuild command with incorrect paths.""" 

        +

        127 

        +

        128 class Namespace: 

        +

        129 """ 

        +

        130 Emulates the behaviour of argparse.Namespace. 

        +

        131 """ 

        +

        132 

        +

        133 metafiles = "/non/existent/path" 

        +

        134 destination = "/non/existing/path" 

        +

        135 contents = "/non/existing/path" 

        +

        136 

        +

        137 try: 

        +

        138 rebuild(Namespace) 

        +

        139 except FileNotFoundError: 

        +

        140 assert True 

        +

        141 

        +

        142 

        +

        143@pytest.mark.parametrize("size", [2**i for i in range(14, 21)]) 

        +

        144def test_file1_hashers(file2, size): 

        +

        145 """Test that all three of the version2 hashers produce the same result.""" 

        +

        146 hasher1 = HasherHybrid(file2, size, progress=False) 

        +

        147 hasher2 = HasherV2(file2, size, progress=False) 

        +

        148 hasher3 = FileHasher(file2, size, progress=False) 

        +

        149 _ = list(hasher3) 

        +

        150 lst = [hasher1.root, hasher2.root, hasher3.root] 

        +

        151 print(lst) 

        +

        152 assert len(set(lst)) == 1 

        +

        153 

        +

        154 

        +

        155@pytest.fixture(scope="package") 

        +

        156def dextra(): 

        +

        157 """Text fixture for creating testing directory.""" 

        +

        158 paths = [ 

        +

        159 "dir3/file1.bin", 

        +

        160 "dir3/subdir1/file2.xls", 

        +

        161 "dir3/subdir1/file3.mov", 

        +

        162 "dir3/subdir2/file4.txt", 

        +

        163 "dir3/subdir2/file5.md", 

        +

        164 "dir3/file6.png", 

        +

        165 "dir3/file7.nfo", 

        +

        166 ] 

        +

        167 temps, start = [], 13 

        +

        168 for path in paths: 

        +

        169 temp = tempfile(path=path, exp=start) 

        +

        170 start += 1 

        +

        171 temps.append(temp) 

        +

        172 return os.path.commonpath(temps) 

        +

        173 

        +

        174 

        +

        175@pytest.mark.parametrize("creator", [TorrentFile, TorrentAssembler]) 

        +

        176@pytest.mark.parametrize("size", list(range(15, 19))) 

        +

        177def test_rebuild_extra_dir(dextra, creator, size): 

        +

        178 """Test rebuild with dir2 and an extra smaller file.""" 

        +

        179 dest = create_dest() 

        +

        180 contents = [os.path.dirname(dextra)] 

        +

        181 outfile = str(dextra) + ".torrent" 

        +

        182 create_torrentfile(dextra, creator, outfile, size) 

        +

        183 assembler = Assembler(contents, contents, dest) 

        +

        184 counter = assembler.assemble_torrents() 

        +

        185 assert counter > 0 

        +
        + + + diff --git a/docs/htmlcover/d_a44f0ac069e85531_test_recheck_py.html b/docs/htmlcover/d_a44f0ac069e85531_test_recheck_py.html new file mode 100644 index 00000000..af41f390 --- /dev/null +++ b/docs/htmlcover/d_a44f0ac069e85531_test_recheck_py.html @@ -0,0 +1,425 @@ + + + + + Coverage for tests\test_recheck.py: 100% + + + + + +
        +
        +

        + Coverage for tests\test_recheck.py: + 100% +

        + +

        + 158 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Testing functions for the progress module. 

        +

        21""" 

        +

        22 

        +

        23import os 

        +

        24import sys 

        +

        25from pathlib import Path 

        +

        26 

        +

        27from tests import ( 

        +

        28 dir1, 

        +

        29 dir2, 

        +

        30 file1, 

        +

        31 file2, 

        +

        32 filemeta1, 

        +

        33 filemeta2, 

        +

        34 metafile1, 

        +

        35 metafile2, 

        +

        36 rmpath, 

        +

        37 sizedfiles, 

        +

        38 sizes, 

        +

        39) 

        +

        40from torrentfile.cli import main_script as main 

        +

        41from torrentfile.recheck import Checker 

        +

        42from torrentfile.utils import ArgumentError 

        +

        43 

        +

        44 

        +

        45def test_fixtures(): 

        +

        46 """ 

        +

        47 Test fixtures exist. 

        +

        48 """ 

        +

        49 assert dir1 and dir2 and file1 and file2 

        +

        50 assert filemeta1 and filemeta2 and metafile1 

        +

        51 assert metafile2 and sizes and sizedfiles 

        +

        52 

        +

        53 

        +

        54def test_checker_class(dir1, metafile1): 

        +

        55 """ 

        +

        56 Test Checker Class against meta files. 

        +

        57 """ 

        +

        58 checker = Checker(metafile1, dir1) 

        +

        59 assert checker.results() == 100 

        +

        60 

        +

        61 

        +

        62def test_checker_first_piece(dir2, sizedfiles): 

        +

        63 """ 

        +

        64 Test Checker Class when first piece is slightly alterred. 

        +

        65 """ 

        +

        66 

        +

        67 def change(path): 

        +

        68 """ 

        +

        69 Change some bytes in file. 

        +

        70 """ 

        +

        71 if path.is_file(): 

        +

        72 new = b"Something other than what was there before." 

        +

        73 with open(path, "rb") as bfile: 

        +

        74 data = bfile.read() 

        +

        75 new_len = len(new) 

        +

        76 content = b"".join([new, data[new_len:]]) 

        +

        77 with open(path, "wb") as bdoc: 

        +

        78 bdoc.write(content) 

        +

        79 elif path.is_dir(): 

        +

        80 for item in path.iterdir(): 

        +

        81 change(item) 

        +

        82 

        +

        83 change(Path(dir2)) 

        +

        84 checker = Checker(sizedfiles, dir2) 

        +

        85 assert checker.results() != 100 

        +

        86 

        +

        87 

        +

        88def test_checker_first_piece_alt(dir2, sizedfiles): 

        +

        89 """ 

        +

        90 Test Checker Class when first piece is slightly alterred. 

        +

        91 """ 

        +

        92 

        +

        93 def change(path): 

        +

        94 """ 

        +

        95 Change some bytes in file. 

        +

        96 """ 

        +

        97 if os.path.isfile(path): 

        +

        98 with open(path, "rb") as bfile: 

        +

        99 data = bfile.read() 

        +

        100 new = b"some_other_bytes_to_use" 

        +

        101 new_len = len(new) 

        +

        102 with open(path, "wb") as wfile: 

        +

        103 wfile.write(new + data[new_len:]) 

        +

        104 elif os.path.isdir(path): 

        +

        105 for item in os.listdir(path): 

        +

        106 change(os.path.join(path, item)) 

        +

        107 

        +

        108 change(dir2) 

        +

        109 checker = Checker(sizedfiles, dir2) 

        +

        110 assert checker.results() != 100 

        +

        111 

        +

        112 

        +

        113def test_partial_metafiles(dir2, sizedfiles): 

        +

        114 """ 

        +

        115 Test Checker with data that is expected to be incomplete. 

        +

        116 """ 

        +

        117 

        +

        118 def shortenfile(path): 

        +

        119 """ 

        +

        120 Shorten a few files for testing purposes. 

        +

        121 """ 

        +

        122 with open(path, "rb") as bfile: 

        +

        123 data = bfile.read() 

        +

        124 with open(path, "wb") as bfile: 

        +

        125 bfile.write(data[: -(2**10)]) 

        +

        126 

        +

        127 for item in os.listdir(dir2): 

        +

        128 full = os.path.join(dir2, item) 

        +

        129 if os.path.isfile(full): 

        +

        130 shortenfile(full) 

        +

        131 

        +

        132 testdir = os.path.dirname(dir2) 

        +

        133 checker = Checker(sizedfiles, testdir) 

        +

        134 assert checker.results() != 100 

        +

        135 

        +

        136 

        +

        137def test_checker_callback(dir1, metafile1): 

        +

        138 """ 

        +

        139 Test Checker class with directory that points to nothing. 

        +

        140 """ 

        +

        141 Checker.register_callback(lambda *x: print(x)) 

        +

        142 checker = Checker(metafile1, str(dir1)) 

        +

        143 assert checker.results() == 100 

        +

        144 

        +

        145 

        +

        146def test_checker_cli_args(dir1, metafile1): 

        +

        147 """ 

        +

        148 Test exclusive Checker Mode CLI. 

        +

        149 """ 

        +

        150 sys.argv = ["torrentfile", "check", str(metafile1), str(dir1)] 

        +

        151 output = main() 

        +

        152 assert output == 100 

        +

        153 

        +

        154 

        +

        155def test_checker_parent_dir(dir1, metafile1): 

        +

        156 """ 

        +

        157 Test providing the parent directory for torrent checking feature. 

        +

        158 """ 

        +

        159 checker = Checker(metafile1, os.path.dirname(dir1)) 

        +

        160 assert checker.results() == 100 

        +

        161 

        +

        162 

        +

        163def test_checker_with_file(file1, filemeta1): 

        +

        164 """ 

        +

        165 Test checker with single file torrent. 

        +

        166 """ 

        +

        167 checker = Checker(filemeta1, file1) 

        +

        168 assert checker.results() == 100 

        +

        169 

        +

        170 

        +

        171def test_checker_no_meta_file(): 

        +

        172 """ 

        +

        173 Test Checker when incorrect metafile is provided. 

        +

        174 """ 

        +

        175 try: 

        +

        176 Checker("peaches", "$") 

        +

        177 except FileNotFoundError: 

        +

        178 assert True 

        +

        179 

        +

        180 

        +

        181def test_checker_wrong_root_dir(metafile1): 

        +

        182 """ 

        +

        183 Test Checker when incorrect root directory is provided. 

        +

        184 """ 

        +

        185 try: 

        +

        186 Checker(metafile1, "fake") 

        +

        187 except FileNotFoundError: 

        +

        188 assert True 

        +

        189 

        +

        190 

        +

        191def test_checker_missing(sizedfiles, dir2): 

        +

        192 """ 

        +

        193 Test Checker class when files are missing from contents. 

        +

        194 """ 

        +

        195 count = 0 

        +

        196 for fd in Path(dir2).iterdir(): 

        +

        197 if fd.is_file() and count < 2: 

        +

        198 rmpath(fd) 

        +

        199 checker = Checker(sizedfiles, dir2) 

        +

        200 assert int(checker.results()) < 100 

        +

        201 

        +

        202 

        +

        203def test_checker_class_allfiles(sizedfiles, dir2): 

        +

        204 """ 

        +

        205 Test Checker class when all files are missing from contents. 

        +

        206 """ 

        +

        207 

        +

        208 def traverse(path): 

        +

        209 """ 

        +

        210 Traverse internal subdirectories. 

        +

        211 """ 

        +

        212 if path.is_file(): 

        +

        213 rmpath(path) 

        +

        214 elif path.is_dir(): 

        +

        215 for item in path.iterdir(): 

        +

        216 traverse(item) 

        +

        217 

        +

        218 traverse(dir2) 

        +

        219 checker = Checker(sizedfiles, dir2) 

        +

        220 assert int(checker.results()) < 100 

        +

        221 

        +

        222 

        +

        223def test_checker_class_allpaths(sizedfiles, dir2): 

        +

        224 """ 

        +

        225 Test Checker class when all files are missing from contents. 

        +

        226 """ 

        +

        227 for item in Path(str(dir2)).iterdir(): 

        +

        228 rmpath(item) 

        +

        229 checker = Checker(sizedfiles, dir2) 

        +

        230 assert int(checker.results()) < 100 

        +

        231 

        +

        232 

        +

        233def test_checker_class_half_file(filemeta2, file2): 

        +

        234 """ 

        +

        235 Test Checker class with half size single file. 

        +

        236 """ 

        +

        237 half = int(os.path.getsize(file2) / 2) 

        +

        238 barr = bytearray(half) 

        +

        239 with open(file2, "rb") as content: 

        +

        240 content.readinto(barr) 

        +

        241 with open(file2, "wb") as content: 

        +

        242 content.write(barr) 

        +

        243 checker = Checker(filemeta2, file2) 

        +

        244 assert int(checker.results()) != 10 

        +

        245 

        +

        246 

        +

        247def test_checker_missing_singles(dir2, sizedfiles): 

        +

        248 """ 

        +

        249 Test Checker class with half size single file. 

        +

        250 """ 

        +

        251 

        +

        252 def walk(root): 

        +

        253 """ 

        +

        254 Remove first file found. 

        +

        255 """ 

        +

        256 if root.is_file(): 

        +

        257 rmpath(root) 

        +

        258 return True 

        +

        259 if root.is_dir(): 

        +

        260 for item in root.iterdir(): 

        +

        261 walk(item) 

        +

        262 return False 

        +

        263 

        +

        264 walk(Path(dir2)) 

        +

        265 checker = Checker(sizedfiles, dir2) 

        +

        266 assert int(checker.results()) < 100 

        +

        267 

        +

        268 

        +

        269def test_checker_result_property(dir1, metafile1): 

        +

        270 """ 

        +

        271 Test Checker class with half size single file. 

        +

        272 """ 

        +

        273 checker = Checker(metafile1, dir1) 

        +

        274 result = checker.results() 

        +

        275 assert checker.results() == result 

        +

        276 

        +

        277 

        +

        278def test_checker_simplest(dir1, metafile1): 

        +

        279 """ 

        +

        280 Test the simplest example. 

        +

        281 """ 

        +

        282 checker = Checker(metafile1, dir1) 

        +

        283 assert checker.results() == 100 

        +

        284 

        +

        285 

        +

        286def test_checker_empty_files(dir2, sizedfiles): 

        +

        287 """ 

        +

        288 Test Checker when directory contains 0 length files. 

        +

        289 """ 

        +

        290 

        +

        291 def empty_files(root): 

        +

        292 """ 

        +

        293 Dump contents of files. 

        +

        294 """ 

        +

        295 if os.path.isfile(root): 

        +

        296 with open(root, "wb") as _: 

        +

        297 pass 

        +

        298 assert os.path.getsize(root) == 0 

        +

        299 elif os.path.isdir(root): 

        +

        300 for item in os.listdir(root): 

        +

        301 return empty_files(os.path.join(root, item)) 

        +

        302 return root 

        +

        303 

        +

        304 empty_files(dir2) 

        +

        305 checker = Checker(sizedfiles, dir2) 

        +

        306 assert checker.results() != 100 

        +

        307 

        +

        308 

        +

        309def test_recheck_wrong_dir(metafile1): 

        +

        310 """ 

        +

        311 Test recheck function with directory that doesn't contain the contents. 

        +

        312 """ 

        +

        313 grandparent = os.path.dirname(os.path.dirname(metafile1)) 

        +

        314 try: 

        +

        315 _ = Checker(metafile1, grandparent) 

        +

        316 except FileNotFoundError: 

        +

        317 assert True 

        +

        318 

        +

        319 

        +

        320def test_recheck_mismatch_args(metafile1): 

        +

        321 """ 

        +

        322 Test recheck function with mismatched directory. 

        +

        323 """ 

        +

        324 grandparent = os.path.dirname(os.path.dirname(metafile1)) 

        +

        325 try: 

        +

        326 _ = Checker(grandparent, metafile1) 

        +

        327 except ArgumentError: 

        +

        328 assert True 

        +
        + + + diff --git a/docs/htmlcover/d_a44f0ac069e85531_test_torrent_py.html b/docs/htmlcover/d_a44f0ac069e85531_test_torrent_py.html new file mode 100644 index 00000000..783b9b35 --- /dev/null +++ b/docs/htmlcover/d_a44f0ac069e85531_test_torrent_py.html @@ -0,0 +1,338 @@ + + + + + Coverage for tests\test_torrent.py: 100% + + + + + +
        +
        +

        + Coverage for tests\test_torrent.py: + 100% +

        + +

        + 122 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Testing functions for the torrent module. 

        +

        21""" 

        +

        22import os 

        +

        23 

        +

        24import pytest 

        +

        25 

        +

        26from tests import dir1, dir2, rmpath, tempfile, torrents 

        +

        27from torrentfile.mixins import ProgMixin, waiting 

        +

        28from torrentfile.torrent import MetaFile 

        +

        29from torrentfile.utils import MissingPathError 

        +

        30 

        +

        31 

        +

        32def test_fixtures(): 

        +

        33 """ 

        +

        34 Test pytest fixtures. 

        +

        35 """ 

        +

        36 assert dir1 and dir2 

        +

        37 

        +

        38 

        +

        39@pytest.mark.parametrize("version", torrents()) 

        +

        40def test_torrentfile_missing_path(version): 

        +

        41 """ 

        +

        42 Test missing path error exception. 

        +

        43 """ 

        +

        44 try: 

        +

        45 version() 

        +

        46 except MissingPathError: 

        +

        47 assert True 

        +

        48 

        +

        49 

        +

        50def test_metafile_assemble(dir1): 

        +

        51 """ 

        +

        52 Test assembling base metafile exception. 

        +

        53 """ 

        +

        54 metafile = MetaFile(path=dir1) 

        +

        55 try: 

        +

        56 metafile.assemble() 

        +

        57 except NotImplementedError: 

        +

        58 assert True 

        +

        59 

        +

        60 

        +

        61@pytest.mark.parametrize("version", torrents()) 

        +

        62def test_torrentfile_one_empty(dir2, version): 

        +

        63 """ 

        +

        64 Test creating a torrent meta file with given directory plus extra. 

        +

        65 """ 

        +

        66 a = next(os.walk(dir2)) 

        +

        67 if len(a[-1]) > 0: 

        +

        68 with open(os.path.join(a[0], a[-1][0]), "w", encoding="utf-8") as _: 

        +

        69 pass 

        +

        70 args = { 

        +

        71 "path": dir2, 

        +

        72 "comment": "somecomment", 

        +

        73 "announce": "announce", 

        +

        74 } 

        +

        75 torrent = version(**args) 

        +

        76 assert torrent.meta["announce"] == "announce" 

        +

        77 

        +

        78 

        +

        79@pytest.mark.parametrize("version", torrents()) 

        +

        80def test_torrentfile_extra(dir2, version): 

        +

        81 """ 

        +

        82 Test creating a torrent meta file with given directory plus extra. 

        +

        83 """ 

        +

        84 

        +

        85 def walk(item): 

        +

        86 """ 

        +

        87 Edit files in directory structure. 

        +

        88 """ 

        +

        89 if item.is_file(): 

        +

        90 with open(item, "ab") as binfile: 

        +

        91 binfile.write(bytes(1000)) 

        +

        92 elif item.is_dir(): 

        +

        93 for sub in item.iterdir(): 

        +

        94 walk(sub) 

        +

        95 

        +

        96 walk(dir2) 

        +

        97 args = { 

        +

        98 "path": dir2, 

        +

        99 "comment": "somecomment", 

        +

        100 "announce": "announce", 

        +

        101 } 

        +

        102 torrent = version(**args) 

        +

        103 assert torrent.meta["announce"] == "announce" 

        +

        104 

        +

        105 

        +

        106@pytest.mark.parametrize("num", list(range(17, 25))) 

        +

        107@pytest.mark.parametrize("piece_length", [2**i for i in range(14, 18)]) 

        +

        108@pytest.mark.parametrize("version", torrents()) 

        +

        109def test_torrentfile_single(version, num, piece_length, capsys): 

        +

        110 """ 

        +

        111 Test creating a torrent file from a single file contents. 

        +

        112 """ 

        +

        113 tfile = tempfile(exp=num) 

        +

        114 with capsys.disabled(): 

        +

        115 version.set_callback(print) 

        +

        116 outfile = str(tfile) + ".torrent" 

        +

        117 args = { 

        +

        118 "path": tfile, 

        +

        119 "comment": "somecomment", 

        +

        120 "announce": "announce", 

        +

        121 "piece_length": piece_length, 

        +

        122 "outfile": outfile, 

        +

        123 } 

        +

        124 trent = version(**args) 

        +

        125 trent.write() 

        +

        126 assert os.path.exists(outfile) 

        +

        127 rmpath(tfile, str(tfile) + ".torrent") 

        +

        128 

        +

        129 

        +

        130@pytest.mark.parametrize("size", list(range(17, 25))) 

        +

        131@pytest.mark.parametrize("piece_length", [2**i for i in range(14, 18)]) 

        +

        132@pytest.mark.parametrize("version", torrents()) 

        +

        133def test_torrentfile_single_extra(version, size, piece_length): 

        +

        134 """ 

        +

        135 Test creating a torrent file from a single file contents plus extra. 

        +

        136 """ 

        +

        137 tfile = tempfile(exp=size) 

        +

        138 with open(tfile, "ab") as binfile: 

        +

        139 binfile.write(bytes(str(tfile).encode("utf-8"))) 

        +

        140 outfile = str(tfile) + ".torrent" 

        +

        141 args = { 

        +

        142 "path": tfile, 

        +

        143 "comment": "somecomment", 

        +

        144 "announce": "announce", 

        +

        145 "piece_length": piece_length, 

        +

        146 "outfile": outfile, 

        +

        147 } 

        +

        148 torrent = version(**args) 

        +

        149 torrent.write() 

        +

        150 assert os.path.exists(outfile) 

        +

        151 rmpath(tfile, outfile) 

        +

        152 

        +

        153 

        +

        154@pytest.mark.parametrize("sze", list(range(17, 25))) 

        +

        155@pytest.mark.parametrize("piecelength", [2**i for i in range(14, 18)]) 

        +

        156@pytest.mark.parametrize("ver", torrents()) 

        +

        157def test_torrentfile_single_under(ver, sze, piecelength): 

        +

        158 """ 

        +

        159 Test creating a torrent file from less than a single file contents. 

        +

        160 """ 

        +

        161 tfile = tempfile(exp=sze) 

        +

        162 with open(tfile, "rb") as binfile: 

        +

        163 data = binfile.read() 

        +

        164 with open(tfile, "wb") as binfile: 

        +

        165 binfile.write(data[: -(2**9)]) 

        +

        166 outfile = str(tfile) + ".torrent" 

        +

        167 kwargs = { 

        +

        168 "path": tfile, 

        +

        169 "comment": "somecomment", 

        +

        170 "announce": "announce", 

        +

        171 "piece_length": piecelength, 

        +

        172 "outfile": outfile, 

        +

        173 } 

        +

        174 torrent = ver(**kwargs) 

        +

        175 outfile, _ = torrent.write() 

        +

        176 assert os.path.exists(outfile) 

        +

        177 rmpath(tfile, outfile) 

        +

        178 

        +

        179 

        +

        180def test_create_cwd_fail(): 

        +

        181 """Test cwd argument with create command failure.""" 

        +

        182 

        +

        183 class SuFile: 

        +

        184 """A mock admin file.""" 

        +

        185 

        +

        186 @staticmethod 

        +

        187 def __fspath__(): 

        +

        188 raise PermissionError 

        +

        189 

        +

        190 def __str__(self): 

        +

        191 return "SuFile" 

        +

        192 

        +

        193 tfile = tempfile() 

        +

        194 torrent = MetaFile(path=tfile) 

        +

        195 sufile = SuFile() 

        +

        196 try: 

        +

        197 assert torrent.write(outfile=sufile) 

        +

        198 except PermissionError: 

        +

        199 assert True 

        +

        200 rmpath(tfile) 

        +

        201 

        +

        202 

        +

        203def test_waiting_mixin(): 

        +

        204 """ 

        +

        205 Test waiting function. 

        +

        206 """ 

        +

        207 msg = "Testing message" 

        +

        208 lst = [] 

        +

        209 timeout = 3 

        +

        210 waiting(msg, lst, timeout=timeout) 

        +

        211 assert len(lst) == 0 

        +

        212 

        +

        213 

        +

        214@pytest.mark.parametrize("version", torrents()) 

        +

        215@pytest.mark.parametrize("progress", [0, 1, 2]) 

        +

        216def test_mbtorrent(version, progress): 

        +

        217 """ 

        +

        218 Test torrent creation for file size larger than 10MB. 

        +

        219 """ 

        +

        220 tfile = tempfile(exp=26) 

        +

        221 outfile = str(tfile) + ".torrent" 

        +

        222 args = { 

        +

        223 "path": tfile, 

        +

        224 "progress": progress, 

        +

        225 "piece_length": "14", 

        +

        226 "outfile": outfile, 

        +

        227 } 

        +

        228 torrent = version(**args) 

        +

        229 outfile, _ = torrent.write() 

        +

        230 assert os.path.exists(outfile) 

        +

        231 rmpath(tfile, outfile) 

        +

        232 

        +

        233 

        +

        234@pytest.mark.parametrize("total", [2**i for i in range(31, 33)]) 

        +

        235def test_progress_bar(total): 

        +

        236 """Testing the prog mixin with various sizes.""" 

        +

        237 progbar = ProgMixin() 

        +

        238 progbar.prog_start(total, "some/fake/path", unit="bytes") 

        +

        239 while progbar.prog.state < total: 

        +

        240 progbar.prog_update(2**27) 

        +

        241 assert progbar.prog.state >= total 

        +
        + + + diff --git a/docs/htmlcover/d_a44f0ac069e85531_test_utils_py.html b/docs/htmlcover/d_a44f0ac069e85531_test_utils_py.html new file mode 100644 index 00000000..60f98580 --- /dev/null +++ b/docs/htmlcover/d_a44f0ac069e85531_test_utils_py.html @@ -0,0 +1,359 @@ + + + + + Coverage for tests\test_utils.py: 100% + + + + + +
        +
        +

        + Coverage for tests\test_utils.py: + 100% +

        + +

        + 89 statements   + + + +

        +

        + « prev     + ^ index     + » next +       + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        + +
        +
        +
        +

        1#! /usr/bin/python3 

        +

        2# -*- coding: utf-8 -*- 

        +

        3 

        +

        4############################################################################## 

        +

        5# Copyright (C) 2021-current alexpdev 

        +

        6# 

        +

        7# Licensed under the Apache License, Version 2.0 (the "License"); 

        +

        8# you may not use this file except in compliance with the License. 

        +

        9# You may obtain a copy of the License at 

        +

        10# 

        +

        11# http://www.apache.org/licenses/LICENSE-2.0 

        +

        12# 

        +

        13# Unless required by applicable law or agreed to in writing, software 

        +

        14# distributed under the License is distributed on an "AS IS" BASIS, 

        +

        15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

        +

        16# See the License for the specific language governing permissions and 

        +

        17# limitations under the License. 

        +

        18############################################################################## 

        +

        19""" 

        +

        20Unittest functions for testing torrentfile utils module. 

        +

        21""" 

        +

        22import math 

        +

        23 

        +

        24import pytest 

        +

        25 

        +

        26from tests import dir1, dir2, rmpath 

        +

        27from torrentfile import utils 

        +

        28from torrentfile.__main__ import main 

        +

        29 

        +

        30 

        +

        31def test_main_exists(): 

        +

        32 """ 

        +

        33 Test if main exists 

        +

        34 """ 

        +

        35 assert main 

        +

        36 

        +

        37 

        +

        38@pytest.mark.parametrize("size", [156634528, 2**30, 67987, 16384, 8563945]) 

        +

        39def test_get_piece_length(size): 

        +

        40 """ 

        +

        41 Test function for best piece length for given size. 

        +

        42 """ 

        +

        43 value = utils.get_piece_length(size) 

        +

        44 assert value % 1024 == 0 

        +

        45 

        +

        46 

        +

        47@pytest.mark.parametrize("size", [156634528, 2**30, 67987, 16384, 8563945]) 

        +

        48def test_get_piece_length_max(size): 

        +

        49 """ 

        +

        50 Test function for best piece length for given size maximum. 

        +

        51 """ 

        +

        52 value = utils.get_piece_length(size) 

        +

        53 assert value < 2**27 

        +

        54 

        +

        55 

        +

        56@pytest.mark.parametrize("size", [156634528, 2**30, 67987, 16384, 8563945]) 

        +

        57def test_get_piece_length_min(size): 

        +

        58 """ 

        +

        59 Test function for best piece length for given size minimum. 

        +

        60 """ 

        +

        61 value = utils.get_piece_length(size) 

        +

        62 assert value >= 2**14 

        +

        63 

        +

        64 

        +

        65def test_get_path_length_mod(dir1): 

        +

        66 """ 

        +

        67 Test function for the best piece length for provided path. 

        +

        68 """ 

        +

        69 assert utils.path_piece_length(dir1) % (2**14) == 0 

        +

        70 

        +

        71 

        +

        72def test_get_path_length_min(dir1): 

        +

        73 """ 

        +

        74 Test function for getting piece length for folders min. 

        +

        75 """ 

        +

        76 assert utils.path_piece_length(dir1) >= (2**14) 

        +

        77 

        +

        78 

        +

        79def test_get_path_length_max(dir1): 

        +

        80 """ 

        +

        81 Test function for getting piece length for folders max. 

        +

        82 """ 

        +

        83 assert utils.path_piece_length(dir1) <= (2**27) 

        +

        84 

        +

        85 

        +

        86def test_path_stat(dir1): 

        +

        87 """ 

        +

        88 Test function for acquiring piece length information on folder. 

        +

        89 """ 

        +

        90 _, _, piece_length = utils.path_stat(dir1) 

        +

        91 assert piece_length % (2**14) == 0 

        +

        92 

        +

        93 

        +

        94def test_path_stat_size(dir1): 

        +

        95 """ 

        +

        96 Test function for acquiring total size information on folder. 

        +

        97 """ 

        +

        98 _, totalsize, _ = utils.path_stat(dir1) 

        +

        99 assert totalsize == (2**18) * 8 

        +

        100 

        +

        101 

        +

        102def test_path_stat_filelist_size(dir1): 

        +

        103 """ 

        +

        104 Test function for acquiring file list information on folder. 

        +

        105 """ 

        +

        106 filelist, _, _ = utils.path_stat(dir1) 

        +

        107 assert len(filelist) == 8 

        +

        108 

        +

        109 

        +

        110def test_get_filelist(dir1): 

        +

        111 """ 

        +

        112 Test function for get a list of files in a directory. 

        +

        113 """ 

        +

        114 filelist = utils.get_file_list(dir1) 

        +

        115 assert len(filelist) == 8 

        +

        116 

        +

        117 

        +

        118def test_get_path_size(dir1): 

        +

        119 """ 

        +

        120 Test function for getting total size of directory. 

        +

        121 """ 

        +

        122 pathsize = utils.path_size(dir1) 

        +

        123 assert pathsize == (2**18) * 8 

        +

        124 

        +

        125 

        +

        126def test_filelist_total(dir1): 

        +

        127 """ 

        +

        128 Test function for acquiring a filelist for directory. 

        +

        129 """ 

        +

        130 total, _ = utils.filelist_total(dir1) 

        +

        131 assert total == (2**18) * 8 

        +

        132 

        +

        133 

        +

        134def test_piecelength_error_fixtures(): 

        +

        135 """ 

        +

        136 Test exception for uninterpretable piece length value. 

        +

        137 """ 

        +

        138 try: 

        +

        139 raise utils.PieceLengthValueError("message") 

        +

        140 except utils.PieceLengthValueError: 

        +

        141 assert True 

        +

        142 assert dir1 

        +

        143 

        +

        144 

        +

        145def test_missing_path_error(): 

        +

        146 """ 

        +

        147 Test exception for missing path parameter. 

        +

        148 """ 

        +

        149 try: 

        +

        150 raise utils.MissingPathError("message") 

        +

        151 except utils.MissingPathError: 

        +

        152 assert True 

        +

        153 assert dir2 

        +

        154 

        +

        155 

        +

        156@pytest.mark.parametrize("value", [5, 32, 18, 225, 16384, 256000]) 

        +

        157def test_next_power_2(value): 

        +

        158 """ 

        +

        159 Test next power of 2 function in utils module. 

        +

        160 """ 

        +

        161 result = utils.next_power_2(value) 

        +

        162 log = math.log2(result) 

        +

        163 assert log == int(log) 

        +

        164 assert result % 2 == 0 

        +

        165 assert result >= value 

        +

        166 

        +

        167 

        +

        168@pytest.mark.parametrize( 

        +

        169 "amount, result", 

        +

        170 [ 

        +

        171 (100, "100"), 

        +

        172 (1100, "1 KiB"), 

        +

        173 (1_100_000, "1 MiB"), 

        +

        174 (1_100_000_000, "1 GiB"), 

        +

        175 (4_400_120_000, "4 GiB"), 

        +

        176 (4_000_120_000, "3 GiB"), 

        +

        177 ], 

        +

        178) 

        +

        179def test_humanize_bytes(amount, result): 

        +

        180 """ 

        +

        181 Test humanize bytes function. 

        +

        182 """ 

        +

        183 assert utils.humanize_bytes(amount) == result 

        +

        184 

        +

        185 

        +

        186@pytest.mark.parametrize( 

        +

        187 "amount, result", [(i, 2**i) for i in range(14, 25)] 

        +

        188) 

        +

        189def test_normalize_piece_length_int(amount, result): 

        +

        190 """Test normalize piece length function. 

        +

        191 

        +

        192 Parameters 

        +

        193 ---------- 

        +

        194 amount : `str` or `int` 

        +

        195 piece length or representation 

        +

        196 result : int 

        +

        197 expected output. 

        +

        198 """ 

        +

        199 assert utils.normalize_piece_length(amount) == result 

        +

        200 

        +

        201 

        +

        202@pytest.mark.parametrize( 

        +

        203 "amount, result", [(str(i), 2**i) for i in range(14, 21)] 

        +

        204) 

        +

        205def test_normalize_piece_length_str(amount, result): 

        +

        206 """Test normalize piece length function. 

        +

        207 

        +

        208 Parameters 

        +

        209 ---------- 

        +

        210 amount : `str` or `int` 

        +

        211 piece length or representation 

        +

        212 result : int 

        +

        213 expected output. 

        +

        214 """ 

        +

        215 assert utils.normalize_piece_length(amount) == result 

        +

        216 

        +

        217 

        +

        218@pytest.mark.parametrize( 

        +

        219 "amount", ["hello", 11, 0, 100000, 28, "zero", "fifteen"] 

        +

        220) 

        +

        221def test_norm_plength_errors(amount): 

        +

        222 """Test function to normalize piece length errors. 

        +

        223 

        +

        224 Parameters 

        +

        225 ---------- 

        +

        226 amount : any 

        +

        227 arguments intended to raise an exception. 

        +

        228 """ 

        +

        229 try: 

        +

        230 assert utils.normalize_piece_length(amount) 

        +

        231 except utils.PieceLengthValueError: 

        +

        232 assert True 

        +

        233 

        +

        234 

        +

        235def test_filelisttotal_missing(dir2): 

        +

        236 """Test function filelist total with missing path. 

        +

        237 

        +

        238 Parameters 

        +

        239 ---------- 

        +

        240 dir2 : pytest.fixture 

        +

        241 fixture containing a temporary directory 

        +

        242 """ 

        +

        243 rmpath(dir2) 

        +

        244 try: 

        +

        245 utils.filelist_total(dir2) 

        +

        246 except utils.MissingPathError: 

        +

        247 assert True 

        +

        248 

        +

        249 

        +

        250def test_argument_error(): 

        +

        251 """ 

        +

        252 Test Argument Error. 

        +

        253 

        +

        254 Raises 

        +

        255 ------ 

        +

        256 utils.ArgumentError 

        +

        257 Arg error 

        +

        258 """ 

        +

        259 try: 

        +

        260 raise utils.ArgumentError("This message raised by argument error") 

        +

        261 except utils.ArgumentError: 

        +

        262 assert True 

        +
        + + + diff --git a/docs/htmlcover/favicon_32.png b/docs/htmlcover/favicon_32.png new file mode 100644 index 0000000000000000000000000000000000000000..8649f0475d8d20793b2ec431fe25a186a414cf10 GIT binary patch literal 1732 zcmV;#20QtQP)K2KOkBOVxIZChq#W-v7@TU%U6P(wycKT1hUJUToW3ke1U1ONa4 z000000000000000bb)GRa9mqwR9|UWHy;^RUrt?IT__Y0JUcxmBP0(51q1>E00030 z|NrOz)aw7%8sJzM<5^g%z7^qE`}_Ot|JUUG(NUkWzR|7K?Zo%@_v-8G-1N%N=D$;; zw;keH4dGY$`1t4M=HK_s*zm^0#KgqfwWhe3qO_HtvXYvtjgX>;-~C$L`&k>^R)9)7 zdPh2TL^pCnHC#0+_4D)M`p?qp!pq{jO_{8;$fbaflbx`Tn52n|n}8VFRTA1&ugOP< zPd{uvFjz7t*Vot1&d$l-xWCk}s;sQL&#O(Bskh6gqNJv>#iB=ypG1e3K!K4yc7!~M zfj4S*g^zZ7eP$+_Sl07Z646l;%urinP#D8a6TwRtnLIRcI!r4f@bK~9-`~;E(N?Lv zSEst7s;rcxsi~}{Nsytfz@MtUoR*iFc8!#vvx}Umhm4blk(_~MdVD-@dW&>!Nn~ro z_E~-ESVQAj6Wmn;(olz(O&_{U2*pZBc1aYjMh>Dq3z|6`jW`RDHV=t3I6yRKJ~LOX zz_z!!vbVXPqob#=pj3^VMT?x6t(irRmSKsMo1~LLkB&=#j!=M%NP35mfqim$drWb9 zYIb>no_LUwc!r^NkDzs4YHu@=ZHRzrafWDZd1EhEVq=tGX?tK$pIa)DTh#bkvh!J- z?^%@YS!U*0E8$q$_*aOTQ&)Ra64g>ep;BdcQgvlg8qQHrP*E$;P{-m=A*@axn@$bO zO-Y4JzS&EAi%YG}N?cn?YFS7ivPY=EMV6~YH;+Xxu|tefLS|Aza)Cg6us#)=JW!uH zQa?H>d^j+YHCtyjL^LulF*05|F$RG!AX_OHVI&MtA~_@=5_lU|0000rbW%=J06GH4 z^5LD8b8apw8vNh1ua1mF{{Hy)_U`NA;Nacc+sCpuHXa-V{r&yz?c(9#+}oX+NmiRW z+W-IqK1oDDR5;6GfCDCOP5}iL5fK(cB~ET81`MFgF2kGa9AjhSIk~-E-4&*tPPKdiilQJ11k_J082ZS z>@TvivP!5ZFG?t@{t+GpR3XR&@*hA_VE1|Lo8@L@)l*h(Z@=?c-NS$Fk&&61IzUU9 z*nPqBM=OBZ-6ka1SJgGAS-Us5EN)r#dUX%>wQZLa2ytPCtMKp)Ob z*xcu38Z&d5<-NBS)@jRD+*!W*cf-m_wmxDEqBf?czI%3U0J$Xik;lA`jg}VH?(S(V zE!M3;X2B8w0TnnW&6(8;_Uc)WD;Ms6PKP+s(sFgO!}B!^ES~GDt4qLPxwYB)^7)XA zZwo9zDy-B0B+jT6V=!=bo(zs_8{eBA78gT9GH$(DVhz;4VAYwz+bOIdZ-PNb|I&rl z^XG=vFLF)1{&nT2*0vMz#}7^9hXzzf&ZdKlEj{LihP;|;Ywqn35ajP?H?7t|i-Un% z&&kxee@9B{nwgv1+S-~0)E1{ob1^Wn`F2isurqThKK=3%&;`@{0{!D- z&CSj80t;uPu&FaJFtSXKH#ajgGj}=sEad7US6jP0|Db@0j)?(5@sf<7`~a9>s;wCa zm^)spe{uxGFmrJYI9cOh7s$>8Npkt-5EWB1UKc`{W{y5Ce$1+nM9Cr;);=Ju#N^62OSlJMn7omiUgP&ErsYzT~iGxcW aE(`!K@+CXylaC4j0000 + + + + Coverage report + + + + + +
        +
        +

        Coverage report: + 100% +

        + +
        + +
        +

        + coverage.py v6.5.0, + created at 2022-10-20 22:23 -0700 +

        +
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        Modulestatementsmissingexcludedcoverage
        tests\__init__.py127012100%
        tests\test_cli.py22403100%
        tests\test_commands.py10201100%
        tests\test_edit.py11100100%
        tests\test_interactive.py7300100%
        tests\test_rebuild.py9701100%
        tests\test_recheck.py15800100%
        tests\test_torrent.py12200100%
        tests\test_utils.py8900100%
        torrentfile\__init__.py700100%
        torrentfile\__main__.py501100%
        torrentfile\cli.py11501100%
        torrentfile\commands.py11200100%
        torrentfile\edit.py5300100%
        torrentfile\hasher.py24400100%
        torrentfile\interactive.py121020100%
        torrentfile\mixins.py10103100%
        torrentfile\rebuild.py25104100%
        torrentfile\recheck.py27903100%
        torrentfile\torrent.py24200100%
        torrentfile\utils.py11405100%
        torrentfile\version.py200100%
        Total2749054100%
        +

        + No items found using the specified filter. +

        +
        + + + diff --git a/docs/htmlcover/keybd_closed.png b/docs/htmlcover/keybd_closed.png new file mode 100644 index 0000000000000000000000000000000000000000..ba119c47df81ed2bbd27a06988abf700139c4f99 GIT binary patch literal 9004 zcmeHLc{tSF+aIY=A^R4_poB4tZAN2XC;O7M(inrW3}(h&Q4}dl*&-65$i9^&vW6_# zcM4g`Qix=GhkBl;=lwnJ@Ap2}^}hc-b6vBXb3XUyzR%~}_c`-Dw+!?&>5p(90RRB> zXe~7($~PP3eT?=X<@3~Q1w84vX~IoSx~1#~02+TopXK(db;4v6!{+W`RHLkkHO zo;+s?)puc`+$yOwHv>I$5^8v^F3<|$44HA8AFnFB0cAP|C`p}aSMJK*-CUB{eQ!;K z-9Ju3OQ+xVPr3P#o4>_lNBT;M+1vgV&B~6!naOGHb-LFA9TkfHv1IFA1Y!Iz!Zl3) z%c#-^zNWPq7U_}6I7aHSmFWi125RZrNBKyvnV^?64)zviS;E!UD%LaGRl6@zn!3E{ zJ`B$5``cH_3a)t1#6I7d==JeB_IcSU%=I#DrRCBGm8GvCmA=+XHEvC2SIfsNa0(h9 z7P^C4U`W@@`9p>2f^zyb5B=lpc*RZMn-%%IqrxSWQF8{ec3i?-AB(_IVe z)XgT>Y^u41MwOMFvU=I4?!^#jaS-%bjnx@ zmL44yVEslR_ynm18F!u}Ru#moEn3EE?1=9@$B1Z5aLi5b8{&?V(IAYBzIar!SiY3< z`l0V)djHtrImy}(!7x-Pmq+njM)JFQ9mx*(C+9a3M)(_SW|lrN=gfxFhStu^zvynS zm@gl;>d8i8wpUkX42vS3BEzE3-yctH%t0#N%s+6-&_<*Fe7+h=`=FM?DOg1)eGL~~ zQvIFm$D*lqEh07XrXY=jb%hdyP4)`wyMCb$=-z9(lOme9=tirVkb)_GOl2MJn;=Ky z^0pV1owR7KP-BSxhI@@@+gG0roD-kXE1;!#R7KY1QiUbyDdTElm|ul7{mMdF1%UDJ z_vp=Vo!TCF?D*?u% zk~}4!xK2MSQd-QKC0${G=ZRv2x8%8ZqdfR!?Dv=5Mj^8WU)?iH;C?o6rSQy*^YwQb zf@5V)q=xah#a3UEIBC~N7on(p4jQd4K$|i7k`d8mw|M{Mxapl46Z^X^9U}JgqH#;T z`CTzafpMD+J-LjzF+3Xau>xM_sXisRj6m-287~i9g|%gHc}v77>n_+p7ZgmJszx!b zSmL4wV;&*5Z|zaCk`rOYFdOjZLLQr!WSV6AlaqYh_OE)>rYdtx`gk$yAMO=-E1b~J zIZY6gM*}1UWsJ)TW(pf1=h?lJy_0TFOr|nALGW>$IE1E7z+$`^2WJY+>$$nJo8Rs` z)xS>AH{N~X3+b=2+8Q_|n(1JoGv55r>TuwBV~MXE&9?3Zw>cIxnOPNs#gh~C4Zo=k z&!s;5)^6UG>!`?hh0Q|r|Qbm>}pgtOt23Vh!NSibozH$`#LSiYL)HR4bkfEJMa zBHwC3TaHx|BzD|MXAr>mm&FbZXeEX-=W}Ji&!pji4sO$#0Wk^Q7j%{8#bJPn$C=E% zPlB}0)@Ti^r_HMJrTMN?9~4LQbIiUiOKBVNm_QjABKY4;zC88yVjvB>ZETNzr%^(~ zI3U&Ont?P`r&4 z#Bp)jcVV_N_{c1_qW}_`dQm)D`NG?h{+S!YOaUgWna4i8SuoLcXAZ|#Jh&GNn7B}3 z?vZ8I{LpmCYT=@6)dLPd@|(;d<08ufov%+V?$mgUYQHYTrc%eA=CDUzK}v|G&9}yJ z)|g*=+RH1IQ>rvkY9UIam=fkxWDyGIKQ2RU{GqOQjD8nG#sl+$V=?wpzJdT=wlNWr z1%lw&+;kVs(z?e=YRWRA&jc75rQ~({*TS<( z8X!j>B}?Bxrrp%wEE7yBefQ?*nM20~+ZoQK(NO_wA`RNhsqVkXHy|sod@mqen=B#@ zmLi=x2*o9rWqTMWoB&qdZph$~qkJJTVNc*8^hU?gH_fY{GYPEBE8Q{j0Y$tvjMv%3 z)j#EyBf^7n)2d8IXDYX2O0S%ZTnGhg4Ss#sEIATKpE_E4TU=GimrD5F6K(%*+T-!o z?Se7^Vm`$ZKDwq+=~jf?w0qC$Kr&R-;IF#{iLF*8zKu8(=#chRO;>x zdM;h{i{RLpJgS!B-ueTFs8&4U4+D8|7nP~UZ@P`J;*0sj^#f_WqT#xpA?@qHonGB& zQ<^;OLtOG1w#)N~&@b0caUL7syAsAxV#R`n>-+eVL9aZwnlklzE>-6!1#!tVA`uNo z>Gv^P)sohc~g_1YMC;^f(N<{2y5C^;QCEXo;LQ^#$0 zr>jCrdoeXuff!dJ^`#=Wy2Gumo^Qt7BZrI~G+Pyl_kL>is3P0^JlE;Sjm-YfF~I>t z_KeNpK|5U&F4;v?WS&#l(jxUWDarfcIcl=-6!8>^S`57!M6;hZea5IFA@)2+*Rt85 zi-MBs_b^DU8LygXXQGkG+86N7<%M|baM(orG*ASffC`p!?@m{qd}IcYmZyi^d}#Q& zNjk-0@CajpUI-gPm20ERVDO!L8@p`tMJ69FD(ASIkdoLdiRV6h9TPKRz>2WK4upHd z6OZK33EP?`GoJkXh)S035}uLUO$;TlXwNdMg-WOhLB)7a`-%*a9lFmjf6n+4ZmIHN z-V@$ z8PXsoR4*`5RwXz=A8|5;aXKtSHFccj%dG7cO~UBJnt)61K>-uPX)`vu{7fcX6_>zZ zw_2V&Li+7mxbf!f7{Rk&VVyY!UtZywac%g!cH+xh#j$a`uf?XWl<``t`36W;p7=_* zO6uf~2{sAdkZn=Ts@p0>8N8rzw2ZLS@$ibV-c-QmG@%|3gUUrRxu=e*ekhTa+f?8q z3$JVGPr9w$VQG~QCq~Y=2ThLIH!T@(>{NihJ6nj*HA_C#Popv)CBa)+UI-bx8u8zfCT^*1|k z&N9oFYsZEijPn31Yx_yO5pFs>0tOAV=oRx~Wpy5ie&S_449m4R^{LWQMA~}vocV1O zIf#1ZV85E>tvZE4mz~zn{hs!pkIQM;EvZMimqiPAJu-9P@mId&nb$lsrICS=)zU3~ zn>a#9>}5*3N)9;PTMZ)$`5k} z?iG}Rwj$>Y*|(D3S3e&fxhaPHma8@vwu(cwdlaCjX+NIK6=$H4U`rfzcWQVOhp{fnzuZhgCCGpw|p zTi`>cv~xVzdx|^`C0vXdlMwPae3S?>3|7v$e*Bs6-5gS>>FMHk_r2M(ADOV{KV7+6 zA@5Q(mdx%7J}MY}K461iuQ}5GwDGI=Yc&g0MZHu)7gC3{5@QZj6SJl*o0MS2Cl_ia zyK?9QmC9tJ6yn{EA-erJ4wk$+!E#X(s~9h^HOmQ_|6V_s1)k;%9Q6Niw}SyT?jxl4 z;HYz2$Nj$8Q_*Xo`TWEUx^Q9b+ik@$o39`mlY&P}G8wnjdE+Dlj?uL;$aB$n;x zWoh-M_u>9}_Ok@d_uidMqz10zJc}RQijPW3Fs&~1am=j*+A$QWTvxf9)6n;n8zTQW z!Q_J1%apTsJzLF`#^P_#mRv2Ya_keUE7iMSP!ha-WQoo0vZZG?gyR;+4q8F6tL#u< zRj8Hu5f-p1$J;)4?WpGL{4@HmJ6&tF9A5Tc8Trp>;Y>{^s?Q1&bam}?OjsnKd?|Z82aix26wUOLxbEW~E)|CgJ#)MLf_me# zv4?F$o@A~Um)6>HlM0=3Bd-vc91EM}D+t6-@!}O%i*&Wl%@#C8X+?5+nv`oPu!!=5 znbL+Fk_#J_%8vOq^FIv~5N(nk03kyo1p@l|1c+rO^zCG3bk2?|%AF;*|4si1XM<`a z1NY0-8$wv?&129!(g_A1lXR!+pD*1*cF?T~e1d6*G1Fz)jcSaZoKpxtA%FNnKP2jo zLXn@OR#1z@6zuH%mMB98}-t zHJqClsZ!G5xMSgIs_=<8sBePXxfoXsuvy`|buON9BX%s-o>OVLA)k3W=wKnw1?so$ zEjm0aS=zu@Xu#;{A)QTjJ$a9_={++ACkRY*sk3jLk&Fu}RxR<-DXR<`5`$VNG*wJE zidM6VzaQ!M0gbQM98@x@;#0qUS8O)p6mrYwTk*;8J~!ovbY6jon^Ki}uggd3#J5G8 z>awvtF85Y<9yE{Iag}J7O7)1O=ylk^255@XmV5J06-{xaaSNASZoTKKp~$tSxdUI~ zU1RZ&UuW37Ro&_ryj^cSt$Jd&pt|+h!A&dwcr&`S=R5E`=6Tm`+(qGm@$YZ8(8@a$ zXfo@Rwtvm7N3RMmVCb7radAs-@QtCXx^CQ-<)V>QPLZy@jH{#dc4#(y zV)6Hp{ZMz!|NG8!>i01gZMy)G<8Hf2X7e&LH_gOaajW<<^Xi55@OnlY*|S|*TS8;u_nHbv7lgmmZ+Q<5 zi!*lLCJmdpyzl(L${$C?(pVo|oR%r~x_B_ocPePa_);27^=n4L=`toZ;xdBut9rSv z?wDQ7j2I3WQBdhz%X7`2YaG_y|wA!7|s?k;A&WNMLMTZEzCaE^d??E&u?f=ejQBR~|< z)=thyP2(p8r6mt?Ad}tXAP_GvF9|P630I;$1cpQ+Ay7C34hK^ZV3H4kjPV8&NP>G5 zKRDEIBrFl{M#j4mfP0)68&?mqJP1S?2mU0djAGTjDV;wZ?6vplNn~3Hn$nP>%!dMi zz@bnC7zzi&k&s{QDWkf&zgrVXKUJjY3Gv3bL0}S4h>OdgEJ$Q^&p-VAr3J}^a*+rz z!jW7(h*+GuCyqcC{MD(Ovj^!{pB^OKUe|uy&bD?CN>KZrf3?v>>l*xSvnQiH-o^ViN$%FRdm9url;%(*jf5H$*S)8;i0xWHdl>$p);nH9v0)YfW?Vz$! zNCeUbi9`NEg(i^57y=fzM@1o*z*Bf6?QCV>2p9}(BLlYsOCfMjFv1pw1mlo)Py{8v zppw{MDfEeWN+n>Ne~oI7%9cU}mz0r3!es2gNF0t5jkGipjIo2lz;-e)7}Ul_#!eDv zw;#>kI>;#-pyfeu3Fsd^2F@6=oh#8r9;A!G0`-mm7%{=S;Ec(bJ=I_`FodKGQVNEY zmXwr4{9*jpDl%4{ggQZ5Ac z%wYTdl*!1c5^)%^E78Q&)ma|27c6j(a=)g4sGrp$r{jv>>M2 z6y)E5|Aooe!PSfKzvKA>`a6pfK3=E8vL14ksP&f=>gOP?}rG6ye@9ZR3 zJF*vsh*P$w390i!FV~~_Hv6t2Zl<4VUi|rNja#boFt{%q~xGb z(2petq9A*_>~B*>?d?Olx^lmYg4)}sH2>G42RE; literal 0 HcmV?d00001 diff --git a/docs/htmlcover/keybd_open.png b/docs/htmlcover/keybd_open.png new file mode 100644 index 0000000000000000000000000000000000000000..a8bac6c9de256626c680f9e9e3f8ee81d9713ecd GIT binary patch literal 9003 zcmeHLc{tST+n?-2i>)FxMv|DtSZA{DOOu_57&BjtZI~H*ma;_1l4LIxB9dJQ*|TO# zN!sjLvQy+8>YUSgf9L)E-g8~=``>Y0!#wx%xj*;)e4hJ$zP?Ym-Z>3679JK52*jqP zscJy|%SHXLGSN|g3$@6f1Az_(`xu?47+^iYt|X!@!3h9Uyj=k>;6<(w94t$&Tmv4vUI0Y(72z4p-=52qQm)ibdMG{Lq zK-QAXj0ngGo#r{-=KfvMuhjI#;F3ml_v?vI<2-B3E&Sb83IPcet8E#VcMLMbDBXp( zietxGS0^|mhdOuNU*! z>lxhuyJ~5HC9jEu^6wu9yggaJEILLJFELe{&yOk3uY^_mY(J*EdTA{CbDHru&S*s5 zFHGCrim@r19P**ASiJAew_7dD+e>cSOtls3Z#(>lZx1iINjrV7NNt%PDNcMkXlA*W z`Bs*%ezf4U5NxJm__K5P?GEB7`Q`04T`~MTc=Sf&%qHuFd;!rn3}>8+-@yEidsy4J zwgV$+ymZ>vxo%s!H&}(*({B{M0j#!`Lt5GDbvmkji<_pajk9^n5DO(1Q=&m;TJ!?& z?dIZM5vQ>Gv(&EdlJNx^(v{pFFPfSP@r^ zUhRTD7bv*AYH`?Gq11M%nz2r;gHNp42jVLD`5tDqtqX8m!12pRUB0&T%w5?UN8u2$ z{33ra^&{S8?zu^Udrw+}HTUH(`Hi#oxx_~8z^KjV88Ir*uZL|Sg~!j^L_s$=4bBRW zop?W3)Xm?LO6n3E9KHt6XpGZ_HN~5oyARM_FU(4I%qcBvz8@9K>nRPh&##*Eoh-~w z_nj&&SNa->_^2rmZKKZTTsb8qBi7eZ+<|^m6k%kJZMtc45f~Vd$|>90cV@0+305_? z$}Q=5?!3a*rg#60fWtWf!9(Na58NEPqWSacwBi#FiX9R?*v-C&eMqb0k&TM0y0Va% zz~=|oCLbfUU9)b69enmUFXBy2)12vO`bS&kb^YOC0g}4%8d0@NbMm6<9C^4VY$)DE z97dE-HVFOL-)`t{@mQPechUcK@>Nbm7VqtmzZyM5U<`U@;RjksVMF8R*E>VhuI zkJSj=K$J!b9wLT59DZFvicVNQpWLaC2991nDs(piR8YcRq>puA}_3int5bZCnSnDDDBIyC`&DN%_Rawgsxlzfrw!$YU zk697D5ny@b5%eg+G2F&np#M_QkwT<~o z=20^H-;eo=m3|I#91GRY0$TY@>nd$|*Y@6PiI*+2I$KO&NY?@M466>Gt%~Lgowk~^JM_8wk%ghs}g}t}vM}#g;++DAjY#7oR5>!9Zb&%tZ@Av?{`s6b=pUPf& z`Ej0w!tuWT?VOSJ(s^!$)o|_8JY0RAMH30nz=QERTWUx%i6hBP9(PAp{ZQXvk!u}#Vab<|7#n z{maX?O+c&it?=GMZ6-mCiq1b`jrvnH%AIwV(c=)Y+Ng zV<#loBasaSDG>p~!~6DW%DmIwBgLM5kIpGHr(+-C2oq1L_i5|QlNU`n4xG_p4P3X+ zRb3J0k2659ugVF3jbY3g*#hm^+qFWErnuOPd#1_kH{$GKT=$ySdOG<2GJTTZieX8- z?SgdRq&e6K0~#g8LaMO>bF{p3>QU`28P6mcPxd#h%a3HMTriHT*5N2RdHdrvo)Hl( z`U&a1G+qKp7@qqMO*C~Dy@6-;0(yrivn$>oJm|n&YNs2%lFk?#rUv7N=CbY!26_#` zOwy)}i?Rp4nN$r%&5zU9O^|X|`}0gh4dooTajuqYy@fN0lYu~6li4||>k%x%XO;xj z5hh>P?#m$1I$s2gk=e^$N7Mm%F()PB*mBjl8#GTm}V z$n>4H{Zn?>tRb54D4BSNiH}riISvV^~kJ4Oqi-Q}*uV!1arYe1u@i3%->Aj(r zIL(E2nn^nhc3)1$LG?M!Z0P!8{kc7jVZ|z31Z9vW;zWG03+NwSV4)_v?8U zWzJng#k|hYcWf&`>pXSb$1J+|*RC+y0H1PLZGt#e5IB@{-e@rJo$|6ec*b&%(FN6?k>rN1-Nr$ z4m|s8prjrxoFseZy3M8c%nY<;8djgwW?!ntbr_BuPh)z_r$EZ(kbFfHIe-m~a@%)q zLHUZt{_ImXka>hsv7(tXD6IvCnD*Y9=OgFxoLemASErKGmb*^Vr}f(jx0bPl+I)E& zdgR_RtTV3aL1y$Y0L5%R`aCZ_j3{hDnOKUvJ-^B&r*-n!H1{M-gxge|1@AvCd1;LQ z&gyHGB7uzB5-;A*PN28V&l6{zV&ytnvv49kQD;x-Jcw{TPutVpBdI*~r2kQt;9y9} zrm;uL{ueR+pCY~(GsbF5WOLs1yA+{d^Nmfm{aCu^(uKBHuPP3>NOHZQeGCtO_(B6)e%e38$iS+A2@EuwaM3TExzF}i&|u$ zKssx-vZFF{(!fLzv#fm`hUWZG5W_HwZrHcibZGYIaTr8bF#XA~Yf^ke%h&0u3Dx%! z^ibu!hA$rmFDYFLiIR1*I%r`O?aUXua(z?Y&59c);yYe5&auIz#2%m$bF*Hyeb18q z{s%|D-an(}lltLeI1PH%zkvDJwfC);yKU+wq>Y~}`Wh1~1YKy!?;AbZMc?c-xx!ID zGU@t4XMu&;EzIlDe3)0mJ*~+gZ-I|7lWVH7XtQ^*7s@OAG%rXhF&W2i7^~4ZIjANP z)iqZodK~wkV=H<3sb9XbJmqa^_fu6Md2TL+@V@LjyB!gdKL)fcuy|X!v>b{(24;h6 zJWY9Lv8*x1KY;xnwHPyvsDJ@ za=nD?=lf8HdL|ib^6{~*M~Z^@X6f4_vccD5U;FmpEMP#m#3a{Hv(qAR7jbY4j^jmY1_kGt2jCr9Hcns@ad#dkAiH(87OC%{OL&%A8E67dds4 zUUa(por`Wt!CH3Hh4y+T!9&*HuNopp&DuC!EBsu2>zv#{TDK;p*zGdw3Q}{Qa3l3P z;iD#9LF=sx7%v`;5kM(4uz1BHUXiwju?VgYWB8vDMa+TeebP^R`85D{{ zc$n4X&Z!+bAB>Phr{s{sU9$^T=t{2+HO8<@oNBifmQ0|Km;F^;iwj#gXkI1ur>(!Z zG@-if3==No%Idh?cck)-zRX2RqlFtoV`vrn=qyc?4xL}sirUxBJ4r!#F?aOvj)juB z%{tu=P8ttd5+4}c=Ud{6@wDYv&cB^kki63NIG@ATX%<^s?;CRDcEa1`cD0Wo0dd{Y z6qjdr3O;ft)T>4e(3iLm_u`QvGhKad%P9zU^Lh8<(*A{x4mEG2wo)t&m&#+lvgmgT zX=0eA>sxXaMJ9`9ydOiNS4<9P-1gH31Wp9bo%!tP$g@wsOnW*#!un#WK&N2z$F93% z)7XXFa=YT;W;+I0qF=FN_Dr$}{`Q67WG7Phqm*HvlkJb*IdK?p`G_u_U_TMccM}%Z z9o(j&Lzg2plsL#1uY|kR zlIJvxnYMIcl8WJUtLEWZ=Jc)J-!GUhx*adO`KdDYV3eE|sbm38a(2si#4)I#TQ{ zu?Gg4M4z6{uc>!WZ(Z|4?1_ml(CD!lWvQIf+81z4K0o}Pq{RyyL8J8^KU+axA#4qy zQ_Hf5_NC-tOOi9sMZFnv)U{y8i$_y>bVIjd zYdd_eZZ%qsKW*^;2wxh(DlFXEIM5O>17AA*?E6crapNmn`L!Jn>AqbENHS$!E&q-T zFo+4DLWSrzdaYa`rye_*o~K22kByy4JzG;|#gQ7C@QCI9JkMy#2(2Fr`Ks(a7O@xQ zvrGC5UmLAPFdMG#Z`W+kDtZAXOA0bEMIr=*Q!fa#N06YRqNk;z^4on3^%f>IEv8Vr zL60-Ew)rk(`mRiv3IpS4>4mi@^GxX`R5ew(n60W&Syt}_o>A)pgE5&E8 zx78ULi@iR42{_udvF!_&adC>f`(&?{`S`^G4hsg;xq4oViQ6kITte;T!WM@^_k;-B zLpb!avBKI!QgmoYY?o2a^F?+Z#*eEd9ik7<*Uqk8Z`^Mqt=+4+d1B;xTx-$WS;2+I zO|PLhqWk+I$Zt%YKlF@o9>2ARqq#A@Bb52^a#Z=0)&8LgZP% zvLw7M+CWwPCk1sR2eGG6T+wj2r>7^(lX?k3vV)7EP$)P82}dHKR0Ndl?LxtNL0!lK zI}|@SQ~@%ML~x}Lh%VqAPOJ^logxQ;Q0Kuv$*HqAH7~01XMmmYE64doj z0dOP&Ap=Dqp-2?`SAXg(2J^eO3;CytR6XHdSXa0h3;}m`{*wopqUP~Oyub7y8&U5O z;RXPi=uW}`Y94?KMc~(#>9W6^Y0Fj&pS3( z&1F|tv?>wjz7teSRSvR~FB(t85%B2UuQo^=N&+ci3&lwQc&G#dB?U#Ha9F4<9xr7h zBPD@Dps>GCX}ORoSQi|yLq#Qr5vV*UoEQ=zjTM7RN}ch1}Yr4mQkNTZ}}B%l(~;?mS?Yyqf^gft3@K-mCDtb{mq zUTl|YXCKf?dRlT2Bn~8 zNJ`0wBY$x>0Z3$OmG6*>Az;WKS>thNbt)y6T5SYptQ`P%b+Oy!-Psp3bv0CFu{+H{ zW!|+@7lT$I0ayx=WJDx7$w79K1@BPq_7qt5XSblw5^=kZyI=sn({MjqP8n+l-yO=r z{~h>Wm<;WSo-Y48oj67^y5TwBJ4^92JfB%Xe{oB{A8>LfZyE$s*XRVaQ0XiJAiuJ z{_M5i?1aClV_U2g4k1M?Txn@MwF0GZQcxRdF#w7}NFk8o(kPUK)Q?*Eot;dyrFddV zfRY`x2B`Z??XBH?2A}#-e!_oF#?v0ysVxLj42qC|iisN`#nA|Hw73Lyh(;hFKeik! z3*R|qe_OKb&N+m^pnnxbcITWzYwc8{p}VWA69FLoS*+iR=YPQc;{UTy|C9T#upizk zL|1QWC)-nWJzf57_`d-DU^q*_0WM_Xzf1jB$PZb5c^FZ1{$Zm&;FtHmOoy*0T=2& zf1cErYE6u!67_|g#zsd&6|{Xdx}%mlVs_OuBZEMDId(pKK*_0xsYXVM7DkP6jBXz- zEd)lyY5I@OKCuXih+u*QN7paQfUw6wG;XcaW~qWCo?T2*0>x(MuCfDKSAqe7lXsSc7qm4=p(o#F8`bgRO G%6|bpD&^7u literal 0 HcmV?d00001 diff --git a/docs/htmlcover/status.json b/docs/htmlcover/status.json new file mode 100644 index 00000000..410e6bcd --- /dev/null +++ b/docs/htmlcover/status.json @@ -0,0 +1 @@ +{"format":2,"version":"6.5.0","globals":"a67aa1dcf9d14a95bbf539a65fe3827f","files":{"d_a44f0ac069e85531___init___py":{"hash":"19c7d1c1a2b273fed9a5b49852623bd5","index":{"nums":[0,1,127,12,0,0,0,0],"html_filename":"d_a44f0ac069e85531___init___py.html","relative_filename":"tests\\__init__.py"}},"d_a44f0ac069e85531_test_cli_py":{"hash":"29a3ff48c73246594703c931f526db65","index":{"nums":[0,1,224,3,0,0,0,0],"html_filename":"d_a44f0ac069e85531_test_cli_py.html","relative_filename":"tests\\test_cli.py"}},"d_a44f0ac069e85531_test_commands_py":{"hash":"23cfbfaab14a176c27bd2b8f848b7c27","index":{"nums":[0,1,102,1,0,0,0,0],"html_filename":"d_a44f0ac069e85531_test_commands_py.html","relative_filename":"tests\\test_commands.py"}},"d_a44f0ac069e85531_test_edit_py":{"hash":"e2b49277e1caca7a37d04a067daeee6c","index":{"nums":[0,1,111,0,0,0,0,0],"html_filename":"d_a44f0ac069e85531_test_edit_py.html","relative_filename":"tests\\test_edit.py"}},"d_a44f0ac069e85531_test_interactive_py":{"hash":"a98d13dec20e858dd1c45fe3bd6d85d9","index":{"nums":[0,1,73,0,0,0,0,0],"html_filename":"d_a44f0ac069e85531_test_interactive_py.html","relative_filename":"tests\\test_interactive.py"}},"d_a44f0ac069e85531_test_rebuild_py":{"hash":"4dc0dd1ad80baae44ba273c10e14e088","index":{"nums":[0,1,97,1,0,0,0,0],"html_filename":"d_a44f0ac069e85531_test_rebuild_py.html","relative_filename":"tests\\test_rebuild.py"}},"d_a44f0ac069e85531_test_recheck_py":{"hash":"377dd86f94e6c5bd22405cf353a111ed","index":{"nums":[0,1,158,0,0,0,0,0],"html_filename":"d_a44f0ac069e85531_test_recheck_py.html","relative_filename":"tests\\test_recheck.py"}},"d_a44f0ac069e85531_test_torrent_py":{"hash":"56666e66d1fb348e4ffbb514f27bd67a","index":{"nums":[0,1,122,0,0,0,0,0],"html_filename":"d_a44f0ac069e85531_test_torrent_py.html","relative_filename":"tests\\test_torrent.py"}},"d_a44f0ac069e85531_test_utils_py":{"hash":"3ac3befebeb3ef8c987579d6edc0021a","index":{"nums":[0,1,89,0,0,0,0,0],"html_filename":"d_a44f0ac069e85531_test_utils_py.html","relative_filename":"tests\\test_utils.py"}},"d_1bc82e0ab2fcb2ec___init___py":{"hash":"f60571cb48a5282d63bcd9aaea269e30","index":{"nums":[0,1,7,0,0,0,0,0],"html_filename":"d_1bc82e0ab2fcb2ec___init___py.html","relative_filename":"torrentfile\\__init__.py"}},"d_1bc82e0ab2fcb2ec___main___py":{"hash":"cf6ef7033e53a22948f3f8ac7c09167d","index":{"nums":[0,1,5,1,0,0,0,0],"html_filename":"d_1bc82e0ab2fcb2ec___main___py.html","relative_filename":"torrentfile\\__main__.py"}},"d_1bc82e0ab2fcb2ec_cli_py":{"hash":"9328101470c1abee6cd3828b5d932da5","index":{"nums":[0,1,115,1,0,0,0,0],"html_filename":"d_1bc82e0ab2fcb2ec_cli_py.html","relative_filename":"torrentfile\\cli.py"}},"d_1bc82e0ab2fcb2ec_commands_py":{"hash":"90cc545442b60e918c18161a8c30b555","index":{"nums":[0,1,112,0,0,0,0,0],"html_filename":"d_1bc82e0ab2fcb2ec_commands_py.html","relative_filename":"torrentfile\\commands.py"}},"d_1bc82e0ab2fcb2ec_edit_py":{"hash":"601e1604ddc1ae11acbf9e5e01544389","index":{"nums":[0,1,53,0,0,0,0,0],"html_filename":"d_1bc82e0ab2fcb2ec_edit_py.html","relative_filename":"torrentfile\\edit.py"}},"d_1bc82e0ab2fcb2ec_hasher_py":{"hash":"6e762ad66c17287e3010232e0edd8518","index":{"nums":[0,1,244,0,0,0,0,0],"html_filename":"d_1bc82e0ab2fcb2ec_hasher_py.html","relative_filename":"torrentfile\\hasher.py"}},"d_1bc82e0ab2fcb2ec_interactive_py":{"hash":"06331d325538d3b1adc18b9e1cb5110b","index":{"nums":[0,1,121,20,0,0,0,0],"html_filename":"d_1bc82e0ab2fcb2ec_interactive_py.html","relative_filename":"torrentfile\\interactive.py"}},"d_1bc82e0ab2fcb2ec_mixins_py":{"hash":"1da8b12f7fdbb9a32d43932685d4ff6b","index":{"nums":[0,1,101,3,0,0,0,0],"html_filename":"d_1bc82e0ab2fcb2ec_mixins_py.html","relative_filename":"torrentfile\\mixins.py"}},"d_1bc82e0ab2fcb2ec_rebuild_py":{"hash":"75bd6ed102bdd5a7a2ab6e026c742274","index":{"nums":[0,1,251,4,0,0,0,0],"html_filename":"d_1bc82e0ab2fcb2ec_rebuild_py.html","relative_filename":"torrentfile\\rebuild.py"}},"d_1bc82e0ab2fcb2ec_recheck_py":{"hash":"e60bafb3601a07c6b4f82aa5d60882d9","index":{"nums":[0,1,279,3,0,0,0,0],"html_filename":"d_1bc82e0ab2fcb2ec_recheck_py.html","relative_filename":"torrentfile\\recheck.py"}},"d_1bc82e0ab2fcb2ec_torrent_py":{"hash":"35e0df1d2a2e6d6f2138e4bf2de06fee","index":{"nums":[0,1,242,0,0,0,0,0],"html_filename":"d_1bc82e0ab2fcb2ec_torrent_py.html","relative_filename":"torrentfile\\torrent.py"}},"d_1bc82e0ab2fcb2ec_utils_py":{"hash":"4183fb8de9451a078e71046987bcb937","index":{"nums":[0,1,114,5,0,0,0,0],"html_filename":"d_1bc82e0ab2fcb2ec_utils_py.html","relative_filename":"torrentfile\\utils.py"}},"d_1bc82e0ab2fcb2ec_version_py":{"hash":"f8e96b59ecb2a24ada90353ae9895154","index":{"nums":[0,1,2,0,0,0,0,0],"html_filename":"d_1bc82e0ab2fcb2ec_version_py.html","relative_filename":"torrentfile\\version.py"}}}} \ No newline at end of file diff --git a/docs/htmlcover/style.css b/docs/htmlcover/style.css new file mode 100644 index 00000000..d6768a35 --- /dev/null +++ b/docs/htmlcover/style.css @@ -0,0 +1,311 @@ +@charset "UTF-8"; +/* Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 */ +/* For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt */ +/* Don't edit this .css file. Edit the .scss file instead! */ +html, body, h1, h2, h3, p, table, td, th { margin: 0; padding: 0; border: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; } + +body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; font-size: 1em; background: #fff; color: #000; } + +@media (prefers-color-scheme: dark) { body { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { body { color: #eee; } } + +html > body { font-size: 16px; } + +a:active, a:focus { outline: 2px dashed #007acc; } + +p { font-size: .875em; line-height: 1.4em; } + +table { border-collapse: collapse; } + +td { vertical-align: top; } + +table tr.hidden { display: none !important; } + +p#no_rows { display: none; font-size: 1.2em; } + +a.nav { text-decoration: none; color: inherit; } + +a.nav:hover { text-decoration: underline; color: inherit; } + +.hidden { display: none; } + +header { background: #f8f8f8; width: 100%; z-index: 2; border-bottom: 1px solid #ccc; } + +@media (prefers-color-scheme: dark) { header { background: black; } } + +@media (prefers-color-scheme: dark) { header { border-color: #333; } } + +header .content { padding: 1rem 3.5rem; } + +header h2 { margin-top: .5em; font-size: 1em; } + +header p.text { margin: .5em 0 -.5em; color: #666; font-style: italic; } + +@media (prefers-color-scheme: dark) { header p.text { color: #aaa; } } + +header.sticky { position: fixed; left: 0; right: 0; height: 2.5em; } + +header.sticky .text { display: none; } + +header.sticky h1, header.sticky h2 { font-size: 1em; margin-top: 0; display: inline-block; } + +header.sticky .content { padding: 0.5rem 3.5rem; } + +header.sticky .content p { font-size: 1em; } + +header.sticky ~ #source { padding-top: 6.5em; } + +main { position: relative; z-index: 1; } + +footer { margin: 1rem 3.5rem; } + +footer .content { padding: 0; color: #666; font-style: italic; } + +@media (prefers-color-scheme: dark) { footer .content { color: #aaa; } } + +#index { margin: 1rem 0 0 3.5rem; } + +h1 { font-size: 1.25em; display: inline-block; } + +#filter_container { float: right; margin: 0 2em 0 0; } + +#filter_container input { width: 10em; padding: 0.2em 0.5em; border: 2px solid #ccc; background: #fff; color: #000; } + +@media (prefers-color-scheme: dark) { #filter_container input { border-color: #444; } } + +@media (prefers-color-scheme: dark) { #filter_container input { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { #filter_container input { color: #eee; } } + +#filter_container input:focus { border-color: #007acc; } + +header button { font-family: inherit; font-size: inherit; border: 1px solid; border-radius: .2em; color: inherit; padding: .1em .5em; margin: 1px calc(.1em + 1px); cursor: pointer; border-color: #ccc; } + +@media (prefers-color-scheme: dark) { header button { border-color: #444; } } + +header button:active, header button:focus { outline: 2px dashed #007acc; } + +header button.run { background: #eeffee; } + +@media (prefers-color-scheme: dark) { header button.run { background: #373d29; } } + +header button.run.show_run { background: #dfd; border: 2px solid #00dd00; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.run.show_run { background: #373d29; } } + +header button.mis { background: #ffeeee; } + +@media (prefers-color-scheme: dark) { header button.mis { background: #4b1818; } } + +header button.mis.show_mis { background: #fdd; border: 2px solid #ff0000; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.mis.show_mis { background: #4b1818; } } + +header button.exc { background: #f7f7f7; } + +@media (prefers-color-scheme: dark) { header button.exc { background: #333; } } + +header button.exc.show_exc { background: #eee; border: 2px solid #808080; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.exc.show_exc { background: #333; } } + +header button.par { background: #ffffd5; } + +@media (prefers-color-scheme: dark) { header button.par { background: #650; } } + +header button.par.show_par { background: #ffa; border: 2px solid #bbbb00; margin: 0 .1em; } + +@media (prefers-color-scheme: dark) { header button.par.show_par { background: #650; } } + +#help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; } + +#source p .annotate.long { white-space: normal; float: right; top: 1.75em; right: 1em; height: auto; } + +#help_panel_wrapper { float: right; position: relative; } + +#keyboard_icon { margin: 5px; } + +#help_panel_state { display: none; } + +#help_panel { top: 25px; right: 0; padding: .75em; border: 1px solid #883; color: #333; } + +#help_panel .keyhelp p { margin-top: .75em; } + +#help_panel .legend { font-style: italic; margin-bottom: 1em; } + +.indexfile #help_panel { width: 25em; } + +.pyfile #help_panel { width: 18em; } + +#help_panel_state:checked ~ #help_panel { display: block; } + +kbd { border: 1px solid black; border-color: #888 #333 #333 #888; padding: .1em .35em; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: bold; background: #eee; border-radius: 3px; } + +#source { padding: 1em 0 1em 3.5rem; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; } + +#source p { position: relative; white-space: pre; } + +#source p * { box-sizing: border-box; } + +#source p .n { float: left; text-align: right; width: 3.5rem; box-sizing: border-box; margin-left: -3.5rem; padding-right: 1em; color: #999; } + +@media (prefers-color-scheme: dark) { #source p .n { color: #777; } } + +#source p .n.highlight { background: #ffdd00; } + +#source p .n a { margin-top: -4em; padding-top: 4em; text-decoration: none; color: #999; } + +@media (prefers-color-scheme: dark) { #source p .n a { color: #777; } } + +#source p .n a:hover { text-decoration: underline; color: #999; } + +@media (prefers-color-scheme: dark) { #source p .n a:hover { color: #777; } } + +#source p .t { display: inline-block; width: 100%; box-sizing: border-box; margin-left: -.5em; padding-left: 0.3em; border-left: 0.2em solid #fff; } + +@media (prefers-color-scheme: dark) { #source p .t { border-color: #1e1e1e; } } + +#source p .t:hover { background: #f2f2f2; } + +@media (prefers-color-scheme: dark) { #source p .t:hover { background: #282828; } } + +#source p .t:hover ~ .r .annotate.long { display: block; } + +#source p .t .com { color: #008000; font-style: italic; line-height: 1px; } + +@media (prefers-color-scheme: dark) { #source p .t .com { color: #6a9955; } } + +#source p .t .key { font-weight: bold; line-height: 1px; } + +#source p .t .str { color: #0451a5; } + +@media (prefers-color-scheme: dark) { #source p .t .str { color: #9cdcfe; } } + +#source p.mis .t { border-left: 0.2em solid #ff0000; } + +#source p.mis.show_mis .t { background: #fdd; } + +@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t { background: #4b1818; } } + +#source p.mis.show_mis .t:hover { background: #f2d2d2; } + +@media (prefers-color-scheme: dark) { #source p.mis.show_mis .t:hover { background: #532323; } } + +#source p.run .t { border-left: 0.2em solid #00dd00; } + +#source p.run.show_run .t { background: #dfd; } + +@media (prefers-color-scheme: dark) { #source p.run.show_run .t { background: #373d29; } } + +#source p.run.show_run .t:hover { background: #d2f2d2; } + +@media (prefers-color-scheme: dark) { #source p.run.show_run .t:hover { background: #404633; } } + +#source p.exc .t { border-left: 0.2em solid #808080; } + +#source p.exc.show_exc .t { background: #eee; } + +@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t { background: #333; } } + +#source p.exc.show_exc .t:hover { background: #e2e2e2; } + +@media (prefers-color-scheme: dark) { #source p.exc.show_exc .t:hover { background: #3c3c3c; } } + +#source p.par .t { border-left: 0.2em solid #bbbb00; } + +#source p.par.show_par .t { background: #ffa; } + +@media (prefers-color-scheme: dark) { #source p.par.show_par .t { background: #650; } } + +#source p.par.show_par .t:hover { background: #f2f2a2; } + +@media (prefers-color-scheme: dark) { #source p.par.show_par .t:hover { background: #6d5d0c; } } + +#source p .r { position: absolute; top: 0; right: 2.5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } + +#source p .annotate { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; color: #666; padding-right: .5em; } + +@media (prefers-color-scheme: dark) { #source p .annotate { color: #ddd; } } + +#source p .annotate.short:hover ~ .long { display: block; } + +#source p .annotate.long { width: 30em; right: 2.5em; } + +#source p input { display: none; } + +#source p input ~ .r label.ctx { cursor: pointer; border-radius: .25em; } + +#source p input ~ .r label.ctx::before { content: "▶ "; } + +#source p input ~ .r label.ctx:hover { background: #e8f4ff; color: #666; } + +@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { background: #0f3a42; } } + +@media (prefers-color-scheme: dark) { #source p input ~ .r label.ctx:hover { color: #aaa; } } + +#source p input:checked ~ .r label.ctx { background: #d0e8ff; color: #666; border-radius: .75em .75em 0 0; padding: 0 .5em; margin: -.25em 0; } + +@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { background: #056; } } + +@media (prefers-color-scheme: dark) { #source p input:checked ~ .r label.ctx { color: #aaa; } } + +#source p input:checked ~ .r label.ctx::before { content: "▼ "; } + +#source p input:checked ~ .ctxs { padding: .25em .5em; overflow-y: scroll; max-height: 10.5em; } + +#source p label.ctx { color: #999; display: inline-block; padding: 0 .5em; font-size: .8333em; } + +@media (prefers-color-scheme: dark) { #source p label.ctx { color: #777; } } + +#source p .ctxs { display: block; max-height: 0; overflow-y: hidden; transition: all .2s; padding: 0 .5em; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; white-space: nowrap; background: #d0e8ff; border-radius: .25em; margin-right: 1.75em; } + +@media (prefers-color-scheme: dark) { #source p .ctxs { background: #056; } } + +#source p .ctxs span { display: block; text-align: right; } + +#index { font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.875em; } + +#index table.index { margin-left: -.5em; } + +#index td, #index th { text-align: right; width: 5em; padding: .25em .5em; border-bottom: 1px solid #eee; } + +@media (prefers-color-scheme: dark) { #index td, #index th { border-color: #333; } } + +#index td.name, #index th.name { text-align: left; width: auto; } + +#index th { font-style: italic; color: #333; cursor: pointer; } + +@media (prefers-color-scheme: dark) { #index th { color: #ddd; } } + +#index th:hover { background: #eee; } + +@media (prefers-color-scheme: dark) { #index th:hover { background: #333; } } + +#index th[aria-sort="ascending"], #index th[aria-sort="descending"] { white-space: nowrap; background: #eee; padding-left: .5em; } + +@media (prefers-color-scheme: dark) { #index th[aria-sort="ascending"], #index th[aria-sort="descending"] { background: #333; } } + +#index th[aria-sort="ascending"]::after { font-family: sans-serif; content: " ↑"; } + +#index th[aria-sort="descending"]::after { font-family: sans-serif; content: " ↓"; } + +#index td.name a { text-decoration: none; color: inherit; } + +#index tr.total td, #index tr.total_dynamic td { font-weight: bold; border-top: 1px solid #ccc; border-bottom: none; } + +#index tr.file:hover { background: #eee; } + +@media (prefers-color-scheme: dark) { #index tr.file:hover { background: #333; } } + +#index tr.file:hover td.name { text-decoration: underline; color: inherit; } + +#scroll_marker { position: fixed; z-index: 3; right: 0; top: 0; width: 16px; height: 100%; background: #fff; border-left: 1px solid #eee; will-change: transform; } + +@media (prefers-color-scheme: dark) { #scroll_marker { background: #1e1e1e; } } + +@media (prefers-color-scheme: dark) { #scroll_marker { border-color: #333; } } + +#scroll_marker .marker { background: #ccc; position: absolute; min-height: 3px; width: 100%; } + +@media (prefers-color-scheme: dark) { #scroll_marker .marker { background: #444; } } diff --git a/docs/index.html b/docs/index.html index 7c0dc6ac..42a4a392 100644 --- a/docs/index.html +++ b/docs/index.html @@ -359,19 +359,6 @@ Create Magnet - -
      • @@ -822,12 +809,6 @@

        Create Magnetmagnet or m with the path to the torrent file.

        1
        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. Use the --i or --interactive flags to activate interactive mode.

        -
        1
        torrentfile -i
        -

        GUI#

        If you prefer a windowed GUI please check out the official GUI frontend here

        diff --git a/docs/objects.inv b/docs/objects.inv index 2bb69c1d53df99f7d616a3eb2f705c46899bbd97..4d3cc6e695243b756116690afe826775d6d75a5e 100644 GIT binary patch delta 1043 zcmV+u1nm3r2#E=hg?~+N+#nFW_g6$}ucc~^y|q!JRBEb9t#U;yo_JCCaABOy_SYA% zH`#8D*MNJmZ18)21H&+b(O3%_?}Y@yrMT$!ACzf&2^a4#ewf~Jc;}nny?%4?_7(p9 zF1a%LUbF$iC39{uf-sKaks(lN+7@hJxbL-ZG`fJ~S1$lFIe&f*&)5F>24r__tYV0R zl%>YN6Yw4(^UI0}odYpSG2OVyi5dISD8enUq{Q=ZXap96g}yh%V8vPu<=?ij>4j`6 z@O%sWAs2rbuhW?d?bC0^3msdhKQ{yRciw3!s1Elz;4O38!P!rh4m~LZi3{c#e6ms& zV+o}q=%&+}bANa=JYgYEsU2(Bu%3Pr8A5vn$<2OaMN>Ky1DbT!aB!|x%iU$Ej=Rfx z4GQdo1Zs`JJiO4}8{(b>Q|s!edlpN+yxd=f;BBTnw1SPafYc9D=cer|q5kZH%xXFZ zvLP=17oEg0%~1UIvka~?+QIr?*qR;X{Y+3_Og`-3^M5Y(UpB1uqi1>uVK_ z8|_N=Igr3tvM|gc2aT!xG>Zs*kS?H6;z{Vq+;8YfA;{I{Hrz}s$z?#oH`8(#!ku>y z&ZOM1R1$TD=({y-p$X4WA{>2*$mJ5kuF_I2ja!AtA+xv!QQtMmHX=#7`u`f^3gd^8#$TzEe8`e-W?)W!ukxx*_!1n&n+PH z?!Zc{I$(WCiy;sBu&x8$ z)N1h6qHd^S=y%+FZ+)(Z|6OpgD~Taj;hI=(%YUF`$Pqd}D@oxCnc^6vv-`z7vG$9# zK&>L_Be`;IB}*4(nevyRuHdrJ3K#5~-@!BZTraV9oOVn;#JY~61t=uD252h$@d$pV z{?DYEI|j(2IupS&n4)#jtx9JuROJPJ3o6?Ihz-$$7e`ja^AhOm(hDBThVWG4?ryoYuXZ=X|l1!DTE`1w$TL zL$tF3OD!A!&dg=%*&MZ_ee}nuLe3iVe*Et$b#9wavZAWV6Wlz4QZ+paIeA_ZI}-1c zkGAcVZ~AQQWgNo-ifyl$4rkDtJ*E<@ zS%SEac4p^uh079n_~R{ufJ#Gx2du*3yqIDvvXXg3)3)9V25(ejnZB6g5v&`JnG8>( N&Db?y{s3iu4!UR^3LgLf delta 1019 zcmV-ZEn5w5hKa!50S=g9qoP*{>g9 zC(SZr8}MEt$N2mF9d{ge7?su_amxh|Citp3e6Cg53Rrzw{ZOrDaAj{_z5lTK_zwSm zmaLWB^2$M&pvKHb5Xz9hP=rFLssim8wym@`l}sS{)pEc{j(?xs|2zBt9z=7ebWIUE zDRYg1S70qdV)Quwgm<#51`286@|Ijpk+QRCH+4XvM&oTrE%6sXCsn z^EK3eDD9v`IRFrsl6)4%U9%NC-jJn+JC>uq5XJ3EK?Ioe4KK zJQx*XLqkQ>1ks~bRiX(eDB-4f%gE^x+^$j;OenJmkwL0)38Jy@DJnM{5T!qxq39^d=< zULaSIu$jCzRVGX0yj-xism|ciP;ukz8{fer_)IG>cb@iCJo~<`Kn;*cb_Y;q#N!!! zqW_Pi8aoDvqMFFy5lr4XA6BWJb6t7HWS_xpLN3dtSaLl#$e888bH)Y5EvSl?FH|yE zfPZP?xGIY4c-k}CX$Kk4TF%Qotse?ve5rHS8*velv$3ZVa#;6#p6O;Qf=g*EYUt|7 z9HPDEm}*)7b!MzmkLJjo?XxdN8FH5J`}w!4(7A0q$%3i|PjKT1Qq{D`#o&2~9|$~8 zZgkbw_SUV|gU6Vo;0LHx3dW{qw`fD@d2}4ljj>I$Yg3Xm`<~z%$O?B)RJ4#p!c=o` zC3%8=cPVU*X`5%HB7wm5Wxf`4EQ$E8WEOrF5-4_UO{F_U-W)JgB8?LGov=}RyW3x< pz}+`%;RIwF;(WLWhp}Rku}I_m_yXGWhrE|-y&vi@e*mqm-&-Q10*?Ry diff --git a/docs/overview/index.html b/docs/overview/index.html index d70c5ee8..6938fe84 100644 --- a/docs/overview/index.html +++ b/docs/overview/index.html @@ -311,23 +311,9 @@ Synopsis -

      • - -
      • - - Description - - -

    • -

      -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 ...

      +

      -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.

      +

      -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"

      +

      --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

      +

      --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.

      +

      -p --private

      +
        +
      • Indicates that the torrent will be used on a private tracker. Disables multi-tracker protocols and DHT.
      • +
    +

    recheck#

    +

    This feature is identical to the ‘recheck’ function provided by torrent clients. It +validates that the file/directory contents match those described in the torrent file.

    Recheck requires two paths as arguments. The first is the path to a torrent file, and and the second is a path to the file of directory containing the downloaded data from that torrentfile. torrentfile recursively validates each file with the hashes -contained in the torrentfile, and displays the amount missing frome each file, plus -a final percentage for the whole torrent at the conclusion. This will display a -progress bar for each file including missing files. It is also permitted to use +contained in the torrentfile, and displays the total percentage of the file that +matches the information from the torrent file. It is also permitted to use the contents parent directory which can help for batch processing many torrent files.

    alias: r, check

    1
     2
    torrentfile recheck <*.torrent> <contents>
     torrentfile r <*.torrent> <contents>
     
    +

    Magnet#

    Generate a magnet URL for a torrent file.

    alias: m

    1
    torrentfile magnet <path/to/*.torrent>
     
    +

    Rebuild#

    Rebuild individual or batches of torrent contents into the original file structure. The program takes a path to a torrent file or directory containing torrent files, @@ -840,8 +875,31 @@

    Rebuild
    1
    torrentfile rebuild <metafiles> <contents> <destination>
    +
    1
    torrentfile rebuild -m <metafiles> -c <contents> -d <destination>
     
    +
      +
    • +

      -m --metafiles

      +
        +
      • path to a torrent file or a directory containing torrent files for batch processing.
      • +
      +
    • +
    • +

      -c --content

      +
        +
      • path to where the contents for the torrentfile can be found. If the path is + for a directory, the directory will be searched recusively for files that match. + For deeply nested directories with lot’s of files in them, this can take time.
      • +
      +
    • +
    • +

      -d -destination

      +
        +
      • path to a directory where the torrent will be rebuilt. Files will be copied + from their original location, not moved.
      • +
      +
    • +
    diff --git a/docs/search/search_index.json b/docs/search/search_index.json index 30f2e2fe..0cd82364 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.6+ Tested on Linux, Windows and Mac \ud83d\udcbb Install # via PyPi: 1 pip install torrentfile via Git: 1 2 3 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 examples can be found in the project documentation on the examples page. \ud83d\udcdd License # Apache Software License v2.0 - See LICENSE \ud83d\udca1 Issues & Requests & PRs # If you encounter any bugs or would like to request a new feature please open a new issue. PRs and other contributions are welcome https://github.com/alexpdev/torrentfile/issues Usage Examples # Creating Bittorrent Files # Basic torrent file creation is as easy and using the create sub-commnand and providing the path to the contents. 1 torrentfile create /path/to/content You can add one or more trackers by using any one of -t , --tracker , -a , --announce flags and listing their URL as a space separated list. 1 torrentfile create /path/to/content -a http://tracker1.com http://tracker2.net If you intend to distribute the file on a private tracker then you should use one of -p , --private flags, which tells your Bittorrent clients to disable DHT and multitracker protocols. 1 torrentfile create /path/to/content --private By default torrentfile displays a progress bar indicating how much of the content has already been processed. To turn off this display you can either use --quiet mode in as a global flag or you can set the --prog flag to 0. 1 torrentfile --quiet create /path/to/content 1 torrentfile create /path/to/content --prog 0 torrentfile automatically extracts the name of the file or directory if the content and saves the file to the current working directory with the extracted title. For example running the follwing command would create ./content.torrent . 1 torrentfile create /path/to/content To specify an alternative path or filename you may use the -o , --out flags followed by the relative or absolute path to your preferred output location. 1 torrentfile create /path/to/content -o /some/other/path/torrent.torrent If the path you specified with the -o flag already exists and is a directory, then torrentfile will save the output to that directory with the default extracted title. For example the following command would create a Bittorrent file at /some/other/path/content.torrent . 1 torrentfile create /path/to/content -o /some/other/path/ Bittorrent V1 is still the most common version of torrent files and the most widely accepted, therefore by default torrentfile uses the version 1 format. However if you are using a modern Bittorrent client and tracker then you may wish to use the newest version Bittorrent V2 or a combination of the two. To do this simply use the --meta-version flag with the appropriate version. Options include 1 (v1 default), 2 (v2), or 3 (v1 & v2). 1 torrentfile create /path/to/content --meta-version 2 1 torrentfile create /path/to/content --meta-version 3 Check/Recheck Torrent # The recheck subcommand allows you to scan a Bittorrent file and compare it\u2019s contents, against a file or directory containing the contents the torrent file was created from. The output provided by this process gives a detailed perspective if any files are missing or have been corrupted in any way. Supports any version of Bittorrent file. 1 torrentfile recheck /path/to/some.torrent /path/to/content Edit Torrent # To edit specific fields of the torrent file, there is the edit subcommand. Using this subcommand you can specify the field with one of the available field flags, for example --tracker and specify the value you wish to change it to. 1 torrentfile edit /path/to/content --tracker https://new.tracker.url1.com https://newtracker.url/2 You can use the -h flag for a full list of available fields that can be edited. 1 torrentfile edit -h 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. 1 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. Use the -i or --interactive flags to activate interactive mode. 1 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.6+ Tested on Linux, Windows and Mac","title":"\ud83d\udd0c Requirements"},{"location":"#install","text":"via PyPi: 1 pip install torrentfile via Git: 1 2 3 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 examples can be found in the project documentation on the examples page.","title":"\ud83d\ude80 Usage"},{"location":"#license","text":"Apache Software License v2.0 - See LICENSE","title":"\ud83d\udcdd License"},{"location":"#issues-requests-prs","text":"If you encounter any bugs or would like to request a new feature please open a new issue. PRs and other contributions are welcome https://github.com/alexpdev/torrentfile/issues","title":"\ud83d\udca1 Issues & Requests & PRs"},{"location":"#usage-examples","text":"","title":"Usage Examples"},{"location":"#creating-bittorrent-files","text":"Basic torrent file creation is as easy and using the create sub-commnand and providing the path to the contents. 1 torrentfile create /path/to/content You can add one or more trackers by using any one of -t , --tracker , -a , --announce flags and listing their URL as a space separated list. 1 torrentfile create /path/to/content -a http://tracker1.com http://tracker2.net If you intend to distribute the file on a private tracker then you should use one of -p , --private flags, which tells your Bittorrent clients to disable DHT and multitracker protocols. 1 torrentfile create /path/to/content --private By default torrentfile displays a progress bar indicating how much of the content has already been processed. To turn off this display you can either use --quiet mode in as a global flag or you can set the --prog flag to 0. 1 torrentfile --quiet create /path/to/content 1 torrentfile create /path/to/content --prog 0 torrentfile automatically extracts the name of the file or directory if the content and saves the file to the current working directory with the extracted title. For example running the follwing command would create ./content.torrent . 1 torrentfile create /path/to/content To specify an alternative path or filename you may use the -o , --out flags followed by the relative or absolute path to your preferred output location. 1 torrentfile create /path/to/content -o /some/other/path/torrent.torrent If the path you specified with the -o flag already exists and is a directory, then torrentfile will save the output to that directory with the default extracted title. For example the following command would create a Bittorrent file at /some/other/path/content.torrent . 1 torrentfile create /path/to/content -o /some/other/path/ Bittorrent V1 is still the most common version of torrent files and the most widely accepted, therefore by default torrentfile uses the version 1 format. However if you are using a modern Bittorrent client and tracker then you may wish to use the newest version Bittorrent V2 or a combination of the two. To do this simply use the --meta-version flag with the appropriate version. Options include 1 (v1 default), 2 (v2), or 3 (v1 & v2). 1 torrentfile create /path/to/content --meta-version 2 1 torrentfile create /path/to/content --meta-version 3","title":"Creating Bittorrent Files"},{"location":"#checkrecheck-torrent","text":"The recheck subcommand allows you to scan a Bittorrent file and compare it\u2019s contents, against a file or directory containing the contents the torrent file was created from. The output provided by this process gives a detailed perspective if any files are missing or have been corrupted in any way. Supports any version of Bittorrent file. 1 torrentfile recheck /path/to/some.torrent /path/to/content","title":"Check/Recheck Torrent"},{"location":"#edit-torrent","text":"To edit specific fields of the torrent file, there is the edit subcommand. Using this subcommand you can specify the field with one of the available field flags, for example --tracker and specify the value you wish to change it to. 1 torrentfile edit /path/to/content --tracker https://new.tracker.url1.com https://newtracker.url/2 You can use the -h flag for a full list of available fields that can be edited. 1 torrentfile edit -h","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. 1 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. Use the -i or --interactive flags to activate interactive mode. 1 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\u2019t 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. 1 2 3 4 5 6 7 8 9 10 11 12 13 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\u2019t 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. 1 2 3 4 5 6 7 8 9 10 11 12 13 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":"api/","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: # \u201cannounce\u201d: The URL of the tracker. \u201cinfo\u201d: This maps to a dictionary, with keys described below. \u201cname\u201d: A display name for the torrent. It is purely advisory. \u201cpiece length\u201d: 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. \u201cmeta version\u201d: 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 \u201cfile tree\u201d: 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. \u2018UTF-8 encoded\u2019 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 \u2018.\u2019 and \u2018..\u2019 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. \u201clength\u201d: 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. \u201cpieces root\u201d: 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. \u201cpiece layers\u201d: 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\u2019s 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. TorrentAssembler \u2014 Assembler class for Bittorrent version 2 and hybrid meta files. 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 Memoize cache. MissingPathError \u2014 Path parameter is required to specify target content. PieceLengthValueError \u2014 Piece Length parameter must equal a perfect power of 2. ArgumentError \u2014 Exception for mismatched or mistyped CLI arguments. Functions copypath ( source , dest ) \u2014 Copy the file located at source to dest. 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 the fields that were not used by the original file creator. 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 # Config : class controls logging configuration TorrentFileHelpFormatter : HelpFormatter the command line help message formatter Classes Config \u2014 Class the controls the logging configuration and output settings. TorrentFileHelpFormatter \u2014 Formatting class for help tips provided by the CLI. Functions execute ( args ) (list) \u2014 Execute program with provided list of arguments. execute ( args ) (list) \u2014 Execute program with provided list of arguments. 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\u2019s 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 Iterate through contents of meta data and verify with file contents. 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. FileHasher \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":"API"},{"location":"api/#torrentfile","text":"","title":"TorrentFile"},{"location":"api/#api-and-source","text":"","title":"API and Source"},{"location":"api/#torrent-module","text":"module torrentfile. torrent Classes and procedures pertaining to the creation of torrent meta files.","title":"Torrent Module"},{"location":"api/#classes","text":"TorrentFile construct .torrent file. TorrentFileV2 construct .torrent v2 files using provided data. MetaFile base class for all MetaFile classes.","title":"Classes"},{"location":"api/#constants","text":"BLOCK_SIZE : int size of leaf hashes for merkle tree. HASH_SIZE : int Length of a sha256 hash.","title":"Constants"},{"location":"api/#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":"api/#meta-version-2-dictionary","text":"\u201cannounce\u201d: The URL of the tracker. \u201cinfo\u201d: This maps to a dictionary, with keys described below. \u201cname\u201d: A display name for the torrent. It is purely advisory. \u201cpiece length\u201d: 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. \u201cmeta version\u201d: 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 \u201cfile tree\u201d: 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. \u2018UTF-8 encoded\u2019 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 \u2018.\u2019 and \u2018..\u2019 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. \u201clength\u201d: 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. \u201cpieces root\u201d: 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. \u201cpiece layers\u201d: 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":"api/#bittorrent-v1","text":"","title":"Bittorrent V1"},{"location":"api/#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\u2019s 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. TorrentAssembler \u2014 Assembler class for Bittorrent version 2 and hybrid meta files.","title":"v1 meta-dictionary"},{"location":"api/#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 Memoize cache. MissingPathError \u2014 Path parameter is required to specify target content. PieceLengthValueError \u2014 Piece Length parameter must equal a perfect power of 2. ArgumentError \u2014 Exception for mismatched or mistyped CLI arguments. Functions copypath ( source , dest ) \u2014 Copy the file located at source to dest. 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":"api/#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":"api/#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 the fields that were not used by the original file creator.","title":"Keywords"},{"location":"api/#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":"api/#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.","title":"CLI Module"},{"location":"api/#functions","text":"main_script : process command line arguments and run program. activate_logger : turns on debug mode and logging facility.","title":"Functions"},{"location":"api/#classes_1","text":"Config : class controls logging configuration TorrentFileHelpFormatter : HelpFormatter the command line help message formatter Classes Config \u2014 Class the controls the logging configuration and output settings. TorrentFileHelpFormatter \u2014 Formatting class for help tips provided by the CLI. Functions execute ( args ) (list) \u2014 Execute program with provided list of arguments. execute ( args ) (list) \u2014 Execute program with provided list of arguments. main ( ) \u2014 Initiate main function for CLI script.","title":"Classes"},{"location":"api/#recheck-module","text":"module torrentfile. recheck Module container Checker Class. The CheckerClass takes a torrentfile and tha path to it\u2019s 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 Iterate through contents of meta data and verify with file contents.","title":"Recheck Module"},{"location":"api/#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. FileHasher \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":"changelog/","text":"TorrentFile # Version 0.8.3 # Added the callback mixin to the rebuild module Fix compatability with GUI frontend torrentfileQt. Version 0.8.2 # Rebuild subcommand now checks on a hash by hash basis Fixed coverage issues Added unittests for the rebuild command Fixed bug with torrentfile creation when a file was a perfect power of 2 Reconfigured the rebuild module Reconfigured the rebuild cli flags and arguments Version 0.8.1 # Further improvements to documentation Fixed bug that interrupted the creation process when using gui version Added unittests Improved docstrings and docstring formatting Renamed a couple of methods Version 0.8.0 # overhaul documentation reconfigured CI files and configuration and packaging files Convert to pyproject.toml setuptools packaging info source Version 0.7.12 # Changed default behavior to save torrent files to cwd edited all unittests to reflect default behavior added deprecation messages for the cli arg and class paramteter last update to version 0.7.x Version 0.7.11 # Fixed issue with progress bar displaying inaccurate details Other minor bug fixes Updated output for Recheck subcommand for better readability Updated documentation Updated Readme added quiet mode to cli global options -q Added unit test to fix coverage gaps Fixed warnings created by pylint Version 0.7.10 # Added rebuild module and subcommand see docs for more info Added documentation entry for rebuild subcommand improved logging messages added unit tests improved and expanded on type hints minor bug fixes Version 0.7.9 # complete rewrite of the recheck procedures Recheck now provides more accuracy and more details improvements to the new custom progressbar changed the cli argument for the progress bar the options are now just 0 and 1 included new unit tests for all new features marked unused functions as deprecated added a new hasher object for v2 and hybrid torrents minor bug fixes and styling changes Version 0.7.8 # 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 \u2013progress flag is now \u2013noprogress Default behavior is to show progress bar use \u2013noprogress 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\u2019s 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-083","text":"Added the callback mixin to the rebuild module Fix compatability with GUI frontend torrentfileQt.","title":"Version 0.8.3"},{"location":"changelog/#version-082","text":"Rebuild subcommand now checks on a hash by hash basis Fixed coverage issues Added unittests for the rebuild command Fixed bug with torrentfile creation when a file was a perfect power of 2 Reconfigured the rebuild module Reconfigured the rebuild cli flags and arguments","title":"Version 0.8.2"},{"location":"changelog/#version-081","text":"Further improvements to documentation Fixed bug that interrupted the creation process when using gui version Added unittests Improved docstrings and docstring formatting Renamed a couple of methods","title":"Version 0.8.1"},{"location":"changelog/#version-080","text":"overhaul documentation reconfigured CI files and configuration and packaging files Convert to pyproject.toml setuptools packaging info source","title":"Version 0.8.0"},{"location":"changelog/#version-0712","text":"Changed default behavior to save torrent files to cwd edited all unittests to reflect default behavior added deprecation messages for the cli arg and class paramteter last update to version 0.7.x","title":"Version 0.7.12"},{"location":"changelog/#version-0711","text":"Fixed issue with progress bar displaying inaccurate details Other minor bug fixes Updated output for Recheck subcommand for better readability Updated documentation Updated Readme added quiet mode to cli global options -q Added unit test to fix coverage gaps Fixed warnings created by pylint","title":"Version 0.7.11"},{"location":"changelog/#version-0710","text":"Added rebuild module and subcommand see docs for more info Added documentation entry for rebuild subcommand improved logging messages added unit tests improved and expanded on type hints minor bug fixes","title":"Version 0.7.10"},{"location":"changelog/#version-079","text":"complete rewrite of the recheck procedures Recheck now provides more accuracy and more details improvements to the new custom progressbar changed the cli argument for the progress bar the options are now just 0 and 1 included new unit tests for all new features marked unused functions as deprecated added a new hasher object for v2 and hybrid torrents minor bug fixes and styling changes","title":"Version 0.7.9"},{"location":"changelog/#version-078","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.8"},{"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 \u2013progress flag is now \u2013noprogress Default behavior is to show progress bar use \u2013noprogress 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\u2019s 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":"overview/","text":"torrentfile Manual # Synopsis # 1 torrentfile [options] [options] Description # torrentfile is a command line toolkit for working with Bittorrent files(.torrent). Some of the tools available include creating torrent files, editing portions of a torrent file, checking the integrity or completeness of downloaded torrent contents, displaying details of a torrentfile, generating magnet URLs for torrentfiles, and individual or batch rebuilding of torrent contents into their original directory structure. Options # -h displays all relevant command line options and subcommands. -V displays program and version. -v enables debug mode and outputs a large amount of information to the terminal. -i activates interactive mode for selecting subcommands and options. Sub-commands # create # alias: c , new 1 2 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\u2019s 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\u2019t 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 --progress --prog Options (0, 1): No status bar will be shown if 0. Otherwise the default and 1 argument means progress bar is shown info # alias: i 1 2 torrentfile info torrentfile i Display detailed information about a torrentfile such as trackers, size of contents, Bittorrent version, any comments left, date the torrent file was created and more. There is only one positional perameter which is the path to the torrent file and there are no optional arguments. /path/to/*.torrent The relative or absolute path to the torrent file. edit # Edit some of the different information detailed in a torrent file. The fields that are editable each have option flags detialed below. 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. alias: e 1 2 torrentfile edit [options] torrentfile e [options] -a -t --announce --tracker Adds the list of url\u2019s 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 # Recheck requires two paths as arguments. The first is the path to a torrent file, and and the second is a path to the file of directory containing the downloaded data from that torrentfile. torrentfile recursively validates each file with the hashes contained in the torrentfile, and displays the amount missing frome each file, plus a final percentage for the whole torrent at the conclusion. This will display a progress bar for each file including missing files. It is also permitted to use the contents parent directory which can help for batch processing many torrent files. alias: r , check 1 2 torrentfile recheck <*.torrent> torrentfile r <*.torrent> Magnet # Generate a magnet URL for a torrent file. alias: m 1 torrentfile magnet Rebuild # Rebuild individual or batches of torrent contents into the original file structure. The program takes a path to a torrent file or directory containing torrent files, the directory containing the torrent contents, and the destination directory to where the rebuilt torrent content wil be located. The program will recursively traverse the content directory searching for file\u2019s that match one of the meta files and creates copies of the matches to the destination directory. The original files are not effected and any existing files in the target directory will not be overwritten. alias: build , b 1 torrentfile rebuild ","title":"Overview"},{"location":"overview/#torrentfile-manual","text":"","title":"torrentfile Manual"},{"location":"overview/#synopsis","text":"1 torrentfile [options] [options] ","title":"Synopsis"},{"location":"overview/#description","text":"torrentfile is a command line toolkit for working with Bittorrent files(.torrent). Some of the tools available include creating torrent files, editing portions of a torrent file, checking the integrity or completeness of downloaded torrent contents, displaying details of a torrentfile, generating magnet URLs for torrentfiles, and individual or batch rebuilding of torrent contents into their original directory structure.","title":"Description"},{"location":"overview/#options","text":"-h displays all relevant command line options and subcommands. -V displays program and version. -v enables debug mode and outputs a large amount of information to the terminal. -i activates interactive mode for selecting subcommands and options.","title":"Options"},{"location":"overview/#sub-commands","text":"","title":"Sub-commands"},{"location":"overview/#create","text":"alias: c , new 1 2 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\u2019s 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\u2019t 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 --progress --prog Options (0, 1): No status bar will be shown if 0. Otherwise the default and 1 argument means progress bar is shown","title":"create"},{"location":"overview/#info","text":"alias: i 1 2 torrentfile info torrentfile i Display detailed information about a torrentfile such as trackers, size of contents, Bittorrent version, any comments left, date the torrent file was created and more. There is only one positional perameter which is the path to the torrent file and there are no optional arguments. /path/to/*.torrent The relative or absolute path to the torrent file.","title":"info"},{"location":"overview/#edit","text":"Edit some of the different information detailed in a torrent file. The fields that are editable each have option flags detialed below. 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. alias: e 1 2 torrentfile edit [options] torrentfile e [options] -a -t --announce --tracker Adds the list of url\u2019s 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":"overview/#recheck","text":"Recheck requires two paths as arguments. The first is the path to a torrent file, and and the second is a path to the file of directory containing the downloaded data from that torrentfile. torrentfile recursively validates each file with the hashes contained in the torrentfile, and displays the amount missing frome each file, plus a final percentage for the whole torrent at the conclusion. This will display a progress bar for each file including missing files. It is also permitted to use the contents parent directory which can help for batch processing many torrent files. alias: r , check 1 2 torrentfile recheck <*.torrent> torrentfile r <*.torrent> ","title":"recheck"},{"location":"overview/#magnet","text":"Generate a magnet URL for a torrent file. alias: m 1 torrentfile magnet ","title":"Magnet"},{"location":"overview/#rebuild","text":"Rebuild individual or batches of torrent contents into the original file structure. The program takes a path to a torrent file or directory containing torrent files, the directory containing the torrent contents, and the destination directory to where the rebuilt torrent content wil be located. The program will recursively traverse the content directory searching for file\u2019s that match one of the meta files and creates copies of the matches to the destination directory. The original files are not effected and any existing files in the target directory will not be overwritten. alias: build , b 1 torrentfile rebuild ","title":"Rebuild"},{"location":"usage/","text":"TorrentFile CLI Menu # Help Messages # Main # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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 # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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). --prog, --progress (0) = no progress bar displayed (1) = progress bar is displayed (default) --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 # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 # 1 2 3 4 5 6 7 8 9 10 11 12 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 # 1 2 3 4 5 6 7 8 9 10 11 Usage ===== torrentfile m [-h] <*.torrent> Positional Arguments -------------------- <*.torrent> Path to Bittorrent meta file. Optional Arguments ------------------ -h, --help show this help message and exit Coverage","title":"Usage"},{"location":"usage/#torrentfile-cli-menu","text":"","title":"TorrentFile CLI Menu"},{"location":"usage/#help-messages","text":"","title":"Help Messages"},{"location":"usage/#main","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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":"usage/#create","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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). --prog, --progress (0) = no progress bar displayed (1) = progress bar is displayed (default) --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":"usage/#edit","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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":"usage/#recheck","text":"1 2 3 4 5 6 7 8 9 10 11 12 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":"usage/#magnet","text":"1 2 3 4 5 6 7 8 9 10 11 Usage ===== torrentfile m [-h] <*.torrent> Positional Arguments -------------------- <*.torrent> Path to Bittorrent meta file. Optional Arguments ------------------ -h, --help show this help message and exit Coverage","title":"Magnet"},{"location":"Source/","text":"Source Code Modules # torrentfile is an open source project. The official source code can be viewed on github at https://github.com/alexpdev/torrentfile . Modules # cli # commands # edit # hasher # interactive # mixins # rebuild # recheck # torrent # utils # version #","title":"Source"},{"location":"Source/#source-code-modules","text":"torrentfile is an open source project. The official source code can be viewed on github at https://github.com/alexpdev/torrentfile .","title":"Source Code Modules"},{"location":"Source/#modules","text":"","title":"Modules"},{"location":"Source/#cli","text":"","title":"cli"},{"location":"Source/#commands","text":"","title":"commands"},{"location":"Source/#edit","text":"","title":"edit"},{"location":"Source/#hasher","text":"","title":"hasher"},{"location":"Source/#interactive","text":"","title":"interactive"},{"location":"Source/#mixins","text":"","title":"mixins"},{"location":"Source/#rebuild","text":"","title":"rebuild"},{"location":"Source/#recheck","text":"","title":"recheck"},{"location":"Source/#torrent","text":"","title":"torrent"},{"location":"Source/#utils","text":"","title":"utils"},{"location":"Source/#version","text":"","title":"version"},{"location":"Source/cli/","text":"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 # Config : class controls logging configuration TorrentFileHelpFormatter : HelpFormatter the command line help message formatter Config # Class the controls the logging configuration and output settings. Controls the logging level, or whether to app should operate in quiet mode. activate_logger () staticmethod # Activate the builtin logging mechanism when passed debug flag from CLI. Source code in torrentfile\\cli.py 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 @staticmethod def activate_logger (): \"\"\" Activate the builtin logging mechanism when passed debug flag from CLI. \"\"\" logging . basicConfig ( level = logging . INFO ) logger = logging . getLogger () console_handler = logging . StreamHandler ( stream = sys . stderr ) stream_formatter = logging . Formatter ( \" %(asctime)s %(levelno)s %(message)s \" , datefmt = \"%m- %d %H:%M:%S\" , style = \"%\" , ) console_handler . setFormatter ( stream_formatter ) console_handler . setLevel ( logging . DEBUG ) logger . setLevel ( logging . DEBUG ) logger . addHandler ( console_handler ) logger . debug ( \"Debug: ON\" ) activate_quiet () staticmethod # Activate quiet mode for the duration of the programs life. When quiet mode is enabled, no logging, progress or state information is output to the terminal Source code in torrentfile\\cli.py 59 60 61 62 63 64 65 66 67 68 69 @staticmethod def activate_quiet (): \"\"\" Activate quiet mode for the duration of the programs life. When quiet mode is enabled, no logging, progress or state information is output to the terminal \"\"\" if sys . stdout or sys . stderr : sys . stdout = io . StringIO () sys . stderr = io . StringIO () TorrentFileHelpFormatter ( prog , width = 45 , max_help_positions = 45 ) # Bases: HelpFormatter Formatting class for help tips provided by the CLI. Subclasses Argparse.HelpFormatter. Construct HelpFormat class for usage output. PARAMETER DESCRIPTION prog Name of the program. TYPE: str width Max width of help message output. TYPE: int DEFAULT: 45 max_help_positions max length until line wrap. TYPE: int DEFAULT: 45 Source code in torrentfile\\cli.py 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 def __init__ ( self , prog , width = 45 , max_help_positions = 45 ): \"\"\" 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 ) execute ( args : Optional [ list ] = None ) -> list # Execute program with provided list of arguments. If no arguments are given then it defaults to using sys.argv. This is the main entrypoint for the program and command line interface. PARAMETER DESCRIPTION args Commandline arguments. default=None TYPE: list DEFAULT: None RETURNS DESCRIPTION list Depends on what the command line args were. Source code in torrentfile\\cli.py 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 def execute ( args : Optional [ list ] = None ) -> list : \"\"\" Execute program with provided list of arguments. If no arguments are given then it defaults to using sys.argv. This is the main entrypoint for the program and command line interface. Parameters ---------- args : list Commandline arguments. default=None Returns ------- list Depends on what the command line args were. \"\"\" if not args : if sys . argv [ 1 :]: args = sys . argv [ 1 :] else : args = [ \"-h\" ] parser = ArgumentParser ( \"torrentfile\" , usage = \"torrentfile [options] command [command options]\" , description = ( \"Command line tools for creating, editing, checking, building \" \"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 ( \"-q\" , \"--quiet\" , help = \"Turn off all text output.\" , dest = \"quiet\" , action = \"store_true\" , ) 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 = \"Commands\" , dest = \"command\" , metavar = \"create, edit, info, magnet, recheck, rebuild \\n \" , ) create_parser = subparsers . add_parser ( \"create\" , help = \"Create a new Bittorrent 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 = \"Explicitly specify the path to write the file.\" , ) create_parser . add_argument ( \"--cwd\" , \"--current\" , action = \"store_true\" , dest = \"cwd\" , help = \"*deprecated* Saving to current directory is default behaviour\" , ) create_parser . add_argument ( \"--prog\" , \"--progress\" , default = \"1\" , action = \"store\" , dest = \"progress\" , help = \"\"\" Set the progress bar level. Options = 0, 1 (0) = Do not display progress bar. (1) = Display progress bar.(default) \"\"\" , ) 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 ) 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 ) 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 = \"Gives a detailed look at how much of the torrent is available.\" , aliases = [ \"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 ) rebuild_parser = subparsers . add_parser ( \"rebuild\" , aliases = [ \"build\" ], help = \"\"\"Re-assemble files obtained from a bittorrent file into the appropriate file structure for re-seeding. Read documentation for more information, or use cases.\"\"\" , formatter_class = TorrentFileHelpFormatter , ) rebuild_parser . add_argument ( \"-m\" , \"--metafiles\" , action = \"store\" , metavar = \"<*.torrent>\" , nargs = \"+\" , dest = \"metafiles\" , required = True , help = \"path(s) to .torrent file(s)/folder(s) containing .torrent files\" , ) rebuild_parser . add_argument ( \"-c\" \"--contents\" , action = \"store\" , dest = \"contents\" , nargs = \"+\" , required = True , metavar = \"\" , help = \"folders that might contain the source contents needed to rebuld\" , ) rebuild_parser . add_argument ( \"-d\" , \"--destination\" , action = \"store\" , dest = \"destination\" , required = True , metavar = \"\" , help = \"path to where torrents will be re-assembled\" , ) rebuild_parser . set_defaults ( func = rebuild ) args = parser . parse_args ( args ) if args . quiet : Config . activate_quiet () elif args . debug : Config . activate_logger () if args . interactive : return select_action () if hasattr ( args , \"func\" ): return args . func ( args ) return args # pragma: nocover main () # Initiate main function for CLI script. Source code in torrentfile\\cli.py 602 603 604 605 606 def main (): \"\"\" Initiate main function for CLI script. \"\"\" execute ()","title":"Cli"},{"location":"Source/cli/#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.","title":"cli"},{"location":"Source/cli/#torrentfile.cli--functions","text":"main_script : process command line arguments and run program. activate_logger : turns on debug mode and logging facility.","title":"Functions"},{"location":"Source/cli/#torrentfile.cli--classes","text":"Config : class controls logging configuration TorrentFileHelpFormatter : HelpFormatter the command line help message formatter","title":"Classes"},{"location":"Source/cli/#torrentfile.cli.Config","text":"Class the controls the logging configuration and output settings. Controls the logging level, or whether to app should operate in quiet mode.","title":"Config"},{"location":"Source/cli/#torrentfile.cli.Config.activate_logger","text":"Activate the builtin logging mechanism when passed debug flag from CLI. Source code in torrentfile\\cli.py 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 @staticmethod def activate_logger (): \"\"\" Activate the builtin logging mechanism when passed debug flag from CLI. \"\"\" logging . basicConfig ( level = logging . INFO ) logger = logging . getLogger () console_handler = logging . StreamHandler ( stream = sys . stderr ) stream_formatter = logging . Formatter ( \" %(asctime)s %(levelno)s %(message)s \" , datefmt = \"%m- %d %H:%M:%S\" , style = \"%\" , ) console_handler . setFormatter ( stream_formatter ) console_handler . setLevel ( logging . DEBUG ) logger . setLevel ( logging . DEBUG ) logger . addHandler ( console_handler ) logger . debug ( \"Debug: ON\" )","title":"activate_logger()"},{"location":"Source/cli/#torrentfile.cli.Config.activate_quiet","text":"Activate quiet mode for the duration of the programs life. When quiet mode is enabled, no logging, progress or state information is output to the terminal Source code in torrentfile\\cli.py 59 60 61 62 63 64 65 66 67 68 69 @staticmethod def activate_quiet (): \"\"\" Activate quiet mode for the duration of the programs life. When quiet mode is enabled, no logging, progress or state information is output to the terminal \"\"\" if sys . stdout or sys . stderr : sys . stdout = io . StringIO () sys . stderr = io . StringIO ()","title":"activate_quiet()"},{"location":"Source/cli/#torrentfile.cli.TorrentFileHelpFormatter","text":"Bases: HelpFormatter Formatting class for help tips provided by the CLI. Subclasses Argparse.HelpFormatter. Construct HelpFormat class for usage output. PARAMETER DESCRIPTION prog Name of the program. TYPE: str width Max width of help message output. TYPE: int DEFAULT: 45 max_help_positions max length until line wrap. TYPE: int DEFAULT: 45 Source code in torrentfile\\cli.py 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 def __init__ ( self , prog , width = 45 , max_help_positions = 45 ): \"\"\" 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":"TorrentFileHelpFormatter"},{"location":"Source/cli/#torrentfile.cli.execute","text":"Execute program with provided list of arguments. If no arguments are given then it defaults to using sys.argv. This is the main entrypoint for the program and command line interface. PARAMETER DESCRIPTION args Commandline arguments. default=None TYPE: list DEFAULT: None RETURNS DESCRIPTION list Depends on what the command line args were. Source code in torrentfile\\cli.py 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 def execute ( args : Optional [ list ] = None ) -> list : \"\"\" Execute program with provided list of arguments. If no arguments are given then it defaults to using sys.argv. This is the main entrypoint for the program and command line interface. Parameters ---------- args : list Commandline arguments. default=None Returns ------- list Depends on what the command line args were. \"\"\" if not args : if sys . argv [ 1 :]: args = sys . argv [ 1 :] else : args = [ \"-h\" ] parser = ArgumentParser ( \"torrentfile\" , usage = \"torrentfile [options] command [command options]\" , description = ( \"Command line tools for creating, editing, checking, building \" \"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 ( \"-q\" , \"--quiet\" , help = \"Turn off all text output.\" , dest = \"quiet\" , action = \"store_true\" , ) 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 = \"Commands\" , dest = \"command\" , metavar = \"create, edit, info, magnet, recheck, rebuild \\n \" , ) create_parser = subparsers . add_parser ( \"create\" , help = \"Create a new Bittorrent 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 = \"Explicitly specify the path to write the file.\" , ) create_parser . add_argument ( \"--cwd\" , \"--current\" , action = \"store_true\" , dest = \"cwd\" , help = \"*deprecated* Saving to current directory is default behaviour\" , ) create_parser . add_argument ( \"--prog\" , \"--progress\" , default = \"1\" , action = \"store\" , dest = \"progress\" , help = \"\"\" Set the progress bar level. Options = 0, 1 (0) = Do not display progress bar. (1) = Display progress bar.(default) \"\"\" , ) 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 ) 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 ) 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 = \"Gives a detailed look at how much of the torrent is available.\" , aliases = [ \"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 ) rebuild_parser = subparsers . add_parser ( \"rebuild\" , aliases = [ \"build\" ], help = \"\"\"Re-assemble files obtained from a bittorrent file into the appropriate file structure for re-seeding. Read documentation for more information, or use cases.\"\"\" , formatter_class = TorrentFileHelpFormatter , ) rebuild_parser . add_argument ( \"-m\" , \"--metafiles\" , action = \"store\" , metavar = \"<*.torrent>\" , nargs = \"+\" , dest = \"metafiles\" , required = True , help = \"path(s) to .torrent file(s)/folder(s) containing .torrent files\" , ) rebuild_parser . add_argument ( \"-c\" \"--contents\" , action = \"store\" , dest = \"contents\" , nargs = \"+\" , required = True , metavar = \"\" , help = \"folders that might contain the source contents needed to rebuld\" , ) rebuild_parser . add_argument ( \"-d\" , \"--destination\" , action = \"store\" , dest = \"destination\" , required = True , metavar = \"\" , help = \"path to where torrents will be re-assembled\" , ) rebuild_parser . set_defaults ( func = rebuild ) args = parser . parse_args ( args ) if args . quiet : Config . activate_quiet () elif args . debug : Config . activate_logger () if args . interactive : return select_action () if hasattr ( args , \"func\" ): return args . func ( args ) return args # pragma: nocover","title":"execute()"},{"location":"Source/cli/#torrentfile.cli.main","text":"Initiate main function for CLI script. Source code in torrentfile\\cli.py 602 603 604 605 606 def main (): \"\"\" Initiate main function for CLI script. \"\"\" execute ()","title":"main()"},{"location":"Source/commands/","text":"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 : Namespace ) -> Namespace # Execute the create CLI sub-command to create a new torrent metafile. PARAMETER DESCRIPTION args positional and optional CLI arguments. TYPE: Namespace RETURNS DESCRIPTION torrentfile . MetaFile object containing the path to created metafile and its contents. Source code in torrentfile\\commands.py 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 def create ( args : Namespace ) -> Namespace : \"\"\" Execute the create CLI sub-command to create a new torrent metafile. Parameters ---------- args : Namespace positional and optional CLI arguments. Returns ------- torrentfile.MetaFile object containing the path to created metafile and its contents. \"\"\" kwargs = vars ( args ) logger . debug ( \"Creating torrent from %s \" , args . content ) if args . meta_version == \"1\" : torrent = TorrentFile ( ** kwargs ) else : torrent = TorrentAssembler ( ** kwargs ) outfile , meta = torrent . write () if args . magnet : magnet ( outfile ) args . torrent = torrent args . kwargs = kwargs args . outfile = outfile args . meta = meta print ( \" \\n Torrent Save Path: \" , os . path . abspath ( str ( outfile ))) logger . debug ( \"Output path: %s \" , str ( outfile )) return args edit ( args : Namespace ) -> str # Execute the edit CLI sub-command with provided arguments. Provides functionality that can change the details of a torrentfile that preserves all of the hash piece information so as not to break the torrentfile. PARAMETER DESCRIPTION args positional and optional CLI arguments. TYPE: Namespace RETURNS DESCRIPTION str path to edited torrent file. Source code in torrentfile\\commands.py 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 def edit ( args : Namespace ) -> str : \"\"\" Execute the edit CLI sub-command with provided arguments. Provides functionality that can change the details of a torrentfile that preserves all of the hash piece information so as not to break the torrentfile. 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 : Namespace ) -> str # Show torrent metafile details to user via stdout. Prints full details of torrent file contents to the terminal in a clean and readable format. PARAMETER DESCRIPTION args command line arguements provided by the user. TYPE: dict RETURNS DESCRIPTION str The output printed to the terminal. Source code in torrentfile\\commands.py 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 def info ( args : Namespace ) -> str : \"\"\" Show torrent metafile details to user via stdout. Prints full details of torrent file contents to the terminal in a clean and readable format. Parameters ---------- args : dict command line arguements provided by the user. Returns ------- str The output printed to the terminal. \"\"\" metafile = args . metafile meta = pyben . load ( metafile ) data = meta [ \"info\" ] del meta [ \"info\" ] meta . update ( data ) 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 : Namespace ) -> str # Create a magnet URI from a Bittorrent meta file. PARAMETER DESCRIPTION metafile Namespace class for CLI arguments. TYPE: Namespace RETURNS DESCRIPTION str created magnet URI. Source code in torrentfile\\commands.py 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 def magnet ( metafile : Namespace ) -> str : \"\"\" Create a magnet URI from a Bittorrent meta file. Parameters ---------- metafile : Namespace 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 ) data = meta [ \"info\" ] binfo = pyben . dumps ( data ) infohash = sha1 ( binfo ) . hexdigest () . upper () # nosec logger . info ( \"Magnet Info Hash: %s \" , infohash ) scheme = \"magnet:\" hasharg = \"?xt=urn:btih:\" + infohash namearg = \"&dn=\" + quote_plus ( data [ \"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 rebuild ( args : Namespace ) -> int # Attempt to rebuild a torrent based on the a torrent file. Recursively look through a directory for files that belong in a given torrent file, and rebuild as much of the torrent file as possible. Currently only checks if the filename and file size are a match. TODO # Check file hashes to improve accuracy PARAMETER DESCRIPTION args command line arguments including the paths neccessary TYPE: Namespace RETURNS DESCRIPTION int total number of content files copied to the rebuild directory Source code in torrentfile\\commands.py 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 def rebuild ( args : Namespace ) -> int : \"\"\" Attempt to rebuild a torrent based on the a torrent file. Recursively look through a directory for files that belong in a given torrent file, and rebuild as much of the torrent file as possible. Currently only checks if the filename and file size are a match. #### TODO 1. Check file hashes to improve accuracy Parameters ---------- args : Namespace command line arguments including the paths neccessary Returns ------- int total number of content files copied to the rebuild directory \"\"\" metafiles = args . metafiles dest = args . destination contents = args . contents for path in [ * metafiles , * contents ]: if not os . path . exists ( path ): raise FileNotFoundError ( path ) assembler = Assembler ( metafiles , contents , dest ) return assembler . assemble_torrents () recheck ( args : Namespace ) -> str # Execute recheck CLI sub-command. Checks the piece hashes within a pre-existing torrent file and does a piece by piece check with the contents of a file or directory for completeness and validation. PARAMETER DESCRIPTION args positional and optional arguments. TYPE: Namespace RETURNS DESCRIPTION str The percentage of content currently saved to disk. Source code in torrentfile\\commands.py 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 def recheck ( args : Namespace ) -> str : \"\"\" Execute recheck CLI sub-command. Checks the piece hashes within a pre-existing torrent file and does a piece by piece check with the contents of a file or directory for completeness and validation. Parameters ---------- args : Namespace positional and optional arguments. Returns ------- str The percentage of content currently saved to disk. \"\"\" metafile = args . metafile content = args . content if os . path . isdir ( metafile ): raise ArgumentError ( f \"Error: Unable to parse directory { metafile } . \" \"Check the order of the parameters.\" ) logger . debug ( \"Validating %s <---------------> %s contents\" , metafile , content ) msg = f \"Rechecking { metafile } ... \\n \" halfterm = shutil . get_terminal_size () . columns / 2 padding = int ( halfterm - ( len ( msg ) / 2 )) * \" \" sys . stdout . write ( padding + msg ) checker = Checker ( metafile , content ) logger . debug ( \"Completed initialization of the Checker class\" ) result = checker . results () message = f \" { content } <- { result } % -> { metafile } \" padding = int ( halfterm - ( len ( message ) / 2 )) * \" \" sys . stdout . write ( padding + message + \" \\n \" ) sys . stdout . flush () return result","title":"Commands"},{"location":"Source/commands/#torrentfile.commands","text":"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.","title":"commands"},{"location":"Source/commands/#torrentfile.commands--functions","text":"create_command info_command edit_command recheck_command magnet_command","title":"Functions"},{"location":"Source/commands/#torrentfile.commands.create","text":"Execute the create CLI sub-command to create a new torrent metafile. PARAMETER DESCRIPTION args positional and optional CLI arguments. TYPE: Namespace RETURNS DESCRIPTION torrentfile . MetaFile object containing the path to created metafile and its contents. Source code in torrentfile\\commands.py 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 def create ( args : Namespace ) -> Namespace : \"\"\" Execute the create CLI sub-command to create a new torrent metafile. Parameters ---------- args : Namespace positional and optional CLI arguments. Returns ------- torrentfile.MetaFile object containing the path to created metafile and its contents. \"\"\" kwargs = vars ( args ) logger . debug ( \"Creating torrent from %s \" , args . content ) if args . meta_version == \"1\" : torrent = TorrentFile ( ** kwargs ) else : torrent = TorrentAssembler ( ** kwargs ) outfile , meta = torrent . write () if args . magnet : magnet ( outfile ) args . torrent = torrent args . kwargs = kwargs args . outfile = outfile args . meta = meta print ( \" \\n Torrent Save Path: \" , os . path . abspath ( str ( outfile ))) logger . debug ( \"Output path: %s \" , str ( outfile )) return args","title":"create()"},{"location":"Source/commands/#torrentfile.commands.edit","text":"Execute the edit CLI sub-command with provided arguments. Provides functionality that can change the details of a torrentfile that preserves all of the hash piece information so as not to break the torrentfile. PARAMETER DESCRIPTION args positional and optional CLI arguments. TYPE: Namespace RETURNS DESCRIPTION str path to edited torrent file. Source code in torrentfile\\commands.py 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 def edit ( args : Namespace ) -> str : \"\"\" Execute the edit CLI sub-command with provided arguments. Provides functionality that can change the details of a torrentfile that preserves all of the hash piece information so as not to break the torrentfile. 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 )","title":"edit()"},{"location":"Source/commands/#torrentfile.commands.info","text":"Show torrent metafile details to user via stdout. Prints full details of torrent file contents to the terminal in a clean and readable format. PARAMETER DESCRIPTION args command line arguements provided by the user. TYPE: dict RETURNS DESCRIPTION str The output printed to the terminal. Source code in torrentfile\\commands.py 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 def info ( args : Namespace ) -> str : \"\"\" Show torrent metafile details to user via stdout. Prints full details of torrent file contents to the terminal in a clean and readable format. Parameters ---------- args : dict command line arguements provided by the user. Returns ------- str The output printed to the terminal. \"\"\" metafile = args . metafile meta = pyben . load ( metafile ) data = meta [ \"info\" ] del meta [ \"info\" ] meta . update ( data ) 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","title":"info()"},{"location":"Source/commands/#torrentfile.commands.magnet","text":"Create a magnet URI from a Bittorrent meta file. PARAMETER DESCRIPTION metafile Namespace class for CLI arguments. TYPE: Namespace RETURNS DESCRIPTION str created magnet URI. Source code in torrentfile\\commands.py 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 def magnet ( metafile : Namespace ) -> str : \"\"\" Create a magnet URI from a Bittorrent meta file. Parameters ---------- metafile : Namespace 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 ) data = meta [ \"info\" ] binfo = pyben . dumps ( data ) infohash = sha1 ( binfo ) . hexdigest () . upper () # nosec logger . info ( \"Magnet Info Hash: %s \" , infohash ) scheme = \"magnet:\" hasharg = \"?xt=urn:btih:\" + infohash namearg = \"&dn=\" + quote_plus ( data [ \"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","title":"magnet()"},{"location":"Source/commands/#torrentfile.commands.rebuild","text":"Attempt to rebuild a torrent based on the a torrent file. Recursively look through a directory for files that belong in a given torrent file, and rebuild as much of the torrent file as possible. Currently only checks if the filename and file size are a match.","title":"rebuild()"},{"location":"Source/commands/#torrentfile.commands.rebuild--todo","text":"Check file hashes to improve accuracy PARAMETER DESCRIPTION args command line arguments including the paths neccessary TYPE: Namespace RETURNS DESCRIPTION int total number of content files copied to the rebuild directory Source code in torrentfile\\commands.py 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 def rebuild ( args : Namespace ) -> int : \"\"\" Attempt to rebuild a torrent based on the a torrent file. Recursively look through a directory for files that belong in a given torrent file, and rebuild as much of the torrent file as possible. Currently only checks if the filename and file size are a match. #### TODO 1. Check file hashes to improve accuracy Parameters ---------- args : Namespace command line arguments including the paths neccessary Returns ------- int total number of content files copied to the rebuild directory \"\"\" metafiles = args . metafiles dest = args . destination contents = args . contents for path in [ * metafiles , * contents ]: if not os . path . exists ( path ): raise FileNotFoundError ( path ) assembler = Assembler ( metafiles , contents , dest ) return assembler . assemble_torrents ()","title":"TODO"},{"location":"Source/commands/#torrentfile.commands.recheck","text":"Execute recheck CLI sub-command. Checks the piece hashes within a pre-existing torrent file and does a piece by piece check with the contents of a file or directory for completeness and validation. PARAMETER DESCRIPTION args positional and optional arguments. TYPE: Namespace RETURNS DESCRIPTION str The percentage of content currently saved to disk. Source code in torrentfile\\commands.py 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 def recheck ( args : Namespace ) -> str : \"\"\" Execute recheck CLI sub-command. Checks the piece hashes within a pre-existing torrent file and does a piece by piece check with the contents of a file or directory for completeness and validation. Parameters ---------- args : Namespace positional and optional arguments. Returns ------- str The percentage of content currently saved to disk. \"\"\" metafile = args . metafile content = args . content if os . path . isdir ( metafile ): raise ArgumentError ( f \"Error: Unable to parse directory { metafile } . \" \"Check the order of the parameters.\" ) logger . debug ( \"Validating %s <---------------> %s contents\" , metafile , content ) msg = f \"Rechecking { metafile } ... \\n \" halfterm = shutil . get_terminal_size () . columns / 2 padding = int ( halfterm - ( len ( msg ) / 2 )) * \" \" sys . stdout . write ( padding + msg ) checker = Checker ( metafile , content ) logger . debug ( \"Completed initialization of the Checker class\" ) result = checker . results () message = f \" { content } <- { result } % -> { metafile } \" padding = int ( halfterm - ( len ( message ) / 2 )) * \" \" sys . stdout . write ( padding + message + \" \\n \" ) sys . stdout . flush () return result","title":"recheck()"},{"location":"Source/edit/","text":"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 : str , args : dict ) -> dict # Edit the properties and values in a torrent meta file. PARAMETER DESCRIPTION metafile path to the torrent meta file. TYPE: str args key value pairs of the properties to be edited. TYPE: dict RETURNS DESCRIPTION dict The edited and nested Meta and info dictionaries. Source code in torrentfile\\edit.py 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 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 : dict , meta : dict , info : dict ) # Remove the fields that were not used by the original file creator. PARAMETER DESCRIPTION args Editable metafile properties from user. TYPE: dict meta Metafile data dictionary. TYPE: dict info Metafile info dictionary. TYPE: dict Source code in torrentfile\\edit.py 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 def filter_empty ( args : dict , meta : dict , info : dict ): \"\"\" Remove the fields that were not used by the original file creator. 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 )","title":"Edit"},{"location":"Source/edit/#torrentfile.edit","text":"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"},{"location":"Source/edit/#torrentfile.edit--keywords","text":"private comment source trackers web-seeds","title":"Keywords"},{"location":"Source/edit/#torrentfile.edit.edit_torrent","text":"Edit the properties and values in a torrent meta file. PARAMETER DESCRIPTION metafile path to the torrent meta file. TYPE: str args key value pairs of the properties to be edited. TYPE: dict RETURNS DESCRIPTION dict The edited and nested Meta and info dictionaries. Source code in torrentfile\\edit.py 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 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","title":"edit_torrent()"},{"location":"Source/edit/#torrentfile.edit.filter_empty","text":"Remove the fields that were not used by the original file creator. PARAMETER DESCRIPTION args Editable metafile properties from user. TYPE: dict meta Metafile data dictionary. TYPE: dict info Metafile info dictionary. TYPE: dict Source code in torrentfile\\edit.py 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 def filter_empty ( args : dict , meta : dict , info : dict ): \"\"\" Remove the fields that were not used by the original file creator. 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 )","title":"filter_empty()"},{"location":"Source/hasher/","text":"hasher # Piece/File Hashers for Bittorrent meta file contents. FileHasher ( path : str , piece_length : int , progress : bool = True , hybrid : bool = False ) # Bases: 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. PARAMETER DESCRIPTION path path to target file. TYPE: str piece_length piece length for data chunks. TYPE: int progress default = None TYPE: int DEFAULT: True Construct Hasher class instances for each file in torrent. Source code in torrentfile\\hasher.py 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 def __init__ ( self , path : str , piece_length : int , progress : bool = True , hybrid : bool = False , ): \"\"\" 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 self . amount = piece_length // BLOCK_SIZE self . end = False self . current = open ( path , \"rb\" ) self . hybrid = hybrid if progress : self . progressbar = True self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) __iter__ () # Return self : needed to implement iterator implementation. Source code in torrentfile\\hasher.py 442 443 444 def __iter__ ( self ): \"\"\"Return `self`: needed to implement iterator implementation.\"\"\" return self __next__ () -> bytes # Calculate layer hashes for contents of file. RETURNS DESCRIPTION bytes The layer merckle root hash. Source code in torrentfile\\hasher.py 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 def __next__ ( self ) -> bytes : \"\"\" Calculate layer hashes for contents of file. Returns ------- bytes The layer merckle root hash. \"\"\" if self . end : self . end = False raise StopIteration plength = self . piece_length blocks = [] piece = sha1 () # nosec total = 0 block = bytearray ( BLOCK_SIZE ) for _ in range ( self . amount ): size = self . current . readinto ( block ) if not size : self . end = True break total += size plength -= size blocks . append ( sha256 ( block [: size ]) . digest ()) if self . hybrid : piece . update ( block [: size ]) if not blocks : self . _calculate_root () raise StopIteration if len ( blocks ) != self . amount : padding = self . _pad_remaining ( len ( blocks )) blocks . extend ( padding ) self . prog_update ( total ) layer_hash = merkle_root ( blocks ) self . layer_hashes . append ( layer_hash ) self . cb ( layer_hash ) if self . end : self . _calculate_root () self . prog_close () if self . hybrid : if plength > 0 : self . padding_file = { \"attr\" : \"p\" , \"length\" : plength , \"path\" : [ \".pad\" , str ( plength )], } piece . update ( bytes ( plength )) piece = piece . digest () self . pieces . append ( piece ) return layer_hash , piece return layer_hash Hasher ( paths : list , piece_length : int , progress : bool = True ) # Bases: 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. PARAMETER DESCRIPTION paths List of files. TYPE: list piece_length Size of chuncks to split the data into. TYPE: int progress default = None TYPE: int DEFAULT: True Generate hashes of piece length data from filelist contents. Source code in torrentfile\\hasher.py 54 55 56 57 58 59 60 61 62 63 64 65 def __init__ ( self , paths : list , piece_length : int , progress : bool = True ): \"\"\"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 : total = os . path . getsize ( self . paths [ 0 ]) self . prog_start ( total , self . paths [ 0 ], unit = \"bytes\" ) logger . debug ( \"Hashing %s \" , str ( self . paths [ 0 ])) __iter__ () # Iterate through feed pieces. RETURNS DESCRIPTION self Iterator for leaves/hash pieces. TYPE: iterator Source code in torrentfile\\hasher.py 67 68 69 70 71 72 73 74 75 76 def __iter__ ( self ): \"\"\" Iterate through feed pieces. Returns ------- self : iterator Iterator for leaves/hash pieces. \"\"\" return self __next__ () -> bytes # Generate piece-length pieces of data from input file list. RETURNS DESCRIPTION bytes SHA1 hash of the piece extracted. Source code in torrentfile\\hasher.py 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 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 () -> bool # Seemlessly transition to next file in file list. RETURNS DESCRIPTION bool True if there is a next file otherwise False. TYPE: bool Source code in torrentfile\\hasher.py 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 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 ] logger . debug ( \"Hashing %s \" , str ( path )) self . current . close () if self . progress : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . current = open ( path , \"rb\" ) return True return False HasherHybrid ( path : str , piece_length : int , progress : bool = True ) # Bases: CbMixin , ProgMixin Calculate root and piece hashes for creating hybrid torrent file. DEPRECATED 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. PARAMETER DESCRIPTION path path to target file. TYPE: str piece_length piece length for data chunks. TYPE: int progress default = None TYPE: int DEFAULT: True Construct Hasher class instances for each file in torrent. DEPRECATED Source code in torrentfile\\hasher.py 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 def __init__ ( self , path : str , piece_length : int , progress : bool = True ): \"\"\" Construct Hasher class instances for each file in torrent. **DEPRECATED** \"\"\" 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 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . amount = piece_length // BLOCK_SIZE with open ( path , \"rb\" ) as data : self . process_file ( data ) process_file ( data : bytearray ) # Calculate layer hashes for contents of file. DEPRECATED PARAMETER DESCRIPTION data File opened in read mode. TYPE: BytesIO Source code in torrentfile\\hasher.py 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 def process_file ( self , data : bytearray ): \"\"\" Calculate layer hashes for contents of file. **DEPRECATED** 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 ) 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 ( path : str , piece_length : int , progress : bool = True ) # Bases: CbMixin , ProgMixin Calculate the root hash and piece layers for file contents. DEPRECATED 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. PARAMETER DESCRIPTION path Path to file. TYPE: str piece_length Size of layer hashes pieces. TYPE: int progress default = None TYPE: int DEFAULT: True Calculate and store hash information for specific file. DEPRECATED Source code in torrentfile\\hasher.py 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 def __init__ ( self , path : str , piece_length : int , progress : bool = True ): \"\"\" Calculate and store hash information for specific file. **DEPRECATED** \"\"\" 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 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) with open ( self . path , \"rb\" ) as fd : self . process_file ( fd ) process_file ( fd : str ) # Calculate hashes over 16KiB chuncks of file content. DEPRECATED PARAMETER DESCRIPTION fd Opened file in read mode. TYPE: TextIOWrapper Source code in torrentfile\\hasher.py 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 def process_file ( self , fd : str ): \"\"\" Calculate hashes over 16KiB chuncks of file content. **DEPRECATED** Parameters ---------- fd : TextIOWrapper 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 ) self . cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) self . _calculate_root () self . prog_close () merkle_root ( blocks : list ) -> bytes # Calculate the merkle root for a seq of sha256 hash digests. PARAMETER DESCRIPTION blocks a sequence of sha256 layer hashes. TYPE: list RETURNS DESCRIPTION bytes the sha256 root hash of the merkle tree. Source code in torrentfile\\hasher.py 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 def merkle_root ( blocks : list ) -> bytes : \"\"\" Calculate the merkle root for a seq of sha256 hash digests. Parameters ---------- blocks : list a sequence of sha256 layer hashes. Returns ------- bytes the sha256 root hash of the merkle tree. \"\"\" if blocks : while len ( blocks ) > 1 : blocks = [ sha256 ( x + y ) . digest () for x , y in zip ( * [ iter ( blocks )] * 2 ) ] return blocks [ 0 ] return blocks","title":"Hasher"},{"location":"Source/hasher/#torrentfile.hasher","text":"Piece/File Hashers for Bittorrent meta file contents.","title":"hasher"},{"location":"Source/hasher/#torrentfile.hasher.FileHasher","text":"Bases: 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. PARAMETER DESCRIPTION path path to target file. TYPE: str piece_length piece length for data chunks. TYPE: int progress default = None TYPE: int DEFAULT: True Construct Hasher class instances for each file in torrent. Source code in torrentfile\\hasher.py 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 def __init__ ( self , path : str , piece_length : int , progress : bool = True , hybrid : bool = False , ): \"\"\" 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 self . amount = piece_length // BLOCK_SIZE self . end = False self . current = open ( path , \"rb\" ) self . hybrid = hybrid if progress : self . progressbar = True self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" )","title":"FileHasher"},{"location":"Source/hasher/#torrentfile.hasher.FileHasher.__iter__","text":"Return self : needed to implement iterator implementation. Source code in torrentfile\\hasher.py 442 443 444 def __iter__ ( self ): \"\"\"Return `self`: needed to implement iterator implementation.\"\"\" return self","title":"__iter__()"},{"location":"Source/hasher/#torrentfile.hasher.FileHasher.__next__","text":"Calculate layer hashes for contents of file. RETURNS DESCRIPTION bytes The layer merckle root hash. Source code in torrentfile\\hasher.py 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 def __next__ ( self ) -> bytes : \"\"\" Calculate layer hashes for contents of file. Returns ------- bytes The layer merckle root hash. \"\"\" if self . end : self . end = False raise StopIteration plength = self . piece_length blocks = [] piece = sha1 () # nosec total = 0 block = bytearray ( BLOCK_SIZE ) for _ in range ( self . amount ): size = self . current . readinto ( block ) if not size : self . end = True break total += size plength -= size blocks . append ( sha256 ( block [: size ]) . digest ()) if self . hybrid : piece . update ( block [: size ]) if not blocks : self . _calculate_root () raise StopIteration if len ( blocks ) != self . amount : padding = self . _pad_remaining ( len ( blocks )) blocks . extend ( padding ) self . prog_update ( total ) layer_hash = merkle_root ( blocks ) self . layer_hashes . append ( layer_hash ) self . cb ( layer_hash ) if self . end : self . _calculate_root () self . prog_close () if self . hybrid : if plength > 0 : self . padding_file = { \"attr\" : \"p\" , \"length\" : plength , \"path\" : [ \".pad\" , str ( plength )], } piece . update ( bytes ( plength )) piece = piece . digest () self . pieces . append ( piece ) return layer_hash , piece return layer_hash","title":"__next__()"},{"location":"Source/hasher/#torrentfile.hasher.Hasher","text":"Bases: 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. PARAMETER DESCRIPTION paths List of files. TYPE: list piece_length Size of chuncks to split the data into. TYPE: int progress default = None TYPE: int DEFAULT: True Generate hashes of piece length data from filelist contents. Source code in torrentfile\\hasher.py 54 55 56 57 58 59 60 61 62 63 64 65 def __init__ ( self , paths : list , piece_length : int , progress : bool = True ): \"\"\"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 : total = os . path . getsize ( self . paths [ 0 ]) self . prog_start ( total , self . paths [ 0 ], unit = \"bytes\" ) logger . debug ( \"Hashing %s \" , str ( self . paths [ 0 ]))","title":"Hasher"},{"location":"Source/hasher/#torrentfile.hasher.Hasher.__iter__","text":"Iterate through feed pieces. RETURNS DESCRIPTION self Iterator for leaves/hash pieces. TYPE: iterator Source code in torrentfile\\hasher.py 67 68 69 70 71 72 73 74 75 76 def __iter__ ( self ): \"\"\" Iterate through feed pieces. Returns ------- self : iterator Iterator for leaves/hash pieces. \"\"\" return self","title":"__iter__()"},{"location":"Source/hasher/#torrentfile.hasher.Hasher.__next__","text":"Generate piece-length pieces of data from input file list. RETURNS DESCRIPTION bytes SHA1 hash of the piece extracted. Source code in torrentfile\\hasher.py 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 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","title":"__next__()"},{"location":"Source/hasher/#torrentfile.hasher.Hasher.next_file","text":"Seemlessly transition to next file in file list. RETURNS DESCRIPTION bool True if there is a next file otherwise False. TYPE: bool Source code in torrentfile\\hasher.py 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 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 ] logger . debug ( \"Hashing %s \" , str ( path )) self . current . close () if self . progress : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . current = open ( path , \"rb\" ) return True return False","title":"next_file()"},{"location":"Source/hasher/#torrentfile.hasher.HasherHybrid","text":"Bases: CbMixin , ProgMixin Calculate root and piece hashes for creating hybrid torrent file. DEPRECATED 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. PARAMETER DESCRIPTION path path to target file. TYPE: str piece_length piece length for data chunks. TYPE: int progress default = None TYPE: int DEFAULT: True Construct Hasher class instances for each file in torrent. DEPRECATED Source code in torrentfile\\hasher.py 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 def __init__ ( self , path : str , piece_length : int , progress : bool = True ): \"\"\" Construct Hasher class instances for each file in torrent. **DEPRECATED** \"\"\" 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 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . amount = piece_length // BLOCK_SIZE with open ( path , \"rb\" ) as data : self . process_file ( data )","title":"HasherHybrid"},{"location":"Source/hasher/#torrentfile.hasher.HasherHybrid.process_file","text":"Calculate layer hashes for contents of file. DEPRECATED PARAMETER DESCRIPTION data File opened in read mode. TYPE: BytesIO Source code in torrentfile\\hasher.py 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 def process_file ( self , data : bytearray ): \"\"\" Calculate layer hashes for contents of file. **DEPRECATED** 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 ) 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 ()","title":"process_file()"},{"location":"Source/hasher/#torrentfile.hasher.HasherV2","text":"Bases: CbMixin , ProgMixin Calculate the root hash and piece layers for file contents. DEPRECATED 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. PARAMETER DESCRIPTION path Path to file. TYPE: str piece_length Size of layer hashes pieces. TYPE: int progress default = None TYPE: int DEFAULT: True Calculate and store hash information for specific file. DEPRECATED Source code in torrentfile\\hasher.py 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 def __init__ ( self , path : str , piece_length : int , progress : bool = True ): \"\"\" Calculate and store hash information for specific file. **DEPRECATED** \"\"\" 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 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) with open ( self . path , \"rb\" ) as fd : self . process_file ( fd )","title":"HasherV2"},{"location":"Source/hasher/#torrentfile.hasher.HasherV2.process_file","text":"Calculate hashes over 16KiB chuncks of file content. DEPRECATED PARAMETER DESCRIPTION fd Opened file in read mode. TYPE: TextIOWrapper Source code in torrentfile\\hasher.py 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 def process_file ( self , fd : str ): \"\"\" Calculate hashes over 16KiB chuncks of file content. **DEPRECATED** Parameters ---------- fd : TextIOWrapper 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 ) self . cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) self . _calculate_root () self . prog_close ()","title":"process_file()"},{"location":"Source/hasher/#torrentfile.hasher.merkle_root","text":"Calculate the merkle root for a seq of sha256 hash digests. PARAMETER DESCRIPTION blocks a sequence of sha256 layer hashes. TYPE: list RETURNS DESCRIPTION bytes the sha256 root hash of the merkle tree. Source code in torrentfile\\hasher.py 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 def merkle_root ( blocks : list ) -> bytes : \"\"\" Calculate the merkle root for a seq of sha256 hash digests. Parameters ---------- blocks : list a sequence of sha256 layer hashes. Returns ------- bytes the sha256 root hash of the merkle tree. \"\"\" if blocks : while len ( blocks ) > 1 : blocks = [ sha256 ( x + y ) . digest () for x , y in zip ( * [ iter ( blocks )] * 2 ) ] return blocks [ 0 ] return blocks","title":"merkle_root()"},{"location":"Source/interactive/","text":"interactive # Module contains the procedures used for Interactive Mode. InteractiveCreator () # Class namespace for interactive program options. Initialize interactive meta file creator dialog. Source code in torrentfile\\interactive.py 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 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 () # Gather details for torrentfile from user. Source code in torrentfile\\interactive.py 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 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 ( metafile : str ) # Interactive dialog class for torrent editing. Initialize the Interactive torrent editor guide. PARAMETER DESCRIPTION metafile user input string identifying the path to a torrent meta file. TYPE: str Source code in torrentfile\\interactive.py 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 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 () # Loop continuosly for edits until user signals DONE. Source code in torrentfile\\interactive.py 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 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 ( key , response ) # Convert the input data into a form recognizable by the program. PARAMETER DESCRIPTION key name of the property and attribute being eddited. TYPE: str response User input value the property is being edited to. TYPE: str Source code in torrentfile\\interactive.py 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 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 () # Display the current met file information to screen. Source code in torrentfile\\interactive.py 217 218 219 220 221 222 223 224 225 226 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 163 164 165 166 167 168 169 170 171 172 173 174 175 176 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 179 180 181 182 183 184 185 186 187 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 : tuple ) # Determine appropriate input function to call. PARAMETER DESCRIPTION *args Arbitrary number of args to pass to next function TYPE: tuple DEFAULT: () RETURNS DESCRIPTION str The results of the function call. Source code in torrentfile\\interactive.py 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 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 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 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 : str ) # Print text to screen in the center position of the terminal. PARAMETER DESCRIPTION txt the preformated message to send to stdout. TYPE: str Source code in torrentfile\\interactive.py 108 109 110 111 112 113 114 115 116 117 118 119 120 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. PARAMETER DESCRIPTION txt text to print to terminal. TYPE: str Source code in torrentfile\\interactive.py 96 97 98 99 100 101 102 103 104 105 def showtext ( txt ): \"\"\" Print contents of txt to screen. Parameters ---------- txt : str text to print to terminal. \"\"\" sys . stdout . write ( txt )","title":"Interactive"},{"location":"Source/interactive/#torrentfile.interactive","text":"Module contains the procedures used for Interactive Mode.","title":"interactive"},{"location":"Source/interactive/#torrentfile.interactive.InteractiveCreator","text":"Class namespace for interactive program options. Initialize interactive meta file creator dialog. Source code in torrentfile\\interactive.py 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 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 ()","title":"InteractiveCreator"},{"location":"Source/interactive/#torrentfile.interactive.InteractiveCreator.get_props","text":"Gather details for torrentfile from user. Source code in torrentfile\\interactive.py 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 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 ()","title":"get_props()"},{"location":"Source/interactive/#torrentfile.interactive.InteractiveEditor","text":"Interactive dialog class for torrent editing. Initialize the Interactive torrent editor guide. PARAMETER DESCRIPTION metafile user input string identifying the path to a torrent meta file. TYPE: str Source code in torrentfile\\interactive.py 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 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 ), }","title":"InteractiveEditor"},{"location":"Source/interactive/#torrentfile.interactive.InteractiveEditor.edit_props","text":"Loop continuosly for edits until user signals DONE. Source code in torrentfile\\interactive.py 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 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 )","title":"edit_props()"},{"location":"Source/interactive/#torrentfile.interactive.InteractiveEditor.sanatize_response","text":"Convert the input data into a form recognizable by the program. PARAMETER DESCRIPTION key name of the property and attribute being eddited. TYPE: str response User input value the property is being edited to. TYPE: str Source code in torrentfile\\interactive.py 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 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","title":"sanatize_response()"},{"location":"Source/interactive/#torrentfile.interactive.InteractiveEditor.show_current","text":"Display the current met file information to screen. Source code in torrentfile\\interactive.py 217 218 219 220 221 222 223 224 225 226 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 )","title":"show_current()"},{"location":"Source/interactive/#torrentfile.interactive.create_torrent","text":"Create new torrent file interactively. Source code in torrentfile\\interactive.py 163 164 165 166 167 168 169 170 171 172 173 174 175 176 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","title":"create_torrent()"},{"location":"Source/interactive/#torrentfile.interactive.edit_action","text":"Edit the editable values of the torrent meta file. Source code in torrentfile\\interactive.py 179 180 181 182 183 184 185 186 187 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 ()","title":"edit_action()"},{"location":"Source/interactive/#torrentfile.interactive.get_input","text":"Determine appropriate input function to call. PARAMETER DESCRIPTION *args Arbitrary number of args to pass to next function TYPE: tuple DEFAULT: () RETURNS DESCRIPTION str The results of the function call. Source code in torrentfile\\interactive.py 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 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 )","title":"get_input()"},{"location":"Source/interactive/#torrentfile.interactive.recheck_torrent","text":"Check torrent download completed percentage. Source code in torrentfile\\interactive.py 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 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","title":"recheck_torrent()"},{"location":"Source/interactive/#torrentfile.interactive.select_action","text":"Operate TorrentFile program interactively through terminal. Source code in torrentfile\\interactive.py 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 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","title":"select_action()"},{"location":"Source/interactive/#torrentfile.interactive.showcenter","text":"Print text to screen in the center position of the terminal. PARAMETER DESCRIPTION txt the preformated message to send to stdout. TYPE: str Source code in torrentfile\\interactive.py 108 109 110 111 112 113 114 115 116 117 118 119 120 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 )","title":"showcenter()"},{"location":"Source/interactive/#torrentfile.interactive.showtext","text":"Print contents of txt to screen. PARAMETER DESCRIPTION txt text to print to terminal. TYPE: str Source code in torrentfile\\interactive.py 96 97 98 99 100 101 102 103 104 105 def showtext ( txt ): \"\"\" Print contents of txt to screen. Parameters ---------- txt : str text to print to terminal. \"\"\" sys . stdout . write ( txt )","title":"showtext()"},{"location":"Source/mixins/","text":"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. cb ( * args , ** kwargs ) classmethod # Do nothing. Source code in torrentfile\\mixins.py 38 39 40 @classmethod def cb ( cls , * args , ** kwargs ): \"\"\"Do nothing.\"\"\" set_callback ( func ) classmethod # Assign a callback to the Hashing class. PARAMETER DESCRIPTION func the callback function TYPE: Callable Source code in torrentfile\\mixins.py 42 43 44 45 46 47 48 49 50 51 52 @classmethod def set_callback ( cls , func ): \"\"\" Assign a callback to the Hashing class. Parameters ---------- func : Callable 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. is_active () -> bool # Test to see if there is an active progress bar for object. RETURNS DESCRIPTION bool True if there is, otherwise False. TYPE: bool Source code in torrentfile\\mixins.py 200 201 202 203 204 205 206 207 208 209 210 211 def is_active ( self ) -> bool : \"\"\" 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 () # 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 188 189 190 191 192 193 194 195 196 197 198 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 ( total : int , path : str , length : int = 50 , unit : str = None ) # Generate a new progress bar for the given file path. PARAMETER DESCRIPTION total the total amount of units accumulating towards. TYPE: int path path to file being hashed. TYPE: str length the number of characters of the actual progress bar. TYPE: int DEFAULT: 50 unit the text representation of the value being measured. TYPE: str DEFAULT: None Source code in torrentfile\\mixins.py 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 def prog_start ( self , total : int , path : str , length : int = 50 , unit : str = 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 = path width = shutil . get_terminal_size () . columns if len ( str ( title )) >= width // 2 : parts = list ( Path ( title ) . parts ) while ( len ( \"//\" . join ( parts )) > ( width // 2 )) and ( len ( parts ) > 0 ): del parts [ 0 ] if parts : title = os . path . join ( * parts ) else : title = os . path . basename ( path ) # pragma: nocover length = min ( length , width // 2 ) start = width - int ( length * 1.5 ) self . prog = ProgressBar ( total , title , length , unit , start ) prog_update ( val : int ) # Update progress bar. Using the value provided, increment the progress bar by that value. PARAMETER DESCRIPTION val the number of bytes count the progress bar should increase. TYPE: int Source code in torrentfile\\mixins.py 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 def prog_update ( self , val : int ): \"\"\" Update progress bar. Using the value provided, increment the progress bar by that value. Parameters ---------- val : int the number of bytes count the progress bar should increase. \"\"\" if self . is_active (): self . prog . state += val pbar = self . prog . get_progress () output = f \" { self . prog . prefix }{ pbar }{ self . prog . suffix } \\r \" sys . stdout . write ( output ) sys . stdout . flush () ProgressBar ( total : int , title : str , length : int , unit : str , start : int ) # Holds the state and details of the terminal progress bars. PARAMETER DESCRIPTION total the total amount to be accumulated. TYPE: int title the subject of the progress tracker TYPE: str length the width of the progress bar TYPE: int unit the text representation incremented TYPE: str start column where the progress bar should be drawn TYPE: int Construct the progress bar object and store state of it\u2019s properties. Source code in torrentfile\\mixins.py 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 def __init__ ( self , total : int , title : str , length : int , unit : str , start : int ): \"\"\" 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 self . show_total = total if not unit : self . unit = \"\" # pragma: nocover elif unit == \"bytes\" : if self . total > 1_000_000_000 : self . show_total = math . floor ( self . total / ( 2 ** 30 )) self . unit = \"GiB\" elif self . total > 1_000_000 : self . show_total = math . floor ( self . total / 1048576 ) self . unit = \"MiB\" elif self . total > 10000 : self . show_total = math . floor ( self . total / 1024 ) self . unit = \"KiB\" self . suffix = f \"/ { self . show_total } { self . unit } \" if len ( title ) > start : title = title [: start - 1 ] padding = ( start - len ( title )) * \" \" self . prefix = \"\" . join ([ title , padding ]) get_progress () -> str # Return the size of the filled portion of the progress bar. RETURNS DESCRIPTION str the progress bar characters TYPE: str Source code in torrentfile\\mixins.py 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 def get_progress ( self ) -> str : \"\"\" 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 ) empt = self . length - fill if self . unit == \"GiB\" : state = math . floor ( self . state / ( 2 ** 30 )) elif 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 * empt , \"| \" , str ( state )] return \"\" . join ( progbar ) waiting ( msg : str , flag : list , timeout : int = 20 ) # Show loading message while thread completes processing. PARAMETER DESCRIPTION msg Message string printed before the progress bar TYPE: str flag Once flag is filled exit loop TYPE: list timeout max amount of time to run the function. TYPE: int DEFAULT: 20 Source code in torrentfile\\mixins.py 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 def waiting ( msg : str , flag : list , timeout : int = 20 ): \"\"\" 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 : str ): \"\"\" Print parameter message to the console. Parameters ---------- text : str output message \"\"\" sys . stdout . write ( text ) sys . stdout . flush () output ( \" \\n \" ) time . sleep ( 0.16 ) while len ( flag ) == 0 : 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 \" )","title":"Mixins"},{"location":"Source/mixins/#torrentfile.mixins","text":"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.","title":"mixins"},{"location":"Source/mixins/#torrentfile.mixins.CbMixin","text":"Mixin class to set a callback during hashing procedure.","title":"CbMixin"},{"location":"Source/mixins/#torrentfile.mixins.CbMixin.cb","text":"Do nothing. Source code in torrentfile\\mixins.py 38 39 40 @classmethod def cb ( cls , * args , ** kwargs ): \"\"\"Do nothing.\"\"\"","title":"cb()"},{"location":"Source/mixins/#torrentfile.mixins.CbMixin.set_callback","text":"Assign a callback to the Hashing class. PARAMETER DESCRIPTION func the callback function TYPE: Callable Source code in torrentfile\\mixins.py 42 43 44 45 46 47 48 49 50 51 52 @classmethod def set_callback ( cls , func ): \"\"\" Assign a callback to the Hashing class. Parameters ---------- func : Callable the callback function \"\"\" cls . cb = func # pragma: nocover","title":"set_callback()"},{"location":"Source/mixins/#torrentfile.mixins.ProgMixin","text":"Progress bar mixin class. Displays progress of hashing individual files, usefull when hashing really big files.","title":"ProgMixin"},{"location":"Source/mixins/#torrentfile.mixins.ProgMixin.is_active","text":"Test to see if there is an active progress bar for object. RETURNS DESCRIPTION bool True if there is, otherwise False. TYPE: bool Source code in torrentfile\\mixins.py 200 201 202 203 204 205 206 207 208 209 210 211 def is_active ( self ) -> bool : \"\"\" 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","title":"is_active()"},{"location":"Source/mixins/#torrentfile.mixins.ProgMixin.prog_close","text":"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 188 189 190 191 192 193 194 195 196 197 198 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","title":"prog_close()"},{"location":"Source/mixins/#torrentfile.mixins.ProgMixin.prog_start","text":"Generate a new progress bar for the given file path. PARAMETER DESCRIPTION total the total amount of units accumulating towards. TYPE: int path path to file being hashed. TYPE: str length the number of characters of the actual progress bar. TYPE: int DEFAULT: 50 unit the text representation of the value being measured. TYPE: str DEFAULT: None Source code in torrentfile\\mixins.py 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 def prog_start ( self , total : int , path : str , length : int = 50 , unit : str = 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 = path width = shutil . get_terminal_size () . columns if len ( str ( title )) >= width // 2 : parts = list ( Path ( title ) . parts ) while ( len ( \"//\" . join ( parts )) > ( width // 2 )) and ( len ( parts ) > 0 ): del parts [ 0 ] if parts : title = os . path . join ( * parts ) else : title = os . path . basename ( path ) # pragma: nocover length = min ( length , width // 2 ) start = width - int ( length * 1.5 ) self . prog = ProgressBar ( total , title , length , unit , start )","title":"prog_start()"},{"location":"Source/mixins/#torrentfile.mixins.ProgMixin.prog_update","text":"Update progress bar. Using the value provided, increment the progress bar by that value. PARAMETER DESCRIPTION val the number of bytes count the progress bar should increase. TYPE: int Source code in torrentfile\\mixins.py 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 def prog_update ( self , val : int ): \"\"\" Update progress bar. Using the value provided, increment the progress bar by that value. Parameters ---------- val : int the number of bytes count the progress bar should increase. \"\"\" if self . is_active (): self . prog . state += val pbar = self . prog . get_progress () output = f \" { self . prog . prefix }{ pbar }{ self . prog . suffix } \\r \" sys . stdout . write ( output ) sys . stdout . flush ()","title":"prog_update()"},{"location":"Source/mixins/#torrentfile.mixins.ProgressBar","text":"Holds the state and details of the terminal progress bars. PARAMETER DESCRIPTION total the total amount to be accumulated. TYPE: int title the subject of the progress tracker TYPE: str length the width of the progress bar TYPE: int unit the text representation incremented TYPE: str start column where the progress bar should be drawn TYPE: int Construct the progress bar object and store state of it\u2019s properties. Source code in torrentfile\\mixins.py 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 def __init__ ( self , total : int , title : str , length : int , unit : str , start : int ): \"\"\" 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 self . show_total = total if not unit : self . unit = \"\" # pragma: nocover elif unit == \"bytes\" : if self . total > 1_000_000_000 : self . show_total = math . floor ( self . total / ( 2 ** 30 )) self . unit = \"GiB\" elif self . total > 1_000_000 : self . show_total = math . floor ( self . total / 1048576 ) self . unit = \"MiB\" elif self . total > 10000 : self . show_total = math . floor ( self . total / 1024 ) self . unit = \"KiB\" self . suffix = f \"/ { self . show_total } { self . unit } \" if len ( title ) > start : title = title [: start - 1 ] padding = ( start - len ( title )) * \" \" self . prefix = \"\" . join ([ title , padding ])","title":"ProgressBar"},{"location":"Source/mixins/#torrentfile.mixins.ProgressBar.get_progress","text":"Return the size of the filled portion of the progress bar. RETURNS DESCRIPTION str the progress bar characters TYPE: str Source code in torrentfile\\mixins.py 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 def get_progress ( self ) -> str : \"\"\" 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 ) empt = self . length - fill if self . unit == \"GiB\" : state = math . floor ( self . state / ( 2 ** 30 )) elif 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 * empt , \"| \" , str ( state )] return \"\" . join ( progbar )","title":"get_progress()"},{"location":"Source/mixins/#torrentfile.mixins.waiting","text":"Show loading message while thread completes processing. PARAMETER DESCRIPTION msg Message string printed before the progress bar TYPE: str flag Once flag is filled exit loop TYPE: list timeout max amount of time to run the function. TYPE: int DEFAULT: 20 Source code in torrentfile\\mixins.py 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 def waiting ( msg : str , flag : list , timeout : int = 20 ): \"\"\" 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 : str ): \"\"\" Print parameter message to the console. Parameters ---------- text : str output message \"\"\" sys . stdout . write ( text ) sys . stdout . flush () output ( \" \\n \" ) time . sleep ( 0.16 ) while len ( flag ) == 0 : 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 \" )","title":"waiting()"},{"location":"Source/rebuild/","text":"rebuild # Clases and functions for the rebuild or reassemble subcommand. Re-assemble a torrent into the propper directory structure as indicated by a torrent meta file, and validate the contents of each file allong the way. Displays a progress bar for each torrent. Assembler ( metafiles : list , contents : list , dest : str ) # Bases: CbMixin Does most of the work in attempting the structure of torrentfiles. Requires three paths as arguments. - torrent metafile or directory containing multiple meta files - directory containing the contents of meta file - directory where torrents will be re-assembled Construct the assembler object. Takes two paths as parameters, - file or directory containing 1 or more torrent meta files - path to where the contents are belived to be located. PARAMETER DESCRIPTION metafiles path to torrent metafile or directory containing torrent metafiles. TYPE: str contents path to content or directory containing content that belongs to torrentfile. TYPE: str dest path to the directory where rebuild will take place. TYPE: str Source code in torrentfile\\rebuild.py 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 def __init__ ( self , metafiles : list , contents : list , dest : str ): \"\"\" Construct the assembler object. Takes two paths as parameters, - file or directory containing 1 or more torrent meta files - path to where the contents are belived to be located. Parameters ---------- metafiles : str path to torrent metafile or directory containing torrent metafiles. contents : str path to content or directory containing content that belongs to torrentfile. dest: str path to the directory where rebuild will take place. \"\"\" self . counter = 0 self . _lastlog = None self . contents = contents Metadata . set_callback ( self . _callback ) self . dest = dest self . meta_paths = metafiles self . metafiles = self . _get_metafiles () filenames = set () for meta in self . metafiles : filenames |= meta . filenames self . filemap = _index_contents ( self . contents , filenames ) assemble_torrents () # Assemble collection of torrent files into original structure. RETURNS DESCRIPTION int number of files copied Source code in torrentfile\\rebuild.py 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 def assemble_torrents ( self ): \"\"\" Assemble collection of torrent files into original structure. Returns ------- int number of files copied \"\"\" for metafile in self . metafiles : logger . info ( \"# %s Searching contents for %s \" , self . counter , metafile . name ) self . rebuild ( metafile ) return self . counter rebuild ( metafile : Metadata ) -> None # Build the torrent file structure from contents of directory. Traverse contents dir and compare discovered files with files listed in torrent metadata and copy the matches to the destination directory respecting folder structures along the way. Source code in torrentfile\\rebuild.py 504 505 506 507 508 509 510 511 512 513 def rebuild ( self , metafile : Metadata ) -> None : \"\"\" Build the torrent file structure from contents of directory. Traverse contents dir and compare discovered files with files listed in torrent metadata and copy the matches to the destination directory respecting folder structures along the way. \"\"\" metafile . rebuild ( self . filemap , self . dest ) Metadata ( path : str ) # Bases: CbMixin , ProgMixin Class containing the metadata contents of a torrent file. Construct metadata object for torrent info. PARAMETER DESCRIPTION path path to the .torrent file. TYPE: str Source code in torrentfile\\rebuild.py 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 def __init__ ( self , path : str ): \"\"\" Construct metadata object for torrent info. Parameters ---------- path : str path to the .torrent file. \"\"\" self . path = os . path . abspath ( path ) self . name = None self . piece_length = 1 self . meta_version = 1 self . pieces = b \"\" self . piece_nodes = [] self . length = 0 self . files = [] self . filenames = set () self . extract () if self . meta_version == 2 : self . num_pieces = len ( self . filenames ) else : self . num_pieces = math . ceil ( len ( self . pieces ) / SHA1 ) extract () # Decode and extract information for the .torrent file. Source code in torrentfile\\rebuild.py 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 def extract ( self ): \"\"\" Decode and extract information for the .torrent file. \"\"\" meta = pyben . load ( self . path ) info = meta [ \"info\" ] self . piece_length = info [ \"piece length\" ] self . name = info [ \"name\" ] self . meta_version = info . get ( \"meta version\" , 1 ) self . pieces = info . get ( \"pieces\" , bytes ()) if self . meta_version == 2 : self . _parse_tree ( info [ \"file tree\" ], [ self . name ]) elif \"length\" in info : self . length += info [ \"length\" ] self . is_file = True self . filenames . add ( info [ \"name\" ]) self . files . append ( { \"path\" : Path ( self . name ) . parent , \"filename\" : self . name , \"full\" : self . name , \"length\" : self . length , } ) elif \"files\" in info : for f in info [ \"files\" ]: path = f [ \"path\" ] full = os . path . join ( self . name , * path ) self . files . append ( { \"path\" : Path ( full ) . parent , \"filename\" : path [ - 1 ], \"full\" : full , \"length\" : f [ \"length\" ], } ) self . length += f [ \"length\" ] self . filenames . add ( path [ - 1 ]) rebuild ( filemap : dict , dest : str ) # Rebuild torrent file contents from filemap at dest. Searches through the contents of the meta file and compares filenames with those in the filemap dict, and if found checks their contents, and copies them to the destination path. PARAMETER DESCRIPTION filemap filesystem information TYPE: dict dest destiantion path TYPE: str Source code in torrentfile\\rebuild.py 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 def rebuild ( self , filemap : dict , dest : str ): \"\"\" Rebuild torrent file contents from filemap at dest. Searches through the contents of the meta file and compares filenames with those in the filemap dict, and if found checks their contents, and copies them to the destination path. Parameters ---------- filemap : dict filesystem information dest : str destiantion path \"\"\" self . _prog = None if self . meta_version == 2 : self . _match_v2 ( filemap , dest ) else : self . _match_v1 ( filemap , dest ) if self . _prog is not None : self . prog_close () PathNode ( start : int = None , stop : int = None , full : str = None , filename : str = None , path : str = None , length : int = None ) # Base class representing information regarding a file included in torrent. Hold file information that contributes to the contents of torrent. PARAMETER DESCRIPTION start where the piece starts, by default None TYPE: int , optional DEFAULT: None stop where the piece ends, by default None TYPE: int , optional DEFAULT: None full full path, by default None TYPE: str , optional DEFAULT: None filename filename, by default None TYPE: str , optional DEFAULT: None path parent path, by default None TYPE: str , optional DEFAULT: None length size, by default None TYPE: int , optional DEFAULT: None Source code in torrentfile\\rebuild.py 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 def __init__ ( self , start : int = None , stop : int = None , full : str = None , filename : str = None , path : str = None , length : int = None , ): \"\"\" Hold file information that contributes to the contents of torrent. Parameters ---------- start : int, optional where the piece starts, by default None stop : int, optional where the piece ends, by default None full : str, optional full path, by default None filename : str, optional filename, by default None path : str, optional parent path, by default None length : int, optional size, by default None \"\"\" self . path = path self . start = start self . stop = stop self . length = length self . filename = filename self . full = full __len__ () -> int # Return size of the file. RETURNS DESCRIPTION int total size Source code in torrentfile\\rebuild.py 104 105 106 107 108 109 110 111 112 113 def __len__ ( self ) -> int : \"\"\" Return size of the file. Returns ------- int total size \"\"\" return self . length get_part ( path : str ) -> bytes # Extract the part of the file needed to complete the hash. PARAMETER DESCRIPTION path filesystem path location of file. TYPE: str RETURNS DESCRIPTION bytes part of the file\u2019s contents Source code in torrentfile\\rebuild.py 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 def get_part ( self , path : str ) -> bytes : \"\"\" Extract the part of the file needed to complete the hash. Parameters ---------- path : str filesystem path location of file. Returns ------- bytes part of the file's contents \"\"\" with open ( path , \"rb\" ) as fd : if self . start : fd . read ( self . start ) if self . stop != - 1 : partial = fd . read ( self . stop - self . start ) else : partial = fd . read () return partial PieceNode ( piece : bytes ) # Base class representing a single SHA1 hash block of data from a torrent. Store information about an individual SHA1 hash for a torrent file. extended_summary PARAMETER DESCRIPTION piece SHA1 hash bytes TYPE: bytes Source code in torrentfile\\rebuild.py 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 def __init__ ( self , piece : bytes ): \"\"\" Store information about an individual SHA1 hash for a torrent file. _extended_summary_ Parameters ---------- piece : bytes SHA1 hash bytes \"\"\" self . piece = piece self . paths = [] self . result = None self . dest = None append ( pathnode : PathNode ) # Append the path argument to the paths list attribute. PARAMETER DESCRIPTION pathnode the pathnode TYPE: PathNode Source code in torrentfile\\rebuild.py 137 138 139 140 141 142 143 144 145 146 def append ( self , pathnode : PathNode ): \"\"\" Append the path argument to the paths list attribute. Parameters ---------- pathnode : PathNode the pathnode \"\"\" self . paths . append ( pathnode ) find_matches ( filemap : dict , dest : str ) -> bool # Find the matching files for each path in the node. PARAMETER DESCRIPTION filemap filename and details TYPE: dict dest target destination path TYPE: str RETURNS DESCRIPTION bool success status Source code in torrentfile\\rebuild.py 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 def find_matches ( self , filemap : dict , dest : str ) -> bool : \"\"\" Find the matching files for each path in the node. Parameters ---------- filemap : dict filename and details dest : str target destination path Returns ------- bool success status \"\"\" self . dest = dest self . result = self . _find_matches ( filemap , self . paths [:], bytes ()) return self . result","title":"Rebuild"},{"location":"Source/rebuild/#torrentfile.rebuild","text":"Clases and functions for the rebuild or reassemble subcommand. Re-assemble a torrent into the propper directory structure as indicated by a torrent meta file, and validate the contents of each file allong the way. Displays a progress bar for each torrent.","title":"rebuild"},{"location":"Source/rebuild/#torrentfile.rebuild.Assembler","text":"Bases: CbMixin Does most of the work in attempting the structure of torrentfiles. Requires three paths as arguments. - torrent metafile or directory containing multiple meta files - directory containing the contents of meta file - directory where torrents will be re-assembled Construct the assembler object. Takes two paths as parameters, - file or directory containing 1 or more torrent meta files - path to where the contents are belived to be located. PARAMETER DESCRIPTION metafiles path to torrent metafile or directory containing torrent metafiles. TYPE: str contents path to content or directory containing content that belongs to torrentfile. TYPE: str dest path to the directory where rebuild will take place. TYPE: str Source code in torrentfile\\rebuild.py 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 def __init__ ( self , metafiles : list , contents : list , dest : str ): \"\"\" Construct the assembler object. Takes two paths as parameters, - file or directory containing 1 or more torrent meta files - path to where the contents are belived to be located. Parameters ---------- metafiles : str path to torrent metafile or directory containing torrent metafiles. contents : str path to content or directory containing content that belongs to torrentfile. dest: str path to the directory where rebuild will take place. \"\"\" self . counter = 0 self . _lastlog = None self . contents = contents Metadata . set_callback ( self . _callback ) self . dest = dest self . meta_paths = metafiles self . metafiles = self . _get_metafiles () filenames = set () for meta in self . metafiles : filenames |= meta . filenames self . filemap = _index_contents ( self . contents , filenames )","title":"Assembler"},{"location":"Source/rebuild/#torrentfile.rebuild.Assembler.assemble_torrents","text":"Assemble collection of torrent files into original structure. RETURNS DESCRIPTION int number of files copied Source code in torrentfile\\rebuild.py 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 def assemble_torrents ( self ): \"\"\" Assemble collection of torrent files into original structure. Returns ------- int number of files copied \"\"\" for metafile in self . metafiles : logger . info ( \"# %s Searching contents for %s \" , self . counter , metafile . name ) self . rebuild ( metafile ) return self . counter","title":"assemble_torrents()"},{"location":"Source/rebuild/#torrentfile.rebuild.Assembler.rebuild","text":"Build the torrent file structure from contents of directory. Traverse contents dir and compare discovered files with files listed in torrent metadata and copy the matches to the destination directory respecting folder structures along the way. Source code in torrentfile\\rebuild.py 504 505 506 507 508 509 510 511 512 513 def rebuild ( self , metafile : Metadata ) -> None : \"\"\" Build the torrent file structure from contents of directory. Traverse contents dir and compare discovered files with files listed in torrent metadata and copy the matches to the destination directory respecting folder structures along the way. \"\"\" metafile . rebuild ( self . filemap , self . dest )","title":"rebuild()"},{"location":"Source/rebuild/#torrentfile.rebuild.Metadata","text":"Bases: CbMixin , ProgMixin Class containing the metadata contents of a torrent file. Construct metadata object for torrent info. PARAMETER DESCRIPTION path path to the .torrent file. TYPE: str Source code in torrentfile\\rebuild.py 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 def __init__ ( self , path : str ): \"\"\" Construct metadata object for torrent info. Parameters ---------- path : str path to the .torrent file. \"\"\" self . path = os . path . abspath ( path ) self . name = None self . piece_length = 1 self . meta_version = 1 self . pieces = b \"\" self . piece_nodes = [] self . length = 0 self . files = [] self . filenames = set () self . extract () if self . meta_version == 2 : self . num_pieces = len ( self . filenames ) else : self . num_pieces = math . ceil ( len ( self . pieces ) / SHA1 )","title":"Metadata"},{"location":"Source/rebuild/#torrentfile.rebuild.Metadata.extract","text":"Decode and extract information for the .torrent file. Source code in torrentfile\\rebuild.py 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 def extract ( self ): \"\"\" Decode and extract information for the .torrent file. \"\"\" meta = pyben . load ( self . path ) info = meta [ \"info\" ] self . piece_length = info [ \"piece length\" ] self . name = info [ \"name\" ] self . meta_version = info . get ( \"meta version\" , 1 ) self . pieces = info . get ( \"pieces\" , bytes ()) if self . meta_version == 2 : self . _parse_tree ( info [ \"file tree\" ], [ self . name ]) elif \"length\" in info : self . length += info [ \"length\" ] self . is_file = True self . filenames . add ( info [ \"name\" ]) self . files . append ( { \"path\" : Path ( self . name ) . parent , \"filename\" : self . name , \"full\" : self . name , \"length\" : self . length , } ) elif \"files\" in info : for f in info [ \"files\" ]: path = f [ \"path\" ] full = os . path . join ( self . name , * path ) self . files . append ( { \"path\" : Path ( full ) . parent , \"filename\" : path [ - 1 ], \"full\" : full , \"length\" : f [ \"length\" ], } ) self . length += f [ \"length\" ] self . filenames . add ( path [ - 1 ])","title":"extract()"},{"location":"Source/rebuild/#torrentfile.rebuild.Metadata.rebuild","text":"Rebuild torrent file contents from filemap at dest. Searches through the contents of the meta file and compares filenames with those in the filemap dict, and if found checks their contents, and copies them to the destination path. PARAMETER DESCRIPTION filemap filesystem information TYPE: dict dest destiantion path TYPE: str Source code in torrentfile\\rebuild.py 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 def rebuild ( self , filemap : dict , dest : str ): \"\"\" Rebuild torrent file contents from filemap at dest. Searches through the contents of the meta file and compares filenames with those in the filemap dict, and if found checks their contents, and copies them to the destination path. Parameters ---------- filemap : dict filesystem information dest : str destiantion path \"\"\" self . _prog = None if self . meta_version == 2 : self . _match_v2 ( filemap , dest ) else : self . _match_v1 ( filemap , dest ) if self . _prog is not None : self . prog_close ()","title":"rebuild()"},{"location":"Source/rebuild/#torrentfile.rebuild.PathNode","text":"Base class representing information regarding a file included in torrent. Hold file information that contributes to the contents of torrent. PARAMETER DESCRIPTION start where the piece starts, by default None TYPE: int , optional DEFAULT: None stop where the piece ends, by default None TYPE: int , optional DEFAULT: None full full path, by default None TYPE: str , optional DEFAULT: None filename filename, by default None TYPE: str , optional DEFAULT: None path parent path, by default None TYPE: str , optional DEFAULT: None length size, by default None TYPE: int , optional DEFAULT: None Source code in torrentfile\\rebuild.py 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 def __init__ ( self , start : int = None , stop : int = None , full : str = None , filename : str = None , path : str = None , length : int = None , ): \"\"\" Hold file information that contributes to the contents of torrent. Parameters ---------- start : int, optional where the piece starts, by default None stop : int, optional where the piece ends, by default None full : str, optional full path, by default None filename : str, optional filename, by default None path : str, optional parent path, by default None length : int, optional size, by default None \"\"\" self . path = path self . start = start self . stop = stop self . length = length self . filename = filename self . full = full","title":"PathNode"},{"location":"Source/rebuild/#torrentfile.rebuild.PathNode.__len__","text":"Return size of the file. RETURNS DESCRIPTION int total size Source code in torrentfile\\rebuild.py 104 105 106 107 108 109 110 111 112 113 def __len__ ( self ) -> int : \"\"\" Return size of the file. Returns ------- int total size \"\"\" return self . length","title":"__len__()"},{"location":"Source/rebuild/#torrentfile.rebuild.PathNode.get_part","text":"Extract the part of the file needed to complete the hash. PARAMETER DESCRIPTION path filesystem path location of file. TYPE: str RETURNS DESCRIPTION bytes part of the file\u2019s contents Source code in torrentfile\\rebuild.py 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 def get_part ( self , path : str ) -> bytes : \"\"\" Extract the part of the file needed to complete the hash. Parameters ---------- path : str filesystem path location of file. Returns ------- bytes part of the file's contents \"\"\" with open ( path , \"rb\" ) as fd : if self . start : fd . read ( self . start ) if self . stop != - 1 : partial = fd . read ( self . stop - self . start ) else : partial = fd . read () return partial","title":"get_part()"},{"location":"Source/rebuild/#torrentfile.rebuild.PieceNode","text":"Base class representing a single SHA1 hash block of data from a torrent. Store information about an individual SHA1 hash for a torrent file. extended_summary PARAMETER DESCRIPTION piece SHA1 hash bytes TYPE: bytes Source code in torrentfile\\rebuild.py 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 def __init__ ( self , piece : bytes ): \"\"\" Store information about an individual SHA1 hash for a torrent file. _extended_summary_ Parameters ---------- piece : bytes SHA1 hash bytes \"\"\" self . piece = piece self . paths = [] self . result = None self . dest = None","title":"PieceNode"},{"location":"Source/rebuild/#torrentfile.rebuild.PieceNode.append","text":"Append the path argument to the paths list attribute. PARAMETER DESCRIPTION pathnode the pathnode TYPE: PathNode Source code in torrentfile\\rebuild.py 137 138 139 140 141 142 143 144 145 146 def append ( self , pathnode : PathNode ): \"\"\" Append the path argument to the paths list attribute. Parameters ---------- pathnode : PathNode the pathnode \"\"\" self . paths . append ( pathnode )","title":"append()"},{"location":"Source/rebuild/#torrentfile.rebuild.PieceNode.find_matches","text":"Find the matching files for each path in the node. PARAMETER DESCRIPTION filemap filename and details TYPE: dict dest target destination path TYPE: str RETURNS DESCRIPTION bool success status Source code in torrentfile\\rebuild.py 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 def find_matches ( self , filemap : dict , dest : str ) -> bool : \"\"\" Find the matching files for each path in the node. Parameters ---------- filemap : dict filename and details dest : str target destination path Returns ------- bool success status \"\"\" self . dest = dest self . result = self . _find_matches ( filemap , self . paths [:], bytes ()) return self . result","title":"find_matches()"},{"location":"Source/recheck/","text":"recheck # Module container Checker Class. The CheckerClass takes a torrentfile and tha path to it\u2019s 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 ( metafile : str , path : str ) # Check a given file or directory to see if it matches a torrentfile. Public constructor for Checker class instance. PARAMETER DESCRIPTION metafile Path to \u201c.torrent\u201d file. TYPE: str path Path where the content is located in filesystem. TYPE: str Example # 1 2 3 4 5 >> 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) Validate data against hashes contained in .torrent file. PARAMETER DESCRIPTION metafile path to .torrent file TYPE: str path path to content or contents parent directory. TYPE: str Source code in torrentfile\\recheck.py 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 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 if os . path . isdir ( metafile ): raise ArgumentError ( \"The must be a .torrent file. Not a directory\" ) self . last_log = None self . log_msg ( \"Checking: %s , %s \" , metafile , path ) self . metafile = metafile self . total = 0 self . paths = [] self . fileinfo = {} print ( \"Extracting data from torrent file...\" ) self . meta = pyben . load ( metafile ) 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 () # Gather all file paths described in the torrent file. Source code in torrentfile\\recheck.py 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 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 ( 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\u2019s name, if not the directories contents will be searched. The returned value will be the absolute path that matches the torrent\u2019s name. PARAMETER DESCRIPTION path root path to torrent content TYPE: str RETURNS DESCRIPTION str root path to content Source code in torrentfile\\recheck.py 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 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 ) iter_hashes () -> tuple # Produce results of comparing torrent contents piece by piece. YIELDS DESCRIPTION chunck hash of data found on disk TYPE: bytes piece hash of data when complete and correct TYPE: bytes path path to file being hashed TYPE: str size length of bytes hashed for piece TYPE: int Source code in torrentfile\\recheck.py 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 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 () for chunk , piece , path , size in checker ( self ): 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 , ) self . _result = ( matched / consumed ) * 100 if consumed > 0 else 0 log_msg ( * args , level : int = logging . INFO ) # Log message msg to logger and send msg to callback hook. PARAMETER DESCRIPTION *args formatting args for log message TYPE: dict DEFAULT: () level Log level for this message; default= logging.INFO TYPE: int DEFAULT: logging.INFO Source code in torrentfile\\recheck.py 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 def log_msg ( self , * args , level : int = 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 () # Check individual pieces of the torrent. RETURNS DESCRIPTION HashChecker | FeedChecker Individual piece hasher. Source code in torrentfile\\recheck.py 123 124 125 126 127 128 129 130 131 132 133 134 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. PARAMETER DESCRIPTION hook callback function for the logging feature. TYPE: function Source code in torrentfile\\recheck.py 111 112 113 114 115 116 117 118 119 120 121 @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 () # Generate result percentage and store for future calls. Source code in torrentfile\\recheck.py 136 137 138 139 140 141 142 143 144 145 146 147 148 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 ( 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\u2019s file tree. PARAMETER DESCRIPTION tree File Tree dict extracted from torrent file. TYPE: dict partials list of intermediate pathnames. TYPE: list Source code in torrentfile\\recheck.py 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 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 ( checker : Checker ) # Bases: ProgMixin Validates torrent content. Seemlesly validate torrent file contents by comparing hashes in metafile against data on disk. PARAMETER DESCRIPTION checker the checker class instance. TYPE: object Generate hashes of piece length data from filelist contents. Source code in torrentfile\\recheck.py 332 333 334 335 336 337 338 339 340 341 342 343 def __init__ ( self , checker : Checker ): \"\"\" 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 . piece_map = {} self . index = 0 self . piece_count = 0 self . it = None __iter__ () # Assign iterator and return self. Source code in torrentfile\\recheck.py 345 346 347 348 349 350 def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" self . it = self . iter_pieces () return self __next__ () # Yield back result of comparison. Source code in torrentfile\\recheck.py 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 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 ( path : str , partial : bytearray ) -> bytearray # Split file paths contents into blocks of data for hash pieces. PARAMETER DESCRIPTION path path to content. TYPE: str partial any remaining content from last file. TYPE: bytes RETURNS DESCRIPTION bytearray Hash digest for block of .torrent contents. Source code in torrentfile\\recheck.py 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 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 () # Iterate through, and hash pieces of torrent contents. YIELDS DESCRIPTION piece hash digest for block of torrent data. TYPE: bytes Source code in torrentfile\\recheck.py 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 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 ( checker : Checker ) # Bases: ProgMixin Iterate through contents of meta data and verify with file contents. PARAMETER DESCRIPTION checker the checker instance that maintains variables. TYPE: Checker Construct a HybridChecker instance. Source code in torrentfile\\recheck.py 483 484 485 486 487 488 489 490 491 492 493 def __init__ ( self , checker : Checker ): \"\"\" Construct a HybridChecker instance. \"\"\" self . checker = checker self . paths = checker . paths self . piece_length = checker . piece_length self . fileinfo = checker . fileinfo self . piece_layers = checker . meta [ \"piece layers\" ] self . current = None self . index = - 1 Padder ( length , piece_length ) # Padding class to generate padding hashes wherever needed. PARAMETER DESCRIPTION length the total size of the mock file generating padding for. piece_length the block size that each hash represents. TYPE: int Construct padding class to Mock missing or incomplete files. PARAMETER DESCRIPTION length size of the file TYPE: int piece_length the piece length for each iteration. TYPE: int Source code in torrentfile\\recheck.py 526 527 528 529 530 531 532 533 534 535 536 537 538 539 def __init__ ( self , length , piece_length ): \"\"\" Construct padding class to Mock missing or incomplete files. Parameters ---------- length : int size of the file piece_length : int the piece length for each iteration. \"\"\" self . length = length self . piece_length = piece_length self . pad = sha256 ( bytearray ( piece_length )) . digest () __iter__ () # Return self to correctly implement iterator type. Source code in torrentfile\\recheck.py 541 542 543 544 545 def __iter__ ( self ): \"\"\" Return self to correctly implement iterator type. \"\"\" return self # pragma: nocover __next__ () -> bytes # Iterate through seemingly endless sha256 hashes of zeros. RETURNS DESCRIPTION tuple returns the padding TYPE: bytes RAISES DESCRIPTION StopIteration Source code in torrentfile\\recheck.py 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 def __next__ ( self ) -> bytes : \"\"\" Iterate through seemingly endless sha256 hashes of zeros. Returns ------- tuple : returns the padding Raises ------ StopIteration \"\"\" if self . length >= self . piece_length : self . length -= self . piece_length return self . pad if self . length > 0 : pad = sha256 ( bytearray ( self . length )) . digest () self . length -= self . length return pad raise StopIteration __iter__ () # Assign iterator and return self. Source code in torrentfile\\recheck.py 495 496 497 498 499 def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" return self __next__ () # Provide the result of comparison. Source code in torrentfile\\recheck.py 501 502 503 504 505 506 507 508 509 510 511 512 def __next__ ( self ): \"\"\" Provide the result of comparison. \"\"\" if self . current is None : self . next_file () try : return self . process_current () except StopIteration as itererr : if self . next_file (): return self . process_current () raise StopIteration from itererr advance () -> tuple # Increment the number of pieces processed for the current file. RETURNS DESCRIPTION tuple the piece and size Source code in torrentfile\\recheck.py 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 def advance ( self ) -> tuple : \"\"\" Increment the number of pieces processed for the current file. Returns ------- tuple the piece and size \"\"\" start = self . count * SHA256 end = start + SHA256 piece = self . pieces [ start : end ] self . count += 1 if self . length >= self . piece_length : self . length -= self . piece_length size = self . piece_length else : size = self . length self . length -= self . length return piece , size next_file () -> bool # Remove all references to processed files and prepare for the next. RETURNS DESCRIPTION bool if there is a next file found Source code in torrentfile\\recheck.py 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 def next_file ( self ) -> bool : \"\"\" Remove all references to processed files and prepare for the next. Returns ------- bool if there is a next file found \"\"\" self . index += 1 self . prog_close () if self . current is None or self . index < len ( self . paths ): self . current = self . paths [ self . index ] self . length = self . fileinfo [ self . index ][ \"length\" ] self . root_hash = self . fileinfo [ self . index ][ \"pieces root\" ] if self . length > self . piece_length : self . pieces = self . piece_layers [ self . root_hash ] else : self . pieces = self . root_hash path = self . paths [ self . index ] self . prog_start ( self . length , path , unit = \"bytes\" ) self . count = 0 if os . path . exists ( self . current ): self . hasher = FileHasher ( path , self . piece_length , progress = 0 ) else : self . hasher = self . Padder ( self . length , self . piece_length ) return True if self . index >= len ( self . paths ): del self . current del self . length del self . root_hash del self . pieces return False process_current () -> tuple # Gather necessary information to compare to metafile details. RETURNS DESCRIPTION tuple a tuple containing the layer, piece, current path and size RAISES DESCRIPTION StopIteration Source code in torrentfile\\recheck.py 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 def process_current ( self ) -> tuple : \"\"\" Gather necessary information to compare to metafile details. Returns ------- tuple a tuple containing the layer, piece, current path and size Raises ------ StopIteration \"\"\" try : layer = next ( self . hasher ) piece , size = self . advance () self . prog_update ( size ) return layer , piece , self . current , size except StopIteration as err : if self . length > 0 and self . count * SHA256 < len ( self . pieces ): self . hasher = self . Padder ( self . length , self . piece_length ) piece , size = self . advance () layer = next ( self . hasher ) self . prog_update ( 0 ) return layer , piece , self . current , size raise StopIteration from err","title":"Recheck"},{"location":"Source/recheck/#torrentfile.recheck","text":"Module container Checker Class. The CheckerClass takes a torrentfile and tha path to it\u2019s 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.","title":"recheck"},{"location":"Source/recheck/#torrentfile.recheck.Checker","text":"Check a given file or directory to see if it matches a torrentfile. Public constructor for Checker class instance. PARAMETER DESCRIPTION metafile Path to \u201c.torrent\u201d file. TYPE: str path Path where the content is located in filesystem. TYPE: str","title":"Checker"},{"location":"Source/recheck/#torrentfile.recheck.Checker--example","text":"1 2 3 4 5 >> 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) Validate data against hashes contained in .torrent file. PARAMETER DESCRIPTION metafile path to .torrent file TYPE: str path path to content or contents parent directory. TYPE: str Source code in torrentfile\\recheck.py 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 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 if os . path . isdir ( metafile ): raise ArgumentError ( \"The must be a .torrent file. Not a directory\" ) self . last_log = None self . log_msg ( \"Checking: %s , %s \" , metafile , path ) self . metafile = metafile self . total = 0 self . paths = [] self . fileinfo = {} print ( \"Extracting data from torrent file...\" ) self . meta = pyben . load ( metafile ) 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 ()","title":"Example"},{"location":"Source/recheck/#torrentfile.recheck.Checker.check_paths","text":"Gather all file paths described in the torrent file. Source code in torrentfile\\recheck.py 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 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\" ], [])","title":"check_paths()"},{"location":"Source/recheck/#torrentfile.recheck.Checker.find_root","text":"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\u2019s name, if not the directories contents will be searched. The returned value will be the absolute path that matches the torrent\u2019s name. PARAMETER DESCRIPTION path root path to torrent content TYPE: str RETURNS DESCRIPTION str root path to content Source code in torrentfile\\recheck.py 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 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 )","title":"find_root()"},{"location":"Source/recheck/#torrentfile.recheck.Checker.iter_hashes","text":"Produce results of comparing torrent contents piece by piece. YIELDS DESCRIPTION chunck hash of data found on disk TYPE: bytes piece hash of data when complete and correct TYPE: bytes path path to file being hashed TYPE: str size length of bytes hashed for piece TYPE: int Source code in torrentfile\\recheck.py 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 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 () for chunk , piece , path , size in checker ( self ): 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 , ) self . _result = ( matched / consumed ) * 100 if consumed > 0 else 0","title":"iter_hashes()"},{"location":"Source/recheck/#torrentfile.recheck.Checker.log_msg","text":"Log message msg to logger and send msg to callback hook. PARAMETER DESCRIPTION *args formatting args for log message TYPE: dict DEFAULT: () level Log level for this message; default= logging.INFO TYPE: int DEFAULT: logging.INFO Source code in torrentfile\\recheck.py 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 def log_msg ( self , * args , level : int = 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 )","title":"log_msg()"},{"location":"Source/recheck/#torrentfile.recheck.Checker.piece_checker","text":"Check individual pieces of the torrent. RETURNS DESCRIPTION HashChecker | FeedChecker Individual piece hasher. Source code in torrentfile\\recheck.py 123 124 125 126 127 128 129 130 131 132 133 134 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","title":"piece_checker()"},{"location":"Source/recheck/#torrentfile.recheck.Checker.register_callback","text":"Register hooks from 3rd party programs to access generated info. PARAMETER DESCRIPTION hook callback function for the logging feature. TYPE: function Source code in torrentfile\\recheck.py 111 112 113 114 115 116 117 118 119 120 121 @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","title":"register_callback()"},{"location":"Source/recheck/#torrentfile.recheck.Checker.results","text":"Generate result percentage and store for future calls. Source code in torrentfile\\recheck.py 136 137 138 139 140 141 142 143 144 145 146 147 148 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","title":"results()"},{"location":"Source/recheck/#torrentfile.recheck.Checker.walk_file_tree","text":"Traverse File Tree dictionary to get file details. Extract full pathnames, length, root hash, and layer hashes for each file included in the .torrent\u2019s file tree. PARAMETER DESCRIPTION tree File Tree dict extracted from torrent file. TYPE: dict partials list of intermediate pathnames. TYPE: list Source code in torrentfile\\recheck.py 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 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 ])","title":"walk_file_tree()"},{"location":"Source/recheck/#torrentfile.recheck.FeedChecker","text":"Bases: ProgMixin Validates torrent content. Seemlesly validate torrent file contents by comparing hashes in metafile against data on disk. PARAMETER DESCRIPTION checker the checker class instance. TYPE: object Generate hashes of piece length data from filelist contents. Source code in torrentfile\\recheck.py 332 333 334 335 336 337 338 339 340 341 342 343 def __init__ ( self , checker : Checker ): \"\"\" 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 . piece_map = {} self . index = 0 self . piece_count = 0 self . it = None","title":"FeedChecker"},{"location":"Source/recheck/#torrentfile.recheck.FeedChecker.__iter__","text":"Assign iterator and return self. Source code in torrentfile\\recheck.py 345 346 347 348 349 350 def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" self . it = self . iter_pieces () return self","title":"__iter__()"},{"location":"Source/recheck/#torrentfile.recheck.FeedChecker.__next__","text":"Yield back result of comparison. Source code in torrentfile\\recheck.py 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 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 )","title":"__next__()"},{"location":"Source/recheck/#torrentfile.recheck.FeedChecker.extract","text":"Split file paths contents into blocks of data for hash pieces. PARAMETER DESCRIPTION path path to content. TYPE: str partial any remaining content from last file. TYPE: bytes RETURNS DESCRIPTION bytearray Hash digest for block of .torrent contents. Source code in torrentfile\\recheck.py 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 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","title":"extract()"},{"location":"Source/recheck/#torrentfile.recheck.FeedChecker.iter_pieces","text":"Iterate through, and hash pieces of torrent contents. YIELDS DESCRIPTION piece hash digest for block of torrent data. TYPE: bytes Source code in torrentfile\\recheck.py 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 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 ()","title":"iter_pieces()"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker","text":"Bases: ProgMixin Iterate through contents of meta data and verify with file contents. PARAMETER DESCRIPTION checker the checker instance that maintains variables. TYPE: Checker Construct a HybridChecker instance. Source code in torrentfile\\recheck.py 483 484 485 486 487 488 489 490 491 492 493 def __init__ ( self , checker : Checker ): \"\"\" Construct a HybridChecker instance. \"\"\" self . checker = checker self . paths = checker . paths self . piece_length = checker . piece_length self . fileinfo = checker . fileinfo self . piece_layers = checker . meta [ \"piece layers\" ] self . current = None self . index = - 1","title":"HashChecker"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.Padder","text":"Padding class to generate padding hashes wherever needed. PARAMETER DESCRIPTION length the total size of the mock file generating padding for. piece_length the block size that each hash represents. TYPE: int Construct padding class to Mock missing or incomplete files. PARAMETER DESCRIPTION length size of the file TYPE: int piece_length the piece length for each iteration. TYPE: int Source code in torrentfile\\recheck.py 526 527 528 529 530 531 532 533 534 535 536 537 538 539 def __init__ ( self , length , piece_length ): \"\"\" Construct padding class to Mock missing or incomplete files. Parameters ---------- length : int size of the file piece_length : int the piece length for each iteration. \"\"\" self . length = length self . piece_length = piece_length self . pad = sha256 ( bytearray ( piece_length )) . digest ()","title":"Padder"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.Padder.__iter__","text":"Return self to correctly implement iterator type. Source code in torrentfile\\recheck.py 541 542 543 544 545 def __iter__ ( self ): \"\"\" Return self to correctly implement iterator type. \"\"\" return self # pragma: nocover","title":"__iter__()"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.Padder.__next__","text":"Iterate through seemingly endless sha256 hashes of zeros. RETURNS DESCRIPTION tuple returns the padding TYPE: bytes RAISES DESCRIPTION StopIteration Source code in torrentfile\\recheck.py 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 def __next__ ( self ) -> bytes : \"\"\" Iterate through seemingly endless sha256 hashes of zeros. Returns ------- tuple : returns the padding Raises ------ StopIteration \"\"\" if self . length >= self . piece_length : self . length -= self . piece_length return self . pad if self . length > 0 : pad = sha256 ( bytearray ( self . length )) . digest () self . length -= self . length return pad raise StopIteration","title":"__next__()"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.__iter__","text":"Assign iterator and return self. Source code in torrentfile\\recheck.py 495 496 497 498 499 def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" return self","title":"__iter__()"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.__next__","text":"Provide the result of comparison. Source code in torrentfile\\recheck.py 501 502 503 504 505 506 507 508 509 510 511 512 def __next__ ( self ): \"\"\" Provide the result of comparison. \"\"\" if self . current is None : self . next_file () try : return self . process_current () except StopIteration as itererr : if self . next_file (): return self . process_current () raise StopIteration from itererr","title":"__next__()"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.advance","text":"Increment the number of pieces processed for the current file. RETURNS DESCRIPTION tuple the piece and size Source code in torrentfile\\recheck.py 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 def advance ( self ) -> tuple : \"\"\" Increment the number of pieces processed for the current file. Returns ------- tuple the piece and size \"\"\" start = self . count * SHA256 end = start + SHA256 piece = self . pieces [ start : end ] self . count += 1 if self . length >= self . piece_length : self . length -= self . piece_length size = self . piece_length else : size = self . length self . length -= self . length return piece , size","title":"advance()"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.next_file","text":"Remove all references to processed files and prepare for the next. RETURNS DESCRIPTION bool if there is a next file found Source code in torrentfile\\recheck.py 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 def next_file ( self ) -> bool : \"\"\" Remove all references to processed files and prepare for the next. Returns ------- bool if there is a next file found \"\"\" self . index += 1 self . prog_close () if self . current is None or self . index < len ( self . paths ): self . current = self . paths [ self . index ] self . length = self . fileinfo [ self . index ][ \"length\" ] self . root_hash = self . fileinfo [ self . index ][ \"pieces root\" ] if self . length > self . piece_length : self . pieces = self . piece_layers [ self . root_hash ] else : self . pieces = self . root_hash path = self . paths [ self . index ] self . prog_start ( self . length , path , unit = \"bytes\" ) self . count = 0 if os . path . exists ( self . current ): self . hasher = FileHasher ( path , self . piece_length , progress = 0 ) else : self . hasher = self . Padder ( self . length , self . piece_length ) return True if self . index >= len ( self . paths ): del self . current del self . length del self . root_hash del self . pieces return False","title":"next_file()"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.process_current","text":"Gather necessary information to compare to metafile details. RETURNS DESCRIPTION tuple a tuple containing the layer, piece, current path and size RAISES DESCRIPTION StopIteration Source code in torrentfile\\recheck.py 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 def process_current ( self ) -> tuple : \"\"\" Gather necessary information to compare to metafile details. Returns ------- tuple a tuple containing the layer, piece, current path and size Raises ------ StopIteration \"\"\" try : layer = next ( self . hasher ) piece , size = self . advance () self . prog_update ( size ) return layer , piece , self . current , size except StopIteration as err : if self . length > 0 and self . count * SHA256 < len ( self . pieces ): self . hasher = self . Padder ( self . length , self . piece_length ) piece , size = self . advance () layer = next ( self . hasher ) self . prog_update ( 0 ) return layer , piece , self . current , size raise StopIteration from err","title":"process_current()"},{"location":"Source/torrent/","text":"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: # \u201cannounce\u201d: The URL of the tracker. \u201cinfo\u201d: This maps to a dictionary, with keys described below. \u201cname\u201d: A display name for the torrent. It is purely advisory. \u201cpiece length\u201d: 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. \u201cmeta version\u201d: 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 \u201cfile tree\u201d: 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. \u2018UTF-8 encoded\u2019 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 \u2018.\u2019 and \u2018..\u2019 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. \u201clength\u201d: 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. \u201cpieces root\u201d: 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. \u201cpiece layers\u201d: 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\u2019s the name of a directory. MetaFile ( 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 , meta_version = None , ** _ ) # Base Class for all TorrentFile classes. PARAMETER DESCRIPTION path target path to torrent content. Default: None TYPE: str DEFAULT: None announce One or more tracker URL\u2019s. Default: None TYPE: str DEFAULT: None comment A comment. Default: None TYPE: str DEFAULT: None piece_length Size of torrent pieces. Default: None TYPE: int DEFAULT: None private For private trackers. Default: None TYPE: bool DEFAULT: False outfile target path to write .torrent file. Default: None TYPE: str DEFAULT: None source Private tracker source. Default: None TYPE: str DEFAULT: None progress level of progress bar displayed Default: \u201c1\u201d TYPE: str DEFAULT: 1 cwd If True change default save location to current directory TYPE: bool DEFAULT: False httpseeds one or more web addresses where torrent content can be found. TYPE: list DEFAULT: None url_list one or more web addressess where torrent content exists. TYPE: list DEFAULT: None content alias for \u2018path\u2019 arg. TYPE: str DEFAULT: None meta_version indicates which Bittorrent protocol to use for hashing content TYPE: int DEFAULT: None Construct MetaFile superclass and assign local attributes. Source code in torrentfile\\torrent.py 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 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 , meta_version = 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 self . meta_version = meta_version 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 self . meta_version = meta_version 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 () # Overload in subclasses. RAISES DESCRIPTION Exception NotImplementedError Source code in torrentfile\\torrent.py 350 351 352 353 354 355 356 357 358 359 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. PARAMETER DESCRIPTION func The callback function which accepts a single paramter. TYPE: function Source code in torrentfile\\torrent.py 240 241 242 243 244 245 246 247 248 249 250 251 @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 () # Sort the info and meta dictionaries. Source code in torrentfile\\torrent.py 361 362 363 364 365 366 367 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 ( outfile = None ) -> tuple # Write meta information to .torrent file. Final step in the torrent file creation process. After hashing and sorting every piece of content write the contents to file using the bencode encoding. PARAMETER DESCRIPTION outfile Destination path for .torrent file. default=None TYPE: str DEFAULT: None RETURNS DESCRIPTION outfile Where the .torrent file was writen. TYPE: str meta .torrent meta information. TYPE: dict Source code in torrentfile\\torrent.py 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 def write ( self , outfile = None ) -> tuple : \"\"\" Write meta information to .torrent file. Final step in the torrent file creation process. After hashing and sorting every piece of content write the contents to file using the bencode encoding. Parameters ---------- outfile : str Destination path for .torrent file. default=None Returns ------- outfile : str Where the .torrent file was writen. meta : dict .torrent meta information. \"\"\" if outfile : self . outfile = outfile elif self . outfile : pass else : self . outfile = os . path . join ( os . getcwd (), self . name ) + \".torrent\" 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 as excp : logger . error ( \"Permission Denied: Could not write to %s \" , self . outfile ) raise PermissionError from excp return self . outfile , self . meta TorrentAssembler ( ** kwargs ) # Bases: MetaFile Assembler class for Bittorrent version 2 and hybrid meta files. This differs from the TorrentFileV2 and TorrentFileHybrid, because it can be used as an iterator and works for both versions. PARAMETER DESCRIPTION **kwargs Keyword arguments for torrent options. TYPE: dict DEFAULT: {} Create Bittorrent v1 v2 hybrid metafiles. Source code in torrentfile\\torrent.py 661 662 663 664 665 666 667 668 669 670 671 672 673 674 def __init__ ( self , ** kwargs ): \"\"\" Create Bittorrent v1 v2 hybrid metafiles. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"Assembling bittorrent Hybrid file\" ) self . name = os . path . basename ( self . path ) self . hashes = [] self . piece_layers = {} self . pieces = bytearray () self . files = [] self . hybrid = self . meta_version == \"3\" self . total = len ( utils . get_file_list ( self . path )) self . assemble () assemble () # Assemble the parts of the torrentfile into meta dictionary. Source code in torrentfile\\torrent.py 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 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 ) else : info [ \"file tree\" ] = self . _traverse ( self . path ) if self . hybrid : info [ \"files\" ] = self . files if self . hybrid : info [ \"pieces\" ] = self . pieces self . meta [ \"piece layers\" ] = self . piece_layers return info TorrentFile ( ** kwargs ) # Bases: MetaFile , ProgMixin Class for creating Bittorrent meta files. Construct Torrentfile class instance object. PARAMETER DESCRIPTION **kwargs Dictionary containing torrent file options. TYPE: dict DEFAULT: {} Construct TorrentFile instance with given keyword args. PARAMETER DESCRIPTION **kwargs dictionary of keyword args passed to superclass. TYPE: dict DEFAULT: {} Source code in torrentfile\\torrent.py 422 423 424 425 426 427 428 429 430 431 432 433 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 ( \"Assembling bittorrent v1 torrent file\" ) self . assemble () assemble () # Assemble components of torrent metafile. RETURNS DESCRIPTION dict metadata dictionary for torrent file Source code in torrentfile\\torrent.py 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 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 ) for piece in feeder : pieces . extend ( piece ) info [ \"pieces\" ] = pieces TorrentFileHybrid ( ** kwargs ) # Bases: MetaFile , ProgMixin Construct the Hybrid torrent meta file with provided parameters. DEPRECATED PARAMETER DESCRIPTION **kwargs Keyword arguments for torrent options. TYPE: dict DEFAULT: {} Create Bittorrent v1 v2 hybrid metafiles. Source code in torrentfile\\torrent.py 565 566 567 568 569 570 571 572 573 574 575 576 577 def __init__ ( self , ** kwargs ): \"\"\" Create Bittorrent v1 v2 hybrid metafiles. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"Assembling bittorrent Hybrid file\" ) 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 )) self . assemble () assemble () # Assemble the parts of the torrentfile into meta dictionary. DEPRECATED Source code in torrentfile\\torrent.py 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 def assemble ( self ): \"\"\" Assemble the parts of the torrentfile into meta dictionary. **DEPRECATED** \"\"\" 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 ) 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 ( ** kwargs ) # Bases: MetaFile , ProgMixin Class for creating Bittorrent meta v2 files. DEPRECATED PARAMETER DESCRIPTION **kwargs Keyword arguments for torrent file options. TYPE: dict DEFAULT: {} Construct TorrentFileV2 Class instance from given parameters. DEPRECATED PARAMETER DESCRIPTION **kwargs keywword arguments to pass to superclass. TYPE: dict DEFAULT: {} Source code in torrentfile\\torrent.py 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 def __init__ ( self , ** kwargs ): \"\"\" Construct `TorrentFileV2` Class instance from given parameters. **DEPRECATED** Parameters ---------- **kwargs : dict keywword arguments to pass to superclass. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"Assembling bittorrent v2 torrent file\" ) self . piece_layers = {} self . hashes = [] self . total = len ( utils . get_file_list ( self . path )) self . assemble () assemble () # Assemble then return the meta dictionary for encoding. DEPRECATED RETURNS DESCRIPTION meta Metainformation about the torrent. TYPE: dict Source code in torrentfile\\torrent.py 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 def assemble ( self ): \"\"\" Assemble then return the meta dictionary for encoding. **DEPRECATED** 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","title":"Torrent"},{"location":"Source/torrent/#torrentfile.torrent","text":"Classes and procedures pertaining to the creation of torrent meta files.","title":"torrent"},{"location":"Source/torrent/#torrentfile.torrent--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/torrent/#torrentfile.torrent--constants","text":"BLOCK_SIZE : int size of leaf hashes for merkle tree. HASH_SIZE : int Length of a sha256 hash.","title":"Constants"},{"location":"Source/torrent/#torrentfile.torrent--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/torrent/#torrentfile.torrent--meta-version-2-dictionary","text":"\u201cannounce\u201d: The URL of the tracker. \u201cinfo\u201d: This maps to a dictionary, with keys described below. \u201cname\u201d: A display name for the torrent. It is purely advisory. \u201cpiece length\u201d: 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. \u201cmeta version\u201d: 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 \u201cfile tree\u201d: 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. \u2018UTF-8 encoded\u2019 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 \u2018.\u2019 and \u2018..\u2019 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. \u201clength\u201d: 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. \u201cpieces root\u201d: 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. \u201cpiece layers\u201d: 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/torrent/#torrentfile.torrent--bittorrent-v1","text":"","title":"Bittorrent V1"},{"location":"Source/torrent/#torrentfile.torrent--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\u2019s the name of a directory.","title":"v1 meta-dictionary"},{"location":"Source/torrent/#torrentfile.torrent.MetaFile","text":"Base Class for all TorrentFile classes. PARAMETER DESCRIPTION path target path to torrent content. Default: None TYPE: str DEFAULT: None announce One or more tracker URL\u2019s. Default: None TYPE: str DEFAULT: None comment A comment. Default: None TYPE: str DEFAULT: None piece_length Size of torrent pieces. Default: None TYPE: int DEFAULT: None private For private trackers. Default: None TYPE: bool DEFAULT: False outfile target path to write .torrent file. Default: None TYPE: str DEFAULT: None source Private tracker source. Default: None TYPE: str DEFAULT: None progress level of progress bar displayed Default: \u201c1\u201d TYPE: str DEFAULT: 1 cwd If True change default save location to current directory TYPE: bool DEFAULT: False httpseeds one or more web addresses where torrent content can be found. TYPE: list DEFAULT: None url_list one or more web addressess where torrent content exists. TYPE: list DEFAULT: None content alias for \u2018path\u2019 arg. TYPE: str DEFAULT: None meta_version indicates which Bittorrent protocol to use for hashing content TYPE: int DEFAULT: None Construct MetaFile superclass and assign local attributes. Source code in torrentfile\\torrent.py 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 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 , meta_version = 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 self . meta_version = meta_version 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 self . meta_version = meta_version parent , self . name = os . path . split ( self . path ) if not self . name : self . name = os . path . basename ( parent ) self . meta [ \"info\" ][ \"name\" ] = self . name","title":"MetaFile"},{"location":"Source/torrent/#torrentfile.torrent.MetaFile.assemble","text":"Overload in subclasses. RAISES DESCRIPTION Exception NotImplementedError Source code in torrentfile\\torrent.py 350 351 352 353 354 355 356 357 358 359 def assemble ( self ): \"\"\" Overload in subclasses. Raises ------ Exception NotImplementedError \"\"\" raise NotImplementedError","title":"assemble()"},{"location":"Source/torrent/#torrentfile.torrent.MetaFile.set_callback","text":"Assign a callback function for the Hashing class to call for each hash. PARAMETER DESCRIPTION func The callback function which accepts a single paramter. TYPE: function Source code in torrentfile\\torrent.py 240 241 242 243 244 245 246 247 248 249 250 251 @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 )","title":"set_callback()"},{"location":"Source/torrent/#torrentfile.torrent.MetaFile.sort_meta","text":"Sort the info and meta dictionaries. Source code in torrentfile\\torrent.py 361 362 363 364 365 366 367 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","title":"sort_meta()"},{"location":"Source/torrent/#torrentfile.torrent.MetaFile.write","text":"Write meta information to .torrent file. Final step in the torrent file creation process. After hashing and sorting every piece of content write the contents to file using the bencode encoding. PARAMETER DESCRIPTION outfile Destination path for .torrent file. default=None TYPE: str DEFAULT: None RETURNS DESCRIPTION outfile Where the .torrent file was writen. TYPE: str meta .torrent meta information. TYPE: dict Source code in torrentfile\\torrent.py 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 def write ( self , outfile = None ) -> tuple : \"\"\" Write meta information to .torrent file. Final step in the torrent file creation process. After hashing and sorting every piece of content write the contents to file using the bencode encoding. Parameters ---------- outfile : str Destination path for .torrent file. default=None Returns ------- outfile : str Where the .torrent file was writen. meta : dict .torrent meta information. \"\"\" if outfile : self . outfile = outfile elif self . outfile : pass else : self . outfile = os . path . join ( os . getcwd (), self . name ) + \".torrent\" 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 as excp : logger . error ( \"Permission Denied: Could not write to %s \" , self . outfile ) raise PermissionError from excp return self . outfile , self . meta","title":"write()"},{"location":"Source/torrent/#torrentfile.torrent.TorrentAssembler","text":"Bases: MetaFile Assembler class for Bittorrent version 2 and hybrid meta files. This differs from the TorrentFileV2 and TorrentFileHybrid, because it can be used as an iterator and works for both versions. PARAMETER DESCRIPTION **kwargs Keyword arguments for torrent options. TYPE: dict DEFAULT: {} Create Bittorrent v1 v2 hybrid metafiles. Source code in torrentfile\\torrent.py 661 662 663 664 665 666 667 668 669 670 671 672 673 674 def __init__ ( self , ** kwargs ): \"\"\" Create Bittorrent v1 v2 hybrid metafiles. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"Assembling bittorrent Hybrid file\" ) self . name = os . path . basename ( self . path ) self . hashes = [] self . piece_layers = {} self . pieces = bytearray () self . files = [] self . hybrid = self . meta_version == \"3\" self . total = len ( utils . get_file_list ( self . path )) self . assemble ()","title":"TorrentAssembler"},{"location":"Source/torrent/#torrentfile.torrent.TorrentAssembler.assemble","text":"Assemble the parts of the torrentfile into meta dictionary. Source code in torrentfile\\torrent.py 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 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 ) else : info [ \"file tree\" ] = self . _traverse ( self . path ) if self . hybrid : info [ \"files\" ] = self . files if self . hybrid : info [ \"pieces\" ] = self . pieces self . meta [ \"piece layers\" ] = self . piece_layers return info","title":"assemble()"},{"location":"Source/torrent/#torrentfile.torrent.TorrentFile","text":"Bases: MetaFile , ProgMixin Class for creating Bittorrent meta files. Construct Torrentfile class instance object. PARAMETER DESCRIPTION **kwargs Dictionary containing torrent file options. TYPE: dict DEFAULT: {} Construct TorrentFile instance with given keyword args. PARAMETER DESCRIPTION **kwargs dictionary of keyword args passed to superclass. TYPE: dict DEFAULT: {} Source code in torrentfile\\torrent.py 422 423 424 425 426 427 428 429 430 431 432 433 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 ( \"Assembling bittorrent v1 torrent file\" ) self . assemble ()","title":"TorrentFile"},{"location":"Source/torrent/#torrentfile.torrent.TorrentFile.assemble","text":"Assemble components of torrent metafile. RETURNS DESCRIPTION dict metadata dictionary for torrent file Source code in torrentfile\\torrent.py 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 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 ) for piece in feeder : pieces . extend ( piece ) info [ \"pieces\" ] = pieces","title":"assemble()"},{"location":"Source/torrent/#torrentfile.torrent.TorrentFileHybrid","text":"Bases: MetaFile , ProgMixin Construct the Hybrid torrent meta file with provided parameters. DEPRECATED PARAMETER DESCRIPTION **kwargs Keyword arguments for torrent options. TYPE: dict DEFAULT: {} Create Bittorrent v1 v2 hybrid metafiles. Source code in torrentfile\\torrent.py 565 566 567 568 569 570 571 572 573 574 575 576 577 def __init__ ( self , ** kwargs ): \"\"\" Create Bittorrent v1 v2 hybrid metafiles. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"Assembling bittorrent Hybrid file\" ) 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 )) self . assemble ()","title":"TorrentFileHybrid"},{"location":"Source/torrent/#torrentfile.torrent.TorrentFileHybrid.assemble","text":"Assemble the parts of the torrentfile into meta dictionary. DEPRECATED Source code in torrentfile\\torrent.py 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 def assemble ( self ): \"\"\" Assemble the parts of the torrentfile into meta dictionary. **DEPRECATED** \"\"\" 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 ) 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","title":"assemble()"},{"location":"Source/torrent/#torrentfile.torrent.TorrentFileV2","text":"Bases: MetaFile , ProgMixin Class for creating Bittorrent meta v2 files. DEPRECATED PARAMETER DESCRIPTION **kwargs Keyword arguments for torrent file options. TYPE: dict DEFAULT: {} Construct TorrentFileV2 Class instance from given parameters. DEPRECATED PARAMETER DESCRIPTION **kwargs keywword arguments to pass to superclass. TYPE: dict DEFAULT: {} Source code in torrentfile\\torrent.py 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 def __init__ ( self , ** kwargs ): \"\"\" Construct `TorrentFileV2` Class instance from given parameters. **DEPRECATED** Parameters ---------- **kwargs : dict keywword arguments to pass to superclass. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"Assembling bittorrent v2 torrent file\" ) self . piece_layers = {} self . hashes = [] self . total = len ( utils . get_file_list ( self . path )) self . assemble ()","title":"TorrentFileV2"},{"location":"Source/torrent/#torrentfile.torrent.TorrentFileV2.assemble","text":"Assemble then return the meta dictionary for encoding. DEPRECATED RETURNS DESCRIPTION meta Metainformation about the torrent. TYPE: dict Source code in torrentfile\\torrent.py 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 def assemble ( self ): \"\"\" Assemble then return the meta dictionary for encoding. **DEPRECATED** 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","title":"assemble()"},{"location":"Source/utils/","text":"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. ArgumentError # Bases: Exception Exception for mismatched or mistyped CLI arguments. Memo ( func ) # Memoize cache. PARAMETER DESCRIPTION func The results of this callable will be cached. TYPE: Callable Construct for memoization. Source code in torrentfile\\utils.py 52 53 54 55 56 57 58 def __init__ ( self , func ): \"\"\" Construct for memoization. \"\"\" self . func = func self . counter = 0 self . cache = {} __call__ ( path ) # Invoke each time memo function is called. PARAMETER DESCRIPTION path The relative or absolute path being used as key in cache dict. TYPE: str RETURNS DESCRIPTION Any The results of calling the function with path. Source code in torrentfile\\utils.py 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 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 MissingPathError ( message : str = None ) # Bases: Exception Path parameter is required to specify target content. Creating a .torrent file with no contents seems rather silly. PARAMETER DESCRIPTION message Message for user (optional). TYPE: str DEFAULT: None 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 94 95 96 97 98 99 100 101 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 ( message : str = None ) # Bases: Exception Piece Length parameter must equal a perfect power of 2. PARAMETER DESCRIPTION message Message for user (optional). TYPE: str DEFAULT: None 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 114 115 116 117 118 119 120 121 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 ) copypath ( source : str , dest : str ) -> None # Copy the file located at source to dest. If one or more directory paths don\u2019t exist in dest, they will be created. If dest already exists and dest and source are the same size, it will be ignored, however if dest is smaller than source, dest will be overwritten. PARAMETER DESCRIPTION source path to source file TYPE: str dest path to target destination TYPE: str Source code in torrentfile\\utils.py 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 def copypath ( source : str , dest : str ) -> None : \"\"\" Copy the file located at source to dest. If one or more directory paths don't exist in dest, they will be created. If dest already exists and dest and source are the same size, it will be ignored, however if dest is smaller than source, dest will be overwritten. Parameters ---------- source : str path to source file dest : str path to target destination \"\"\" if not os . path . exists ( source ): return if os . path . exists ( dest ): if os . path . getsize ( source ) <= os . path . getsize ( dest ): return shutil . copy ( source , dest ) # pragma: nocover return # pragma: nocover path_parts = iter ( Path ( dest ) . parts [: - 1 ]) try : root = next ( path_parts ) except StopIteration : # pragma: nocover return if not os . path . exists ( root ): os . mkdir ( root ) # pragma: nocover for part in path_parts : path = os . path . join ( root , part ) if not os . path . exists ( path ): os . mkdir ( path ) root = path shutil . copy ( source , dest ) filelist_total ( pathstring : str ) -> os . PathLike # Perform error checking and format conversion to os.PathLike. PARAMETER DESCRIPTION pathstring An existing filesystem path. TYPE: str RETURNS DESCRIPTION os . PathLike Input path converted to bytes format. RAISES DESCRIPTION MissingPathError File could not be found. Source code in torrentfile\\utils.py 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 @Memo def filelist_total ( pathstring : str ) -> os . PathLike : \"\"\" Perform error checking and format conversion to os.PathLike. Parameters ---------- pathstring : str An existing filesystem path. Returns ------- os.PathLike Input path converted to bytes format. Raises ------ MissingPathError File could not be found. \"\"\" if os . path . exists ( pathstring ): path = Path ( pathstring ) return _filelist_total ( path ) raise MissingPathError get_file_list ( path : str ) -> list # Return a sorted list of file paths contained in directory. PARAMETER DESCRIPTION path target file or directory. TYPE: str RETURNS DESCRIPTION list sorted list of file paths. TYPE: list Source code in torrentfile\\utils.py 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 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 : int ) -> int # Calculate the ideal piece length for bittorrent data. PARAMETER DESCRIPTION size Total bits of all files incluided in .torrent file. TYPE: int RETURNS DESCRIPTION int Ideal piece length. Source code in torrentfile\\utils.py 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 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 ) > 1000 and exp < 24 : exp += 1 return 2 ** exp humanize_bytes ( amount : int ) -> str # Convert integer into human readable memory sized denomination. PARAMETER DESCRIPTION amount total number of bytes. TYPE: int RETURNS DESCRIPTION str human readable representation of the given amount of bytes. Source code in torrentfile\\utils.py 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 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 : int ) -> int # Calculate the next perfect power of 2 equal to or greater than value. PARAMETER DESCRIPTION value integer value that is less than some perfect power of 2. TYPE: int RETURNS DESCRIPTION int The next power of 2 greater than value, or value if already power of 2. Source code in torrentfile\\utils.py 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 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 : int ) -> int # Verify input piece_length is valid and convert accordingly. PARAMETER DESCRIPTION piece_length The piece length provided by user. TYPE: int | str RETURNS DESCRIPTION int normalized piece length. RAISES DESCRIPTION PieceLengthValueError : Piece length is improper value. Source code in torrentfile\\utils.py 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 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 : 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 piece_length > ( 1 << 14 ): if 2 ** math . log2 ( piece_length ) == piece_length : return piece_length 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 : str ) -> int # Calculate piece length for input path and contents. PARAMETER DESCRIPTION path The absolute path to directory and contents. TYPE: str RETURNS DESCRIPTION int The size of pieces of torrent content. Source code in torrentfile\\utils.py 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 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 : str ) -> int # Return the total size of all files in path recursively. PARAMETER DESCRIPTION path path to target file or directory. TYPE: str RETURNS DESCRIPTION int total size of files. Source code in torrentfile\\utils.py 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 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 : str ) -> tuple # Calculate directory statistics. PARAMETER DESCRIPTION path The path to start calculating from. TYPE: str RETURNS DESCRIPTION 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. Source code in torrentfile\\utils.py 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 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 )","title":"Utils"},{"location":"Source/utils/#torrentfile.utils","text":"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.","title":"utils"},{"location":"Source/utils/#torrentfile.utils.ArgumentError","text":"Bases: Exception Exception for mismatched or mistyped CLI arguments.","title":"ArgumentError"},{"location":"Source/utils/#torrentfile.utils.Memo","text":"Memoize cache. PARAMETER DESCRIPTION func The results of this callable will be cached. TYPE: Callable Construct for memoization. Source code in torrentfile\\utils.py 52 53 54 55 56 57 58 def __init__ ( self , func ): \"\"\" Construct for memoization. \"\"\" self . func = func self . counter = 0 self . cache = {}","title":"Memo"},{"location":"Source/utils/#torrentfile.utils.Memo.__call__","text":"Invoke each time memo function is called. PARAMETER DESCRIPTION path The relative or absolute path being used as key in cache dict. TYPE: str RETURNS DESCRIPTION Any The results of calling the function with path. Source code in torrentfile\\utils.py 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 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","title":"__call__()"},{"location":"Source/utils/#torrentfile.utils.MissingPathError","text":"Bases: Exception Path parameter is required to specify target content. Creating a .torrent file with no contents seems rather silly. PARAMETER DESCRIPTION message Message for user (optional). TYPE: str DEFAULT: None 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 94 95 96 97 98 99 100 101 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 )","title":"MissingPathError"},{"location":"Source/utils/#torrentfile.utils.PieceLengthValueError","text":"Bases: Exception Piece Length parameter must equal a perfect power of 2. PARAMETER DESCRIPTION message Message for user (optional). TYPE: str DEFAULT: None 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 114 115 116 117 118 119 120 121 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 )","title":"PieceLengthValueError"},{"location":"Source/utils/#torrentfile.utils.copypath","text":"Copy the file located at source to dest. If one or more directory paths don\u2019t exist in dest, they will be created. If dest already exists and dest and source are the same size, it will be ignored, however if dest is smaller than source, dest will be overwritten. PARAMETER DESCRIPTION source path to source file TYPE: str dest path to target destination TYPE: str Source code in torrentfile\\utils.py 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 def copypath ( source : str , dest : str ) -> None : \"\"\" Copy the file located at source to dest. If one or more directory paths don't exist in dest, they will be created. If dest already exists and dest and source are the same size, it will be ignored, however if dest is smaller than source, dest will be overwritten. Parameters ---------- source : str path to source file dest : str path to target destination \"\"\" if not os . path . exists ( source ): return if os . path . exists ( dest ): if os . path . getsize ( source ) <= os . path . getsize ( dest ): return shutil . copy ( source , dest ) # pragma: nocover return # pragma: nocover path_parts = iter ( Path ( dest ) . parts [: - 1 ]) try : root = next ( path_parts ) except StopIteration : # pragma: nocover return if not os . path . exists ( root ): os . mkdir ( root ) # pragma: nocover for part in path_parts : path = os . path . join ( root , part ) if not os . path . exists ( path ): os . mkdir ( path ) root = path shutil . copy ( source , dest )","title":"copypath()"},{"location":"Source/utils/#torrentfile.utils.filelist_total","text":"Perform error checking and format conversion to os.PathLike. PARAMETER DESCRIPTION pathstring An existing filesystem path. TYPE: str RETURNS DESCRIPTION os . PathLike Input path converted to bytes format. RAISES DESCRIPTION MissingPathError File could not be found. Source code in torrentfile\\utils.py 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 @Memo def filelist_total ( pathstring : str ) -> os . PathLike : \"\"\" Perform error checking and format conversion to os.PathLike. Parameters ---------- pathstring : str An existing filesystem path. Returns ------- os.PathLike Input path converted to bytes format. Raises ------ MissingPathError File could not be found. \"\"\" if os . path . exists ( pathstring ): path = Path ( pathstring ) return _filelist_total ( path ) raise MissingPathError","title":"filelist_total()"},{"location":"Source/utils/#torrentfile.utils.get_file_list","text":"Return a sorted list of file paths contained in directory. PARAMETER DESCRIPTION path target file or directory. TYPE: str RETURNS DESCRIPTION list sorted list of file paths. TYPE: list Source code in torrentfile\\utils.py 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 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","title":"get_file_list()"},{"location":"Source/utils/#torrentfile.utils.get_piece_length","text":"Calculate the ideal piece length for bittorrent data. PARAMETER DESCRIPTION size Total bits of all files incluided in .torrent file. TYPE: int RETURNS DESCRIPTION int Ideal piece length. Source code in torrentfile\\utils.py 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 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 ) > 1000 and exp < 24 : exp += 1 return 2 ** exp","title":"get_piece_length()"},{"location":"Source/utils/#torrentfile.utils.humanize_bytes","text":"Convert integer into human readable memory sized denomination. PARAMETER DESCRIPTION amount total number of bytes. TYPE: int RETURNS DESCRIPTION str human readable representation of the given amount of bytes. Source code in torrentfile\\utils.py 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 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\"","title":"humanize_bytes()"},{"location":"Source/utils/#torrentfile.utils.next_power_2","text":"Calculate the next perfect power of 2 equal to or greater than value. PARAMETER DESCRIPTION value integer value that is less than some perfect power of 2. TYPE: int RETURNS DESCRIPTION int The next power of 2 greater than value, or value if already power of 2. Source code in torrentfile\\utils.py 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 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","title":"next_power_2()"},{"location":"Source/utils/#torrentfile.utils.normalize_piece_length","text":"Verify input piece_length is valid and convert accordingly. PARAMETER DESCRIPTION piece_length The piece length provided by user. TYPE: int | str RETURNS DESCRIPTION int normalized piece length. RAISES DESCRIPTION PieceLengthValueError : Piece length is improper value. Source code in torrentfile\\utils.py 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 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 : 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 piece_length > ( 1 << 14 ): if 2 ** math . log2 ( piece_length ) == piece_length : return piece_length 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","title":"normalize_piece_length()"},{"location":"Source/utils/#torrentfile.utils.path_piece_length","text":"Calculate piece length for input path and contents. PARAMETER DESCRIPTION path The absolute path to directory and contents. TYPE: str RETURNS DESCRIPTION int The size of pieces of torrent content. Source code in torrentfile\\utils.py 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 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 )","title":"path_piece_length()"},{"location":"Source/utils/#torrentfile.utils.path_size","text":"Return the total size of all files in path recursively. PARAMETER DESCRIPTION path path to target file or directory. TYPE: str RETURNS DESCRIPTION int total size of files. Source code in torrentfile\\utils.py 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 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","title":"path_size()"},{"location":"Source/utils/#torrentfile.utils.path_stat","text":"Calculate directory statistics. PARAMETER DESCRIPTION path The path to start calculating from. TYPE: str RETURNS DESCRIPTION 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. Source code in torrentfile\\utils.py 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 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 )","title":"path_stat()"},{"location":"Source/version/","text":"version # Holds the release version number.","title":"Version"},{"location":"Source/version/#torrentfile.version","text":"Holds the release version number.","title":"version"},{"location":"coverage/","text":".md-content { max-width: none !important; } article h1, article > a { display: none; } var coviframe = document.getElementById(\"coviframe\"); function resizeIframe() { coviframe.style.height = coviframe.contentWindow.document.documentElement.offsetHeight + 'px'; } coviframe.contentWindow.document.body.onclick = function() { coviframe.contentWindow.location.reload(); }","title":"Coverage"}]} \ No newline at end of file +{"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.6+ Tested on Linux, Windows and Mac \ud83d\udcbb Install # via PyPi: 1 pip install torrentfile via Git: 1 2 3 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 examples can be found in the project documentation on the examples page. \ud83d\udcdd License # Apache Software License v2.0 - See LICENSE \ud83d\udca1 Issues & Requests & PRs # If you encounter any bugs or would like to request a new feature please open a new issue. PRs and other contributions are welcome https://github.com/alexpdev/torrentfile/issues Usage Examples # Creating Bittorrent Files # Basic torrent file creation is as easy and using the create sub-commnand and providing the path to the contents. 1 torrentfile create /path/to/content You can add one or more trackers by using any one of -t , --tracker , -a , --announce flags and listing their URL as a space separated list. 1 torrentfile create /path/to/content -a http://tracker1.com http://tracker2.net If you intend to distribute the file on a private tracker then you should use one of -p , --private flags, which tells your Bittorrent clients to disable DHT and multitracker protocols. 1 torrentfile create /path/to/content --private By default torrentfile displays a progress bar indicating how much of the content has already been processed. To turn off this display you can either use --quiet mode in as a global flag or you can set the --prog flag to 0. 1 torrentfile --quiet create /path/to/content 1 torrentfile create /path/to/content --prog 0 torrentfile automatically extracts the name of the file or directory if the content and saves the file to the current working directory with the extracted title. For example running the follwing command would create ./content.torrent . 1 torrentfile create /path/to/content To specify an alternative path or filename you may use the -o , --out flags followed by the relative or absolute path to your preferred output location. 1 torrentfile create /path/to/content -o /some/other/path/torrent.torrent If the path you specified with the -o flag already exists and is a directory, then torrentfile will save the output to that directory with the default extracted title. For example the following command would create a Bittorrent file at /some/other/path/content.torrent . 1 torrentfile create /path/to/content -o /some/other/path/ Bittorrent V1 is still the most common version of torrent files and the most widely accepted, therefore by default torrentfile uses the version 1 format. However if you are using a modern Bittorrent client and tracker then you may wish to use the newest version Bittorrent V2 or a combination of the two. To do this simply use the --meta-version flag with the appropriate version. Options include 1 (v1 default), 2 (v2), or 3 (v1 & v2). 1 torrentfile create /path/to/content --meta-version 2 1 torrentfile create /path/to/content --meta-version 3 Check/Recheck Torrent # The recheck subcommand allows you to scan a Bittorrent file and compare it\u2019s contents, against a file or directory containing the contents the torrent file was created from. The output provided by this process gives a detailed perspective if any files are missing or have been corrupted in any way. Supports any version of Bittorrent file. 1 torrentfile recheck /path/to/some.torrent /path/to/content Edit Torrent # To edit specific fields of the torrent file, there is the edit subcommand. Using this subcommand you can specify the field with one of the available field flags, for example --tracker and specify the value you wish to change it to. 1 torrentfile edit /path/to/content --tracker https://new.tracker.url1.com https://newtracker.url/2 You can use the -h flag for a full list of available fields that can be edited. 1 torrentfile edit -h 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. 1 torrentfile magnet /path/to/some.torrent 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.6+ Tested on Linux, Windows and Mac","title":"\ud83d\udd0c Requirements"},{"location":"#install","text":"via PyPi: 1 pip install torrentfile via Git: 1 2 3 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 examples can be found in the project documentation on the examples page.","title":"\ud83d\ude80 Usage"},{"location":"#license","text":"Apache Software License v2.0 - See LICENSE","title":"\ud83d\udcdd License"},{"location":"#issues-requests-prs","text":"If you encounter any bugs or would like to request a new feature please open a new issue. PRs and other contributions are welcome https://github.com/alexpdev/torrentfile/issues","title":"\ud83d\udca1 Issues & Requests & PRs"},{"location":"#usage-examples","text":"","title":"Usage Examples"},{"location":"#creating-bittorrent-files","text":"Basic torrent file creation is as easy and using the create sub-commnand and providing the path to the contents. 1 torrentfile create /path/to/content You can add one or more trackers by using any one of -t , --tracker , -a , --announce flags and listing their URL as a space separated list. 1 torrentfile create /path/to/content -a http://tracker1.com http://tracker2.net If you intend to distribute the file on a private tracker then you should use one of -p , --private flags, which tells your Bittorrent clients to disable DHT and multitracker protocols. 1 torrentfile create /path/to/content --private By default torrentfile displays a progress bar indicating how much of the content has already been processed. To turn off this display you can either use --quiet mode in as a global flag or you can set the --prog flag to 0. 1 torrentfile --quiet create /path/to/content 1 torrentfile create /path/to/content --prog 0 torrentfile automatically extracts the name of the file or directory if the content and saves the file to the current working directory with the extracted title. For example running the follwing command would create ./content.torrent . 1 torrentfile create /path/to/content To specify an alternative path or filename you may use the -o , --out flags followed by the relative or absolute path to your preferred output location. 1 torrentfile create /path/to/content -o /some/other/path/torrent.torrent If the path you specified with the -o flag already exists and is a directory, then torrentfile will save the output to that directory with the default extracted title. For example the following command would create a Bittorrent file at /some/other/path/content.torrent . 1 torrentfile create /path/to/content -o /some/other/path/ Bittorrent V1 is still the most common version of torrent files and the most widely accepted, therefore by default torrentfile uses the version 1 format. However if you are using a modern Bittorrent client and tracker then you may wish to use the newest version Bittorrent V2 or a combination of the two. To do this simply use the --meta-version flag with the appropriate version. Options include 1 (v1 default), 2 (v2), or 3 (v1 & v2). 1 torrentfile create /path/to/content --meta-version 2 1 torrentfile create /path/to/content --meta-version 3","title":"Creating Bittorrent Files"},{"location":"#checkrecheck-torrent","text":"The recheck subcommand allows you to scan a Bittorrent file and compare it\u2019s contents, against a file or directory containing the contents the torrent file was created from. The output provided by this process gives a detailed perspective if any files are missing or have been corrupted in any way. Supports any version of Bittorrent file. 1 torrentfile recheck /path/to/some.torrent /path/to/content","title":"Check/Recheck Torrent"},{"location":"#edit-torrent","text":"To edit specific fields of the torrent file, there is the edit subcommand. Using this subcommand you can specify the field with one of the available field flags, for example --tracker and specify the value you wish to change it to. 1 torrentfile edit /path/to/content --tracker https://new.tracker.url1.com https://newtracker.url/2 You can use the -h flag for a full list of available fields that can be edited. 1 torrentfile edit -h","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. 1 torrentfile magnet /path/to/some.torrent","title":"Create Magnet"},{"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\u2019t 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. 1 2 3 4 5 6 7 8 9 10 11 12 13 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\u2019t 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. 1 2 3 4 5 6 7 8 9 10 11 12 13 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":"api/","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: # \u201cannounce\u201d: The URL of the tracker. \u201cinfo\u201d: This maps to a dictionary, with keys described below. \u201cname\u201d: A display name for the torrent. It is purely advisory. \u201cpiece length\u201d: 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. \u201cmeta version\u201d: 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 \u201cfile tree\u201d: 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. \u2018UTF-8 encoded\u2019 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 \u2018.\u2019 and \u2018..\u2019 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. \u201clength\u201d: 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. \u201cpieces root\u201d: 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. \u201cpiece layers\u201d: 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\u2019s 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. TorrentAssembler \u2014 Assembler class for Bittorrent version 2 and hybrid meta files. 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 Memoize cache. MissingPathError \u2014 Path parameter is required to specify target content. PieceLengthValueError \u2014 Piece Length parameter must equal a perfect power of 2. ArgumentError \u2014 Exception for mismatched or mistyped CLI arguments. Functions copypath ( source , dest ) \u2014 Copy the file located at source to dest. debug_is_on ( ) (bool) \u2014 Return True if debug mode is on in environment variables. 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. toggle_debug_mode ( switch_on ) \u2014 Switch the environment variable debug indicator on or off. 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 the fields that were not used by the original file creator. 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 # Config : class controls logging configuration TorrentFileHelpFormatter : HelpFormatter the command line help message formatter Classes Config \u2014 Class the controls the logging configuration and output settings. TorrentFileHelpFormatter \u2014 Formatting class for help tips provided by the CLI. Functions execute ( args ) (list) \u2014 Execute program with provided list of arguments. execute ( args ) (list) \u2014 Execute program with provided list of arguments. 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\u2019s 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 Iterate through contents of meta data and verify with file contents. 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. FileHasher \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. Coverage","title":"API"},{"location":"api/#torrentfile","text":"","title":"TorrentFile"},{"location":"api/#api-and-source","text":"","title":"API and Source"},{"location":"api/#torrent-module","text":"module torrentfile. torrent Classes and procedures pertaining to the creation of torrent meta files.","title":"Torrent Module"},{"location":"api/#classes","text":"TorrentFile construct .torrent file. TorrentFileV2 construct .torrent v2 files using provided data. MetaFile base class for all MetaFile classes.","title":"Classes"},{"location":"api/#constants","text":"BLOCK_SIZE : int size of leaf hashes for merkle tree. HASH_SIZE : int Length of a sha256 hash.","title":"Constants"},{"location":"api/#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":"api/#meta-version-2-dictionary","text":"\u201cannounce\u201d: The URL of the tracker. \u201cinfo\u201d: This maps to a dictionary, with keys described below. \u201cname\u201d: A display name for the torrent. It is purely advisory. \u201cpiece length\u201d: 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. \u201cmeta version\u201d: 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 \u201cfile tree\u201d: 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. \u2018UTF-8 encoded\u2019 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 \u2018.\u2019 and \u2018..\u2019 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. \u201clength\u201d: 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. \u201cpieces root\u201d: 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. \u201cpiece layers\u201d: 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":"api/#bittorrent-v1","text":"","title":"Bittorrent V1"},{"location":"api/#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\u2019s 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. TorrentAssembler \u2014 Assembler class for Bittorrent version 2 and hybrid meta files.","title":"v1 meta-dictionary"},{"location":"api/#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 Memoize cache. MissingPathError \u2014 Path parameter is required to specify target content. PieceLengthValueError \u2014 Piece Length parameter must equal a perfect power of 2. ArgumentError \u2014 Exception for mismatched or mistyped CLI arguments. Functions copypath ( source , dest ) \u2014 Copy the file located at source to dest. debug_is_on ( ) (bool) \u2014 Return True if debug mode is on in environment variables. 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. toggle_debug_mode ( switch_on ) \u2014 Switch the environment variable debug indicator on or off.","title":"Utils Module"},{"location":"api/#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":"api/#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 the fields that were not used by the original file creator.","title":"Keywords"},{"location":"api/#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":"api/#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.","title":"CLI Module"},{"location":"api/#functions","text":"main_script : process command line arguments and run program. activate_logger : turns on debug mode and logging facility.","title":"Functions"},{"location":"api/#classes_1","text":"Config : class controls logging configuration TorrentFileHelpFormatter : HelpFormatter the command line help message formatter Classes Config \u2014 Class the controls the logging configuration and output settings. TorrentFileHelpFormatter \u2014 Formatting class for help tips provided by the CLI. Functions execute ( args ) (list) \u2014 Execute program with provided list of arguments. execute ( args ) (list) \u2014 Execute program with provided list of arguments. main ( ) \u2014 Initiate main function for CLI script.","title":"Classes"},{"location":"api/#recheck-module","text":"module torrentfile. recheck Module container Checker Class. The CheckerClass takes a torrentfile and tha path to it\u2019s 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 Iterate through contents of meta data and verify with file contents.","title":"Recheck Module"},{"location":"api/#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. FileHasher \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. Coverage","title":"Hasher Module"},{"location":"changelog/","text":"TorrentFile # Version 0.8.5 # Fixed bug with linux platforms not installing a binary cli command Fixed debug logging errors with the rebuild command Improved log message readability Compatability upgrades for torrentfileQt synchronization Added coverage details to documentation folder Version 0.8.4 # Documentation Updates Fixed logging issues with rebuild module. Improved algorithm for rebuild module. Improved testing for rebuild module. Version 0.8.3 # Added the callback mixin to the rebuild module Fix compatability with GUI frontend torrentfileQt. Version 0.8.2 # Rebuild subcommand now checks on a hash by hash basis Fixed coverage issues Added unittests for the rebuild command Fixed bug with torrentfile creation when a file was a perfect power of 2 Reconfigured the rebuild module Reconfigured the rebuild cli flags and arguments Version 0.8.1 # Further improvements to documentation Fixed bug that interrupted the creation process when using gui version Added unittests Improved docstrings and docstring formatting Renamed a couple of methods Version 0.8.0 # overhaul documentation reconfigured CI files and configuration and packaging files Convert to pyproject.toml setuptools packaging info source Version 0.7.12 # Changed default behavior to save torrent files to cwd edited all unittests to reflect default behavior added deprecation messages for the cli arg and class paramteter last update to version 0.7.x Version 0.7.11 # Fixed issue with progress bar displaying inaccurate details Other minor bug fixes Updated output for Recheck subcommand for better readability Updated documentation Updated Readme added quiet mode to cli global options -q Added unit test to fix coverage gaps Fixed warnings created by pylint Version 0.7.10 # Added rebuild module and subcommand see docs for more info Added documentation entry for rebuild subcommand improved logging messages added unit tests improved and expanded on type hints minor bug fixes Version 0.7.9 # complete rewrite of the recheck procedures Recheck now provides more accuracy and more details improvements to the new custom progressbar changed the cli argument for the progress bar the options are now just 0 and 1 included new unit tests for all new features marked unused functions as deprecated added a new hasher object for v2 and hybrid torrents minor bug fixes and styling changes Version 0.7.8 # 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 \u2013progress flag is now \u2013noprogress Default behavior is to show progress bar use \u2013noprogress 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\u2019s 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-085","text":"Fixed bug with linux platforms not installing a binary cli command Fixed debug logging errors with the rebuild command Improved log message readability Compatability upgrades for torrentfileQt synchronization Added coverage details to documentation folder","title":"Version 0.8.5"},{"location":"changelog/#version-084","text":"Documentation Updates Fixed logging issues with rebuild module. Improved algorithm for rebuild module. Improved testing for rebuild module.","title":"Version 0.8.4"},{"location":"changelog/#version-083","text":"Added the callback mixin to the rebuild module Fix compatability with GUI frontend torrentfileQt.","title":"Version 0.8.3"},{"location":"changelog/#version-082","text":"Rebuild subcommand now checks on a hash by hash basis Fixed coverage issues Added unittests for the rebuild command Fixed bug with torrentfile creation when a file was a perfect power of 2 Reconfigured the rebuild module Reconfigured the rebuild cli flags and arguments","title":"Version 0.8.2"},{"location":"changelog/#version-081","text":"Further improvements to documentation Fixed bug that interrupted the creation process when using gui version Added unittests Improved docstrings and docstring formatting Renamed a couple of methods","title":"Version 0.8.1"},{"location":"changelog/#version-080","text":"overhaul documentation reconfigured CI files and configuration and packaging files Convert to pyproject.toml setuptools packaging info source","title":"Version 0.8.0"},{"location":"changelog/#version-0712","text":"Changed default behavior to save torrent files to cwd edited all unittests to reflect default behavior added deprecation messages for the cli arg and class paramteter last update to version 0.7.x","title":"Version 0.7.12"},{"location":"changelog/#version-0711","text":"Fixed issue with progress bar displaying inaccurate details Other minor bug fixes Updated output for Recheck subcommand for better readability Updated documentation Updated Readme added quiet mode to cli global options -q Added unit test to fix coverage gaps Fixed warnings created by pylint","title":"Version 0.7.11"},{"location":"changelog/#version-0710","text":"Added rebuild module and subcommand see docs for more info Added documentation entry for rebuild subcommand improved logging messages added unit tests improved and expanded on type hints minor bug fixes","title":"Version 0.7.10"},{"location":"changelog/#version-079","text":"complete rewrite of the recheck procedures Recheck now provides more accuracy and more details improvements to the new custom progressbar changed the cli argument for the progress bar the options are now just 0 and 1 included new unit tests for all new features marked unused functions as deprecated added a new hasher object for v2 and hybrid torrents minor bug fixes and styling changes","title":"Version 0.7.9"},{"location":"changelog/#version-078","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.8"},{"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 \u2013progress flag is now \u2013noprogress Default behavior is to show progress bar use \u2013noprogress 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\u2019s 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":"overview/","text":"torrentfile Manual # Synopsis # torrentfile is a command line utility for working with Bittorrent files(.torrent). Some of the tools available include creating torrent files, editing portions of a torrent files, checking the integrity or completeness of downloaded torrent contents, displaying details of a torrentfile, generating magnet URLs for torrentfiles, and individual or batch rebuilding of torrent contents into their original directory structure. 1 torrentfile [options] [options] -h displays all relevant command line options and subcommands. -V displays program and version. -v enables debug mode and outputs a large amount of information to the terminal. -i activates interactive mode for selecting subcommands and options. #Deprecated Sub-commands # create # alias: c The create subcommand is used for generating new torrent files. The only required argument is the path( ) to the contents file or directory. 1 2 torrentfile create [options] torrentfile c [options] -a -t --announce --tracker Adds the list of url\u2019s that follow to the list of trackers for the newly created torrent file. Example -t http://url1 https://url2 ... -p --private Indicates that the torrent will be used on a private tracker. Disables multi-tracker protocols and DHT. -w --web-seeds Adds the list of urls that follow to the list of web-seed addresses for the newly created torrent file. --http-seed Same as the -w argument except using Hoffman style web-seeds. -c --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 --piece-length Uses the number that follows as the piece-length size for the newly created torrent file. If option isn\u2019t 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 #deprecated This is now the default behaviour. 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 --progress --prog Options (0, 1): No status bar will be shown if 0. Otherwise the default and 1 argument means progress bar is shown info # alias: i Display detailed information about a torrentfile such as trackers, size of contents, Bittorrent version, any comments left, date the torrent file was created and more. There is only one positional perameter which is the path to the torrent file and there are no optional arguments. 1 2 torrentfile info torrentfile i /path/to/*.torrent The relative or absolute path to the torrent file. edit # Edit some of the different information detailed in a torrent file. The fields that are editable each have option flags detialed below. 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. alias: e 1 2 torrentfile edit [options] torrentfile e [options] -a -t --announce --tracker Adds the list of url\u2019s 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 # This feature is identical to the \u2018recheck\u2019 function provided by torrent clients. It validates that the file/directory contents match those described in the torrent file. Recheck requires two paths as arguments. The first is the path to a torrent file, and and the second is a path to the file of directory containing the downloaded data from that torrentfile. torrentfile recursively validates each file with the hashes contained in the torrentfile, and displays the total percentage of the file that matches the information from the torrent file. It is also permitted to use the contents parent directory which can help for batch processing many torrent files. alias: r , check 1 2 torrentfile recheck <*.torrent> torrentfile r <*.torrent> Magnet # Generate a magnet URL for a torrent file. alias: m 1 torrentfile magnet Rebuild # Rebuild individual or batches of torrent contents into the original file structure. The program takes a path to a torrent file or directory containing torrent files, the directory containing the torrent contents, and the destination directory to where the rebuilt torrent content wil be located. The program will recursively traverse the content directory searching for file\u2019s that match one of the meta files and creates copies of the matches to the destination directory. The original files are not effected and any existing files in the target directory will not be overwritten. alias: build , b 1 torrentfile rebuild -m -c -d -m --metafiles path to a torrent file or a directory containing torrent files for batch processing. -c --content path to where the contents for the torrentfile can be found. If the path is for a directory, the directory will be searched recusively for files that match. For deeply nested directories with lot\u2019s of files in them, this can take time. -d -destination path to a directory where the torrent will be rebuilt. Files will be copied from their original location, not moved.","title":"Overview"},{"location":"overview/#torrentfile-manual","text":"","title":"torrentfile Manual"},{"location":"overview/#synopsis","text":"torrentfile is a command line utility for working with Bittorrent files(.torrent). Some of the tools available include creating torrent files, editing portions of a torrent files, checking the integrity or completeness of downloaded torrent contents, displaying details of a torrentfile, generating magnet URLs for torrentfiles, and individual or batch rebuilding of torrent contents into their original directory structure. 1 torrentfile [options] [options] -h displays all relevant command line options and subcommands. -V displays program and version. -v enables debug mode and outputs a large amount of information to the terminal. -i activates interactive mode for selecting subcommands and options. #Deprecated","title":"Synopsis"},{"location":"overview/#sub-commands","text":"","title":"Sub-commands"},{"location":"overview/#create","text":"alias: c The create subcommand is used for generating new torrent files. The only required argument is the path( ) to the contents file or directory. 1 2 torrentfile create [options] torrentfile c [options] -a -t --announce --tracker Adds the list of url\u2019s that follow to the list of trackers for the newly created torrent file. Example -t http://url1 https://url2 ... -p --private Indicates that the torrent will be used on a private tracker. Disables multi-tracker protocols and DHT. -w --web-seeds Adds the list of urls that follow to the list of web-seed addresses for the newly created torrent file. --http-seed Same as the -w argument except using Hoffman style web-seeds. -c --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 --piece-length Uses the number that follows as the piece-length size for the newly created torrent file. If option isn\u2019t 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 #deprecated This is now the default behaviour. 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 --progress --prog Options (0, 1): No status bar will be shown if 0. Otherwise the default and 1 argument means progress bar is shown","title":"create"},{"location":"overview/#info","text":"alias: i Display detailed information about a torrentfile such as trackers, size of contents, Bittorrent version, any comments left, date the torrent file was created and more. There is only one positional perameter which is the path to the torrent file and there are no optional arguments. 1 2 torrentfile info torrentfile i /path/to/*.torrent The relative or absolute path to the torrent file.","title":"info"},{"location":"overview/#edit","text":"Edit some of the different information detailed in a torrent file. The fields that are editable each have option flags detialed below. 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. alias: e 1 2 torrentfile edit [options] torrentfile e [options] -a -t --announce --tracker Adds the list of url\u2019s 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":"overview/#recheck","text":"This feature is identical to the \u2018recheck\u2019 function provided by torrent clients. It validates that the file/directory contents match those described in the torrent file. Recheck requires two paths as arguments. The first is the path to a torrent file, and and the second is a path to the file of directory containing the downloaded data from that torrentfile. torrentfile recursively validates each file with the hashes contained in the torrentfile, and displays the total percentage of the file that matches the information from the torrent file. It is also permitted to use the contents parent directory which can help for batch processing many torrent files. alias: r , check 1 2 torrentfile recheck <*.torrent> torrentfile r <*.torrent> ","title":"recheck"},{"location":"overview/#magnet","text":"Generate a magnet URL for a torrent file. alias: m 1 torrentfile magnet ","title":"Magnet"},{"location":"overview/#rebuild","text":"Rebuild individual or batches of torrent contents into the original file structure. The program takes a path to a torrent file or directory containing torrent files, the directory containing the torrent contents, and the destination directory to where the rebuilt torrent content wil be located. The program will recursively traverse the content directory searching for file\u2019s that match one of the meta files and creates copies of the matches to the destination directory. The original files are not effected and any existing files in the target directory will not be overwritten. alias: build , b 1 torrentfile rebuild -m -c -d -m --metafiles path to a torrent file or a directory containing torrent files for batch processing. -c --content path to where the contents for the torrentfile can be found. If the path is for a directory, the directory will be searched recusively for files that match. For deeply nested directories with lot\u2019s of files in them, this can take time. -d -destination path to a directory where the torrent will be rebuilt. Files will be copied from their original location, not moved.","title":"Rebuild"},{"location":"usage/","text":"TorrentFile CLI Menu # Help Messages # Main # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Usage ===== torrentfile [options] command [command options] Command line tools for creating, editing, checking, building and interacting with Bittorrent metainfo files Options ------- -h, --help show this help message and exit -i, --interactive select program options interactively -q, --quiet Turn off all text output. -V, --version show program version and exit -v, --verbose output debug information Commands -------- create, edit, info, magnet, recheck, rebuild create (c, new) Create a new Bittorrent file. edit (e) Edit existing torrent meta file. info (i) Show detailed information about a torrent file. magnet (m) Generate magnet url from an existing Bittorrent meta file. recheck (check) Gives a detailed look at how much of the torrent is available. rebuild (build) Re-assemble files obtained from a bittorrent file into the appropriate file structure for re-seeding. Read documentation for more information, or use cases. Create # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 Usage ===== torrentfile [options] create [-h] [-a [ ...]] [-p] [-s ] [-m] [-c ] [-o ] [--cwd] [--prog PROGRESS] [--meta-version ] [--piece-length ] [-w [ ...]] [--http-seed [ ...]] [] Positional Arguments -------------------- Path to content file or directory Options ------- -h, --help show this help message and exit -a [ ...], -t [ ...], --announce [ ...], --tracker [ ...] One or more space-seperated torrent tracker url(s). -p, --private Creates private torrent with multi-tracker and DHT turned off. -s , --source Add a source string. Useful for cross-seeding. -m, --magnet -c , --comment Include a comment in file metadata -o , --out Explicitly specify the path to write the file. --cwd, --current *deprecated* Saving to current directory is default behaviour --prog PROGRESS, --progress PROGRESS Set the progress bar level. Options = 0, 1 (0) = Do not display progress bar. (1) = Display progress bar.(default) --meta-version Bittorrent metafile version. Options = 1, 2, 3 (1) = Bittorrent v1 (Default) (2) = Bittorrent v2 (3) = Bittorrent v1 & v2 hybrid --piece-length (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] -w [ ...], --web-seed [ ...] list of web addresses where torrent data exists (GetRight). --http-seed [ ...] list of URLs, addresses where content can be found (Hoffman). Edit # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 # 1 2 3 4 5 6 7 8 9 10 11 12 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 # 1 2 3 4 5 6 7 8 9 10 11 Usage ===== torrentfile m [-h] <*.torrent> Positional Arguments -------------------- <*.torrent> Path to Bittorrent meta file. Optional Arguments ------------------ -h, --help show this help message and exit","title":"Usage"},{"location":"usage/#torrentfile-cli-menu","text":"","title":"TorrentFile CLI Menu"},{"location":"usage/#help-messages","text":"","title":"Help Messages"},{"location":"usage/#main","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Usage ===== torrentfile [options] command [command options] Command line tools for creating, editing, checking, building and interacting with Bittorrent metainfo files Options ------- -h, --help show this help message and exit -i, --interactive select program options interactively -q, --quiet Turn off all text output. -V, --version show program version and exit -v, --verbose output debug information Commands -------- create, edit, info, magnet, recheck, rebuild create (c, new) Create a new Bittorrent file. edit (e) Edit existing torrent meta file. info (i) Show detailed information about a torrent file. magnet (m) Generate magnet url from an existing Bittorrent meta file. recheck (check) Gives a detailed look at how much of the torrent is available. rebuild (build) Re-assemble files obtained from a bittorrent file into the appropriate file structure for re-seeding. Read documentation for more information, or use cases.","title":"Main"},{"location":"usage/#create","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 Usage ===== torrentfile [options] create [-h] [-a [ ...]] [-p] [-s ] [-m] [-c ] [-o ] [--cwd] [--prog PROGRESS] [--meta-version ] [--piece-length ] [-w [ ...]] [--http-seed [ ...]] [] Positional Arguments -------------------- Path to content file or directory Options ------- -h, --help show this help message and exit -a [ ...], -t [ ...], --announce [ ...], --tracker [ ...] One or more space-seperated torrent tracker url(s). -p, --private Creates private torrent with multi-tracker and DHT turned off. -s , --source Add a source string. Useful for cross-seeding. -m, --magnet -c , --comment Include a comment in file metadata -o , --out Explicitly specify the path to write the file. --cwd, --current *deprecated* Saving to current directory is default behaviour --prog PROGRESS, --progress PROGRESS Set the progress bar level. Options = 0, 1 (0) = Do not display progress bar. (1) = Display progress bar.(default) --meta-version Bittorrent metafile version. Options = 1, 2, 3 (1) = Bittorrent v1 (Default) (2) = Bittorrent v2 (3) = Bittorrent v1 & v2 hybrid --piece-length (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] -w [ ...], --web-seed [ ...] list of web addresses where torrent data exists (GetRight). --http-seed [ ...] list of URLs, addresses where content can be found (Hoffman).","title":"Create"},{"location":"usage/#edit","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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":"usage/#recheck","text":"1 2 3 4 5 6 7 8 9 10 11 12 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":"usage/#magnet","text":"1 2 3 4 5 6 7 8 9 10 11 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":"Source/","text":"Source Code Modules # torrentfile is an open source project. The official source code can be viewed on github at https://github.com/alexpdev/torrentfile . Modules # cli # commands # edit # hasher # interactive # mixins # rebuild # recheck # torrent # utils # version #","title":"Source"},{"location":"Source/#source-code-modules","text":"torrentfile is an open source project. The official source code can be viewed on github at https://github.com/alexpdev/torrentfile .","title":"Source Code Modules"},{"location":"Source/#modules","text":"","title":"Modules"},{"location":"Source/#cli","text":"","title":"cli"},{"location":"Source/#commands","text":"","title":"commands"},{"location":"Source/#edit","text":"","title":"edit"},{"location":"Source/#hasher","text":"","title":"hasher"},{"location":"Source/#interactive","text":"","title":"interactive"},{"location":"Source/#mixins","text":"","title":"mixins"},{"location":"Source/#rebuild","text":"","title":"rebuild"},{"location":"Source/#recheck","text":"","title":"recheck"},{"location":"Source/#torrent","text":"","title":"torrent"},{"location":"Source/#utils","text":"","title":"utils"},{"location":"Source/#version","text":"","title":"version"},{"location":"Source/cli/","text":"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 # Config : class controls logging configuration TorrentFileHelpFormatter : HelpFormatter the command line help message formatter Config # Class the controls the logging configuration and output settings. Controls the logging level, or whether to app should operate in quiet mode. activate_logger () staticmethod # Activate the builtin logging mechanism when passed debug flag from CLI. Source code in torrentfile\\cli.py 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 @staticmethod def activate_logger (): \"\"\" Activate the builtin logging mechanism when passed debug flag from CLI. \"\"\" logging . basicConfig ( level = logging . WARNING ) logger = logging . getLogger () console_handler = logging . StreamHandler ( stream = sys . stderr ) stream_formatter = logging . Formatter ( \"[ %(asctime)s ] [ %(levelno)s ] %(message)s \" , datefmt = \"%H:%M:%S\" , style = \"%\" , ) console_handler . setFormatter ( stream_formatter ) console_handler . setLevel ( logging . DEBUG ) logger . setLevel ( logging . DEBUG ) logger . addHandler ( console_handler ) logger . debug ( \"Debug: ON\" ) toggle_debug_mode ( True ) activate_quiet () staticmethod # Activate quiet mode for the duration of the programs life. When quiet mode is enabled, no logging, progress or state information is output to the terminal Source code in torrentfile\\cli.py 60 61 62 63 64 65 66 67 68 69 70 @staticmethod def activate_quiet (): \"\"\" Activate quiet mode for the duration of the programs life. When quiet mode is enabled, no logging, progress or state information is output to the terminal \"\"\" if sys . stdout or sys . stderr : sys . stdout = io . StringIO () sys . stderr = io . StringIO () TorrentFileHelpFormatter ( prog , width = 45 , max_help_positions = 45 ) # Bases: HelpFormatter Formatting class for help tips provided by the CLI. Subclasses Argparse.HelpFormatter. Construct HelpFormat class for usage output. PARAMETER DESCRIPTION prog Name of the program. TYPE: str width Max width of help message output. TYPE: int DEFAULT: 45 max_help_positions max length until line wrap. TYPE: int DEFAULT: 45 Source code in torrentfile\\cli.py 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 def __init__ ( self , prog , width = 45 , max_help_positions = 45 ): \"\"\" 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 ) execute ( args : Optional [ list ] = None ) -> list # Execute program with provided list of arguments. If no arguments are given then it defaults to using sys.argv. This is the main entrypoint for the program and command line interface. PARAMETER DESCRIPTION args Commandline arguments. default=None TYPE: list DEFAULT: None RETURNS DESCRIPTION list Depends on what the command line args were. Source code in torrentfile\\cli.py 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 def execute ( args : Optional [ list ] = None ) -> list : \"\"\" Execute program with provided list of arguments. If no arguments are given then it defaults to using sys.argv. This is the main entrypoint for the program and command line interface. Parameters ---------- args : list Commandline arguments. default=None Returns ------- list Depends on what the command line args were. \"\"\" toggle_debug_mode ( False ) if not args : if sys . argv [ 1 :]: args = sys . argv [ 1 :] else : args = [ \"-h\" ] parser = ArgumentParser ( \"torrentfile\" , usage = \"torrentfile \" , description = ( \"Command line tools for creating, editing, checking, building \" \"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 ( \"-q\" , \"--quiet\" , help = \"Turn off all text output.\" , dest = \"quiet\" , action = \"store_true\" , ) 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 = \"Commands\" , dest = \"command\" , metavar = \"create, edit, info, magnet, recheck, rebuild \\n \" , ) create_parser = subparsers . add_parser ( \"create\" , help = \"Create a new Bittorrent file.\" , prefix_chars = \"-\" , aliases = [ \"c\" , \"new\" , \"build\" ], 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 = \"Explicitly specify the path to write the file.\" , ) create_parser . add_argument ( \"--cwd\" , \"--current\" , action = \"store_true\" , dest = \"cwd\" , help = \"*deprecated* Saving to current directory is default behaviour\" , ) create_parser . add_argument ( \"--prog\" , \"--progress\" , default = \"1\" , action = \"store\" , dest = \"progress\" , help = \"\"\" Set the progress bar level. Options = 0, 1 (0) = Do not display progress bar. (1) = Display progress bar.(default) \"\"\" , ) 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 ) 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 ) 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 = \"Gives a detailed look at how much of the torrent is available.\" , aliases = [ \"check\" , \"r\" ], 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 ) rebuild_parser = subparsers . add_parser ( \"rebuild\" , aliases = [ \"build\" ], help = \"\"\"Re-assemble files obtained from a bittorrent file into the appropriate file structure for re-seeding. Read documentation for more information, or use cases.\"\"\" , formatter_class = TorrentFileHelpFormatter , ) rebuild_parser . add_argument ( \"-m\" , \"--metafiles\" , action = \"store\" , metavar = \"<*.torrent>\" , nargs = \"+\" , dest = \"metafiles\" , required = True , help = \"path(s) to .torrent file(s)/folder(s) containing .torrent files\" , ) rebuild_parser . add_argument ( \"-c\" \"--contents\" , action = \"store\" , dest = \"contents\" , nargs = \"+\" , required = True , metavar = \"\" , help = \"folders that might contain the source contents needed to rebuld\" , ) rebuild_parser . add_argument ( \"-d\" , \"--destination\" , action = \"store\" , dest = \"destination\" , required = True , metavar = \"\" , help = \"path to where torrents will be re-assembled\" , ) rebuild_parser . set_defaults ( func = rebuild ) args = parser . parse_args ( args ) if args . quiet : Config . activate_quiet () elif args . debug : Config . activate_logger () if args . interactive : return select_action () if hasattr ( args , \"func\" ): return args . func ( args ) return args # pragma: nocover main () # Initiate main function for CLI script. Source code in torrentfile\\cli.py 604 605 606 607 608 def main (): \"\"\" Initiate main function for CLI script. \"\"\" execute ()","title":"Cli"},{"location":"Source/cli/#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.","title":"cli"},{"location":"Source/cli/#torrentfile.cli--functions","text":"main_script : process command line arguments and run program. activate_logger : turns on debug mode and logging facility.","title":"Functions"},{"location":"Source/cli/#torrentfile.cli--classes","text":"Config : class controls logging configuration TorrentFileHelpFormatter : HelpFormatter the command line help message formatter","title":"Classes"},{"location":"Source/cli/#torrentfile.cli.Config","text":"Class the controls the logging configuration and output settings. Controls the logging level, or whether to app should operate in quiet mode.","title":"Config"},{"location":"Source/cli/#torrentfile.cli.Config.activate_logger","text":"Activate the builtin logging mechanism when passed debug flag from CLI. Source code in torrentfile\\cli.py 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 @staticmethod def activate_logger (): \"\"\" Activate the builtin logging mechanism when passed debug flag from CLI. \"\"\" logging . basicConfig ( level = logging . WARNING ) logger = logging . getLogger () console_handler = logging . StreamHandler ( stream = sys . stderr ) stream_formatter = logging . Formatter ( \"[ %(asctime)s ] [ %(levelno)s ] %(message)s \" , datefmt = \"%H:%M:%S\" , style = \"%\" , ) console_handler . setFormatter ( stream_formatter ) console_handler . setLevel ( logging . DEBUG ) logger . setLevel ( logging . DEBUG ) logger . addHandler ( console_handler ) logger . debug ( \"Debug: ON\" ) toggle_debug_mode ( True )","title":"activate_logger()"},{"location":"Source/cli/#torrentfile.cli.Config.activate_quiet","text":"Activate quiet mode for the duration of the programs life. When quiet mode is enabled, no logging, progress or state information is output to the terminal Source code in torrentfile\\cli.py 60 61 62 63 64 65 66 67 68 69 70 @staticmethod def activate_quiet (): \"\"\" Activate quiet mode for the duration of the programs life. When quiet mode is enabled, no logging, progress or state information is output to the terminal \"\"\" if sys . stdout or sys . stderr : sys . stdout = io . StringIO () sys . stderr = io . StringIO ()","title":"activate_quiet()"},{"location":"Source/cli/#torrentfile.cli.TorrentFileHelpFormatter","text":"Bases: HelpFormatter Formatting class for help tips provided by the CLI. Subclasses Argparse.HelpFormatter. Construct HelpFormat class for usage output. PARAMETER DESCRIPTION prog Name of the program. TYPE: str width Max width of help message output. TYPE: int DEFAULT: 45 max_help_positions max length until line wrap. TYPE: int DEFAULT: 45 Source code in torrentfile\\cli.py 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 def __init__ ( self , prog , width = 45 , max_help_positions = 45 ): \"\"\" 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":"TorrentFileHelpFormatter"},{"location":"Source/cli/#torrentfile.cli.execute","text":"Execute program with provided list of arguments. If no arguments are given then it defaults to using sys.argv. This is the main entrypoint for the program and command line interface. PARAMETER DESCRIPTION args Commandline arguments. default=None TYPE: list DEFAULT: None RETURNS DESCRIPTION list Depends on what the command line args were. Source code in torrentfile\\cli.py 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 def execute ( args : Optional [ list ] = None ) -> list : \"\"\" Execute program with provided list of arguments. If no arguments are given then it defaults to using sys.argv. This is the main entrypoint for the program and command line interface. Parameters ---------- args : list Commandline arguments. default=None Returns ------- list Depends on what the command line args were. \"\"\" toggle_debug_mode ( False ) if not args : if sys . argv [ 1 :]: args = sys . argv [ 1 :] else : args = [ \"-h\" ] parser = ArgumentParser ( \"torrentfile\" , usage = \"torrentfile \" , description = ( \"Command line tools for creating, editing, checking, building \" \"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 ( \"-q\" , \"--quiet\" , help = \"Turn off all text output.\" , dest = \"quiet\" , action = \"store_true\" , ) 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 = \"Commands\" , dest = \"command\" , metavar = \"create, edit, info, magnet, recheck, rebuild \\n \" , ) create_parser = subparsers . add_parser ( \"create\" , help = \"Create a new Bittorrent file.\" , prefix_chars = \"-\" , aliases = [ \"c\" , \"new\" , \"build\" ], 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 = \"Explicitly specify the path to write the file.\" , ) create_parser . add_argument ( \"--cwd\" , \"--current\" , action = \"store_true\" , dest = \"cwd\" , help = \"*deprecated* Saving to current directory is default behaviour\" , ) create_parser . add_argument ( \"--prog\" , \"--progress\" , default = \"1\" , action = \"store\" , dest = \"progress\" , help = \"\"\" Set the progress bar level. Options = 0, 1 (0) = Do not display progress bar. (1) = Display progress bar.(default) \"\"\" , ) 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 ) 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 ) 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 = \"Gives a detailed look at how much of the torrent is available.\" , aliases = [ \"check\" , \"r\" ], 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 ) rebuild_parser = subparsers . add_parser ( \"rebuild\" , aliases = [ \"build\" ], help = \"\"\"Re-assemble files obtained from a bittorrent file into the appropriate file structure for re-seeding. Read documentation for more information, or use cases.\"\"\" , formatter_class = TorrentFileHelpFormatter , ) rebuild_parser . add_argument ( \"-m\" , \"--metafiles\" , action = \"store\" , metavar = \"<*.torrent>\" , nargs = \"+\" , dest = \"metafiles\" , required = True , help = \"path(s) to .torrent file(s)/folder(s) containing .torrent files\" , ) rebuild_parser . add_argument ( \"-c\" \"--contents\" , action = \"store\" , dest = \"contents\" , nargs = \"+\" , required = True , metavar = \"\" , help = \"folders that might contain the source contents needed to rebuld\" , ) rebuild_parser . add_argument ( \"-d\" , \"--destination\" , action = \"store\" , dest = \"destination\" , required = True , metavar = \"\" , help = \"path to where torrents will be re-assembled\" , ) rebuild_parser . set_defaults ( func = rebuild ) args = parser . parse_args ( args ) if args . quiet : Config . activate_quiet () elif args . debug : Config . activate_logger () if args . interactive : return select_action () if hasattr ( args , \"func\" ): return args . func ( args ) return args # pragma: nocover","title":"execute()"},{"location":"Source/cli/#torrentfile.cli.main","text":"Initiate main function for CLI script. Source code in torrentfile\\cli.py 604 605 606 607 608 def main (): \"\"\" Initiate main function for CLI script. \"\"\" execute ()","title":"main()"},{"location":"Source/commands/","text":"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 : Namespace ) -> Namespace # Execute the create CLI sub-command to create a new torrent metafile. PARAMETER DESCRIPTION args positional and optional CLI arguments. TYPE: Namespace RETURNS DESCRIPTION torrentfile . MetaFile object containing the path to created metafile and its contents. Source code in torrentfile\\commands.py 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 def create ( args : Namespace ) -> Namespace : \"\"\" Execute the create CLI sub-command to create a new torrent metafile. Parameters ---------- args : Namespace positional and optional CLI arguments. Returns ------- torrentfile.MetaFile object containing the path to created metafile and its contents. \"\"\" kwargs = vars ( args ) logger . debug ( \"Creating torrent from %s \" , args . content ) if args . meta_version == \"1\" : torrent = TorrentFile ( ** kwargs ) else : torrent = TorrentAssembler ( ** kwargs ) outfile , meta = torrent . write () if args . magnet : magnet ( outfile ) args . torrent = torrent args . kwargs = kwargs args . outfile = outfile args . meta = meta print ( \" \\n Torrent Save Path: \" , os . path . abspath ( str ( outfile ))) logger . debug ( \"Output path: %s \" , str ( outfile )) return args edit ( args : Namespace ) -> str # Execute the edit CLI sub-command with provided arguments. Provides functionality that can change the details of a torrentfile that preserves all of the hash piece information so as not to break the torrentfile. PARAMETER DESCRIPTION args positional and optional CLI arguments. TYPE: Namespace RETURNS DESCRIPTION str path to edited torrent file. Source code in torrentfile\\commands.py 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 def edit ( args : Namespace ) -> str : \"\"\" Execute the edit CLI sub-command with provided arguments. Provides functionality that can change the details of a torrentfile that preserves all of the hash piece information so as not to break the torrentfile. 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 : Namespace ) -> str # Show torrent metafile details to user via stdout. Prints full details of torrent file contents to the terminal in a clean and readable format. PARAMETER DESCRIPTION args command line arguements provided by the user. TYPE: dict RETURNS DESCRIPTION str The output printed to the terminal. Source code in torrentfile\\commands.py 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 def info ( args : Namespace ) -> str : \"\"\" Show torrent metafile details to user via stdout. Prints full details of torrent file contents to the terminal in a clean and readable format. Parameters ---------- args : dict command line arguements provided by the user. Returns ------- str The output printed to the terminal. \"\"\" metafile = args . metafile meta = pyben . load ( metafile ) data = meta [ \"info\" ] del meta [ \"info\" ] meta . update ( data ) 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 : Namespace ) -> str # Create a magnet URI from a Bittorrent meta file. PARAMETER DESCRIPTION metafile Namespace class for CLI arguments. TYPE: Namespace RETURNS DESCRIPTION str created magnet URI. Source code in torrentfile\\commands.py 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 def magnet ( metafile : Namespace ) -> str : \"\"\" Create a magnet URI from a Bittorrent meta file. Parameters ---------- metafile : Namespace 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 ) data = meta [ \"info\" ] binfo = pyben . dumps ( data ) infohash = sha1 ( binfo ) . hexdigest () . upper () # nosec logger . info ( \"Magnet Info Hash: %s \" , infohash ) scheme = \"magnet:\" hasharg = \"?xt=urn:btih:\" + infohash namearg = \"&dn=\" + quote_plus ( data [ \"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 rebuild ( args : Namespace ) -> int # Attempt to rebuild a torrent based on the a torrent file. Recursively look through a directory for files that belong in a given torrent file, and rebuild as much of the torrent file as possible. Currently only checks if the filename and file size are a match. TODO # Check file hashes to improve accuracy PARAMETER DESCRIPTION args command line arguments including the paths neccessary TYPE: Namespace RETURNS DESCRIPTION int total number of content files copied to the rebuild directory Source code in torrentfile\\commands.py 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 def rebuild ( args : Namespace ) -> int : \"\"\" Attempt to rebuild a torrent based on the a torrent file. Recursively look through a directory for files that belong in a given torrent file, and rebuild as much of the torrent file as possible. Currently only checks if the filename and file size are a match. #### TODO 1. Check file hashes to improve accuracy Parameters ---------- args : Namespace command line arguments including the paths neccessary Returns ------- int total number of content files copied to the rebuild directory \"\"\" metafiles = args . metafiles dest = args . destination contents = args . contents for path in [ * metafiles , * contents ]: if not os . path . exists ( path ): raise FileNotFoundError ( path ) assembler = Assembler ( metafiles , contents , dest ) return assembler . assemble_torrents () recheck ( args : Namespace ) -> str # Execute recheck CLI sub-command. Checks the piece hashes within a pre-existing torrent file and does a piece by piece check with the contents of a file or directory for completeness and validation. PARAMETER DESCRIPTION args positional and optional arguments. TYPE: Namespace RETURNS DESCRIPTION str The percentage of content currently saved to disk. Source code in torrentfile\\commands.py 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 def recheck ( args : Namespace ) -> str : \"\"\" Execute recheck CLI sub-command. Checks the piece hashes within a pre-existing torrent file and does a piece by piece check with the contents of a file or directory for completeness and validation. Parameters ---------- args : Namespace positional and optional arguments. Returns ------- str The percentage of content currently saved to disk. \"\"\" metafile = args . metafile content = args . content if os . path . isdir ( metafile ): raise ArgumentError ( f \"Error: Unable to parse directory { metafile } . \" \"Check the order of the parameters.\" ) logger . debug ( \"Validating %s <---------------> %s contents\" , metafile , content ) msg = f \"Rechecking { metafile } ... \\n \" halfterm = shutil . get_terminal_size () . columns / 2 padding = int ( halfterm - ( len ( msg ) / 2 )) * \" \" sys . stdout . write ( padding + msg ) checker = Checker ( metafile , content ) logger . debug ( \"Completed initialization of the Checker class\" ) result = checker . results () message = f \" { content } <- { result } % -> { metafile } \" padding = int ( halfterm - ( len ( message ) / 2 )) * \" \" sys . stdout . write ( padding + message + \" \\n \" ) sys . stdout . flush () return result","title":"Commands"},{"location":"Source/commands/#torrentfile.commands","text":"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.","title":"commands"},{"location":"Source/commands/#torrentfile.commands--functions","text":"create_command info_command edit_command recheck_command magnet_command","title":"Functions"},{"location":"Source/commands/#torrentfile.commands.create","text":"Execute the create CLI sub-command to create a new torrent metafile. PARAMETER DESCRIPTION args positional and optional CLI arguments. TYPE: Namespace RETURNS DESCRIPTION torrentfile . MetaFile object containing the path to created metafile and its contents. Source code in torrentfile\\commands.py 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 def create ( args : Namespace ) -> Namespace : \"\"\" Execute the create CLI sub-command to create a new torrent metafile. Parameters ---------- args : Namespace positional and optional CLI arguments. Returns ------- torrentfile.MetaFile object containing the path to created metafile and its contents. \"\"\" kwargs = vars ( args ) logger . debug ( \"Creating torrent from %s \" , args . content ) if args . meta_version == \"1\" : torrent = TorrentFile ( ** kwargs ) else : torrent = TorrentAssembler ( ** kwargs ) outfile , meta = torrent . write () if args . magnet : magnet ( outfile ) args . torrent = torrent args . kwargs = kwargs args . outfile = outfile args . meta = meta print ( \" \\n Torrent Save Path: \" , os . path . abspath ( str ( outfile ))) logger . debug ( \"Output path: %s \" , str ( outfile )) return args","title":"create()"},{"location":"Source/commands/#torrentfile.commands.edit","text":"Execute the edit CLI sub-command with provided arguments. Provides functionality that can change the details of a torrentfile that preserves all of the hash piece information so as not to break the torrentfile. PARAMETER DESCRIPTION args positional and optional CLI arguments. TYPE: Namespace RETURNS DESCRIPTION str path to edited torrent file. Source code in torrentfile\\commands.py 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 def edit ( args : Namespace ) -> str : \"\"\" Execute the edit CLI sub-command with provided arguments. Provides functionality that can change the details of a torrentfile that preserves all of the hash piece information so as not to break the torrentfile. 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 )","title":"edit()"},{"location":"Source/commands/#torrentfile.commands.info","text":"Show torrent metafile details to user via stdout. Prints full details of torrent file contents to the terminal in a clean and readable format. PARAMETER DESCRIPTION args command line arguements provided by the user. TYPE: dict RETURNS DESCRIPTION str The output printed to the terminal. Source code in torrentfile\\commands.py 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 def info ( args : Namespace ) -> str : \"\"\" Show torrent metafile details to user via stdout. Prints full details of torrent file contents to the terminal in a clean and readable format. Parameters ---------- args : dict command line arguements provided by the user. Returns ------- str The output printed to the terminal. \"\"\" metafile = args . metafile meta = pyben . load ( metafile ) data = meta [ \"info\" ] del meta [ \"info\" ] meta . update ( data ) 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","title":"info()"},{"location":"Source/commands/#torrentfile.commands.magnet","text":"Create a magnet URI from a Bittorrent meta file. PARAMETER DESCRIPTION metafile Namespace class for CLI arguments. TYPE: Namespace RETURNS DESCRIPTION str created magnet URI. Source code in torrentfile\\commands.py 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 def magnet ( metafile : Namespace ) -> str : \"\"\" Create a magnet URI from a Bittorrent meta file. Parameters ---------- metafile : Namespace 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 ) data = meta [ \"info\" ] binfo = pyben . dumps ( data ) infohash = sha1 ( binfo ) . hexdigest () . upper () # nosec logger . info ( \"Magnet Info Hash: %s \" , infohash ) scheme = \"magnet:\" hasharg = \"?xt=urn:btih:\" + infohash namearg = \"&dn=\" + quote_plus ( data [ \"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","title":"magnet()"},{"location":"Source/commands/#torrentfile.commands.rebuild","text":"Attempt to rebuild a torrent based on the a torrent file. Recursively look through a directory for files that belong in a given torrent file, and rebuild as much of the torrent file as possible. Currently only checks if the filename and file size are a match.","title":"rebuild()"},{"location":"Source/commands/#torrentfile.commands.rebuild--todo","text":"Check file hashes to improve accuracy PARAMETER DESCRIPTION args command line arguments including the paths neccessary TYPE: Namespace RETURNS DESCRIPTION int total number of content files copied to the rebuild directory Source code in torrentfile\\commands.py 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 def rebuild ( args : Namespace ) -> int : \"\"\" Attempt to rebuild a torrent based on the a torrent file. Recursively look through a directory for files that belong in a given torrent file, and rebuild as much of the torrent file as possible. Currently only checks if the filename and file size are a match. #### TODO 1. Check file hashes to improve accuracy Parameters ---------- args : Namespace command line arguments including the paths neccessary Returns ------- int total number of content files copied to the rebuild directory \"\"\" metafiles = args . metafiles dest = args . destination contents = args . contents for path in [ * metafiles , * contents ]: if not os . path . exists ( path ): raise FileNotFoundError ( path ) assembler = Assembler ( metafiles , contents , dest ) return assembler . assemble_torrents ()","title":"TODO"},{"location":"Source/commands/#torrentfile.commands.recheck","text":"Execute recheck CLI sub-command. Checks the piece hashes within a pre-existing torrent file and does a piece by piece check with the contents of a file or directory for completeness and validation. PARAMETER DESCRIPTION args positional and optional arguments. TYPE: Namespace RETURNS DESCRIPTION str The percentage of content currently saved to disk. Source code in torrentfile\\commands.py 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 def recheck ( args : Namespace ) -> str : \"\"\" Execute recheck CLI sub-command. Checks the piece hashes within a pre-existing torrent file and does a piece by piece check with the contents of a file or directory for completeness and validation. Parameters ---------- args : Namespace positional and optional arguments. Returns ------- str The percentage of content currently saved to disk. \"\"\" metafile = args . metafile content = args . content if os . path . isdir ( metafile ): raise ArgumentError ( f \"Error: Unable to parse directory { metafile } . \" \"Check the order of the parameters.\" ) logger . debug ( \"Validating %s <---------------> %s contents\" , metafile , content ) msg = f \"Rechecking { metafile } ... \\n \" halfterm = shutil . get_terminal_size () . columns / 2 padding = int ( halfterm - ( len ( msg ) / 2 )) * \" \" sys . stdout . write ( padding + msg ) checker = Checker ( metafile , content ) logger . debug ( \"Completed initialization of the Checker class\" ) result = checker . results () message = f \" { content } <- { result } % -> { metafile } \" padding = int ( halfterm - ( len ( message ) / 2 )) * \" \" sys . stdout . write ( padding + message + \" \\n \" ) sys . stdout . flush () return result","title":"recheck()"},{"location":"Source/edit/","text":"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 : str , args : dict ) -> dict # Edit the properties and values in a torrent meta file. PARAMETER DESCRIPTION metafile path to the torrent meta file. TYPE: str args key value pairs of the properties to be edited. TYPE: dict RETURNS DESCRIPTION dict The edited and nested Meta and info dictionaries. Source code in torrentfile\\edit.py 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 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 : dict , meta : dict , info : dict ) # Remove the fields that were not used by the original file creator. PARAMETER DESCRIPTION args Editable metafile properties from user. TYPE: dict meta Metafile data dictionary. TYPE: dict info Metafile info dictionary. TYPE: dict Source code in torrentfile\\edit.py 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 def filter_empty ( args : dict , meta : dict , info : dict ): \"\"\" Remove the fields that were not used by the original file creator. 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 )","title":"Edit"},{"location":"Source/edit/#torrentfile.edit","text":"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"},{"location":"Source/edit/#torrentfile.edit--keywords","text":"private comment source trackers web-seeds","title":"Keywords"},{"location":"Source/edit/#torrentfile.edit.edit_torrent","text":"Edit the properties and values in a torrent meta file. PARAMETER DESCRIPTION metafile path to the torrent meta file. TYPE: str args key value pairs of the properties to be edited. TYPE: dict RETURNS DESCRIPTION dict The edited and nested Meta and info dictionaries. Source code in torrentfile\\edit.py 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 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","title":"edit_torrent()"},{"location":"Source/edit/#torrentfile.edit.filter_empty","text":"Remove the fields that were not used by the original file creator. PARAMETER DESCRIPTION args Editable metafile properties from user. TYPE: dict meta Metafile data dictionary. TYPE: dict info Metafile info dictionary. TYPE: dict Source code in torrentfile\\edit.py 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 def filter_empty ( args : dict , meta : dict , info : dict ): \"\"\" Remove the fields that were not used by the original file creator. 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 )","title":"filter_empty()"},{"location":"Source/hasher/","text":"hasher # Piece/File Hashers for Bittorrent meta file contents. FileHasher ( path : str , piece_length : int , progress : bool = True , hybrid : bool = False ) # Bases: 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. PARAMETER DESCRIPTION path path to target file. TYPE: str piece_length piece length for data chunks. TYPE: int progress default = None TYPE: int DEFAULT: True Construct Hasher class instances for each file in torrent. Source code in torrentfile\\hasher.py 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 def __init__ ( self , path : str , piece_length : int , progress : bool = True , hybrid : bool = False , ): \"\"\" 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 self . amount = piece_length // BLOCK_SIZE self . end = False self . current = open ( path , \"rb\" ) self . hybrid = hybrid if progress : self . progressbar = True self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) __iter__ () # Return self : needed to implement iterator implementation. Source code in torrentfile\\hasher.py 442 443 444 def __iter__ ( self ): \"\"\"Return `self`: needed to implement iterator implementation.\"\"\" return self __next__ () -> bytes # Calculate layer hashes for contents of file. RETURNS DESCRIPTION bytes The layer merckle root hash. Source code in torrentfile\\hasher.py 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 def __next__ ( self ) -> bytes : \"\"\" Calculate layer hashes for contents of file. Returns ------- bytes The layer merckle root hash. \"\"\" if self . end : self . end = False raise StopIteration plength = self . piece_length blocks = [] piece = sha1 () # nosec total = 0 block = bytearray ( BLOCK_SIZE ) for _ in range ( self . amount ): size = self . current . readinto ( block ) if not size : self . end = True break total += size plength -= size blocks . append ( sha256 ( block [: size ]) . digest ()) if self . hybrid : piece . update ( block [: size ]) if not blocks : self . _calculate_root () raise StopIteration if len ( blocks ) != self . amount : padding = self . _pad_remaining ( len ( blocks )) blocks . extend ( padding ) self . prog_update ( total ) layer_hash = merkle_root ( blocks ) self . layer_hashes . append ( layer_hash ) self . cb ( layer_hash ) if self . end : self . _calculate_root () self . prog_close () if self . hybrid : if plength > 0 : self . padding_file = { \"attr\" : \"p\" , \"length\" : plength , \"path\" : [ \".pad\" , str ( plength )], } piece . update ( bytes ( plength )) piece = piece . digest () self . pieces . append ( piece ) return layer_hash , piece return layer_hash Hasher ( paths : list , piece_length : int , progress : bool = True ) # Bases: 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. PARAMETER DESCRIPTION paths List of files. TYPE: list piece_length Size of chuncks to split the data into. TYPE: int progress default = None TYPE: int DEFAULT: True Generate hashes of piece length data from filelist contents. Source code in torrentfile\\hasher.py 54 55 56 57 58 59 60 61 62 63 64 65 def __init__ ( self , paths : list , piece_length : int , progress : bool = True ): \"\"\"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 : total = os . path . getsize ( self . paths [ 0 ]) self . prog_start ( total , self . paths [ 0 ], unit = \"bytes\" ) logger . debug ( \"Hashing %s \" , str ( self . paths [ 0 ])) __iter__ () # Iterate through feed pieces. RETURNS DESCRIPTION self Iterator for leaves/hash pieces. TYPE: iterator Source code in torrentfile\\hasher.py 67 68 69 70 71 72 73 74 75 76 def __iter__ ( self ): \"\"\" Iterate through feed pieces. Returns ------- self : iterator Iterator for leaves/hash pieces. \"\"\" return self __next__ () -> bytes # Generate piece-length pieces of data from input file list. RETURNS DESCRIPTION bytes SHA1 hash of the piece extracted. Source code in torrentfile\\hasher.py 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 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 () -> bool # Seemlessly transition to next file in file list. RETURNS DESCRIPTION bool True if there is a next file otherwise False. TYPE: bool Source code in torrentfile\\hasher.py 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 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 ] logger . debug ( \"Hashing %s \" , str ( path )) self . current . close () if self . progress : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . current = open ( path , \"rb\" ) return True return False HasherHybrid ( path : str , piece_length : int , progress : bool = True ) # Bases: CbMixin , ProgMixin Calculate root and piece hashes for creating hybrid torrent file. DEPRECATED 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. PARAMETER DESCRIPTION path path to target file. TYPE: str piece_length piece length for data chunks. TYPE: int progress default = None TYPE: int DEFAULT: True Construct Hasher class instances for each file in torrent. DEPRECATED Source code in torrentfile\\hasher.py 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 def __init__ ( self , path : str , piece_length : int , progress : bool = True ): \"\"\" Construct Hasher class instances for each file in torrent. **DEPRECATED** \"\"\" 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 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . amount = piece_length // BLOCK_SIZE with open ( path , \"rb\" ) as data : self . process_file ( data ) process_file ( data : bytearray ) # Calculate layer hashes for contents of file. DEPRECATED PARAMETER DESCRIPTION data File opened in read mode. TYPE: BytesIO Source code in torrentfile\\hasher.py 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 def process_file ( self , data : bytearray ): \"\"\" Calculate layer hashes for contents of file. **DEPRECATED** 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 ) 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 ( path : str , piece_length : int , progress : bool = True ) # Bases: CbMixin , ProgMixin Calculate the root hash and piece layers for file contents. DEPRECATED 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. PARAMETER DESCRIPTION path Path to file. TYPE: str piece_length Size of layer hashes pieces. TYPE: int progress default = None TYPE: int DEFAULT: True Calculate and store hash information for specific file. DEPRECATED Source code in torrentfile\\hasher.py 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 def __init__ ( self , path : str , piece_length : int , progress : bool = True ): \"\"\" Calculate and store hash information for specific file. **DEPRECATED** \"\"\" 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 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) with open ( self . path , \"rb\" ) as fd : self . process_file ( fd ) process_file ( fd : str ) # Calculate hashes over 16KiB chuncks of file content. DEPRECATED PARAMETER DESCRIPTION fd Opened file in read mode. TYPE: TextIOWrapper Source code in torrentfile\\hasher.py 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 def process_file ( self , fd : str ): \"\"\" Calculate hashes over 16KiB chuncks of file content. **DEPRECATED** Parameters ---------- fd : TextIOWrapper 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 ) self . cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) self . _calculate_root () self . prog_close () merkle_root ( blocks : list ) -> bytes # Calculate the merkle root for a seq of sha256 hash digests. PARAMETER DESCRIPTION blocks a sequence of sha256 layer hashes. TYPE: list RETURNS DESCRIPTION bytes the sha256 root hash of the merkle tree. Source code in torrentfile\\hasher.py 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 def merkle_root ( blocks : list ) -> bytes : \"\"\" Calculate the merkle root for a seq of sha256 hash digests. Parameters ---------- blocks : list a sequence of sha256 layer hashes. Returns ------- bytes the sha256 root hash of the merkle tree. \"\"\" if blocks : while len ( blocks ) > 1 : blocks = [ sha256 ( x + y ) . digest () for x , y in zip ( * [ iter ( blocks )] * 2 ) ] return blocks [ 0 ] return blocks","title":"Hasher"},{"location":"Source/hasher/#torrentfile.hasher","text":"Piece/File Hashers for Bittorrent meta file contents.","title":"hasher"},{"location":"Source/hasher/#torrentfile.hasher.FileHasher","text":"Bases: 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. PARAMETER DESCRIPTION path path to target file. TYPE: str piece_length piece length for data chunks. TYPE: int progress default = None TYPE: int DEFAULT: True Construct Hasher class instances for each file in torrent. Source code in torrentfile\\hasher.py 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 def __init__ ( self , path : str , piece_length : int , progress : bool = True , hybrid : bool = False , ): \"\"\" 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 self . amount = piece_length // BLOCK_SIZE self . end = False self . current = open ( path , \"rb\" ) self . hybrid = hybrid if progress : self . progressbar = True self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" )","title":"FileHasher"},{"location":"Source/hasher/#torrentfile.hasher.FileHasher.__iter__","text":"Return self : needed to implement iterator implementation. Source code in torrentfile\\hasher.py 442 443 444 def __iter__ ( self ): \"\"\"Return `self`: needed to implement iterator implementation.\"\"\" return self","title":"__iter__()"},{"location":"Source/hasher/#torrentfile.hasher.FileHasher.__next__","text":"Calculate layer hashes for contents of file. RETURNS DESCRIPTION bytes The layer merckle root hash. Source code in torrentfile\\hasher.py 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 def __next__ ( self ) -> bytes : \"\"\" Calculate layer hashes for contents of file. Returns ------- bytes The layer merckle root hash. \"\"\" if self . end : self . end = False raise StopIteration plength = self . piece_length blocks = [] piece = sha1 () # nosec total = 0 block = bytearray ( BLOCK_SIZE ) for _ in range ( self . amount ): size = self . current . readinto ( block ) if not size : self . end = True break total += size plength -= size blocks . append ( sha256 ( block [: size ]) . digest ()) if self . hybrid : piece . update ( block [: size ]) if not blocks : self . _calculate_root () raise StopIteration if len ( blocks ) != self . amount : padding = self . _pad_remaining ( len ( blocks )) blocks . extend ( padding ) self . prog_update ( total ) layer_hash = merkle_root ( blocks ) self . layer_hashes . append ( layer_hash ) self . cb ( layer_hash ) if self . end : self . _calculate_root () self . prog_close () if self . hybrid : if plength > 0 : self . padding_file = { \"attr\" : \"p\" , \"length\" : plength , \"path\" : [ \".pad\" , str ( plength )], } piece . update ( bytes ( plength )) piece = piece . digest () self . pieces . append ( piece ) return layer_hash , piece return layer_hash","title":"__next__()"},{"location":"Source/hasher/#torrentfile.hasher.Hasher","text":"Bases: 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. PARAMETER DESCRIPTION paths List of files. TYPE: list piece_length Size of chuncks to split the data into. TYPE: int progress default = None TYPE: int DEFAULT: True Generate hashes of piece length data from filelist contents. Source code in torrentfile\\hasher.py 54 55 56 57 58 59 60 61 62 63 64 65 def __init__ ( self , paths : list , piece_length : int , progress : bool = True ): \"\"\"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 : total = os . path . getsize ( self . paths [ 0 ]) self . prog_start ( total , self . paths [ 0 ], unit = \"bytes\" ) logger . debug ( \"Hashing %s \" , str ( self . paths [ 0 ]))","title":"Hasher"},{"location":"Source/hasher/#torrentfile.hasher.Hasher.__iter__","text":"Iterate through feed pieces. RETURNS DESCRIPTION self Iterator for leaves/hash pieces. TYPE: iterator Source code in torrentfile\\hasher.py 67 68 69 70 71 72 73 74 75 76 def __iter__ ( self ): \"\"\" Iterate through feed pieces. Returns ------- self : iterator Iterator for leaves/hash pieces. \"\"\" return self","title":"__iter__()"},{"location":"Source/hasher/#torrentfile.hasher.Hasher.__next__","text":"Generate piece-length pieces of data from input file list. RETURNS DESCRIPTION bytes SHA1 hash of the piece extracted. Source code in torrentfile\\hasher.py 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 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","title":"__next__()"},{"location":"Source/hasher/#torrentfile.hasher.Hasher.next_file","text":"Seemlessly transition to next file in file list. RETURNS DESCRIPTION bool True if there is a next file otherwise False. TYPE: bool Source code in torrentfile\\hasher.py 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 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 ] logger . debug ( \"Hashing %s \" , str ( path )) self . current . close () if self . progress : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . current = open ( path , \"rb\" ) return True return False","title":"next_file()"},{"location":"Source/hasher/#torrentfile.hasher.HasherHybrid","text":"Bases: CbMixin , ProgMixin Calculate root and piece hashes for creating hybrid torrent file. DEPRECATED 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. PARAMETER DESCRIPTION path path to target file. TYPE: str piece_length piece length for data chunks. TYPE: int progress default = None TYPE: int DEFAULT: True Construct Hasher class instances for each file in torrent. DEPRECATED Source code in torrentfile\\hasher.py 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 def __init__ ( self , path : str , piece_length : int , progress : bool = True ): \"\"\" Construct Hasher class instances for each file in torrent. **DEPRECATED** \"\"\" 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 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) self . amount = piece_length // BLOCK_SIZE with open ( path , \"rb\" ) as data : self . process_file ( data )","title":"HasherHybrid"},{"location":"Source/hasher/#torrentfile.hasher.HasherHybrid.process_file","text":"Calculate layer hashes for contents of file. DEPRECATED PARAMETER DESCRIPTION data File opened in read mode. TYPE: BytesIO Source code in torrentfile\\hasher.py 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 def process_file ( self , data : bytearray ): \"\"\" Calculate layer hashes for contents of file. **DEPRECATED** 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 ) 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 ()","title":"process_file()"},{"location":"Source/hasher/#torrentfile.hasher.HasherV2","text":"Bases: CbMixin , ProgMixin Calculate the root hash and piece layers for file contents. DEPRECATED 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. PARAMETER DESCRIPTION path Path to file. TYPE: str piece_length Size of layer hashes pieces. TYPE: int progress default = None TYPE: int DEFAULT: True Calculate and store hash information for specific file. DEPRECATED Source code in torrentfile\\hasher.py 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 def __init__ ( self , path : str , piece_length : int , progress : bool = True ): \"\"\" Calculate and store hash information for specific file. **DEPRECATED** \"\"\" 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 : self . prog_start ( os . path . getsize ( path ), path , unit = \"bytes\" ) with open ( self . path , \"rb\" ) as fd : self . process_file ( fd )","title":"HasherV2"},{"location":"Source/hasher/#torrentfile.hasher.HasherV2.process_file","text":"Calculate hashes over 16KiB chuncks of file content. DEPRECATED PARAMETER DESCRIPTION fd Opened file in read mode. TYPE: TextIOWrapper Source code in torrentfile\\hasher.py 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 def process_file ( self , fd : str ): \"\"\" Calculate hashes over 16KiB chuncks of file content. **DEPRECATED** Parameters ---------- fd : TextIOWrapper 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 ) self . cb ( layer_hash ) self . layer_hashes . append ( layer_hash ) self . _calculate_root () self . prog_close ()","title":"process_file()"},{"location":"Source/hasher/#torrentfile.hasher.merkle_root","text":"Calculate the merkle root for a seq of sha256 hash digests. PARAMETER DESCRIPTION blocks a sequence of sha256 layer hashes. TYPE: list RETURNS DESCRIPTION bytes the sha256 root hash of the merkle tree. Source code in torrentfile\\hasher.py 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 def merkle_root ( blocks : list ) -> bytes : \"\"\" Calculate the merkle root for a seq of sha256 hash digests. Parameters ---------- blocks : list a sequence of sha256 layer hashes. Returns ------- bytes the sha256 root hash of the merkle tree. \"\"\" if blocks : while len ( blocks ) > 1 : blocks = [ sha256 ( x + y ) . digest () for x , y in zip ( * [ iter ( blocks )] * 2 ) ] return blocks [ 0 ] return blocks","title":"merkle_root()"},{"location":"Source/interactive/","text":"interactive # Module contains the procedures used for Interactive Mode. InteractiveCreator () # Class namespace for interactive program options. Initialize interactive meta file creator dialog. Source code in torrentfile\\interactive.py 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 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 () # Gather details for torrentfile from user. Source code in torrentfile\\interactive.py 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 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 ( metafile : str ) # Interactive dialog class for torrent editing. Initialize the Interactive torrent editor guide. PARAMETER DESCRIPTION metafile user input string identifying the path to a torrent meta file. TYPE: str Source code in torrentfile\\interactive.py 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 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 () # Loop continuosly for edits until user signals DONE. Source code in torrentfile\\interactive.py 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 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 ( key , response ) # Convert the input data into a form recognizable by the program. PARAMETER DESCRIPTION key name of the property and attribute being eddited. TYPE: str response User input value the property is being edited to. TYPE: str Source code in torrentfile\\interactive.py 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 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 () # Display the current met file information to screen. Source code in torrentfile\\interactive.py 224 225 226 227 228 229 230 231 232 233 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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 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 186 187 188 189 190 191 192 193 194 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 : tuple ) # Determine appropriate input function to call. PARAMETER DESCRIPTION *args Arbitrary number of args to pass to next function TYPE: tuple DEFAULT: () RETURNS DESCRIPTION str The results of the function call. Source code in torrentfile\\interactive.py 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 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. DEPRECATION WARNING: The interactive CLI feature will be deprecated in the future. Source code in torrentfile\\interactive.py 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 def select_action (): \"\"\" Operate TorrentFile program interactively through terminal. DEPRECATION WARNING: The interactive CLI feature will be deprecated in the future. \"\"\" showcenter ( \"TorrentFile: Starting Interactive Mode\" ) showcenter ( \"DEPRECATION WARNING: The interactive feature will be\" \"deprecated in the near future.\" ) 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 : str ) # Print text to screen in the center position of the terminal. PARAMETER DESCRIPTION txt the preformated message to send to stdout. TYPE: str Source code in torrentfile\\interactive.py 108 109 110 111 112 113 114 115 116 117 118 119 120 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. PARAMETER DESCRIPTION txt text to print to terminal. TYPE: str Source code in torrentfile\\interactive.py 96 97 98 99 100 101 102 103 104 105 def showtext ( txt ): \"\"\" Print contents of txt to screen. Parameters ---------- txt : str text to print to terminal. \"\"\" sys . stdout . write ( txt )","title":"Interactive"},{"location":"Source/interactive/#torrentfile.interactive","text":"Module contains the procedures used for Interactive Mode.","title":"interactive"},{"location":"Source/interactive/#torrentfile.interactive.InteractiveCreator","text":"Class namespace for interactive program options. Initialize interactive meta file creator dialog. Source code in torrentfile\\interactive.py 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 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 ()","title":"InteractiveCreator"},{"location":"Source/interactive/#torrentfile.interactive.InteractiveCreator.get_props","text":"Gather details for torrentfile from user. Source code in torrentfile\\interactive.py 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 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 ()","title":"get_props()"},{"location":"Source/interactive/#torrentfile.interactive.InteractiveEditor","text":"Interactive dialog class for torrent editing. Initialize the Interactive torrent editor guide. PARAMETER DESCRIPTION metafile user input string identifying the path to a torrent meta file. TYPE: str Source code in torrentfile\\interactive.py 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 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 ), }","title":"InteractiveEditor"},{"location":"Source/interactive/#torrentfile.interactive.InteractiveEditor.edit_props","text":"Loop continuosly for edits until user signals DONE. Source code in torrentfile\\interactive.py 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 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 )","title":"edit_props()"},{"location":"Source/interactive/#torrentfile.interactive.InteractiveEditor.sanatize_response","text":"Convert the input data into a form recognizable by the program. PARAMETER DESCRIPTION key name of the property and attribute being eddited. TYPE: str response User input value the property is being edited to. TYPE: str Source code in torrentfile\\interactive.py 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 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","title":"sanatize_response()"},{"location":"Source/interactive/#torrentfile.interactive.InteractiveEditor.show_current","text":"Display the current met file information to screen. Source code in torrentfile\\interactive.py 224 225 226 227 228 229 230 231 232 233 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 )","title":"show_current()"},{"location":"Source/interactive/#torrentfile.interactive.create_torrent","text":"Create new torrent file interactively. Source code in torrentfile\\interactive.py 170 171 172 173 174 175 176 177 178 179 180 181 182 183 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","title":"create_torrent()"},{"location":"Source/interactive/#torrentfile.interactive.edit_action","text":"Edit the editable values of the torrent meta file. Source code in torrentfile\\interactive.py 186 187 188 189 190 191 192 193 194 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 ()","title":"edit_action()"},{"location":"Source/interactive/#torrentfile.interactive.get_input","text":"Determine appropriate input function to call. PARAMETER DESCRIPTION *args Arbitrary number of args to pass to next function TYPE: tuple DEFAULT: () RETURNS DESCRIPTION str The results of the function call. Source code in torrentfile\\interactive.py 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 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 )","title":"get_input()"},{"location":"Source/interactive/#torrentfile.interactive.recheck_torrent","text":"Check torrent download completed percentage. Source code in torrentfile\\interactive.py 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 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","title":"recheck_torrent()"},{"location":"Source/interactive/#torrentfile.interactive.select_action","text":"Operate TorrentFile program interactively through terminal. DEPRECATION WARNING: The interactive CLI feature will be deprecated in the future. Source code in torrentfile\\interactive.py 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 def select_action (): \"\"\" Operate TorrentFile program interactively through terminal. DEPRECATION WARNING: The interactive CLI feature will be deprecated in the future. \"\"\" showcenter ( \"TorrentFile: Starting Interactive Mode\" ) showcenter ( \"DEPRECATION WARNING: The interactive feature will be\" \"deprecated in the near future.\" ) 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","title":"select_action()"},{"location":"Source/interactive/#torrentfile.interactive.showcenter","text":"Print text to screen in the center position of the terminal. PARAMETER DESCRIPTION txt the preformated message to send to stdout. TYPE: str Source code in torrentfile\\interactive.py 108 109 110 111 112 113 114 115 116 117 118 119 120 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 )","title":"showcenter()"},{"location":"Source/interactive/#torrentfile.interactive.showtext","text":"Print contents of txt to screen. PARAMETER DESCRIPTION txt text to print to terminal. TYPE: str Source code in torrentfile\\interactive.py 96 97 98 99 100 101 102 103 104 105 def showtext ( txt ): \"\"\" Print contents of txt to screen. Parameters ---------- txt : str text to print to terminal. \"\"\" sys . stdout . write ( txt )","title":"showtext()"},{"location":"Source/mixins/","text":"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. cb ( * args , ** kwargs ) classmethod # Do nothing. Source code in torrentfile\\mixins.py 40 41 42 @classmethod def cb ( cls , * args , ** kwargs ): \"\"\"Do nothing.\"\"\" set_callback ( func ) classmethod # Assign a callback to the Hashing class. PARAMETER DESCRIPTION func the callback function TYPE: Callable Source code in torrentfile\\mixins.py 44 45 46 47 48 49 50 51 52 53 54 @classmethod def set_callback ( cls , func ): \"\"\" Assign a callback to the Hashing class. Parameters ---------- func : Callable 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. is_active () -> bool # Test to see if there is an active progress bar for object. RETURNS DESCRIPTION bool True if there is, otherwise False. TYPE: bool Source code in torrentfile\\mixins.py 202 203 204 205 206 207 208 209 210 211 212 213 def is_active ( self ) -> bool : \"\"\" Test to see if there is an active progress bar for object. Returns ------- bool : True if there is, otherwise False. \"\"\" if not debug_is_on () and hasattr ( self , \"prog\" ): return True return False prog_close () # 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 190 191 192 193 194 195 196 197 198 199 200 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 ( total : int , path : str , length : int = 50 , unit : str = None ) # Generate a new progress bar for the given file path. PARAMETER DESCRIPTION total the total amount of units accumulating towards. TYPE: int path path to file being hashed. TYPE: str length the number of characters of the actual progress bar. TYPE: int DEFAULT: 50 unit the text representation of the value being measured. TYPE: str DEFAULT: None Source code in torrentfile\\mixins.py 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 def prog_start ( self , total : int , path : str , length : int = 50 , unit : str = 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 = path width = shutil . get_terminal_size () . columns if len ( str ( title )) >= width // 2 : parts = list ( Path ( title ) . parts ) while ( len ( \"//\" . join ( parts )) > ( width // 2 )) and ( len ( parts ) > 0 ): del parts [ 0 ] if parts : title = os . path . join ( * parts ) else : title = os . path . basename ( path ) # pragma: nocover length = min ( length , width // 2 ) start = width - int ( length * 1.5 ) self . prog = ProgressBar ( total , title , length , unit , start ) prog_update ( val : int ) # Update progress bar. Using the value provided, increment the progress bar by that value. PARAMETER DESCRIPTION val the number of bytes count the progress bar should increase. TYPE: int Source code in torrentfile\\mixins.py 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 def prog_update ( self , val : int ): \"\"\" Update progress bar. Using the value provided, increment the progress bar by that value. Parameters ---------- val : int the number of bytes count the progress bar should increase. \"\"\" if self . is_active (): self . prog . state += val pbar = self . prog . get_progress () output = f \" { self . prog . prefix }{ pbar }{ self . prog . suffix } \\r \" sys . stdout . write ( output ) sys . stdout . flush () ProgressBar ( total : int , title : str , length : int , unit : str , start : int ) # Holds the state and details of the terminal progress bars. PARAMETER DESCRIPTION total the total amount to be accumulated. TYPE: int title the subject of the progress tracker TYPE: str length the width of the progress bar TYPE: int unit the text representation incremented TYPE: str start column where the progress bar should be drawn TYPE: int Construct the progress bar object and store state of it\u2019s properties. Source code in torrentfile\\mixins.py 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 def __init__ ( self , total : int , title : str , length : int , unit : str , start : int ): \"\"\" 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 self . show_total = total if not unit : self . unit = \"\" # pragma: nocover elif unit == \"bytes\" : if self . total > 1_000_000_000 : self . show_total = math . floor ( self . total / ( 2 ** 30 )) self . unit = \"GiB\" elif self . total > 1_000_000 : self . show_total = math . floor ( self . total / 1048576 ) self . unit = \"MiB\" elif self . total > 10000 : self . show_total = math . floor ( self . total / 1024 ) self . unit = \"KiB\" self . suffix = f \"/ { self . show_total } { self . unit } \" if len ( title ) > start : title = title [: start - 1 ] padding = ( start - len ( title )) * \" \" self . prefix = \"\" . join ([ title , padding ]) get_progress () -> str # Return the size of the filled portion of the progress bar. RETURNS DESCRIPTION str the progress bar characters TYPE: str Source code in torrentfile\\mixins.py 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 def get_progress ( self ) -> str : \"\"\" 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 ) empt = self . length - fill if self . unit == \"GiB\" : state = math . floor ( self . state / ( 2 ** 30 )) elif 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 * empt , \"| \" , str ( state )] return \"\" . join ( progbar ) waiting ( msg : str , flag : list , timeout : int = 20 ) # Show loading message while thread completes processing. PARAMETER DESCRIPTION msg Message string printed before the progress bar TYPE: str flag Once flag is filled exit loop TYPE: list timeout max amount of time to run the function. TYPE: int DEFAULT: 20 Source code in torrentfile\\mixins.py 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 def waiting ( msg : str , flag : list , timeout : int = 20 ): \"\"\" 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 : str ): \"\"\" Print parameter message to the console. Parameters ---------- text : str output message \"\"\" sys . stdout . write ( text ) sys . stdout . flush () output ( \" \\n \" ) time . sleep ( 0.16 ) while len ( flag ) == 0 : 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 \" )","title":"Mixins"},{"location":"Source/mixins/#torrentfile.mixins","text":"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.","title":"mixins"},{"location":"Source/mixins/#torrentfile.mixins.CbMixin","text":"Mixin class to set a callback during hashing procedure.","title":"CbMixin"},{"location":"Source/mixins/#torrentfile.mixins.CbMixin.cb","text":"Do nothing. Source code in torrentfile\\mixins.py 40 41 42 @classmethod def cb ( cls , * args , ** kwargs ): \"\"\"Do nothing.\"\"\"","title":"cb()"},{"location":"Source/mixins/#torrentfile.mixins.CbMixin.set_callback","text":"Assign a callback to the Hashing class. PARAMETER DESCRIPTION func the callback function TYPE: Callable Source code in torrentfile\\mixins.py 44 45 46 47 48 49 50 51 52 53 54 @classmethod def set_callback ( cls , func ): \"\"\" Assign a callback to the Hashing class. Parameters ---------- func : Callable the callback function \"\"\" cls . cb = func # pragma: nocover","title":"set_callback()"},{"location":"Source/mixins/#torrentfile.mixins.ProgMixin","text":"Progress bar mixin class. Displays progress of hashing individual files, usefull when hashing really big files.","title":"ProgMixin"},{"location":"Source/mixins/#torrentfile.mixins.ProgMixin.is_active","text":"Test to see if there is an active progress bar for object. RETURNS DESCRIPTION bool True if there is, otherwise False. TYPE: bool Source code in torrentfile\\mixins.py 202 203 204 205 206 207 208 209 210 211 212 213 def is_active ( self ) -> bool : \"\"\" Test to see if there is an active progress bar for object. Returns ------- bool : True if there is, otherwise False. \"\"\" if not debug_is_on () and hasattr ( self , \"prog\" ): return True return False","title":"is_active()"},{"location":"Source/mixins/#torrentfile.mixins.ProgMixin.prog_close","text":"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 190 191 192 193 194 195 196 197 198 199 200 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","title":"prog_close()"},{"location":"Source/mixins/#torrentfile.mixins.ProgMixin.prog_start","text":"Generate a new progress bar for the given file path. PARAMETER DESCRIPTION total the total amount of units accumulating towards. TYPE: int path path to file being hashed. TYPE: str length the number of characters of the actual progress bar. TYPE: int DEFAULT: 50 unit the text representation of the value being measured. TYPE: str DEFAULT: None Source code in torrentfile\\mixins.py 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 def prog_start ( self , total : int , path : str , length : int = 50 , unit : str = 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 = path width = shutil . get_terminal_size () . columns if len ( str ( title )) >= width // 2 : parts = list ( Path ( title ) . parts ) while ( len ( \"//\" . join ( parts )) > ( width // 2 )) and ( len ( parts ) > 0 ): del parts [ 0 ] if parts : title = os . path . join ( * parts ) else : title = os . path . basename ( path ) # pragma: nocover length = min ( length , width // 2 ) start = width - int ( length * 1.5 ) self . prog = ProgressBar ( total , title , length , unit , start )","title":"prog_start()"},{"location":"Source/mixins/#torrentfile.mixins.ProgMixin.prog_update","text":"Update progress bar. Using the value provided, increment the progress bar by that value. PARAMETER DESCRIPTION val the number of bytes count the progress bar should increase. TYPE: int Source code in torrentfile\\mixins.py 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 def prog_update ( self , val : int ): \"\"\" Update progress bar. Using the value provided, increment the progress bar by that value. Parameters ---------- val : int the number of bytes count the progress bar should increase. \"\"\" if self . is_active (): self . prog . state += val pbar = self . prog . get_progress () output = f \" { self . prog . prefix }{ pbar }{ self . prog . suffix } \\r \" sys . stdout . write ( output ) sys . stdout . flush ()","title":"prog_update()"},{"location":"Source/mixins/#torrentfile.mixins.ProgressBar","text":"Holds the state and details of the terminal progress bars. PARAMETER DESCRIPTION total the total amount to be accumulated. TYPE: int title the subject of the progress tracker TYPE: str length the width of the progress bar TYPE: int unit the text representation incremented TYPE: str start column where the progress bar should be drawn TYPE: int Construct the progress bar object and store state of it\u2019s properties. Source code in torrentfile\\mixins.py 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 def __init__ ( self , total : int , title : str , length : int , unit : str , start : int ): \"\"\" 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 self . show_total = total if not unit : self . unit = \"\" # pragma: nocover elif unit == \"bytes\" : if self . total > 1_000_000_000 : self . show_total = math . floor ( self . total / ( 2 ** 30 )) self . unit = \"GiB\" elif self . total > 1_000_000 : self . show_total = math . floor ( self . total / 1048576 ) self . unit = \"MiB\" elif self . total > 10000 : self . show_total = math . floor ( self . total / 1024 ) self . unit = \"KiB\" self . suffix = f \"/ { self . show_total } { self . unit } \" if len ( title ) > start : title = title [: start - 1 ] padding = ( start - len ( title )) * \" \" self . prefix = \"\" . join ([ title , padding ])","title":"ProgressBar"},{"location":"Source/mixins/#torrentfile.mixins.ProgressBar.get_progress","text":"Return the size of the filled portion of the progress bar. RETURNS DESCRIPTION str the progress bar characters TYPE: str Source code in torrentfile\\mixins.py 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 def get_progress ( self ) -> str : \"\"\" 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 ) empt = self . length - fill if self . unit == \"GiB\" : state = math . floor ( self . state / ( 2 ** 30 )) elif 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 * empt , \"| \" , str ( state )] return \"\" . join ( progbar )","title":"get_progress()"},{"location":"Source/mixins/#torrentfile.mixins.waiting","text":"Show loading message while thread completes processing. PARAMETER DESCRIPTION msg Message string printed before the progress bar TYPE: str flag Once flag is filled exit loop TYPE: list timeout max amount of time to run the function. TYPE: int DEFAULT: 20 Source code in torrentfile\\mixins.py 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 def waiting ( msg : str , flag : list , timeout : int = 20 ): \"\"\" 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 : str ): \"\"\" Print parameter message to the console. Parameters ---------- text : str output message \"\"\" sys . stdout . write ( text ) sys . stdout . flush () output ( \" \\n \" ) time . sleep ( 0.16 ) while len ( flag ) == 0 : 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 \" )","title":"waiting()"},{"location":"Source/rebuild/","text":"rebuild # Clases and functions for the rebuild or reassemble subcommand. Re-assemble a torrent into the propper directory structure as indicated by a torrent meta file, and validate the contents of each file allong the way. Displays a progress bar for each torrent. Assembler ( metafiles : list , contents : list , dest : str ) # Bases: CbMixin Does most of the work in attempting the structure of torrentfiles. Requires three paths as arguments. - torrent metafile or directory containing multiple meta files - directory containing the contents of meta file - directory where torrents will be re-assembled Reassemble given torrent file from given cli arguments. Rebuild metafiles and contents into their original directory structure as much as possible in the destination directory. Takes two paths as parameters, - file or directory containing 1 or more torrent meta files - path to where the contents are belived to be located. PARAMETER DESCRIPTION metafiles path to torrent metafile or directory containing torrent metafiles. TYPE: str contents path to content or directory containing content that belongs to torrentfile. TYPE: str dest path to the directory where rebuild will take place. TYPE: str Source code in torrentfile\\rebuild.py 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 def __init__ ( self , metafiles : list , contents : list , dest : str ): \"\"\" Reassemble given torrent file from given cli arguments. Rebuild metafiles and contents into their original directory structure as much as possible in the destination directory. Takes two paths as parameters, - file or directory containing 1 or more torrent meta files - path to where the contents are belived to be located. Parameters ---------- metafiles : str path to torrent metafile or directory containing torrent metafiles. contents : str path to content or directory containing content that belongs to torrentfile. dest: str path to the directory where rebuild will take place. \"\"\" self . counter = 0 self . _lastlog = None self . contents = contents Metadata . set_callback ( self . _callback ) self . dest = dest self . meta_paths = metafiles self . metafiles = self . _get_metafiles () filenames = set () for meta in self . metafiles : filenames |= meta . filenames self . filemap = _index_contents ( self . contents , filenames ) assemble_torrents () # Assemble collection of torrent files into original structure. RETURNS DESCRIPTION int number of files copied Source code in torrentfile\\rebuild.py 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 def assemble_torrents ( self ): \"\"\" Assemble collection of torrent files into original structure. Returns ------- int number of files copied \"\"\" for metafile in self . metafiles : logger . info ( \"# %s Searching contents for %s \" , self . counter , metafile . name ) self . rebuild ( metafile ) return self . counter rebuild ( metafile : Metadata ) -> None # Build the torrent file structure from contents of directory. Traverse contents dir and compare discovered files with files listed in torrent metadata and copy the matches to the destination directory respecting folder structures along the way. Source code in torrentfile\\rebuild.py 505 506 507 508 509 510 511 512 513 514 def rebuild ( self , metafile : Metadata ) -> None : \"\"\" Build the torrent file structure from contents of directory. Traverse contents dir and compare discovered files with files listed in torrent metadata and copy the matches to the destination directory respecting folder structures along the way. \"\"\" metafile . rebuild ( self . filemap , self . dest ) Metadata ( path : str ) # Bases: CbMixin , ProgMixin Class containing the metadata contents of a torrent file. Construct metadata object for torrent info. PARAMETER DESCRIPTION path path to the .torrent file. TYPE: str Source code in torrentfile\\rebuild.py 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 def __init__ ( self , path : str ): \"\"\" Construct metadata object for torrent info. Parameters ---------- path : str path to the .torrent file. \"\"\" self . path = os . path . abspath ( path ) self . name = None self . piece_length = 1 self . meta_version = 1 self . pieces = b \"\" self . piece_nodes = [] self . length = 0 self . files = [] self . filenames = set () self . extract () if self . meta_version == 2 : self . num_pieces = len ( self . filenames ) else : self . num_pieces = math . ceil ( len ( self . pieces ) / SHA1 ) extract () # Decode and extract information for the .torrent file. Source code in torrentfile\\rebuild.py 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 def extract ( self ): \"\"\" Decode and extract information for the .torrent file. \"\"\" meta = pyben . load ( self . path ) info = meta [ \"info\" ] self . piece_length = info [ \"piece length\" ] self . name = info [ \"name\" ] self . meta_version = info . get ( \"meta version\" , 1 ) self . pieces = info . get ( \"pieces\" , bytes ()) if self . meta_version == 2 : self . _parse_tree ( info [ \"file tree\" ], [ self . name ]) elif \"length\" in info : self . length += info [ \"length\" ] self . is_file = True self . filenames . add ( info [ \"name\" ]) self . files . append ( { \"path\" : Path ( self . name ) . parent , \"filename\" : self . name , \"full\" : self . name , \"length\" : self . length , } ) elif \"files\" in info : for f in info [ \"files\" ]: path = f [ \"path\" ] full = os . path . join ( self . name , * path ) self . files . append ( { \"path\" : Path ( full ) . parent , \"filename\" : path [ - 1 ], \"full\" : full , \"length\" : f [ \"length\" ], } ) self . length += f [ \"length\" ] self . filenames . add ( path [ - 1 ]) rebuild ( filemap : dict , dest : str ) # Rebuild torrent file contents from filemap at dest. Searches through the contents of the meta file and compares filenames with those in the filemap dict, and if found checks their contents, and copies them to the destination path. PARAMETER DESCRIPTION filemap filesystem information TYPE: dict dest destiantion path TYPE: str Source code in torrentfile\\rebuild.py 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 def rebuild ( self , filemap : dict , dest : str ): \"\"\" Rebuild torrent file contents from filemap at dest. Searches through the contents of the meta file and compares filenames with those in the filemap dict, and if found checks their contents, and copies them to the destination path. Parameters ---------- filemap : dict filesystem information dest : str destiantion path \"\"\" self . _prog = None if self . meta_version == 2 : self . _match_v2 ( filemap , dest ) else : self . _match_v1 ( filemap , dest ) if self . _prog is not None : self . prog_close () PathNode ( start : int = None , stop : int = None , full : str = None , filename : str = None , path : str = None , length : int = None ) # Base class representing information regarding a file included in torrent. Hold file information that contributes to the contents of torrent. PARAMETER DESCRIPTION start where the piece starts, by default None TYPE: int , optional DEFAULT: None stop where the piece ends, by default None TYPE: int , optional DEFAULT: None full full path, by default None TYPE: str , optional DEFAULT: None filename filename, by default None TYPE: str , optional DEFAULT: None path parent path, by default None TYPE: str , optional DEFAULT: None length size, by default None TYPE: int , optional DEFAULT: None Source code in torrentfile\\rebuild.py 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 def __init__ ( self , start : int = None , stop : int = None , full : str = None , filename : str = None , path : str = None , length : int = None , ): \"\"\" Hold file information that contributes to the contents of torrent. Parameters ---------- start : int, optional where the piece starts, by default None stop : int, optional where the piece ends, by default None full : str, optional full path, by default None filename : str, optional filename, by default None path : str, optional parent path, by default None length : int, optional size, by default None \"\"\" self . path = path self . start = start self . stop = stop self . length = length self . filename = filename self . full = full __len__ () -> int # Return size of the file. RETURNS DESCRIPTION int total size Source code in torrentfile\\rebuild.py 104 105 106 107 108 109 110 111 112 113 def __len__ ( self ) -> int : \"\"\" Return size of the file. Returns ------- int total size \"\"\" return self . length get_part ( path : str ) -> bytes # Extract the part of the file needed to complete the hash. PARAMETER DESCRIPTION path filesystem path location of file. TYPE: str RETURNS DESCRIPTION bytes part of the file\u2019s contents Source code in torrentfile\\rebuild.py 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 def get_part ( self , path : str ) -> bytes : \"\"\" Extract the part of the file needed to complete the hash. Parameters ---------- path : str filesystem path location of file. Returns ------- bytes part of the file's contents \"\"\" with open ( path , \"rb\" ) as fd : if self . start : fd . seek ( self . start ) if self . stop != - 1 : partial = fd . read ( self . stop - self . start ) else : partial = fd . read () return partial PieceNode ( piece : bytes ) # Base class representing a single SHA1 hash block of data from a torrent. Store information about an individual SHA1 hash for a torrent file. extended_summary PARAMETER DESCRIPTION piece SHA1 hash bytes TYPE: bytes Source code in torrentfile\\rebuild.py 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 def __init__ ( self , piece : bytes ): \"\"\" Store information about an individual SHA1 hash for a torrent file. _extended_summary_ Parameters ---------- piece : bytes SHA1 hash bytes \"\"\" self . piece = piece self . paths = [] self . result = None self . dest = None append ( pathnode : PathNode ) # Append the path argument to the paths list attribute. PARAMETER DESCRIPTION pathnode the pathnode TYPE: PathNode Source code in torrentfile\\rebuild.py 137 138 139 140 141 142 143 144 145 146 def append ( self , pathnode : PathNode ): \"\"\" Append the path argument to the paths list attribute. Parameters ---------- pathnode : PathNode the pathnode \"\"\" self . paths . append ( pathnode ) find_matches ( filemap : dict , dest : str ) -> bool # Find the matching files for each path in the node. PARAMETER DESCRIPTION filemap filename and details TYPE: dict dest target destination path TYPE: str RETURNS DESCRIPTION bool success status Source code in torrentfile\\rebuild.py 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 def find_matches ( self , filemap : dict , dest : str ) -> bool : \"\"\" Find the matching files for each path in the node. Parameters ---------- filemap : dict filename and details dest : str target destination path Returns ------- bool success status \"\"\" self . dest = dest self . result = self . _find_matches ( filemap , self . paths [:], bytes ()) return self . result","title":"Rebuild"},{"location":"Source/rebuild/#torrentfile.rebuild","text":"Clases and functions for the rebuild or reassemble subcommand. Re-assemble a torrent into the propper directory structure as indicated by a torrent meta file, and validate the contents of each file allong the way. Displays a progress bar for each torrent.","title":"rebuild"},{"location":"Source/rebuild/#torrentfile.rebuild.Assembler","text":"Bases: CbMixin Does most of the work in attempting the structure of torrentfiles. Requires three paths as arguments. - torrent metafile or directory containing multiple meta files - directory containing the contents of meta file - directory where torrents will be re-assembled Reassemble given torrent file from given cli arguments. Rebuild metafiles and contents into their original directory structure as much as possible in the destination directory. Takes two paths as parameters, - file or directory containing 1 or more torrent meta files - path to where the contents are belived to be located. PARAMETER DESCRIPTION metafiles path to torrent metafile or directory containing torrent metafiles. TYPE: str contents path to content or directory containing content that belongs to torrentfile. TYPE: str dest path to the directory where rebuild will take place. TYPE: str Source code in torrentfile\\rebuild.py 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 def __init__ ( self , metafiles : list , contents : list , dest : str ): \"\"\" Reassemble given torrent file from given cli arguments. Rebuild metafiles and contents into their original directory structure as much as possible in the destination directory. Takes two paths as parameters, - file or directory containing 1 or more torrent meta files - path to where the contents are belived to be located. Parameters ---------- metafiles : str path to torrent metafile or directory containing torrent metafiles. contents : str path to content or directory containing content that belongs to torrentfile. dest: str path to the directory where rebuild will take place. \"\"\" self . counter = 0 self . _lastlog = None self . contents = contents Metadata . set_callback ( self . _callback ) self . dest = dest self . meta_paths = metafiles self . metafiles = self . _get_metafiles () filenames = set () for meta in self . metafiles : filenames |= meta . filenames self . filemap = _index_contents ( self . contents , filenames )","title":"Assembler"},{"location":"Source/rebuild/#torrentfile.rebuild.Assembler.assemble_torrents","text":"Assemble collection of torrent files into original structure. RETURNS DESCRIPTION int number of files copied Source code in torrentfile\\rebuild.py 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 def assemble_torrents ( self ): \"\"\" Assemble collection of torrent files into original structure. Returns ------- int number of files copied \"\"\" for metafile in self . metafiles : logger . info ( \"# %s Searching contents for %s \" , self . counter , metafile . name ) self . rebuild ( metafile ) return self . counter","title":"assemble_torrents()"},{"location":"Source/rebuild/#torrentfile.rebuild.Assembler.rebuild","text":"Build the torrent file structure from contents of directory. Traverse contents dir and compare discovered files with files listed in torrent metadata and copy the matches to the destination directory respecting folder structures along the way. Source code in torrentfile\\rebuild.py 505 506 507 508 509 510 511 512 513 514 def rebuild ( self , metafile : Metadata ) -> None : \"\"\" Build the torrent file structure from contents of directory. Traverse contents dir and compare discovered files with files listed in torrent metadata and copy the matches to the destination directory respecting folder structures along the way. \"\"\" metafile . rebuild ( self . filemap , self . dest )","title":"rebuild()"},{"location":"Source/rebuild/#torrentfile.rebuild.Metadata","text":"Bases: CbMixin , ProgMixin Class containing the metadata contents of a torrent file. Construct metadata object for torrent info. PARAMETER DESCRIPTION path path to the .torrent file. TYPE: str Source code in torrentfile\\rebuild.py 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 def __init__ ( self , path : str ): \"\"\" Construct metadata object for torrent info. Parameters ---------- path : str path to the .torrent file. \"\"\" self . path = os . path . abspath ( path ) self . name = None self . piece_length = 1 self . meta_version = 1 self . pieces = b \"\" self . piece_nodes = [] self . length = 0 self . files = [] self . filenames = set () self . extract () if self . meta_version == 2 : self . num_pieces = len ( self . filenames ) else : self . num_pieces = math . ceil ( len ( self . pieces ) / SHA1 )","title":"Metadata"},{"location":"Source/rebuild/#torrentfile.rebuild.Metadata.extract","text":"Decode and extract information for the .torrent file. Source code in torrentfile\\rebuild.py 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 def extract ( self ): \"\"\" Decode and extract information for the .torrent file. \"\"\" meta = pyben . load ( self . path ) info = meta [ \"info\" ] self . piece_length = info [ \"piece length\" ] self . name = info [ \"name\" ] self . meta_version = info . get ( \"meta version\" , 1 ) self . pieces = info . get ( \"pieces\" , bytes ()) if self . meta_version == 2 : self . _parse_tree ( info [ \"file tree\" ], [ self . name ]) elif \"length\" in info : self . length += info [ \"length\" ] self . is_file = True self . filenames . add ( info [ \"name\" ]) self . files . append ( { \"path\" : Path ( self . name ) . parent , \"filename\" : self . name , \"full\" : self . name , \"length\" : self . length , } ) elif \"files\" in info : for f in info [ \"files\" ]: path = f [ \"path\" ] full = os . path . join ( self . name , * path ) self . files . append ( { \"path\" : Path ( full ) . parent , \"filename\" : path [ - 1 ], \"full\" : full , \"length\" : f [ \"length\" ], } ) self . length += f [ \"length\" ] self . filenames . add ( path [ - 1 ])","title":"extract()"},{"location":"Source/rebuild/#torrentfile.rebuild.Metadata.rebuild","text":"Rebuild torrent file contents from filemap at dest. Searches through the contents of the meta file and compares filenames with those in the filemap dict, and if found checks their contents, and copies them to the destination path. PARAMETER DESCRIPTION filemap filesystem information TYPE: dict dest destiantion path TYPE: str Source code in torrentfile\\rebuild.py 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 def rebuild ( self , filemap : dict , dest : str ): \"\"\" Rebuild torrent file contents from filemap at dest. Searches through the contents of the meta file and compares filenames with those in the filemap dict, and if found checks their contents, and copies them to the destination path. Parameters ---------- filemap : dict filesystem information dest : str destiantion path \"\"\" self . _prog = None if self . meta_version == 2 : self . _match_v2 ( filemap , dest ) else : self . _match_v1 ( filemap , dest ) if self . _prog is not None : self . prog_close ()","title":"rebuild()"},{"location":"Source/rebuild/#torrentfile.rebuild.PathNode","text":"Base class representing information regarding a file included in torrent. Hold file information that contributes to the contents of torrent. PARAMETER DESCRIPTION start where the piece starts, by default None TYPE: int , optional DEFAULT: None stop where the piece ends, by default None TYPE: int , optional DEFAULT: None full full path, by default None TYPE: str , optional DEFAULT: None filename filename, by default None TYPE: str , optional DEFAULT: None path parent path, by default None TYPE: str , optional DEFAULT: None length size, by default None TYPE: int , optional DEFAULT: None Source code in torrentfile\\rebuild.py 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 def __init__ ( self , start : int = None , stop : int = None , full : str = None , filename : str = None , path : str = None , length : int = None , ): \"\"\" Hold file information that contributes to the contents of torrent. Parameters ---------- start : int, optional where the piece starts, by default None stop : int, optional where the piece ends, by default None full : str, optional full path, by default None filename : str, optional filename, by default None path : str, optional parent path, by default None length : int, optional size, by default None \"\"\" self . path = path self . start = start self . stop = stop self . length = length self . filename = filename self . full = full","title":"PathNode"},{"location":"Source/rebuild/#torrentfile.rebuild.PathNode.__len__","text":"Return size of the file. RETURNS DESCRIPTION int total size Source code in torrentfile\\rebuild.py 104 105 106 107 108 109 110 111 112 113 def __len__ ( self ) -> int : \"\"\" Return size of the file. Returns ------- int total size \"\"\" return self . length","title":"__len__()"},{"location":"Source/rebuild/#torrentfile.rebuild.PathNode.get_part","text":"Extract the part of the file needed to complete the hash. PARAMETER DESCRIPTION path filesystem path location of file. TYPE: str RETURNS DESCRIPTION bytes part of the file\u2019s contents Source code in torrentfile\\rebuild.py 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 def get_part ( self , path : str ) -> bytes : \"\"\" Extract the part of the file needed to complete the hash. Parameters ---------- path : str filesystem path location of file. Returns ------- bytes part of the file's contents \"\"\" with open ( path , \"rb\" ) as fd : if self . start : fd . seek ( self . start ) if self . stop != - 1 : partial = fd . read ( self . stop - self . start ) else : partial = fd . read () return partial","title":"get_part()"},{"location":"Source/rebuild/#torrentfile.rebuild.PieceNode","text":"Base class representing a single SHA1 hash block of data from a torrent. Store information about an individual SHA1 hash for a torrent file. extended_summary PARAMETER DESCRIPTION piece SHA1 hash bytes TYPE: bytes Source code in torrentfile\\rebuild.py 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 def __init__ ( self , piece : bytes ): \"\"\" Store information about an individual SHA1 hash for a torrent file. _extended_summary_ Parameters ---------- piece : bytes SHA1 hash bytes \"\"\" self . piece = piece self . paths = [] self . result = None self . dest = None","title":"PieceNode"},{"location":"Source/rebuild/#torrentfile.rebuild.PieceNode.append","text":"Append the path argument to the paths list attribute. PARAMETER DESCRIPTION pathnode the pathnode TYPE: PathNode Source code in torrentfile\\rebuild.py 137 138 139 140 141 142 143 144 145 146 def append ( self , pathnode : PathNode ): \"\"\" Append the path argument to the paths list attribute. Parameters ---------- pathnode : PathNode the pathnode \"\"\" self . paths . append ( pathnode )","title":"append()"},{"location":"Source/rebuild/#torrentfile.rebuild.PieceNode.find_matches","text":"Find the matching files for each path in the node. PARAMETER DESCRIPTION filemap filename and details TYPE: dict dest target destination path TYPE: str RETURNS DESCRIPTION bool success status Source code in torrentfile\\rebuild.py 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 def find_matches ( self , filemap : dict , dest : str ) -> bool : \"\"\" Find the matching files for each path in the node. Parameters ---------- filemap : dict filename and details dest : str target destination path Returns ------- bool success status \"\"\" self . dest = dest self . result = self . _find_matches ( filemap , self . paths [:], bytes ()) return self . result","title":"find_matches()"},{"location":"Source/recheck/","text":"recheck # Module container Checker Class. The CheckerClass takes a torrentfile and tha path to it\u2019s 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 ( metafile : str , path : str ) # Check a given file or directory to see if it matches a torrentfile. Public constructor for Checker class instance. PARAMETER DESCRIPTION metafile Path to \u201c.torrent\u201d file. TYPE: str path Path where the content is located in filesystem. TYPE: str Example # 1 2 3 4 5 >> 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) Validate data against hashes contained in .torrent file. PARAMETER DESCRIPTION metafile path to .torrent file TYPE: str path path to content or contents parent directory. TYPE: str Source code in torrentfile\\recheck.py 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 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 if os . path . isdir ( metafile ): raise ArgumentError ( \"The must be a .torrent file. Not a directory\" ) self . last_log = None self . log_msg ( \"Checking: %s , %s \" , metafile , path ) self . metafile = metafile self . total = 0 self . paths = [] self . fileinfo = {} print ( \"Extracting data from torrent file...\" ) self . meta = pyben . load ( metafile ) 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 () # Gather all file paths described in the torrent file. Source code in torrentfile\\recheck.py 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 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 ( 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\u2019s name, if not the directories contents will be searched. The returned value will be the absolute path that matches the torrent\u2019s name. PARAMETER DESCRIPTION path root path to torrent content TYPE: str RETURNS DESCRIPTION str root path to content Source code in torrentfile\\recheck.py 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 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 ) iter_hashes () -> tuple # Produce results of comparing torrent contents piece by piece. YIELDS DESCRIPTION chunck hash of data found on disk TYPE: bytes piece hash of data when complete and correct TYPE: bytes path path to file being hashed TYPE: str size length of bytes hashed for piece TYPE: int Source code in torrentfile\\recheck.py 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 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 () for chunk , piece , path , size in checker ( self ): 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 , ) self . _result = ( matched / consumed ) * 100 if consumed > 0 else 0 log_msg ( * args , level : int = logging . INFO ) # Log message msg to logger and send msg to callback hook. PARAMETER DESCRIPTION *args formatting args for log message TYPE: dict DEFAULT: () level Log level for this message; default= logging.INFO TYPE: int DEFAULT: logging.INFO Source code in torrentfile\\recheck.py 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 def log_msg ( self , * args , level : int = 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 () # Check individual pieces of the torrent. RETURNS DESCRIPTION HashChecker | FeedChecker Individual piece hasher. Source code in torrentfile\\recheck.py 123 124 125 126 127 128 129 130 131 132 133 134 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. PARAMETER DESCRIPTION hook callback function for the logging feature. TYPE: function Source code in torrentfile\\recheck.py 111 112 113 114 115 116 117 118 119 120 121 @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 () # Generate result percentage and store for future calls. Source code in torrentfile\\recheck.py 136 137 138 139 140 141 142 143 144 145 146 147 148 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 ( 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\u2019s file tree. PARAMETER DESCRIPTION tree File Tree dict extracted from torrent file. TYPE: dict partials list of intermediate pathnames. TYPE: list Source code in torrentfile\\recheck.py 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 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 ( checker : Checker ) # Bases: ProgMixin Validates torrent content. Seemlesly validate torrent file contents by comparing hashes in metafile against data on disk. PARAMETER DESCRIPTION checker the checker class instance. TYPE: object Generate hashes of piece length data from filelist contents. Source code in torrentfile\\recheck.py 332 333 334 335 336 337 338 339 340 341 342 343 def __init__ ( self , checker : Checker ): \"\"\" 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 . piece_map = {} self . index = 0 self . piece_count = 0 self . it = None __iter__ () # Assign iterator and return self. Source code in torrentfile\\recheck.py 345 346 347 348 349 350 def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" self . it = self . iter_pieces () return self __next__ () # Yield back result of comparison. Source code in torrentfile\\recheck.py 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 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 ( path : str , partial : bytearray ) -> bytearray # Split file paths contents into blocks of data for hash pieces. PARAMETER DESCRIPTION path path to content. TYPE: str partial any remaining content from last file. TYPE: bytes RETURNS DESCRIPTION bytearray Hash digest for block of .torrent contents. Source code in torrentfile\\recheck.py 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 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 () # Iterate through, and hash pieces of torrent contents. YIELDS DESCRIPTION piece hash digest for block of torrent data. TYPE: bytes Source code in torrentfile\\recheck.py 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 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 ( checker : Checker ) # Bases: ProgMixin Iterate through contents of meta data and verify with file contents. PARAMETER DESCRIPTION checker the checker instance that maintains variables. TYPE: Checker Construct a HybridChecker instance. Source code in torrentfile\\recheck.py 483 484 485 486 487 488 489 490 491 492 493 def __init__ ( self , checker : Checker ): \"\"\" Construct a HybridChecker instance. \"\"\" self . checker = checker self . paths = checker . paths self . piece_length = checker . piece_length self . fileinfo = checker . fileinfo self . piece_layers = checker . meta [ \"piece layers\" ] self . current = None self . index = - 1 Padder ( length , piece_length ) # Padding class to generate padding hashes wherever needed. PARAMETER DESCRIPTION length the total size of the mock file generating padding for. piece_length the block size that each hash represents. TYPE: int Construct padding class to Mock missing or incomplete files. PARAMETER DESCRIPTION length size of the file TYPE: int piece_length the piece length for each iteration. TYPE: int Source code in torrentfile\\recheck.py 526 527 528 529 530 531 532 533 534 535 536 537 538 539 def __init__ ( self , length , piece_length ): \"\"\" Construct padding class to Mock missing or incomplete files. Parameters ---------- length : int size of the file piece_length : int the piece length for each iteration. \"\"\" self . length = length self . piece_length = piece_length self . pad = sha256 ( bytearray ( piece_length )) . digest () __iter__ () # Return self to correctly implement iterator type. Source code in torrentfile\\recheck.py 541 542 543 544 545 def __iter__ ( self ): \"\"\" Return self to correctly implement iterator type. \"\"\" return self # pragma: nocover __next__ () -> bytes # Iterate through seemingly endless sha256 hashes of zeros. RETURNS DESCRIPTION tuple returns the padding TYPE: bytes RAISES DESCRIPTION StopIteration Source code in torrentfile\\recheck.py 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 def __next__ ( self ) -> bytes : \"\"\" Iterate through seemingly endless sha256 hashes of zeros. Returns ------- tuple : returns the padding Raises ------ StopIteration \"\"\" if self . length >= self . piece_length : self . length -= self . piece_length return self . pad if self . length > 0 : pad = sha256 ( bytearray ( self . length )) . digest () self . length -= self . length return pad raise StopIteration __iter__ () # Assign iterator and return self. Source code in torrentfile\\recheck.py 495 496 497 498 499 def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" return self __next__ () # Provide the result of comparison. Source code in torrentfile\\recheck.py 501 502 503 504 505 506 507 508 509 510 511 512 def __next__ ( self ): \"\"\" Provide the result of comparison. \"\"\" if self . current is None : self . next_file () try : return self . process_current () except StopIteration as itererr : if self . next_file (): return self . process_current () raise StopIteration from itererr advance () -> tuple # Increment the number of pieces processed for the current file. RETURNS DESCRIPTION tuple the piece and size Source code in torrentfile\\recheck.py 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 def advance ( self ) -> tuple : \"\"\" Increment the number of pieces processed for the current file. Returns ------- tuple the piece and size \"\"\" start = self . count * SHA256 end = start + SHA256 piece = self . pieces [ start : end ] self . count += 1 if self . length >= self . piece_length : self . length -= self . piece_length size = self . piece_length else : size = self . length self . length -= self . length return piece , size next_file () -> bool # Remove all references to processed files and prepare for the next. RETURNS DESCRIPTION bool if there is a next file found Source code in torrentfile\\recheck.py 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 def next_file ( self ) -> bool : \"\"\" Remove all references to processed files and prepare for the next. Returns ------- bool if there is a next file found \"\"\" self . index += 1 self . prog_close () if self . current is None or self . index < len ( self . paths ): self . current = self . paths [ self . index ] self . length = self . fileinfo [ self . index ][ \"length\" ] self . root_hash = self . fileinfo [ self . index ][ \"pieces root\" ] if self . length > self . piece_length : self . pieces = self . piece_layers [ self . root_hash ] else : self . pieces = self . root_hash path = self . paths [ self . index ] self . prog_start ( self . length , path , unit = \"bytes\" ) self . count = 0 if os . path . exists ( self . current ): self . hasher = FileHasher ( path , self . piece_length , progress = 0 ) else : self . hasher = self . Padder ( self . length , self . piece_length ) return True if self . index >= len ( self . paths ): del self . current del self . length del self . root_hash del self . pieces return False process_current () -> tuple # Gather necessary information to compare to metafile details. RETURNS DESCRIPTION tuple a tuple containing the layer, piece, current path and size RAISES DESCRIPTION StopIteration Source code in torrentfile\\recheck.py 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 def process_current ( self ) -> tuple : \"\"\" Gather necessary information to compare to metafile details. Returns ------- tuple a tuple containing the layer, piece, current path and size Raises ------ StopIteration \"\"\" try : layer = next ( self . hasher ) piece , size = self . advance () self . prog_update ( size ) return layer , piece , self . current , size except StopIteration as err : if self . length > 0 and self . count * SHA256 < len ( self . pieces ): self . hasher = self . Padder ( self . length , self . piece_length ) piece , size = self . advance () layer = next ( self . hasher ) self . prog_update ( 0 ) return layer , piece , self . current , size raise StopIteration from err","title":"Recheck"},{"location":"Source/recheck/#torrentfile.recheck","text":"Module container Checker Class. The CheckerClass takes a torrentfile and tha path to it\u2019s 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.","title":"recheck"},{"location":"Source/recheck/#torrentfile.recheck.Checker","text":"Check a given file or directory to see if it matches a torrentfile. Public constructor for Checker class instance. PARAMETER DESCRIPTION metafile Path to \u201c.torrent\u201d file. TYPE: str path Path where the content is located in filesystem. TYPE: str","title":"Checker"},{"location":"Source/recheck/#torrentfile.recheck.Checker--example","text":"1 2 3 4 5 >> 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) Validate data against hashes contained in .torrent file. PARAMETER DESCRIPTION metafile path to .torrent file TYPE: str path path to content or contents parent directory. TYPE: str Source code in torrentfile\\recheck.py 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 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 if os . path . isdir ( metafile ): raise ArgumentError ( \"The must be a .torrent file. Not a directory\" ) self . last_log = None self . log_msg ( \"Checking: %s , %s \" , metafile , path ) self . metafile = metafile self . total = 0 self . paths = [] self . fileinfo = {} print ( \"Extracting data from torrent file...\" ) self . meta = pyben . load ( metafile ) 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 ()","title":"Example"},{"location":"Source/recheck/#torrentfile.recheck.Checker.check_paths","text":"Gather all file paths described in the torrent file. Source code in torrentfile\\recheck.py 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 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\" ], [])","title":"check_paths()"},{"location":"Source/recheck/#torrentfile.recheck.Checker.find_root","text":"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\u2019s name, if not the directories contents will be searched. The returned value will be the absolute path that matches the torrent\u2019s name. PARAMETER DESCRIPTION path root path to torrent content TYPE: str RETURNS DESCRIPTION str root path to content Source code in torrentfile\\recheck.py 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 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 )","title":"find_root()"},{"location":"Source/recheck/#torrentfile.recheck.Checker.iter_hashes","text":"Produce results of comparing torrent contents piece by piece. YIELDS DESCRIPTION chunck hash of data found on disk TYPE: bytes piece hash of data when complete and correct TYPE: bytes path path to file being hashed TYPE: str size length of bytes hashed for piece TYPE: int Source code in torrentfile\\recheck.py 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 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 () for chunk , piece , path , size in checker ( self ): 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 , ) self . _result = ( matched / consumed ) * 100 if consumed > 0 else 0","title":"iter_hashes()"},{"location":"Source/recheck/#torrentfile.recheck.Checker.log_msg","text":"Log message msg to logger and send msg to callback hook. PARAMETER DESCRIPTION *args formatting args for log message TYPE: dict DEFAULT: () level Log level for this message; default= logging.INFO TYPE: int DEFAULT: logging.INFO Source code in torrentfile\\recheck.py 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 def log_msg ( self , * args , level : int = 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 )","title":"log_msg()"},{"location":"Source/recheck/#torrentfile.recheck.Checker.piece_checker","text":"Check individual pieces of the torrent. RETURNS DESCRIPTION HashChecker | FeedChecker Individual piece hasher. Source code in torrentfile\\recheck.py 123 124 125 126 127 128 129 130 131 132 133 134 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","title":"piece_checker()"},{"location":"Source/recheck/#torrentfile.recheck.Checker.register_callback","text":"Register hooks from 3rd party programs to access generated info. PARAMETER DESCRIPTION hook callback function for the logging feature. TYPE: function Source code in torrentfile\\recheck.py 111 112 113 114 115 116 117 118 119 120 121 @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","title":"register_callback()"},{"location":"Source/recheck/#torrentfile.recheck.Checker.results","text":"Generate result percentage and store for future calls. Source code in torrentfile\\recheck.py 136 137 138 139 140 141 142 143 144 145 146 147 148 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","title":"results()"},{"location":"Source/recheck/#torrentfile.recheck.Checker.walk_file_tree","text":"Traverse File Tree dictionary to get file details. Extract full pathnames, length, root hash, and layer hashes for each file included in the .torrent\u2019s file tree. PARAMETER DESCRIPTION tree File Tree dict extracted from torrent file. TYPE: dict partials list of intermediate pathnames. TYPE: list Source code in torrentfile\\recheck.py 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 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 ])","title":"walk_file_tree()"},{"location":"Source/recheck/#torrentfile.recheck.FeedChecker","text":"Bases: ProgMixin Validates torrent content. Seemlesly validate torrent file contents by comparing hashes in metafile against data on disk. PARAMETER DESCRIPTION checker the checker class instance. TYPE: object Generate hashes of piece length data from filelist contents. Source code in torrentfile\\recheck.py 332 333 334 335 336 337 338 339 340 341 342 343 def __init__ ( self , checker : Checker ): \"\"\" 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 . piece_map = {} self . index = 0 self . piece_count = 0 self . it = None","title":"FeedChecker"},{"location":"Source/recheck/#torrentfile.recheck.FeedChecker.__iter__","text":"Assign iterator and return self. Source code in torrentfile\\recheck.py 345 346 347 348 349 350 def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" self . it = self . iter_pieces () return self","title":"__iter__()"},{"location":"Source/recheck/#torrentfile.recheck.FeedChecker.__next__","text":"Yield back result of comparison. Source code in torrentfile\\recheck.py 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 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 )","title":"__next__()"},{"location":"Source/recheck/#torrentfile.recheck.FeedChecker.extract","text":"Split file paths contents into blocks of data for hash pieces. PARAMETER DESCRIPTION path path to content. TYPE: str partial any remaining content from last file. TYPE: bytes RETURNS DESCRIPTION bytearray Hash digest for block of .torrent contents. Source code in torrentfile\\recheck.py 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 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","title":"extract()"},{"location":"Source/recheck/#torrentfile.recheck.FeedChecker.iter_pieces","text":"Iterate through, and hash pieces of torrent contents. YIELDS DESCRIPTION piece hash digest for block of torrent data. TYPE: bytes Source code in torrentfile\\recheck.py 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 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 ()","title":"iter_pieces()"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker","text":"Bases: ProgMixin Iterate through contents of meta data and verify with file contents. PARAMETER DESCRIPTION checker the checker instance that maintains variables. TYPE: Checker Construct a HybridChecker instance. Source code in torrentfile\\recheck.py 483 484 485 486 487 488 489 490 491 492 493 def __init__ ( self , checker : Checker ): \"\"\" Construct a HybridChecker instance. \"\"\" self . checker = checker self . paths = checker . paths self . piece_length = checker . piece_length self . fileinfo = checker . fileinfo self . piece_layers = checker . meta [ \"piece layers\" ] self . current = None self . index = - 1","title":"HashChecker"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.Padder","text":"Padding class to generate padding hashes wherever needed. PARAMETER DESCRIPTION length the total size of the mock file generating padding for. piece_length the block size that each hash represents. TYPE: int Construct padding class to Mock missing or incomplete files. PARAMETER DESCRIPTION length size of the file TYPE: int piece_length the piece length for each iteration. TYPE: int Source code in torrentfile\\recheck.py 526 527 528 529 530 531 532 533 534 535 536 537 538 539 def __init__ ( self , length , piece_length ): \"\"\" Construct padding class to Mock missing or incomplete files. Parameters ---------- length : int size of the file piece_length : int the piece length for each iteration. \"\"\" self . length = length self . piece_length = piece_length self . pad = sha256 ( bytearray ( piece_length )) . digest ()","title":"Padder"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.Padder.__iter__","text":"Return self to correctly implement iterator type. Source code in torrentfile\\recheck.py 541 542 543 544 545 def __iter__ ( self ): \"\"\" Return self to correctly implement iterator type. \"\"\" return self # pragma: nocover","title":"__iter__()"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.Padder.__next__","text":"Iterate through seemingly endless sha256 hashes of zeros. RETURNS DESCRIPTION tuple returns the padding TYPE: bytes RAISES DESCRIPTION StopIteration Source code in torrentfile\\recheck.py 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 def __next__ ( self ) -> bytes : \"\"\" Iterate through seemingly endless sha256 hashes of zeros. Returns ------- tuple : returns the padding Raises ------ StopIteration \"\"\" if self . length >= self . piece_length : self . length -= self . piece_length return self . pad if self . length > 0 : pad = sha256 ( bytearray ( self . length )) . digest () self . length -= self . length return pad raise StopIteration","title":"__next__()"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.__iter__","text":"Assign iterator and return self. Source code in torrentfile\\recheck.py 495 496 497 498 499 def __iter__ ( self ): \"\"\" Assign iterator and return self. \"\"\" return self","title":"__iter__()"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.__next__","text":"Provide the result of comparison. Source code in torrentfile\\recheck.py 501 502 503 504 505 506 507 508 509 510 511 512 def __next__ ( self ): \"\"\" Provide the result of comparison. \"\"\" if self . current is None : self . next_file () try : return self . process_current () except StopIteration as itererr : if self . next_file (): return self . process_current () raise StopIteration from itererr","title":"__next__()"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.advance","text":"Increment the number of pieces processed for the current file. RETURNS DESCRIPTION tuple the piece and size Source code in torrentfile\\recheck.py 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 def advance ( self ) -> tuple : \"\"\" Increment the number of pieces processed for the current file. Returns ------- tuple the piece and size \"\"\" start = self . count * SHA256 end = start + SHA256 piece = self . pieces [ start : end ] self . count += 1 if self . length >= self . piece_length : self . length -= self . piece_length size = self . piece_length else : size = self . length self . length -= self . length return piece , size","title":"advance()"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.next_file","text":"Remove all references to processed files and prepare for the next. RETURNS DESCRIPTION bool if there is a next file found Source code in torrentfile\\recheck.py 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 def next_file ( self ) -> bool : \"\"\" Remove all references to processed files and prepare for the next. Returns ------- bool if there is a next file found \"\"\" self . index += 1 self . prog_close () if self . current is None or self . index < len ( self . paths ): self . current = self . paths [ self . index ] self . length = self . fileinfo [ self . index ][ \"length\" ] self . root_hash = self . fileinfo [ self . index ][ \"pieces root\" ] if self . length > self . piece_length : self . pieces = self . piece_layers [ self . root_hash ] else : self . pieces = self . root_hash path = self . paths [ self . index ] self . prog_start ( self . length , path , unit = \"bytes\" ) self . count = 0 if os . path . exists ( self . current ): self . hasher = FileHasher ( path , self . piece_length , progress = 0 ) else : self . hasher = self . Padder ( self . length , self . piece_length ) return True if self . index >= len ( self . paths ): del self . current del self . length del self . root_hash del self . pieces return False","title":"next_file()"},{"location":"Source/recheck/#torrentfile.recheck.HashChecker.process_current","text":"Gather necessary information to compare to metafile details. RETURNS DESCRIPTION tuple a tuple containing the layer, piece, current path and size RAISES DESCRIPTION StopIteration Source code in torrentfile\\recheck.py 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 def process_current ( self ) -> tuple : \"\"\" Gather necessary information to compare to metafile details. Returns ------- tuple a tuple containing the layer, piece, current path and size Raises ------ StopIteration \"\"\" try : layer = next ( self . hasher ) piece , size = self . advance () self . prog_update ( size ) return layer , piece , self . current , size except StopIteration as err : if self . length > 0 and self . count * SHA256 < len ( self . pieces ): self . hasher = self . Padder ( self . length , self . piece_length ) piece , size = self . advance () layer = next ( self . hasher ) self . prog_update ( 0 ) return layer , piece , self . current , size raise StopIteration from err","title":"process_current()"},{"location":"Source/torrent/","text":"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: # \u201cannounce\u201d: The URL of the tracker. \u201cinfo\u201d: This maps to a dictionary, with keys described below. \u201cname\u201d: A display name for the torrent. It is purely advisory. \u201cpiece length\u201d: 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. \u201cmeta version\u201d: 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 \u201cfile tree\u201d: 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. \u2018UTF-8 encoded\u2019 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 \u2018.\u2019 and \u2018..\u2019 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. \u201clength\u201d: 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. \u201cpieces root\u201d: 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. \u201cpiece layers\u201d: 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\u2019s the name of a directory. MetaFile ( 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 , meta_version = None , ** _ ) # Base Class for all TorrentFile classes. PARAMETER DESCRIPTION path target path to torrent content. Default: None TYPE: str DEFAULT: None announce One or more tracker URL\u2019s. Default: None TYPE: str DEFAULT: None comment A comment. Default: None TYPE: str DEFAULT: None piece_length Size of torrent pieces. Default: None TYPE: int DEFAULT: None private For private trackers. Default: None TYPE: bool DEFAULT: False outfile target path to write .torrent file. Default: None TYPE: str DEFAULT: None source Private tracker source. Default: None TYPE: str DEFAULT: None progress level of progress bar displayed Default: \u201c1\u201d TYPE: str DEFAULT: 1 cwd If True change default save location to current directory TYPE: bool DEFAULT: False httpseeds one or more web addresses where torrent content can be found. TYPE: list DEFAULT: None url_list one or more web addressess where torrent content exists. TYPE: list DEFAULT: None content alias for \u2018path\u2019 arg. TYPE: str DEFAULT: None meta_version indicates which Bittorrent protocol to use for hashing content TYPE: int DEFAULT: None Construct MetaFile superclass and assign local attributes. Source code in torrentfile\\torrent.py 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 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 , meta_version = 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 self . meta_version = meta_version 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 self . meta_version = meta_version 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 () # Overload in subclasses. RAISES DESCRIPTION Exception NotImplementedError Source code in torrentfile\\torrent.py 350 351 352 353 354 355 356 357 358 359 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. PARAMETER DESCRIPTION func The callback function which accepts a single paramter. TYPE: function Source code in torrentfile\\torrent.py 240 241 242 243 244 245 246 247 248 249 250 251 @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 () # Sort the info and meta dictionaries. Source code in torrentfile\\torrent.py 361 362 363 364 365 366 367 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 ( outfile = None ) -> tuple # Write meta information to .torrent file. Final step in the torrent file creation process. After hashing and sorting every piece of content write the contents to file using the bencode encoding. PARAMETER DESCRIPTION outfile Destination path for .torrent file. default=None TYPE: str DEFAULT: None RETURNS DESCRIPTION outfile Where the .torrent file was writen. TYPE: str meta .torrent meta information. TYPE: dict Source code in torrentfile\\torrent.py 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 def write ( self , outfile = None ) -> tuple : \"\"\" Write meta information to .torrent file. Final step in the torrent file creation process. After hashing and sorting every piece of content write the contents to file using the bencode encoding. Parameters ---------- outfile : str Destination path for .torrent file. default=None Returns ------- outfile : str Where the .torrent file was writen. meta : dict .torrent meta information. \"\"\" if outfile : self . outfile = outfile elif self . outfile : pass else : self . outfile = os . path . join ( os . getcwd (), self . name ) + \".torrent\" 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 as excp : logger . error ( \"Permission Denied: Could not write to %s \" , self . outfile ) raise PermissionError from excp return self . outfile , self . meta TorrentAssembler ( ** kwargs ) # Bases: MetaFile Assembler class for Bittorrent version 2 and hybrid meta files. This differs from the TorrentFileV2 and TorrentFileHybrid, because it can be used as an iterator and works for both versions. PARAMETER DESCRIPTION **kwargs Keyword arguments for torrent options. TYPE: dict DEFAULT: {} Create Bittorrent v1 v2 hybrid metafiles. Source code in torrentfile\\torrent.py 647 648 649 650 651 652 653 654 655 656 657 658 659 660 def __init__ ( self , ** kwargs ): \"\"\" Create Bittorrent v1 v2 hybrid metafiles. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"Assembling bittorrent Hybrid file\" ) self . name = os . path . basename ( self . path ) self . hashes = [] self . piece_layers = {} self . pieces = bytearray () self . files = [] self . hybrid = self . meta_version == \"3\" self . total = len ( utils . get_file_list ( self . path )) self . assemble () assemble () # Assemble the parts of the torrentfile into meta dictionary. Source code in torrentfile\\torrent.py 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 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 ) else : info [ \"file tree\" ] = self . _traverse ( self . path ) if self . hybrid : info [ \"files\" ] = self . files if self . hybrid : info [ \"pieces\" ] = self . pieces self . meta [ \"piece layers\" ] = self . piece_layers return info TorrentFile ( ** kwargs ) # Bases: MetaFile , ProgMixin Class for creating Bittorrent meta files. Construct Torrentfile class instance object. PARAMETER DESCRIPTION **kwargs Dictionary containing torrent file options. TYPE: dict DEFAULT: {} Construct TorrentFile instance with given keyword args. PARAMETER DESCRIPTION **kwargs dictionary of keyword args passed to superclass. TYPE: dict DEFAULT: {} Source code in torrentfile\\torrent.py 422 423 424 425 426 427 428 429 430 431 432 433 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 ( \"Assembling bittorrent v1 torrent file\" ) self . assemble () assemble () # Assemble components of torrent metafile. RETURNS DESCRIPTION dict metadata dictionary for torrent file Source code in torrentfile\\torrent.py 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 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 ) for piece in feeder : pieces . extend ( piece ) info [ \"pieces\" ] = pieces TorrentFileHybrid ( ** kwargs ) # Bases: MetaFile , ProgMixin Construct the Hybrid torrent meta file with provided parameters. PARAMETER DESCRIPTION **kwargs Keyword arguments for torrent options. TYPE: dict DEFAULT: {} Create Bittorrent v1 v2 hybrid metafiles. Source code in torrentfile\\torrent.py 555 556 557 558 559 560 561 562 563 564 565 566 567 def __init__ ( self , ** kwargs ): \"\"\" Create Bittorrent v1 v2 hybrid metafiles. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"Assembling bittorrent Hybrid file\" ) 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 )) self . assemble () assemble () # Assemble the parts of the torrentfile into meta dictionary. Source code in torrentfile\\torrent.py 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 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 ) 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 ( ** kwargs ) # Bases: MetaFile , ProgMixin Class for creating Bittorrent meta v2 files. PARAMETER DESCRIPTION **kwargs Keyword arguments for torrent file options. TYPE: dict DEFAULT: {} Construct TorrentFileV2 Class instance from given parameters. PARAMETER DESCRIPTION **kwargs keywword arguments to pass to superclass. TYPE: dict DEFAULT: {} Source code in torrentfile\\torrent.py 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 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 ( \"Assembling bittorrent v2 torrent file\" ) self . piece_layers = {} self . hashes = [] self . total = len ( utils . get_file_list ( self . path )) self . assemble () assemble () # Assemble then return the meta dictionary for encoding. RETURNS DESCRIPTION meta Metainformation about the torrent. TYPE: dict Source code in torrentfile\\torrent.py 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 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","title":"Torrent"},{"location":"Source/torrent/#torrentfile.torrent","text":"Classes and procedures pertaining to the creation of torrent meta files.","title":"torrent"},{"location":"Source/torrent/#torrentfile.torrent--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/torrent/#torrentfile.torrent--constants","text":"BLOCK_SIZE : int size of leaf hashes for merkle tree. HASH_SIZE : int Length of a sha256 hash.","title":"Constants"},{"location":"Source/torrent/#torrentfile.torrent--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/torrent/#torrentfile.torrent--meta-version-2-dictionary","text":"\u201cannounce\u201d: The URL of the tracker. \u201cinfo\u201d: This maps to a dictionary, with keys described below. \u201cname\u201d: A display name for the torrent. It is purely advisory. \u201cpiece length\u201d: 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. \u201cmeta version\u201d: 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 \u201cfile tree\u201d: 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. \u2018UTF-8 encoded\u2019 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 \u2018.\u2019 and \u2018..\u2019 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. \u201clength\u201d: 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. \u201cpieces root\u201d: 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. \u201cpiece layers\u201d: 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/torrent/#torrentfile.torrent--bittorrent-v1","text":"","title":"Bittorrent V1"},{"location":"Source/torrent/#torrentfile.torrent--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\u2019s the name of a directory.","title":"v1 meta-dictionary"},{"location":"Source/torrent/#torrentfile.torrent.MetaFile","text":"Base Class for all TorrentFile classes. PARAMETER DESCRIPTION path target path to torrent content. Default: None TYPE: str DEFAULT: None announce One or more tracker URL\u2019s. Default: None TYPE: str DEFAULT: None comment A comment. Default: None TYPE: str DEFAULT: None piece_length Size of torrent pieces. Default: None TYPE: int DEFAULT: None private For private trackers. Default: None TYPE: bool DEFAULT: False outfile target path to write .torrent file. Default: None TYPE: str DEFAULT: None source Private tracker source. Default: None TYPE: str DEFAULT: None progress level of progress bar displayed Default: \u201c1\u201d TYPE: str DEFAULT: 1 cwd If True change default save location to current directory TYPE: bool DEFAULT: False httpseeds one or more web addresses where torrent content can be found. TYPE: list DEFAULT: None url_list one or more web addressess where torrent content exists. TYPE: list DEFAULT: None content alias for \u2018path\u2019 arg. TYPE: str DEFAULT: None meta_version indicates which Bittorrent protocol to use for hashing content TYPE: int DEFAULT: None Construct MetaFile superclass and assign local attributes. Source code in torrentfile\\torrent.py 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 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 , meta_version = 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 self . meta_version = meta_version 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 self . meta_version = meta_version parent , self . name = os . path . split ( self . path ) if not self . name : self . name = os . path . basename ( parent ) self . meta [ \"info\" ][ \"name\" ] = self . name","title":"MetaFile"},{"location":"Source/torrent/#torrentfile.torrent.MetaFile.assemble","text":"Overload in subclasses. RAISES DESCRIPTION Exception NotImplementedError Source code in torrentfile\\torrent.py 350 351 352 353 354 355 356 357 358 359 def assemble ( self ): \"\"\" Overload in subclasses. Raises ------ Exception NotImplementedError \"\"\" raise NotImplementedError","title":"assemble()"},{"location":"Source/torrent/#torrentfile.torrent.MetaFile.set_callback","text":"Assign a callback function for the Hashing class to call for each hash. PARAMETER DESCRIPTION func The callback function which accepts a single paramter. TYPE: function Source code in torrentfile\\torrent.py 240 241 242 243 244 245 246 247 248 249 250 251 @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 )","title":"set_callback()"},{"location":"Source/torrent/#torrentfile.torrent.MetaFile.sort_meta","text":"Sort the info and meta dictionaries. Source code in torrentfile\\torrent.py 361 362 363 364 365 366 367 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","title":"sort_meta()"},{"location":"Source/torrent/#torrentfile.torrent.MetaFile.write","text":"Write meta information to .torrent file. Final step in the torrent file creation process. After hashing and sorting every piece of content write the contents to file using the bencode encoding. PARAMETER DESCRIPTION outfile Destination path for .torrent file. default=None TYPE: str DEFAULT: None RETURNS DESCRIPTION outfile Where the .torrent file was writen. TYPE: str meta .torrent meta information. TYPE: dict Source code in torrentfile\\torrent.py 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 def write ( self , outfile = None ) -> tuple : \"\"\" Write meta information to .torrent file. Final step in the torrent file creation process. After hashing and sorting every piece of content write the contents to file using the bencode encoding. Parameters ---------- outfile : str Destination path for .torrent file. default=None Returns ------- outfile : str Where the .torrent file was writen. meta : dict .torrent meta information. \"\"\" if outfile : self . outfile = outfile elif self . outfile : pass else : self . outfile = os . path . join ( os . getcwd (), self . name ) + \".torrent\" 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 as excp : logger . error ( \"Permission Denied: Could not write to %s \" , self . outfile ) raise PermissionError from excp return self . outfile , self . meta","title":"write()"},{"location":"Source/torrent/#torrentfile.torrent.TorrentAssembler","text":"Bases: MetaFile Assembler class for Bittorrent version 2 and hybrid meta files. This differs from the TorrentFileV2 and TorrentFileHybrid, because it can be used as an iterator and works for both versions. PARAMETER DESCRIPTION **kwargs Keyword arguments for torrent options. TYPE: dict DEFAULT: {} Create Bittorrent v1 v2 hybrid metafiles. Source code in torrentfile\\torrent.py 647 648 649 650 651 652 653 654 655 656 657 658 659 660 def __init__ ( self , ** kwargs ): \"\"\" Create Bittorrent v1 v2 hybrid metafiles. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"Assembling bittorrent Hybrid file\" ) self . name = os . path . basename ( self . path ) self . hashes = [] self . piece_layers = {} self . pieces = bytearray () self . files = [] self . hybrid = self . meta_version == \"3\" self . total = len ( utils . get_file_list ( self . path )) self . assemble ()","title":"TorrentAssembler"},{"location":"Source/torrent/#torrentfile.torrent.TorrentAssembler.assemble","text":"Assemble the parts of the torrentfile into meta dictionary. Source code in torrentfile\\torrent.py 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 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 ) else : info [ \"file tree\" ] = self . _traverse ( self . path ) if self . hybrid : info [ \"files\" ] = self . files if self . hybrid : info [ \"pieces\" ] = self . pieces self . meta [ \"piece layers\" ] = self . piece_layers return info","title":"assemble()"},{"location":"Source/torrent/#torrentfile.torrent.TorrentFile","text":"Bases: MetaFile , ProgMixin Class for creating Bittorrent meta files. Construct Torrentfile class instance object. PARAMETER DESCRIPTION **kwargs Dictionary containing torrent file options. TYPE: dict DEFAULT: {} Construct TorrentFile instance with given keyword args. PARAMETER DESCRIPTION **kwargs dictionary of keyword args passed to superclass. TYPE: dict DEFAULT: {} Source code in torrentfile\\torrent.py 422 423 424 425 426 427 428 429 430 431 432 433 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 ( \"Assembling bittorrent v1 torrent file\" ) self . assemble ()","title":"TorrentFile"},{"location":"Source/torrent/#torrentfile.torrent.TorrentFile.assemble","text":"Assemble components of torrent metafile. RETURNS DESCRIPTION dict metadata dictionary for torrent file Source code in torrentfile\\torrent.py 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 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 ) for piece in feeder : pieces . extend ( piece ) info [ \"pieces\" ] = pieces","title":"assemble()"},{"location":"Source/torrent/#torrentfile.torrent.TorrentFileHybrid","text":"Bases: MetaFile , ProgMixin Construct the Hybrid torrent meta file with provided parameters. PARAMETER DESCRIPTION **kwargs Keyword arguments for torrent options. TYPE: dict DEFAULT: {} Create Bittorrent v1 v2 hybrid metafiles. Source code in torrentfile\\torrent.py 555 556 557 558 559 560 561 562 563 564 565 566 567 def __init__ ( self , ** kwargs ): \"\"\" Create Bittorrent v1 v2 hybrid metafiles. \"\"\" super () . __init__ ( ** kwargs ) logger . debug ( \"Assembling bittorrent Hybrid file\" ) 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 )) self . assemble ()","title":"TorrentFileHybrid"},{"location":"Source/torrent/#torrentfile.torrent.TorrentFileHybrid.assemble","text":"Assemble the parts of the torrentfile into meta dictionary. Source code in torrentfile\\torrent.py 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 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 ) 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","title":"assemble()"},{"location":"Source/torrent/#torrentfile.torrent.TorrentFileV2","text":"Bases: MetaFile , ProgMixin Class for creating Bittorrent meta v2 files. PARAMETER DESCRIPTION **kwargs Keyword arguments for torrent file options. TYPE: dict DEFAULT: {} Construct TorrentFileV2 Class instance from given parameters. PARAMETER DESCRIPTION **kwargs keywword arguments to pass to superclass. TYPE: dict DEFAULT: {} Source code in torrentfile\\torrent.py 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 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 ( \"Assembling bittorrent v2 torrent file\" ) self . piece_layers = {} self . hashes = [] self . total = len ( utils . get_file_list ( self . path )) self . assemble ()","title":"TorrentFileV2"},{"location":"Source/torrent/#torrentfile.torrent.TorrentFileV2.assemble","text":"Assemble then return the meta dictionary for encoding. RETURNS DESCRIPTION meta Metainformation about the torrent. TYPE: dict Source code in torrentfile\\torrent.py 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 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","title":"assemble()"},{"location":"Source/utils/","text":"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. ArgumentError # Bases: Exception Exception for mismatched or mistyped CLI arguments. Memo ( func ) # Memoize cache. PARAMETER DESCRIPTION func The results of this callable will be cached. TYPE: Callable Construct for memoization. Source code in torrentfile\\utils.py 52 53 54 55 56 57 58 def __init__ ( self , func ): \"\"\" Construct for memoization. \"\"\" self . func = func self . counter = 0 self . cache = {} __call__ ( path ) # Invoke each time memo function is called. PARAMETER DESCRIPTION path The relative or absolute path being used as key in cache dict. TYPE: str RETURNS DESCRIPTION Any The results of calling the function with path. Source code in torrentfile\\utils.py 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 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 MissingPathError ( message : str = None ) # Bases: Exception Path parameter is required to specify target content. Creating a .torrent file with no contents seems rather silly. PARAMETER DESCRIPTION message Message for user (optional). TYPE: str DEFAULT: None 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 94 95 96 97 98 99 100 101 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 ( message : str = None ) # Bases: Exception Piece Length parameter must equal a perfect power of 2. PARAMETER DESCRIPTION message Message for user (optional). TYPE: str DEFAULT: None 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 114 115 116 117 118 119 120 121 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 ) copypath ( source : str , dest : str ) -> None # Copy the file located at source to dest. If one or more directory paths don\u2019t exist in dest, they will be created. If dest already exists and dest and source are the same size, it will be ignored, however if dest is smaller than source, dest will be overwritten. PARAMETER DESCRIPTION source path to source file TYPE: str dest path to target destination TYPE: str Source code in torrentfile\\utils.py 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 def copypath ( source : str , dest : str ) -> None : \"\"\" Copy the file located at source to dest. If one or more directory paths don't exist in dest, they will be created. If dest already exists and dest and source are the same size, it will be ignored, however if dest is smaller than source, dest will be overwritten. Parameters ---------- source : str path to source file dest : str path to target destination \"\"\" if not os . path . exists ( source ): return if os . path . exists ( dest ): if os . path . getsize ( source ) <= os . path . getsize ( dest ): return shutil . copy ( source , dest ) # pragma: nocover return # pragma: nocover path_parts = iter ( Path ( dest ) . parts [: - 1 ]) try : root = next ( path_parts ) except StopIteration : # pragma: nocover return if not os . path . exists ( root ): os . mkdir ( root ) # pragma: nocover for part in path_parts : path = os . path . join ( root , part ) if not os . path . exists ( path ): os . mkdir ( path ) root = path shutil . copy ( source , dest ) debug_is_on () -> bool # Return True if debug mode is on in environment variables. RETURNS DESCRIPTION bool is debug mode on Source code in torrentfile\\utils.py 417 418 419 420 421 422 423 424 425 426 def debug_is_on () -> bool : \"\"\" Return True if debug mode is on in environment variables. Returns ------- bool is debug mode on \"\"\" return os . environ [ \"TORRENTFILE_DEBUG\" ] == \"ON\" filelist_total ( pathstring : str ) -> os . PathLike # Perform error checking and format conversion to os.PathLike. PARAMETER DESCRIPTION pathstring An existing filesystem path. TYPE: str RETURNS DESCRIPTION os . PathLike Input path converted to bytes format. RAISES DESCRIPTION MissingPathError File could not be found. Source code in torrentfile\\utils.py 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 @Memo def filelist_total ( pathstring : str ) -> os . PathLike : \"\"\" Perform error checking and format conversion to os.PathLike. Parameters ---------- pathstring : str An existing filesystem path. Returns ------- os.PathLike Input path converted to bytes format. Raises ------ MissingPathError File could not be found. \"\"\" if os . path . exists ( pathstring ): path = Path ( pathstring ) return _filelist_total ( path ) raise MissingPathError get_file_list ( path : str ) -> list # Return a sorted list of file paths contained in directory. PARAMETER DESCRIPTION path target file or directory. TYPE: str RETURNS DESCRIPTION list sorted list of file paths. TYPE: list Source code in torrentfile\\utils.py 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 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 : int ) -> int # Calculate the ideal piece length for bittorrent data. PARAMETER DESCRIPTION size Total bits of all files incluided in .torrent file. TYPE: int RETURNS DESCRIPTION int Ideal piece length. Source code in torrentfile\\utils.py 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 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 ) > 1000 and exp < 24 : exp += 1 return 2 ** exp humanize_bytes ( amount : int ) -> str # Convert integer into human readable memory sized denomination. PARAMETER DESCRIPTION amount total number of bytes. TYPE: int RETURNS DESCRIPTION str human readable representation of the given amount of bytes. Source code in torrentfile\\utils.py 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 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 : int ) -> int # Calculate the next perfect power of 2 equal to or greater than value. PARAMETER DESCRIPTION value integer value that is less than some perfect power of 2. TYPE: int RETURNS DESCRIPTION int The next power of 2 greater than value, or value if already power of 2. Source code in torrentfile\\utils.py 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 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 : int ) -> int # Verify input piece_length is valid and convert accordingly. PARAMETER DESCRIPTION piece_length The piece length provided by user. TYPE: int | str RETURNS DESCRIPTION int normalized piece length. RAISES DESCRIPTION PieceLengthValueError : Piece length is improper value. Source code in torrentfile\\utils.py 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 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 : 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 piece_length > ( 1 << 14 ): if 2 ** math . log2 ( piece_length ) == piece_length : return piece_length 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 : str ) -> int # Calculate piece length for input path and contents. PARAMETER DESCRIPTION path The absolute path to directory and contents. TYPE: str RETURNS DESCRIPTION int The size of pieces of torrent content. Source code in torrentfile\\utils.py 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 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 : str ) -> int # Return the total size of all files in path recursively. PARAMETER DESCRIPTION path path to target file or directory. TYPE: str RETURNS DESCRIPTION int total size of files. Source code in torrentfile\\utils.py 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 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 : str ) -> tuple # Calculate directory statistics. PARAMETER DESCRIPTION path The path to start calculating from. TYPE: str RETURNS DESCRIPTION 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. Source code in torrentfile\\utils.py 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 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 ) toggle_debug_mode ( switch_on : bool ) # Switch the environment variable debug indicator on or off. PARAMETER DESCRIPTION switch_on if true turn debug mode on otherwise off TYPE: bool Source code in torrentfile\\utils.py 405 406 407 408 409 410 411 412 413 414 def toggle_debug_mode ( switch_on : bool ): \"\"\" Switch the environment variable debug indicator on or off. Parameters ---------- switch_on : bool if true turn debug mode on otherwise off \"\"\" os . environ [ \"TORRENTFILE_DEBUG\" ] = \"ON\" if switch_on else \"OFF\"","title":"Utils"},{"location":"Source/utils/#torrentfile.utils","text":"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.","title":"utils"},{"location":"Source/utils/#torrentfile.utils.ArgumentError","text":"Bases: Exception Exception for mismatched or mistyped CLI arguments.","title":"ArgumentError"},{"location":"Source/utils/#torrentfile.utils.Memo","text":"Memoize cache. PARAMETER DESCRIPTION func The results of this callable will be cached. TYPE: Callable Construct for memoization. Source code in torrentfile\\utils.py 52 53 54 55 56 57 58 def __init__ ( self , func ): \"\"\" Construct for memoization. \"\"\" self . func = func self . counter = 0 self . cache = {}","title":"Memo"},{"location":"Source/utils/#torrentfile.utils.Memo.__call__","text":"Invoke each time memo function is called. PARAMETER DESCRIPTION path The relative or absolute path being used as key in cache dict. TYPE: str RETURNS DESCRIPTION Any The results of calling the function with path. Source code in torrentfile\\utils.py 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 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","title":"__call__()"},{"location":"Source/utils/#torrentfile.utils.MissingPathError","text":"Bases: Exception Path parameter is required to specify target content. Creating a .torrent file with no contents seems rather silly. PARAMETER DESCRIPTION message Message for user (optional). TYPE: str DEFAULT: None 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 94 95 96 97 98 99 100 101 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 )","title":"MissingPathError"},{"location":"Source/utils/#torrentfile.utils.PieceLengthValueError","text":"Bases: Exception Piece Length parameter must equal a perfect power of 2. PARAMETER DESCRIPTION message Message for user (optional). TYPE: str DEFAULT: None 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 114 115 116 117 118 119 120 121 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 )","title":"PieceLengthValueError"},{"location":"Source/utils/#torrentfile.utils.copypath","text":"Copy the file located at source to dest. If one or more directory paths don\u2019t exist in dest, they will be created. If dest already exists and dest and source are the same size, it will be ignored, however if dest is smaller than source, dest will be overwritten. PARAMETER DESCRIPTION source path to source file TYPE: str dest path to target destination TYPE: str Source code in torrentfile\\utils.py 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 def copypath ( source : str , dest : str ) -> None : \"\"\" Copy the file located at source to dest. If one or more directory paths don't exist in dest, they will be created. If dest already exists and dest and source are the same size, it will be ignored, however if dest is smaller than source, dest will be overwritten. Parameters ---------- source : str path to source file dest : str path to target destination \"\"\" if not os . path . exists ( source ): return if os . path . exists ( dest ): if os . path . getsize ( source ) <= os . path . getsize ( dest ): return shutil . copy ( source , dest ) # pragma: nocover return # pragma: nocover path_parts = iter ( Path ( dest ) . parts [: - 1 ]) try : root = next ( path_parts ) except StopIteration : # pragma: nocover return if not os . path . exists ( root ): os . mkdir ( root ) # pragma: nocover for part in path_parts : path = os . path . join ( root , part ) if not os . path . exists ( path ): os . mkdir ( path ) root = path shutil . copy ( source , dest )","title":"copypath()"},{"location":"Source/utils/#torrentfile.utils.debug_is_on","text":"Return True if debug mode is on in environment variables. RETURNS DESCRIPTION bool is debug mode on Source code in torrentfile\\utils.py 417 418 419 420 421 422 423 424 425 426 def debug_is_on () -> bool : \"\"\" Return True if debug mode is on in environment variables. Returns ------- bool is debug mode on \"\"\" return os . environ [ \"TORRENTFILE_DEBUG\" ] == \"ON\"","title":"debug_is_on()"},{"location":"Source/utils/#torrentfile.utils.filelist_total","text":"Perform error checking and format conversion to os.PathLike. PARAMETER DESCRIPTION pathstring An existing filesystem path. TYPE: str RETURNS DESCRIPTION os . PathLike Input path converted to bytes format. RAISES DESCRIPTION MissingPathError File could not be found. Source code in torrentfile\\utils.py 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 @Memo def filelist_total ( pathstring : str ) -> os . PathLike : \"\"\" Perform error checking and format conversion to os.PathLike. Parameters ---------- pathstring : str An existing filesystem path. Returns ------- os.PathLike Input path converted to bytes format. Raises ------ MissingPathError File could not be found. \"\"\" if os . path . exists ( pathstring ): path = Path ( pathstring ) return _filelist_total ( path ) raise MissingPathError","title":"filelist_total()"},{"location":"Source/utils/#torrentfile.utils.get_file_list","text":"Return a sorted list of file paths contained in directory. PARAMETER DESCRIPTION path target file or directory. TYPE: str RETURNS DESCRIPTION list sorted list of file paths. TYPE: list Source code in torrentfile\\utils.py 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 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","title":"get_file_list()"},{"location":"Source/utils/#torrentfile.utils.get_piece_length","text":"Calculate the ideal piece length for bittorrent data. PARAMETER DESCRIPTION size Total bits of all files incluided in .torrent file. TYPE: int RETURNS DESCRIPTION int Ideal piece length. Source code in torrentfile\\utils.py 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 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 ) > 1000 and exp < 24 : exp += 1 return 2 ** exp","title":"get_piece_length()"},{"location":"Source/utils/#torrentfile.utils.humanize_bytes","text":"Convert integer into human readable memory sized denomination. PARAMETER DESCRIPTION amount total number of bytes. TYPE: int RETURNS DESCRIPTION str human readable representation of the given amount of bytes. Source code in torrentfile\\utils.py 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 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\"","title":"humanize_bytes()"},{"location":"Source/utils/#torrentfile.utils.next_power_2","text":"Calculate the next perfect power of 2 equal to or greater than value. PARAMETER DESCRIPTION value integer value that is less than some perfect power of 2. TYPE: int RETURNS DESCRIPTION int The next power of 2 greater than value, or value if already power of 2. Source code in torrentfile\\utils.py 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 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","title":"next_power_2()"},{"location":"Source/utils/#torrentfile.utils.normalize_piece_length","text":"Verify input piece_length is valid and convert accordingly. PARAMETER DESCRIPTION piece_length The piece length provided by user. TYPE: int | str RETURNS DESCRIPTION int normalized piece length. RAISES DESCRIPTION PieceLengthValueError : Piece length is improper value. Source code in torrentfile\\utils.py 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 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 : 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 piece_length > ( 1 << 14 ): if 2 ** math . log2 ( piece_length ) == piece_length : return piece_length 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","title":"normalize_piece_length()"},{"location":"Source/utils/#torrentfile.utils.path_piece_length","text":"Calculate piece length for input path and contents. PARAMETER DESCRIPTION path The absolute path to directory and contents. TYPE: str RETURNS DESCRIPTION int The size of pieces of torrent content. Source code in torrentfile\\utils.py 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 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 )","title":"path_piece_length()"},{"location":"Source/utils/#torrentfile.utils.path_size","text":"Return the total size of all files in path recursively. PARAMETER DESCRIPTION path path to target file or directory. TYPE: str RETURNS DESCRIPTION int total size of files. Source code in torrentfile\\utils.py 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 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","title":"path_size()"},{"location":"Source/utils/#torrentfile.utils.path_stat","text":"Calculate directory statistics. PARAMETER DESCRIPTION path The path to start calculating from. TYPE: str RETURNS DESCRIPTION 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. Source code in torrentfile\\utils.py 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 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 )","title":"path_stat()"},{"location":"Source/utils/#torrentfile.utils.toggle_debug_mode","text":"Switch the environment variable debug indicator on or off. PARAMETER DESCRIPTION switch_on if true turn debug mode on otherwise off TYPE: bool Source code in torrentfile\\utils.py 405 406 407 408 409 410 411 412 413 414 def toggle_debug_mode ( switch_on : bool ): \"\"\" Switch the environment variable debug indicator on or off. Parameters ---------- switch_on : bool if true turn debug mode on otherwise off \"\"\" os . environ [ \"TORRENTFILE_DEBUG\" ] = \"ON\" if switch_on else \"OFF\"","title":"toggle_debug_mode()"},{"location":"Source/version/","text":"version # Holds the release version number.","title":"Version"},{"location":"Source/version/#torrentfile.version","text":"Holds the release version number.","title":"version"},{"location":"coverage/","text":".md-content { max-width: none !important; } article h1, article > a { display: none; } var coviframe = document.getElementById(\"coviframe\"); function resizeIframe() { coviframe.style.height = coviframe.contentWindow.document.documentElement.offsetHeight + 'px'; } coviframe.contentWindow.document.body.onclick = function() { coviframe.contentWindow.location.reload(); }","title":"Coverage"}]} \ No newline at end of file diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 66389072..ab617cb2 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -2,97 +2,97 @@ https://alexpdev.github.io/torrentfile/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/Apache2/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/api/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/changelog/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/overview/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/usage/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/Source/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/Source/cli/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/Source/commands/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/Source/edit/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/Source/hasher/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/Source/interactive/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/Source/mixins/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/Source/rebuild/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/Source/recheck/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/Source/torrent/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/Source/utils/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/Source/version/ - 2022-10-09 + 2022-10-21 daily https://alexpdev.github.io/torrentfile/coverage/ - 2022-10-09 + 2022-10-21 daily \ No newline at end of file diff --git a/docs/sitemap.xml.gz b/docs/sitemap.xml.gz index 83f1bdb8b7e822573066e4c6f4e17d97c0d7ca94..76118f2806a92b8bd86f465f94e77e08b958a0b7 100644 GIT binary patch delta 290 zcmV+-0p0%20?q<|ABzYGF)&hN0{?SqbY*Q}a4vXlYyj1lOK!v<42Jh}iimwel2uz} zGMY^<(Dncfc!E_PDUe5RUowfR>?>8l8^AKZJ|tWEX)d!?*$B>~HD^*4CCR{WE6{Xj z^6~zfKaop&Y6hn~ge(>te+}lActFlmImt_m~cl0w4qD!{x0)DThZ2KUFTJq*HtXpa=S7v znd#u};Hwo3)ptX8nH7ruQvHGN+OzoV`tY2r$HOzOs>*hUC$({AMuh3`po3@K@rUTm o4vr^-$rUCLgKD*AM)*{CL10LQRWdKZlMVqm2>l>F4IvEx08S)`_W%F@ delta 290 zcmV+-0p0%20?q<|ABzYGloUc^0{?SqbY*Q}a4vXlYyj1lOK!v<42Jh}iYWU)CaX@B zWYkSB(Dncfc!*UVDUe5RUowfR>?>8l8^AKZJ|tWEsjstE*$B>~HBEjdWuAfIR-lnh z{_*}=JmuH+R1Z#h2w5yPzR9}~`e#a~X(Ap2Xx4jTour2>bs`9%nC(+ss6zC(zE#N>hPSb$HOzOs>;g4liD~lBf@le(7`i#{2_X? ogX76ya)rqdpjxe&5k3`O5ExQn?U?VtlMVqm2!M1oTOkbq046k#ZU6uP diff --git a/docs/usage/index.html b/docs/usage/index.html index b439ce8f..23ce16b9 100644 --- a/docs/usage/index.html +++ b/docs/usage/index.html @@ -304,39 +304,11 @@ Main - - -
  • - - Create - -
  • - - -
  • - - Edit - - -
  • - -
  • - - Recheck - - -
  • - -
  • - - Magnet - -
  • @@ -664,33 +636,33 @@

    Main#25 26

    Usage
     =====
    -torrentfile [-h] [-i] [-V] [-v]
    -                <create> <edit> <magnet> <recheck> ...
    +    torrentfile [options] command [command options]
     
    -CLI Tool for creating, checking, editing... Bittorrent meta files. TorrentFile supports all versions of torrent files.
    +Command line tools for creating, editing, checking, building and interacting with Bittorrent metainfo 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
    --------
    -<create> <edit> <magnet> <recheck>
    -    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.
    +    -h, --help             show this help message and exit
    +    -i, --interactive      select program options interactively
    +    -q, --quiet            Turn off all text output.
    +    -V, --version          show program version and exit
    +    -v, --verbose          output debug information
    +
    +Commands
    +--------
    +create, edit, info, magnet, recheck, rebuild
    +
    +    create (c, new)      Create a new Bittorrent file.
    +    edit (e)             Edit existing torrent meta file.
    +    info (i)             Show detailed information about a torrent file.
    +    magnet (m)           Generate magnet url from an existing Bittorrent meta file.
    +    recheck (check)      Gives a detailed look at how much of the torrent is available.
    +    rebuild (build)      Re-assemble files obtained from a bittorrent file into the
    +                        appropriate file structure for re-seeding.  Read documentation
    +                        for more information, or use cases.
     


    -

    Create#

    +

    Create#

     1
      2
      3
    @@ -726,42 +698,74 @@ 

    Create33 34 35 -36

    Usage
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    Usage
     =====
    -torrentfile c [-h] [-a <url> [<url> ...]] [-p] [-s <source>] [-m]
    -                    [-c <comment>] [-o <path>] [-t <url> [<url> ...]]
    -                    [--noprogress] [--meta-version <int>]
    -                    [--piece-length <int>] [-w <url> [<url> ...]]
    -                    <content>
    +
    +    torrentfile [options] create
    +    [-h] [-a <url> [<url> ...]] [-p]
    +    [-s <source>] [-m] [-c <comment>]
    +    [-o <path>] [--cwd] [--prog PROGRESS]
    +    [--meta-version <int>]
    +    [--piece-length <int>]
    +    [-w <url> [<url> ...]]
    +    [--http-seed <url> [<url> ...]]
    +    [<content>]
     
     Positional Arguments
     --------------------
    -<content>                                           Path to content file or directory
    +<content>              Path to content file or directory
     
    -Optional Arguments
    -------------------
    --h, --help                                          show this help message and exit
    --a <url> [<url> ...], --announce <url> [<url> ...]  Alias for -t/--tracker
    --p, --private                                       Create a private torrent file
    --s <source>, --source <source>                      Useful for cross-seeding
    --m, --magnet                                        Output Magnet Link after creation completes
    --c <comment>, --comment <comment>                   Include a comment in file metadata
    --o <path>, --out <path>                             Output path for created .torrent file
    --t <url> [<url> ...], --tracker <url> [<url> ...]   One or more Bittorrent tracker announce url(s).
    ---prog, --progress <level>                          (0) = no progress bar displayed
    -                                                    (1) = progress bar is displayed (default)
    ---meta-version <int>                                Bittorrent metafile version.
    -                                                    Options - {1, 2, 3}
    -                                                    (1) = Bittorrent v1 (Default)
    -                                                    (2) = Bittorrent v2
    -                                                    (3) = Bittorrent v1 & v2 hybrid
    ---piece-length <int>                                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 <url> [<url> ...], --web-seed <url> [<url> ...]  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
    +Options
    +-------
    +    -h, --help             show this help message and exit
    +    -a <url> [<url> ...], -t <url> [<url> ...], --announce <url> [<url> ...], --tracker <url> [<url> ...]
    +                        One or more space-seperated torrent tracker url(s).
    +    -p, --private          Creates private torrent with multi-tracker and DHT turned off.
    +    -s <source>, --source <source>
    +                        Add a source string. Useful for cross-seeding.
    +    -m, --magnet
    +    -c <comment>, --comment <comment>
    +                        Include a comment in file metadata
    +    -o <path>, --out <path>
    +                        Explicitly specify the path to write the file.
    +    --cwd, --current       *deprecated* Saving to current directory is default behaviour
    +    --prog PROGRESS, --progress PROGRESS
    +                        Set the progress bar level.
    +                        Options = 0, 1
    +                        (0) = Do not display progress bar.
    +                        (1) = Display progress bar.(default)
    +
    +    --meta-version <int>   Bittorrent metafile version.
    +                        Options = 1, 2, 3
    +                        (1) = Bittorrent v1 (Default)
    +                        (2) = Bittorrent v2
    +                        (3) = Bittorrent v1 & v2 hybrid
    +
    +    --piece-length <int>   (Default: <blank>) 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]
    +
    +    -w <url> [<url> ...], --web-seed <url> [<url> ...]
    +                        list of web addresses where torrent data exists (GetRight).
    +    --http-seed <url> [<url> ...]
    +                        list of URLs, addresses where content can be found (Hoffman).
     

    Edit#

    @@ -860,7 +864,6 @@

    MagnetCoverage

    diff --git a/mkdocs.yml b/mkdocs.yml index a31757da..d520723b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -41,7 +41,7 @@ nav: - API : api.md - Coverage: coverage.md - Source: - - Source/index.md + - Source/index.md - Source/cli.md - Source/commands.md - Source/edit.md @@ -89,7 +89,7 @@ plugins: - search - coverage: page_name: coverage - html_report_dir: htmlcov + html_report_dir: htmlcover - section-index - autorefs - mkapi diff --git a/pyproject.toml b/pyproject.toml index ecdff91d..96049871 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "torrentfile" authors = [{name = "alexpdev", email = "alexpdev@pm.me"}] description = "Terminal based command line tool for creating Bittorrent files." license = {file = "LICENSE"} -version = "0.8.4" +version = "0.8.5" readme = "README.md" requires-python = ">=3.6" keywords = ["Bittorrent", "torrent", "bittorrent-metafiles", "CLI"] @@ -32,7 +32,7 @@ classifiers = [ dependencies = ["pyben"] [project.scripts] -torrentfile = "torrentfile.__main__:main" +torrentfile = "torrentfile:main" [tool.bandit] skips = ["B101"] diff --git a/setup.cfg b/setup.cfg index a00b8b87..b043f596 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ name = torrentfile long_description_content_type = text/markdown long_description = file: README.md description = Terminal based command line tool for creating Bittorrent files. -version = 0.8.4 +version = 0.8.5 classifiers = Environment :: Console Development Status :: 3 - Alpha @@ -33,6 +33,10 @@ setup_requires = setuptools wheel +[options.entry_points] +console_scripts = + torrentfile = torrentfile:main + [options.package_data] exclude = .env diff --git a/site/api.md b/site/api.md index 1d3aaa98..26a1449b 100644 --- a/site/api.md +++ b/site/api.md @@ -29,3 +29,6 @@ ## `Hasher` Module ![mkapi](torrentfile.hasher) + + +[Coverage](https://codecov.io/gh/alexpdev/torrentfile/branch/master/graphs/icicle.svg?token=EWF7NIL9SQ) diff --git a/site/changelog.md b/site/changelog.md index 409214fa..8766bb5f 100644 --- a/site/changelog.md +++ b/site/changelog.md @@ -1,5 +1,24 @@ # TorrentFile +## Version 0.8.5 + +- Fixed bug with linux platforms not installing a binary cli command +- Fixed debug logging errors with the rebuild command +- Improved log message readability +- Compatability upgrades for torrentfileQt synchronization +- Added coverage details to documentation folder + +* * * + +## Version 0.8.4 + +- Documentation Updates +- Fixed logging issues with rebuild module. +- Improved algorithm for rebuild module. +- Improved testing for rebuild module. + +* * * + ## Version 0.8.3 diff --git a/site/index.md b/site/index.md index 985b93fa..85b23aa7 100644 --- a/site/index.md +++ b/site/index.md @@ -185,16 +185,6 @@ To create a magnet URI for a pre-existing torrent meta file, use the sub-command 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. Use the -`-i` or `--interactive` flags to activate interactive mode. - -```bash -torrentfile -i -``` - ### GUI If you prefer a windowed GUI please check out the official GUI frontend [here](https://github.com/alexpdev/TorrentFileQt) diff --git a/site/overview.md b/site/overview.md index 30858fb5..b23cdbe3 100644 --- a/site/overview.md +++ b/site/overview.md @@ -2,98 +2,100 @@ ## Synopsis - torrentfile [options] [options] - -## Description - -**`torrentfile`** is a command line toolkit for working with Bittorrent files(.torrent). +**`torrentfile`** is a command line utility for working with Bittorrent files(.torrent). Some of the tools available include creating torrent files, editing portions of a -torrent file, checking the integrity or completeness of downloaded torrent contents, +torrent files, checking the integrity or completeness of downloaded torrent contents, displaying details of a torrentfile, generating magnet URLs for torrentfiles, and individual or batch rebuilding of torrent contents into their original directory structure. -### Options + torrentfile [options] [options] - `-h` - displays all relevant command line options and subcommands. + - displays all relevant command line options and subcommands. - `-V` - displays program and version. + - displays program and version. - `-v` - enables debug mode and outputs a large amount of information to the terminal. + - enables debug mode and outputs a large amount of information to the terminal. - `-i` - activates interactive mode for selecting subcommands and options. + - activates interactive mode for selecting subcommands and options. #Deprecated + +* * * ### Sub-commands #### create -alias: `c`, `new` - - torrentfile create [options] - torrentfile c [options] +alias: `c` The create subcommand is used for generating new torrent files. The only required argument is the path(``) to the contents file or directory. + torrentfile create [options] + torrentfile c [options] + - `-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 ...` + - 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 ...` + +- `-p` `--private` + - Indicates that the torrent will be used on a private tracker. Disables multi-tracker protocols and DHT. - `-w` `--web-seeds` - Adds the list of urls that follow to the list of web-seed addresses for the newly created torrent file. + - 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"` +- `--http-seed` + - Same as the `-w` argument except using Hoffman style web-seeds. -- `--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` +- `-c` `--comment` + - Includes the comment that follows to the metadata saved in the newly created torrent file. + - Example: `--comment "Created for MyTrackerExample.com"` -- `-p` `--private` - Indicates that the torrent will be used on a private tracker. Disables multi-tracker protocols and DHT. +- `--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` - `--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`. + - 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` + - 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` + - 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` +- `--cwd` `--current` #deprecated + - This is now the default behaviour. 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` - `--progress` `--prog` - Options (0, 1): No status bar will be shown if 0. Otherwise the default and 1 argument means progress bar is shown + - Options (0, 1): No status bar will be shown if 0. Otherwise the default and 1 argument means progress bar is shown + +* * * #### info alias: `i` - torrentfile info - torrentfile i - Display detailed information about a torrentfile such as trackers, size of contents, Bittorrent version, any comments left, date the torrent file was created and more. There is only one positional perameter which is the path to the torrent file and there are no optional arguments. + torrentfile info + torrentfile i + - `/path/to/*.torrent` - The relative or absolute path to the torrent file. + - The relative or absolute path to the torrent file. + +* * * #### edit @@ -110,32 +112,37 @@ alias: `e` torrentfile e [options] - `-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 ...` + - 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. + - 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"` + - 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` + - 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. + - Indicates that the torrent will be used on a private tracker. Disables multi-tracker protocols and DHT. + +* * * #### recheck +This feature is identical to the 'recheck' function provided by torrent clients. It +validates that the file/directory contents match those described in the torrent file. + Recheck requires two paths as arguments. The first is the path to a torrent file, and and the second is a path to the file of directory containing the downloaded data from that torrentfile. `torrentfile` recursively validates each file with the hashes -contained in the torrentfile, and displays the amount missing frome each file, plus -a final percentage for the whole torrent at the conclusion. This will display a -progress bar for each file including missing files. It is also permitted to use +contained in the torrentfile, and displays the total percentage of the file that +matches the information from the torrent file. It is also permitted to use the contents parent directory which can help for batch processing many torrent files. alias: `r`, `check` @@ -143,6 +150,8 @@ alias: `r`, `check` torrentfile recheck <*.torrent> torrentfile r <*.torrent> +* * * + ### Magnet Generate a magnet URL for a torrent file. @@ -151,6 +160,8 @@ alias: `m` torrentfile magnet +* * * + ### Rebuild Rebuild individual or batches of torrent contents into the original file structure. @@ -164,4 +175,16 @@ overwritten. alias: `build`, `b` - torrentfile rebuild + torrentfile rebuild -m -c -d + +- `-m` `--metafiles` + - path to a torrent file or a directory containing torrent files for batch processing. + +- `-c` `--content` + - path to where the contents for the torrentfile can be found. If the path is + for a directory, the directory will be searched recusively for files that match. + For deeply nested directories with lot's of files in them, this can take time. + +- `-d` `-destination` + - path to a directory where the torrent will be rebuilt. Files will be copied + from their original location, not moved. diff --git a/site/usage.md b/site/usage.md index 98c02b2b..e7fe7736 100644 --- a/site/usage.md +++ b/site/usage.md @@ -3,74 +3,89 @@ ## Help Messages ### Main - Usage ===== - torrentfile [-h] [-i] [-V] [-v] - ... + torrentfile [options] command [command options] - CLI Tool for creating, checking, editing... Bittorrent meta files. TorrentFile supports all versions of torrent files. + Command line tools for creating, editing, checking, building and interacting with Bittorrent metainfo 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. + -h, --help show this help message and exit + -i, --interactive select program options interactively + -q, --quiet Turn off all text output. + -V, --version show program version and exit + -v, --verbose output debug information + + Commands + -------- + create, edit, info, magnet, recheck, rebuild + + create (c, new) Create a new Bittorrent file. + edit (e) Edit existing torrent meta file. + info (i) Show detailed information about a torrent file. + magnet (m) Generate magnet url from an existing Bittorrent meta file. + recheck (check) Gives a detailed look at how much of the torrent is available. + rebuild (build) Re-assemble files obtained from a bittorrent file into the + appropriate file structure for re-seeding. Read documentation + for more information, or use cases. * * * -### Create +# Create Usage ===== - torrentfile c [-h] [-a [ ...]] [-p] [-s ] [-m] - [-c ] [-o ] [-t [ ...]] - [--noprogress] [--meta-version ] - [--piece-length ] [-w [ ...]] - + + torrentfile [options] create + [-h] [-a [ ...]] [-p] + [-s ] [-m] [-c ] + [-o ] [--cwd] [--prog PROGRESS] + [--meta-version ] + [--piece-length ] + [-w [ ...]] + [--http-seed [ ...]] + [] Positional Arguments -------------------- - Path to content file or directory + 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). - --prog, --progress (0) = no progress bar displayed - (1) = progress bar is displayed (default) - --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 + Options + ------- + -h, --help show this help message and exit + -a [ ...], -t [ ...], --announce [ ...], --tracker [ ...] + One or more space-seperated torrent tracker url(s). + -p, --private Creates private torrent with multi-tracker and DHT turned off. + -s , --source + Add a source string. Useful for cross-seeding. + -m, --magnet + -c , --comment + Include a comment in file metadata + -o , --out + Explicitly specify the path to write the file. + --cwd, --current *deprecated* Saving to current directory is default behaviour + --prog PROGRESS, --progress PROGRESS + Set the progress bar level. + Options = 0, 1 + (0) = Do not display progress bar. + (1) = Display progress bar.(default) + + --meta-version Bittorrent metafile version. + Options = 1, 2, 3 + (1) = Bittorrent v1 (Default) + (2) = Bittorrent v2 + (3) = Bittorrent v1 & v2 hybrid + + --piece-length (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] + + -w [ ...], --web-seed [ ...] + list of web addresses where torrent data exists (GetRight). + --http-seed [ ...] + list of URLs, addresses where content can be found (Hoffman). * * * @@ -133,5 +148,3 @@ -h, --help show this help message and exit * * * - -[Coverage](https://codecov.io/gh/alexpdev/torrentfile/branch/master/graphs/icicle.svg?token=EWF7NIL9SQ) diff --git a/tests/__init__.py b/tests/__init__.py index 0c1c4a3d..54251cc0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -241,7 +241,6 @@ def metafile2(dir2, request): torrent = torrent_class(**args) outfile, _ = torrent.write(outfile=outfile) yield outfile - rmpath(outfile) @pytest.fixture(scope="package") @@ -251,7 +250,6 @@ def file1(): """ path = tempfile() yield path - rmpath(path) @pytest.fixture(scope="package", params=torrents()) diff --git a/torrentfile/__init__.py b/torrentfile/__init__.py index e7874add..bcf6c9b3 100644 --- a/torrentfile/__init__.py +++ b/torrentfile/__init__.py @@ -30,8 +30,11 @@ exceptions: Custom Exceptions used in package. utils: Utilities used throughout package. """ -from torrentfile.cli import execute +from torrentfile.cli import execute, main from torrentfile.commands import create, edit, info, magnet, recheck +from torrentfile.utils import toggle_debug_mode from torrentfile.version import __version__ -__all__ = ["execute", "create", "edit", "info", "magnet", "recheck"] +toggle_debug_mode(False) + +__all__ = ["execute", "create", "edit", "info", "magnet", "recheck", "main"] diff --git a/torrentfile/cli.py b/torrentfile/cli.py index ca6e473b..3f5e093b 100644 --- a/torrentfile/cli.py +++ b/torrentfile/cli.py @@ -46,6 +46,7 @@ from torrentfile.commands import create, edit, info, magnet, rebuild, recheck from torrentfile.interactive import select_action +from torrentfile.utils import toggle_debug_mode from torrentfile.version import __version__ as version @@ -73,20 +74,20 @@ def activate_logger(): """ Activate the builtin logging mechanism when passed debug flag from CLI. """ - logging.basicConfig(level=logging.INFO) + logging.basicConfig(level=logging.WARNING) logger = logging.getLogger() console_handler = logging.StreamHandler(stream=sys.stderr) stream_formatter = logging.Formatter( - "%(asctime)s %(levelno)s %(message)s", - datefmt="%m-%d %H:%M:%S", + "[%(asctime)s] [%(levelno)s] %(message)s", + datefmt="%H:%M:%S", style="%", ) console_handler.setFormatter(stream_formatter) console_handler.setLevel(logging.DEBUG) - logger.setLevel(logging.DEBUG) logger.addHandler(console_handler) logger.debug("Debug: ON") + toggle_debug_mode(True) class TorrentFileHelpFormatter(HelpFormatter): @@ -210,6 +211,7 @@ def execute(args: Optional[list] = None) -> list: list Depends on what the command line args were. """ + toggle_debug_mode(False) if not args: if sys.argv[1:]: args = sys.argv[1:] @@ -218,7 +220,7 @@ def execute(args: Optional[list] = None) -> list: parser = ArgumentParser( "torrentfile", - usage="torrentfile [options] command [command options]", + usage="torrentfile ", description=( "Command line tools for creating, editing, checking, building " "and interacting with Bittorrent metainfo files" @@ -272,7 +274,7 @@ def execute(args: Optional[list] = None) -> list: "create", help="Create a new Bittorrent file.", prefix_chars="-", - aliases=["c", "new"], + aliases=["c", "new", "build"], formatter_class=TorrentFileHelpFormatter, ) @@ -518,7 +520,7 @@ def execute(args: Optional[list] = None) -> list: check_parser = subparsers.add_parser( "recheck", help="Gives a detailed look at how much of the torrent is available.", - aliases=["check"], + aliases=["check", "r"], prefix_chars="-", formatter_class=TorrentFileHelpFormatter, ) diff --git a/torrentfile/interactive.py b/torrentfile/interactive.py index 305227f1..64eaa4a8 100644 --- a/torrentfile/interactive.py +++ b/torrentfile/interactive.py @@ -123,8 +123,15 @@ def showcenter(txt: str): def select_action(): """ Operate TorrentFile program interactively through terminal. + + DEPRECATION WARNING: The interactive CLI feature will be deprecated + in the future. """ showcenter("TorrentFile: Starting Interactive Mode") + showcenter( + "DEPRECATION WARNING: The interactive feature will be" + "deprecated in the near future." + ) action = get_input( "Enter the action you wish to perform.\n" "Action ( Create (c) | Edit (e) | Recheck (r) ): " diff --git a/torrentfile/mixins.py b/torrentfile/mixins.py index d48fb093..07fe4101 100644 --- a/torrentfile/mixins.py +++ b/torrentfile/mixins.py @@ -29,6 +29,8 @@ import time from pathlib import Path +from torrentfile.utils import debug_is_on + class CbMixin: """ @@ -206,7 +208,7 @@ def is_active(self) -> bool: bool : True if there is, otherwise False. """ - if hasattr(self, "prog"): + if not debug_is_on() and hasattr(self, "prog"): return True return False diff --git a/torrentfile/rebuild.py b/torrentfile/rebuild.py index d5da5b11..691aebc5 100644 --- a/torrentfile/rebuild.py +++ b/torrentfile/rebuild.py @@ -94,7 +94,7 @@ def get_part(self, path: str) -> bytes: """ with open(path, "rb") as fd: if self.start: - fd.read(self.start) + fd.seek(self.start) if self.stop != -1: partial = fd.read(self.stop - self.start) else: @@ -437,8 +437,10 @@ class Assembler(CbMixin): def __init__(self, metafiles: list, contents: list, dest: str): """ - Construct the assembler object. + Reassemble given torrent file from given cli arguments. + Rebuild metafiles and contents into their original directory + structure as much as possible in the destination directory. Takes two paths as parameters, - file or directory containing 1 or more torrent meta files - path to where the contents are belived to be located. @@ -482,8 +484,7 @@ def _callback(self, filename: str, dest: str, num_pieces: int): message = f"Matched:{num_pieces} {filename} -> {dest}" if message != self._lastlog: self._lastlog = message - # logger.info(message) - self.cb(message) + logger.debug(message) def assemble_torrents(self): """ @@ -538,7 +539,12 @@ def _iter_files(self, path: str) -> list: def _get_metafiles(self) -> list: """ - Collect all .torrent meta files from give directory or file. + Collect all .torrent meta files from given directory or file. + + Returns + ------- + list + metafile objects """ metafiles = [] for path in self.meta_paths: diff --git a/torrentfile/torrent.py b/torrentfile/torrent.py index 592ce9c0..68f7da22 100644 --- a/torrentfile/torrent.py +++ b/torrentfile/torrent.py @@ -466,8 +466,6 @@ class TorrentFileV2(MetaFile, ProgMixin): """ Class for creating Bittorrent meta v2 files. - **DEPRECATED** - Parameters ---------- **kwargs : dict @@ -480,8 +478,6 @@ def __init__(self, **kwargs): """ Construct `TorrentFileV2` Class instance from given parameters. - **DEPRECATED** - Parameters ---------- **kwargs : dict @@ -498,8 +494,6 @@ def assemble(self): """ Assemble then return the meta dictionary for encoding. - **DEPRECATED** - Returns ------- meta : dict @@ -520,8 +514,6 @@ def _traverse(self, path: str) -> dict: """ Walk directory tree. - **DEPRECATED** - Parameters ---------- path : str @@ -552,8 +544,6 @@ class TorrentFileHybrid(MetaFile, ProgMixin): """ Construct the Hybrid torrent meta file with provided parameters. - **DEPRECATED** - Parameters ---------- **kwargs : dict @@ -579,8 +569,6 @@ def __init__(self, **kwargs): def assemble(self): """ Assemble the parts of the torrentfile into meta dictionary. - - **DEPRECATED** """ info = self.meta["info"] info["meta version"] = 2 @@ -601,8 +589,6 @@ def _traverse(self, path: str) -> dict: """ Build meta dictionary while walking directory. - **DEPRECATED** - Parameters ---------- path : str diff --git a/torrentfile/utils.py b/torrentfile/utils.py index cdf8c34a..c5060ecf 100644 --- a/torrentfile/utils.py +++ b/torrentfile/utils.py @@ -400,3 +400,27 @@ def copypath(source: str, dest: str) -> None: os.mkdir(path) root = path shutil.copy(source, dest) + + +def toggle_debug_mode(switch_on: bool): + """ + Switch the environment variable debug indicator on or off. + + Parameters + ---------- + switch_on : bool + if true turn debug mode on otherwise off + """ + os.environ["TORRENTFILE_DEBUG"] = "ON" if switch_on else "OFF" + + +def debug_is_on() -> bool: + """ + Return True if debug mode is on in environment variables. + + Returns + ------- + bool + is debug mode on + """ + return os.environ["TORRENTFILE_DEBUG"] == "ON" diff --git a/torrentfile/version.py b/torrentfile/version.py index 39752939..8f60d407 100644 --- a/torrentfile/version.py +++ b/torrentfile/version.py @@ -20,4 +20,4 @@ Holds the release version number. """ -__version__ = "0.8.4" +__version__ = "0.8.5"