def main(argv): # Validate the configuration. for abbr, replacement in TRACKER_ABBR.items(): if not isinstance(abbr, str): print("Configuration error: invalid tracker abbrevation: '%s' " "(must be a string instead)" % abbr, file=sys.stderr) return 1 if not isinstance(replacement, (str, list)): print("Configuration error: invalid tracker abbreviation: '%s' " "(must be a string or list of strings instead)" % str(replacement), file=sys.stderr) return 1 # Create OptionParser. kwargs = { 'usage': "%prog [options] <file-or-directory> <main-tracker-url> " "[<backup-tracker-url> ...]", 'version': "%%prog v%s" % VERSION, 'description': "py3createtorrent is a comprehensive command line utility for " "creating torrents." } parser = optparse.OptionParser(**kwargs) # Add options to the OptionParser. # Note: Commonly used options are added first. parser.add_option("-p", "--piece-length", type="int", action="store", dest="piece_length", default=0, help="piece size in KiB. 0 = automatic selection (default).") parser.add_option("-P", "--private", action="store_true", dest="private", default=False, help="create private torrent") parser.add_option("-c", "--comment", type="string", action="store", dest="comment", default=False, help="include comment") parser.add_option("-f", "--force", action="store_true", dest="force", default=False, help="dont ask anything, just do it") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="verbose mode") parser.add_option("-q", "--quiet", action="store_true", dest="quiet", default=False, help="be quiet, e.g. don't print summary") parser.add_option("-o", "--output", type="string", action="store", dest="output", default=None, metavar="PATH", help="custom output location (directory or complete " "path). default = current directory.") parser.add_option("-e", "--exclude", type="string", action="append", dest="exclude", default=[], metavar="PATH", help="exclude path (can be repeated)") parser.add_option("--exclude-pattern", type="string", action="append", dest="exclude_pattern", default=[], metavar="REGEXP", help="exclude paths matching the regular expression " "(can be repeated)") parser.add_option("--exclude-pattern-ci", type="string", action="append", dest="exclude_pattern_ci", default=[], metavar="REGEXP", help="exclude paths matching the case-insensitive regular " "expression (can be repeated)") parser.add_option("-d", "--date", type="int", action="store", dest="date", default=-1, metavar="TIMESTAMP", help="set creation date (unix timestamp). -1 = now " "(default). -2 = disable.") parser.add_option("-n", "--name", type="string", action="store", dest="name", default=None, help="use this file (or directory) name instead of the " "real one") parser.add_option("--md5", action="store_true", dest="include_md5", default=False, help="include MD5 hashes in torrent file") (options, args) = parser.parse_args(args = argv[1:]) # Positional arguments must have been provided: # -> file / directory plus at least one tracker. if len(args) < 2: parser.error("You must specify a valid path and at least one tracker.") # Ask the user if he really wants to use uncommon piece lengths. # (Unless the force option has been set.) if not options.force and 0 < options.piece_length < 16: if "yes" != input("It is strongly recommended to use a piece length " "greater or equal than 16 KiB! Do you really want " "to continue? yes/no: "): parser.error("Aborted.") if not options.force and options.piece_length > 1024: if "yes" != input("It is strongly recommended to use a maximum piece " "length of 1024 KiB! Do you really want to " "continue? yes/no: "): parser.error("Aborted.") # Verbose and quiet options may not be used together. if options.verbose and options.quiet: parser.error("Being verbose and quiet exclude each other.") global VERBOSE VERBOSE = options.verbose # ########################################## # CALCULATE/SET THE FOLLOWING METAINFO DATA: # - info # - pieces (concatenated 20 byte sha1 hashes of all the data) # - files (if multiple files) # - length and md5sum (if single file) # - name (may be overwritten in the next section by the --name option) node = os.path.abspath(args[0]) trackers = args[1:] # Validate the given path. if not os.path.isfile(node) and not os.path.isdir(node): parser.error("'%s' neither is a file nor a directory." % node) # Evaluate / apply the tracker abbreviations. trackers = replace_in_list(trackers, TRACKER_ABBR) # Remove duplicate trackers. trackers = remove_duplicates(trackers) # Validate tracker URLs. invalid_trackers = False regexp = re.compile(r"^(http|https|udp)://", re.I) for t in trackers: if not regexp.search(t): print("Warning: Not a valid tracker URL: %s" % t, file=sys.stderr) invalid_trackers = True if invalid_trackers and not options.force: if "yes" != input("Some tracker URLs are invalid. Continue? yes/no: "): parser.error("Aborted.") # Parse and validate excluded paths. excluded_paths = frozenset([os.path.normcase(os.path.abspath(path)) \ for path in options.exclude]) # Parse exclude patterns. excluded_regexps = set(re.compile(regexp) for regexp in options.exclude_pattern) excluded_regexps |= set(re.compile(regexp, re.IGNORECASE) for regexp in options.exclude_pattern_ci) # Warn the user if he attempts to exclude any paths when creating # a torrent for a single file (makes no sense). if os.path.isfile(node) and (len(excluded_paths) > 0 or \ len(excluded_regexps) > 0): print("Warning: Excluding paths is not possible when creating a " "torrent for a single file.", file=sys.stderr) # Warn the user if he attempts to exclude a specific path, that does not # even exist. for path in excluded_paths: if not os.path.exists(path): print("Warning: You're excluding a path that does not exist: '%s'" % path, file=sys.stderr) # Get the torrent's files and / or calculate its size. if os.path.isfile(node): torrent_size = os.path.getsize(node) else: torrent_files = get_files_in_directory(node, excluded_paths=excluded_paths, excluded_regexps=excluded_regexps) torrent_size = sum([os.path.getsize(os.path.join(node, file)) for file in torrent_files]) # Torrents for 0 byte data can't be created. if torrent_size == 0: print("Error: Can't create torrent for 0 byte data.", file=sys.stderr) print("Check your files and exclusions!", file=sys.stderr) return 1 # Calculate or parse the piece size. if options.piece_length == 0: piece_length = calculate_piece_length(torrent_size) elif options.piece_length > 0: piece_length = options.piece_length * KIB else: parser.error("Invalid piece size: '%d'" % options.piece_length) # Do the main work now. # -> prepare the metainfo dictionary. if os.path.isfile(node): info = create_single_file_info(node, piece_length, options.include_md5) else: info = create_multi_file_info(node, torrent_files, piece_length, options.include_md5) assert len(info['pieces']) % 20 == 0, "len(pieces) not a multiple of 20" # ########################### # FINISH METAINFO DICTIONARY: # - info # - piece length # - name (eventually overwrite) # - private # - announce # - announce-list (if multiple trackers) # - creation date (may be disabled as well) # - created by # - comment (may be disabled as well (if ADVERTISE = False)) # Finish sub-dict "info". info['piece length'] = piece_length if options.private: info['private'] = 1 # Construct outer metainfo dict, which contains the torrent's whole # information. metainfo = { 'info': info, 'announce': trackers[0], } # Make "announce-list" field, if there are multiple trackers. if len(trackers) > 1: metainfo['announce-list'] = [[tracker] for tracker in trackers] # Set "creation date". # The user may specify a custom creation date. He may also decide not # to include the creation date field at all. if options.date == -1: # use current time metainfo['creation date'] = int(time.time()) elif options.date >= 0: # use specified timestamp directly metainfo['creation date'] = options.date # Add the "created by" field. metainfo['created by'] = 'py3createtorrent v%s' % VERSION # Add user's comment or advertise py3createtorrent (unless this behaviour # has been disabled by the user). # The user may also decide not to include the comment field at all # by specifying an empty comment. if isinstance(options.comment, str): if len(options.comment) > 0: metainfo['comment'] = options.comment elif ADVERTISE: metainfo['comment'] = "created with " + metainfo['created by'] # Add the name field. # By default this is the name of directory or file the torrent # is being created for. if options.name: options.name = options.name.strip() regexp = re.compile("^[A-Z0-9_\-\., ]+$", re.I) if not regexp.match(options.name): parser.error("Invalid name: '%s'. Allowed chars: A_Z, a-z, 0-9, " "any of {.,_-} plus spaces." % options.name) metainfo['info']['name'] = options.name # ################################################### # BENCODE METAINFO DICTIONARY AND WRITE TORRENT FILE: # - take into consideration the --output option # - properly handle KeyboardInterrups while writing the file # Respect the custom output location. if not options.output: # Use current directory. output_path = metainfo['info']['name'] + ".torrent" else: # Use the directory or filename specified by the user. options.output = os.path.abspath(options.output) # The user specified an output directory: if os.path.isdir(options.output): output_path = os.path.join(options.output, metainfo['info']['name']+".torrent") if os.path.isfile(output_path): if not options.force and os.path.exists(output_path): if "yes" != input("'%s' does already exist. Overwrite? " "yes/no: " % output_path): parser.error("Aborted.") # The user specified a filename: else: # Is there already a file with this path? -> overwrite?! if os.path.isfile(options.output): if not options.force and os.path.exists(options.output): if "yes" != input("'%s' does already exist. Overwrite? " "yes/no: " % options.output): parser.error("Aborted.") output_path = options.output # Actually write the torrent file now. try: with open(output_path, "wb") as fh: fh.write(bencode(metainfo)) except IOError as exc: print("IOError: " + str(exc), file=sys.stderr) print("Could not write the torrent file. Check torrent name and your " "privileges.", file=sys.stderr) print("Absolute output path: '%s'" % os.path.abspath(output_path), file=sys.stderr) return 1 except KeyboardInterrupt: # Properly handle KeyboardInterrupts. # todo: open()'s context manager may already do this on his own? if os.path.exists(output_path): os.remove(output_path) # ######################### # PREPARE AND PRINT SUMMARY # - but check quiet option # If the quiet option has been set, we're already finished here, # because we don't print a summary in this case. if options.quiet: return 0 # Print summary! print("Successfully created torrent:") # Create the list of backup trackers. backup_trackers = "" if 'announce-list' in metainfo: _backup_trackers = metainfo['announce-list'][1:] _backup_trackers.sort(key=lambda x: x[0].lower()) for tracker in _backup_trackers: backup_trackers += " " + tracker[0] + "\n" backup_trackers = backup_trackers.rstrip() else: backup_trackers = " (none)" # Calculate piece count. piece_count = math.ceil(torrent_size / metainfo['info']['piece length']) # Make torrent size human readable. if torrent_size > 10*MIB: size = "%.2f MiB" % (torrent_size / MIB) else: size = "%d KiB" % (torrent_size / KIB) # Make creation date human readable (ISO format). if 'creation date' in metainfo: creation_date = datetime.datetime.fromtimestamp(metainfo['creation \ date']).isoformat(' ') else: creation_date = "(none)" # Now actually print the summary table. print(" Name: %s\n" " Size: %s\n" " Pieces: %d x %d KiB\n" " Comment: %s\n" " Private: %s\n" " Creation date: %s\n" " Primary tracker: %s\n" " Backup trackers:\n" "%s" % (metainfo['info']['name'], size, piece_count, piece_length / KIB, metainfo['comment'] if 'comment' in metainfo else "(none)", "yes" if options.private else "no", creation_date, metainfo['announce'], backup_trackers)) return 0
def main(argv): # Validate the configuration. for abbr, replacement in TRACKER_ABBR.items(): if not isinstance(abbr, str): print("Configuration error: invalid tracker abbrevation: '%s' " "(must be a string instead)" % abbr, file=sys.stderr) return 1 if not isinstance(replacement, (str, list)): print("Configuration error: invalid tracker abbreviation: '%s' " "(must be a string or list of strings instead)" % str(replacement), file=sys.stderr) return 1 # Create OptionParser. kwargs = { 'usage': "%prog [options] <file-or-directory> <main-tracker-url> " "[<backup-tracker-url> ...]", 'version': "%%prog v%s" % VERSION, 'description': "py3createtorrent is a comprehensive command line utility for " "creating torrents." } parser = optparse.OptionParser(**kwargs) # Add options to the OptionParser. # Note: Commonly used options are added first. parser.add_option( "-p", "--piece-length", type="int", action="store", dest="piece_length", default=0, help="piece size in KiB. 0 = automatic selection (default).") parser.add_option("-P", "--private", action="store_true", dest="private", default=False, help="create private torrent") parser.add_option("-c", "--comment", type="string", action="store", dest="comment", default=False, help="include comment") parser.add_option( "-s", "--source", type="string", action="store", dest="source", default=False, help= "source string, used to create a different infohash for cross-seeding") parser.add_option("-f", "--force", action="store_true", dest="force", default=False, help="dont ask anything, just do it") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="verbose mode") parser.add_option("-q", "--quiet", action="store_true", dest="quiet", default=False, help="be quiet, e.g. don't print summary") parser.add_option("-o", "--output", type="string", action="store", dest="output", default=None, metavar="PATH", help="custom output location (directory or complete " "path). default = current directory.") parser.add_option("-e", "--exclude", type="string", action="append", dest="exclude", default=[], metavar="PATH", help="exclude path (can be repeated)") parser.add_option("--exclude-pattern", type="string", action="append", dest="exclude_pattern", default=[], metavar="REGEXP", help="exclude paths matching the regular expression " "(can be repeated)") parser.add_option( "--exclude-pattern-ci", type="string", action="append", dest="exclude_pattern_ci", default=[], metavar="REGEXP", help="exclude paths matching the case-insensitive regular " "expression (can be repeated)") parser.add_option("-d", "--date", type="int", action="store", dest="date", default=-1, metavar="TIMESTAMP", help="set creation date (unix timestamp). -1 = now " "(default). -2 = disable.") parser.add_option("-n", "--name", type="string", action="store", dest="name", default=None, help="use this file (or directory) name instead of the " "real one") parser.add_option("--md5", action="store_true", dest="include_md5", default=False, help="include MD5 hashes in torrent file") (options, args) = parser.parse_args(args=argv[1:]) # Positional arguments must have been provided: # -> file / directory plus at least one tracker. if len(args) < 2: parser.error("You must specify a valid path and at least one tracker.") # Ask the user if he really wants to use uncommon piece lengths. # (Unless the force option has been set.) if not options.force and 0 < options.piece_length < 16: if "yes" != input("It is strongly recommended to use a piece length " "greater or equal than 16 KiB! Do you really want " "to continue? yes/no: "): parser.error("Aborted.") if not options.force and options.piece_length > 1024: if "yes" != input("It is strongly recommended to use a maximum piece " "length of 1024 KiB! Do you really want to " "continue? yes/no: "): parser.error("Aborted.") # Verbose and quiet options may not be used together. if options.verbose and options.quiet: parser.error("Being verbose and quiet exclude each other.") global VERBOSE VERBOSE = options.verbose # ########################################## # CALCULATE/SET THE FOLLOWING METAINFO DATA: # - info # - pieces (concatenated 20 byte sha1 hashes of all the data) # - files (if multiple files) # - length and md5sum (if single file) # - name (may be overwritten in the next section by the --name option) node = os.path.abspath(args[0]) trackers = args[1:] # Validate the given path. if not os.path.isfile(node) and not os.path.isdir(node): parser.error("'%s' neither is a file nor a directory." % node) # Evaluate / apply the tracker abbreviations. trackers = replace_in_list(trackers, TRACKER_ABBR) # Remove duplicate trackers. trackers = remove_duplicates(trackers) # Validate tracker URLs. invalid_trackers = False regexp = re.compile(r"^(http|https|udp)://", re.I) for t in trackers: if not regexp.search(t): print("Warning: Not a valid tracker URL: %s" % t, file=sys.stderr) invalid_trackers = True if invalid_trackers and not options.force: if "yes" != input("Some tracker URLs are invalid. Continue? yes/no: "): parser.error("Aborted.") # Parse and validate excluded paths. excluded_paths = frozenset([os.path.normcase(os.path.abspath(path)) \ for path in options.exclude]) # Parse exclude patterns. excluded_regexps = set( re.compile(regexp) for regexp in options.exclude_pattern) excluded_regexps |= set( re.compile(regexp, re.IGNORECASE) for regexp in options.exclude_pattern_ci) # Warn the user if he attempts to exclude any paths when creating # a torrent for a single file (makes no sense). if os.path.isfile(node) and (len(excluded_paths) > 0 or \ len(excluded_regexps) > 0): print( "Warning: Excluding paths is not possible when creating a " "torrent for a single file.", file=sys.stderr) # Warn the user if he attempts to exclude a specific path, that does not # even exist. for path in excluded_paths: if not os.path.exists(path): print( "Warning: You're excluding a path that does not exist: '%s'" % path, file=sys.stderr) # Get the torrent's files and / or calculate its size. if os.path.isfile(node): torrent_size = os.path.getsize(node) else: torrent_files = get_files_in_directory( node, excluded_paths=excluded_paths, excluded_regexps=excluded_regexps) torrent_size = sum([ os.path.getsize(os.path.join(node, file)) for file in torrent_files ]) # Torrents for 0 byte data can't be created. if torrent_size == 0: print("Error: Can't create torrent for 0 byte data.", file=sys.stderr) print("Check your files and exclusions!", file=sys.stderr) return 1 # Calculate or parse the piece size. if options.piece_length == 0: piece_length = calculate_piece_length(torrent_size) elif options.piece_length > 0: piece_length = options.piece_length * KIB else: parser.error("Invalid piece size: '%d'" % options.piece_length) # Do the main work now. # -> prepare the metainfo dictionary. if os.path.isfile(node): info = create_single_file_info(node, piece_length, options.include_md5) else: info = create_multi_file_info(node, torrent_files, piece_length, options.include_md5) assert len(info['pieces']) % 20 == 0, "len(pieces) not a multiple of 20" # ########################### # FINISH METAINFO DICTIONARY: # - info # - piece length # - name (eventually overwrite) # - private # - announce # - announce-list (if multiple trackers) # - creation date (may be disabled as well) # - created by # - comment (may be disabled as well (if ADVERTISE = False)) # Finish sub-dict "info". info['piece length'] = piece_length if options.private: info['private'] = 1 # Construct outer metainfo dict, which contains the torrent's whole # information. metainfo = { 'info': info, 'announce': trackers[0], } # Make "announce-list" field, if there are multiple trackers. if len(trackers) > 1: metainfo['announce-list'] = [[tracker] for tracker in trackers] # Set "creation date". # The user may specify a custom creation date. He may also decide not # to include the creation date field at all. if options.date == -1: # use current time metainfo['creation date'] = int(time.time()) elif options.date >= 0: # use specified timestamp directly metainfo['creation date'] = options.date # Add the "created by" field. metainfo['created by'] = 'py3createtorrent' # Add user's comment or advertise py3createtorrent (unless this behaviour # has been disabled by the user). # The user may also decide not to include the comment field at all # by specifying an empty comment. if isinstance(options.comment, str): if len(options.comment) > 0: metainfo['comment'] = options.comment elif ADVERTISE: metainfo['comment'] = "" # Add a source string, which is used to create a different infohash for cross-seeding if isinstance(options.source, str): if len(options.source) > 0: metainfo['info']['source'] = options.source #else: # metainfo['info']['source'] = "" # Add the name field. # By default this is the name of directory or file the torrent # is being created for. if options.name: options.name = options.name.strip() regexp = re.compile("^[A-Z0-9_\-\., ]+$", re.I) if not regexp.match(options.name): parser.error("Invalid name: '%s'. Allowed chars: A_Z, a-z, 0-9, " "any of {.,_-} plus spaces." % options.name) metainfo['info']['name'] = options.name # ################################################### # BENCODE METAINFO DICTIONARY AND WRITE TORRENT FILE: # - take into consideration the --output option # - properly handle KeyboardInterrups while writing the file # Respect the custom output location. if not options.output: # Use current directory. output_path = metainfo['info']['name'] + ".torrent" else: # Use the directory or filename specified by the user. options.output = os.path.abspath(options.output) # The user specified an output directory: if os.path.isdir(options.output): output_path = os.path.join(options.output, metainfo['info']['name'] + ".torrent") if os.path.isfile(output_path): if not options.force and os.path.exists(output_path): if "yes" != input("'%s' does already exist. Overwrite? " "yes/no: " % output_path): parser.error("Aborted.") # The user specified a filename: else: # Is there already a file with this path? -> overwrite?! if os.path.isfile(options.output): if not options.force and os.path.exists(options.output): if "yes" != input("'%s' does already exist. Overwrite? " "yes/no: " % options.output): parser.error("Aborted.") output_path = options.output # Actually write the torrent file now. try: with open(output_path, "wb") as fh: fh.write(bencode(metainfo)) except IOError as exc: print("IOError: " + str(exc), file=sys.stderr) print( "Could not write the torrent file. Check torrent name and your " "privileges.", file=sys.stderr) print("Absolute output path: '%s'" % os.path.abspath(output_path), file=sys.stderr) return 1 except KeyboardInterrupt: # Properly handle KeyboardInterrupts. # todo: open()'s context manager may already do this on his own? if os.path.exists(output_path): os.remove(output_path) # ######################### # PREPARE AND PRINT SUMMARY # - but check quiet option # If the quiet option has been set, we're already finished here, # because we don't print a summary in this case. if options.quiet: return 0 # Print summary! print("Successfully created torrent:") # Create the list of backup trackers. backup_trackers = "" if 'announce-list' in metainfo: _backup_trackers = metainfo['announce-list'][1:] _backup_trackers.sort(key=lambda x: x[0].lower()) for tracker in _backup_trackers: backup_trackers += " " + tracker[0] + "\n" backup_trackers = backup_trackers.rstrip() else: backup_trackers = " (none)" # Calculate piece count. piece_count = math.ceil(torrent_size / metainfo['info']['piece length']) # Make torrent size human readable. if torrent_size > 10 * MIB: size = "%.2f MiB" % (torrent_size / MIB) else: size = "%d KiB" % (torrent_size / KIB) # Make creation date human readable (ISO format). if 'creation date' in metainfo: creation_date = datetime.datetime.fromtimestamp(metainfo['creation \ date']).isoformat(' ') else: creation_date = "(none)" # Now actually print the summary table. print(" Name: %s\n" " Size: %s\n" " Pieces: %d x %d KiB\n" " Comment: %s\n" " Source String: %s\n" " Private: %s\n" " Creation date: %s\n" " Primary tracker: %s\n" " Backup trackers:\n" "%s" % (metainfo['info']['name'], size, piece_count, piece_length / KIB, metainfo['comment'] if 'comment' in metainfo else "(none)", metainfo['info']['source'] if 'source' in metainfo['info'] else "(none)", "yes" if options.private else "no", creation_date, metainfo['announce'], backup_trackers)) return 0
def main(argv): # Validate the configuration. # Create OptionParser. kwargs = { 'usage': "%prog [options] <file-or-directory> " "[<backup-tracker-url> ...]", 'version': "%%prog v%s" % VERSION, 'description': "py3createtorrent is a comprehensive command line utility for " "creating torrents." } parser = optparse.OptionParser(**kwargs) # Add options to the OptionParser. # Note: Commonly used options are added first. parser.add_option( "-p", "--piece-length", type="int", action="store", dest="piece_length", default=0, help="piece size in KiB. 0 = automatic selection (default).") parser.add_option("-P", "--private", action="store_true", dest="private", default=False, help="create private torrent") parser.add_option("-c", "--comment", type="string", action="store", dest="comment", default=False, help="include comment") parser.add_option("-f", "--force", action="store_true", dest="force", default=False, help="dont ask anything, just do it") parser.add_option("-o", "--output", type="string", action="store", dest="output", default=None, metavar="PATH", help="custom output location (directory or complete " "path). default = current directory.") parser.add_option("-e", "--exclude", type="string", action="append", dest="exclude", default=[], metavar="PATH", help="exclude path (can be repeated)") parser.add_option("--exclude-pattern", type="string", action="append", dest="exclude_pattern", default=[], metavar="REGEXP", help="exclude paths matching the regular expression " "(can be repeated)") parser.add_option( "--exclude-pattern-ci", type="string", action="append", dest="exclude_pattern_ci", default=[], metavar="REGEXP", help="exclude paths matching the case-insensitive regular " "expression (can be repeated)") parser.add_option("-d", "--date", type="int", action="store", dest="date", default=-1, metavar="TIMESTAMP", help="set creation date (unix timestamp). -1 = now " "(default). -2 = disable.") parser.add_option("-n", "--name", type="string", action="store", dest="name", default=None, help="use this file (or directory) name instead of the " "real one") parser.add_option("--md5", action="store_true", dest="include_md5", default=False, help="include MD5 hashes in torrent file") (options, args) = parser.parse_args(args=argv[1:]) # Positional arguments must have been provided: # Ask the user if he really wants to use uncommon piece lengths. # (Unless the force option has been set.) if not options.force and 0 < options.piece_length < 16: if "yes" != input("It is strongly recommended to use a piece length " "greater or equal than 16 KiB! Do you really want " "to continue? yes/no: "): parser.error("Aborted.") if not options.force and options.piece_length > 1024: if "yes" != input("It is strongly recommended to use a maximum piece " "length of 1024 KiB! Do you really want to " "continue? yes/no: "): parser.error("Aborted.") # ########################################## # CALCULATE/SET THE FOLLOWING METAINFO DATA: # - info # - pieces (concatenated 20 byte sha1 hashes of all the data) # - files (if multiple files) # - length and md5sum (if single file) # - name (may be overwritten in the next section by the --name option) node = os.path.abspath(args[0]) # Validate the given path. if not os.path.isfile(node) and not os.path.isdir(node): parser.error("'%s' neither is a file nor a directory." % node) # Parse and validate excluded paths. excluded_paths = frozenset([os.path.normcase(os.path.abspath(path)) \ for path in options.exclude]) # Parse exclude patterns. excluded_regexps = set( re.compile(regexp) for regexp in options.exclude_pattern) excluded_regexps |= set( re.compile(regexp, re.IGNORECASE) for regexp in options.exclude_pattern_ci) # Warn the user if he attempts to exclude any paths when creating # a torrent for a single file (makes no sense). if os.path.isfile(node) and (len(excluded_paths) > 0 or \ len(excluded_regexps) > 0): print( "Warning: Excluding paths is not possible when creating a " "torrent for a single file.", file=sys.stderr) # Warn the user if he attempts to exclude a specific path, that does not # even exist. for path in excluded_paths: if not os.path.exists(path): print( "Warning: You're excluding a path that does not exist: '%s'" % path, file=sys.stderr) # Get the torrent's files and / or calculate its size. if os.path.isfile(node): torrent_size = os.path.getsize(node) else: torrent_files = get_files_in_directory( node, excluded_paths=excluded_paths, excluded_regexps=excluded_regexps) torrent_size = sum([ os.path.getsize(os.path.join(node, file)) for file in torrent_files ]) # Torrents for 0 byte data can't be created. if torrent_size == 0: raise Exception("No data for torrent.") # Calculate or parse the piece size. if options.piece_length == 0: piece_length = calculate_piece_length(torrent_size) elif options.piece_length > 0: piece_length = options.piece_length * KIB else: parser.error("Invalid piece size: '%d'" % options.piece_length) # Do the main work now. # -> prepare the metainfo dictionary. if os.path.isfile(node): info = create_single_file_info(node, piece_length, options.include_md5) else: info = create_multi_file_info(node, torrent_files, piece_length, options.include_md5) assert len(info['pieces']) % 20 == 0, "len(pieces) not a multiple of 20" # ########################### # FINISH METAINFO DICTIONARY: # - info # - piece length # - name (eventually overwrite) # - private # - announce # - announce-list (if multiple trackers) # - creation date (may be disabled as well) # - created by # Finish sub-dict "info". info['piece length'] = piece_length if options.private: info['private'] = 1 # Construct outer metainfo dict, which contains the torrent's whole # information. metainfo = { 'info': info, 'announce': 'http://academictorrents.com/announce.php', } # Set "creation date". # The user may specify a custom creation date. He may also decide not # to include the creation date field at all. if options.date == -1: # use current time metainfo['creation date'] = int(time.time()) elif options.date >= 0: # use specified timestamp directly metainfo['creation date'] = options.date # Add the "created by" field. metainfo['created by'] = 'py3createtorrent v%s' % VERSION # Add user's comment or advertise py3createtorrent (unless this behaviour # has been disabled by the user). # The user may also decide not to include the comment field at all # by specifying an empty comment. if isinstance(options.comment, str): if len(options.comment) > 0: metainfo['comment'] = options.comment # Add the name field. # By default this is the name of directory or file the torrent # is being created for. if options.name: options.name = options.name.strip() regexp = re.compile("^[A-Z0-9_\-\., ]+$", re.I) if not regexp.match(options.name): parser.error("Invalid name: '%s'. Allowed chars: A_Z, a-z, 0-9, " "any of {.,_-} plus spaces." % options.name) metainfo['info']['name'] = options.name # ################################################### # BENCODE METAINFO DICTIONARY AND WRITE TORRENT FILE: # - take into consideration the --output option # - properly handle KeyboardInterrups while writing the file # Respect the custom output location. if not options.output: # Use current directory. output_path = metainfo['info']['name'] + ".torrent" else: # Use the directory or filename specified by the user. options.output = os.path.abspath(options.output) # The user specified an output directory: if os.path.isdir(options.output): output_path = os.path.join(options.output, metainfo['info']['name'] + ".torrent") if os.path.isfile(output_path): if not options.force and os.path.exists(output_path): if "yes" != input("'%s' does already exist. Overwrite? " "yes/no: " % output_path): parser.error("Aborted.") # The user specified a filename: else: # Is there already a file with this path? -> overwrite?! if os.path.isfile(options.output): if not options.force and os.path.exists(options.output): if "yes" != input("'%s' does already exist. Overwrite? " "yes/no: " % options.output): parser.error("Aborted.") output_path = options.output # Actually write the torrent file now. try: with open(output_path, "wb") as fh: fh.write(bencode(metainfo)) except IOError as exc: print("IOError: " + str(exc), file=sys.stderr) print( "Could not write the torrent file. Check torrent name and your " "privileges.", file=sys.stderr) print("Absolute output path: '%s'" % os.path.abspath(output_path), file=sys.stderr) return 1 except KeyboardInterrupt: # Properly handle KeyboardInterrupts. # todo: open()'s context manager may already do this on his own? if os.path.exists(output_path): os.remove(output_path) return 0
def make_torrent(node): # CALCULATE/SET THE FOLLOWING METAINFO DATA: # - info # - pieces (concatenated 20 byte sha1 hashes of all the data) # - files (if multiple files) # - length and md5sum (if single file) # - name (may be overwritten in the next section by the --name option) node = os.path.abspath(node) # Validate the given path. if not os.path.isfile(node) and not os.path.isdir(node): raise Exception("'%s' neither is a file nor a directory." % node) # Get the torrent's files and / or calculate its size. if os.path.isfile(node): torrent_size = os.path.getsize(node) else: torrent_files = get_files_in_directory(node) torrent_size = sum([os.path.getsize(os.path.join(node, file)) for file in torrent_files]) # Torrents for 0 byte data can't be created. if torrent_size == 0: raise Exception("No data for torrent.") piece_length = calculate_piece_length(torrent_size) # Do the main work now. # -> prepare the metainfo dictionary. if os.path.isfile(node): info = create_single_file_info(node, piece_length) else: info = create_multi_file_info(node, torrent_files, piece_length) info['piece length'] = piece_length # Finish sub-dict "info". # Construct outer metainfo dict, which contains the torrent's whole # information. metainfo = { 'info': info, 'announce': 'http://academictorrents.com/announce.php', 'creation date': int(time.time()), 'created by': '', } # ################################################### # BENCODE METAINFO DICTIONARY AND WRITE TORRENT FILE: # - properly handle KeyboardInterrups while writing the file # Use current directory. output_path = metainfo['info']['name'] + ".torrent" # Actually write the torrent file now. try: with open(output_path, "wb") as fh: fh.write(bencode(metainfo)) except IOError as exc: print("IOError: " + str(exc), file=sys.stderr) print("Could not write the torrent file. Check torrent name and your " "privileges.", file=sys.stderr) return 1 except KeyboardInterrupt: # Properly handle KeyboardInterrupts. # todo: open()'s context manager may already do this on his own? if os.path.exists(output_path): os.remove(output_path) return output_path
def main(folder, tracker): (options, args) = option_parse().parse_args(args=[folder, tracker]) # Positional arguments must have been provided: # -> file / directory plus at least one tracker. if len(args) < 2: parser.error("You must specify a valid path and at least one tracker.") # Ask the user if he really wants to use uncommon piece lengths. # (Unless the force option has been set.) if not options.force and 0 < options.piece_length < 16: if "yes" != input("It is strongly recommended to use a piece length \ greater or equal than 16 KiB! Do you really want to continue? yes/no: "): parser.error("Aborted.") if not options.force and options.piece_length > 1024: if "yes" != input("It is strongly recommended to use a maximum piece \ length of 1024 KiB! Do you really want to continue? yes/no: "): parser.error("Aborted.") # Verbose and quiet options may not be used together. if options.verbose and options.quiet: parser.error("Being verbose and quiet exclude each other.") ## manually setting private flag to True options.private = True global VERBOSE VERBOSE = False # ########################################## # CALCULATE/SET THE FOLLOWING METAINFO DATA: # - info # - pieces (concatenated 20 byte sha1 hashes of all the data) # - files (if multiple files) # - length and md5sum (if single file) # - name (may be overwritten in the next section by the --name option) node = folder trackers = [tracker] # Validate the given path. if not os.path.isfile(node) and not os.path.isdir(node): parser.error("'%s' neither is a file nor a directory." % node) # Parse and validate excluded paths. excluded_paths = frozenset([os.path.normcase(os.path.abspath(path)) \ for path in options.exclude]) # Parse exclude patterns. excluded_regexps = frozenset(options.exclude_pattern) # Warn the user if he attempts to exclude any paths when creating # a torrent for a single file (makes no sense). if os.path.isfile(node) and (len(excluded_paths) > 0 or \ len(excluded_regexps) > 0): print("Warning: Excluding paths is not possible when creating a \ torrent for a single file.") # Warn the user if he attempts to exclude a specific path, that does not # even exist. for path in excluded_paths: if not os.path.exists(path): print( "Warning: You're excluding a path that does not exist: '%s'" % path) # Get the torrent's files and / or calculate its size. if os.path.isfile(node): torrent_size = os.path.getsize(node) else: torrent_files = get_files_in_directory( node, excluded_paths=excluded_paths, excluded_regexps=excluded_regexps) torrent_size = int(sum([os.path.getsize(f) for f in torrent_files])) # Torrents for 0 byte data can't be created. if torrent_size == 0: print("Error: Can't create torrent for 0 byte data.") print("Check your files and exclusions!") return 1 # Calculate or parse the piece size. if options.piece_length == 0: piece_length = calculate_piece_length(torrent_size) elif options.piece_length > 0: piece_length = options.piece_length * KIB else: parser.error("Invalid piece size: '%d'" % options.piece_length) # Do the main work now. # -> prepare the metainfo dictionary. if os.path.isfile(node): info = create_single_file_info(node, piece_length) else: info = create_multi_file_info(node, torrent_files, piece_length) assert len(info['pieces']) % 20 == 0, "len(pieces) not a multiple of 20" # ########################### # FINISH METAINFO DICTIONARY: # - info # - piece length # - name (eventually overwrite) # - private # - announce # - announce-list (if multiple trackers) # - creation date (may be disabled as well) # - created by # - comment (may be disabled as well (if ADVERTISE = False)) # Finish sub-dict "info". info['piece length'] = piece_length # flaming - private flag should always be on (see above) if options.private: info['private'] = 1 # Construct outer metainfo dict, which contains the torrent's whole # information. metainfo = { 'info': info, 'announce': trackers[0], } # Make "announce-list" field, if there are multiple trackers. if len(trackers) > 1: metainfo['announce-list'] = [[tracker] for tracker in trackers] # Set "creation date". # The user may specify a custom creation date. He may also decide not # to include the creation date field at all. if options.date == -1: # use current time metainfo['creation date'] = int(time.time()) elif options.date >= 0: # use specified timestamp directly metainfo['creation date'] = options.date # Add the "created by" field. metainfo['created by'] = 'py3ct v%s' % VERSION # Comment field # flaming - disabled for now ## if isinstance(options.comment, str): ## if len(options.comment) > 0: ## metainfo['comment'] = options.comment # Add the name field. # By default this is the name of directory or file the torrent # is being created for. if options.name: options.name = options.name.strip() regexp = re.compile("^[A-Z0-9_\-\., ]+$", re.I) if not regexp.match(options.name): parser.error("Invalid name: '%s'. Allowed chars: A_Z, a-z, \ 0-9, any of {.,_-} plus spaces." % options.name) metainfo['info']['name'] = options.name # ################################################### # BENCODE METAINFO DICTIONARY AND WRITE TORRENT FILE: # - take into consideration the --output option # - properly handle KeyboardInterrups while writing the file # Respect the custom output location. if not options.output: # Use current directory. output_path = metainfo['info']['name'] + ".torrent" else: # Use the directory or filename specified by the user. options.output = os.path.abspath(options.output) # The user specified an output directory: if os.path.isdir(options.output): output_path = os.path.join(options.output, metainfo['info']['name'] + ".torrent") if os.path.isfile(output_path): if not options.force and os.path.exists(output_path): if "yes" != input("'%s' does already exist. Overwrite? \ yes/no: " % output_path): parser.error("Aborted.") # The user specified a filename: else: # Is there already a file with this path? -> overwrite?! if os.path.isfile(options.output): if not options.force and os.path.exists(options.output): if "yes" != input("'%s' does already exist. Overwrite? \ yes/no: " % options.output): parser.error("Aborted.") output_path = options.output # Actually write the torrent file now. try: fh = open(output_path, 'wb') fh.write(bencode(metainfo)) fh.close() except IOError, exc: print("IOError: " + str(exc)) print("Could not write the torrent file. Check torrent name and your \ privileges.") print("Absolute output path: '%s'" % os.path.abspath(output_path)) return 1
localFile, headers = urlretrieve(latestURI, file_location / filename) command = [ '/usr/bin/python3', '{}/py3createtorrent.py'.format(script_location), '-o', tempDir, localFile, ] + trackers run(command) # Calculating the magnet link and storing it in a file torrent = Path(tempDir + '/' + filename + '.torrent').open('rb').read() metadata = bdecode(torrent) hashcontents = bencode(metadata['info']) digest = hashlib.sha1(hashcontents).hexdigest() params = { 'dn': metadata['info']['name'], 'tr': metadata['announce'], 'xl': metadata['info']['length'] } paramstr = urlencode(params) magneturi = 'magnet:?xt=urn:btih:{}&{}'.format(digest, paramstr) with (webroot / 'magnetLinks' / (filename + '.txt')).open('w') as file: file.write(magneturi + '\n') # Magnet has been calculated, put a copy of the torrent in the web root copy(tempDir + '/' + filename + '.torrent', webroot / 'torrents/')
def main(folder,tracker): (options, args) = option_parse().parse_args(args = [folder,tracker]) # Positional arguments must have been provided: # -> file / directory plus at least one tracker. if len(args) < 2: parser.error("You must specify a valid path and at least one tracker.") # Ask the user if he really wants to use uncommon piece lengths. # (Unless the force option has been set.) if not options.force and 0 < options.piece_length < 16: if "yes" != input("It is strongly recommended to use a piece length \ greater or equal than 16 KiB! Do you really want to continue? yes/no: "): parser.error("Aborted.") if not options.force and options.piece_length > 1024: if "yes" != input("It is strongly recommended to use a maximum piece \ length of 1024 KiB! Do you really want to continue? yes/no: "): parser.error("Aborted.") # Verbose and quiet options may not be used together. if options.verbose and options.quiet: parser.error("Being verbose and quiet exclude each other.") ## manually setting private flag to True options.private = True global VERBOSE VERBOSE = False # ########################################## # CALCULATE/SET THE FOLLOWING METAINFO DATA: # - info # - pieces (concatenated 20 byte sha1 hashes of all the data) # - files (if multiple files) # - length and md5sum (if single file) # - name (may be overwritten in the next section by the --name option) node = folder trackers = [tracker] # Validate the given path. if not os.path.isfile(node) and not os.path.isdir(node): parser.error("'%s' neither is a file nor a directory." % node) # Parse and validate excluded paths. excluded_paths = frozenset([os.path.normcase(os.path.abspath(path)) \ for path in options.exclude]) # Parse exclude patterns. excluded_regexps = frozenset(options.exclude_pattern) # Warn the user if he attempts to exclude any paths when creating # a torrent for a single file (makes no sense). if os.path.isfile(node) and (len(excluded_paths) > 0 or \ len(excluded_regexps) > 0): print("Warning: Excluding paths is not possible when creating a \ torrent for a single file.") # Warn the user if he attempts to exclude a specific path, that does not # even exist. for path in excluded_paths: if not os.path.exists(path): print("Warning: You're excluding a path that does not exist: '%s'" % path) # Get the torrent's files and / or calculate its size. if os.path.isfile(node): torrent_size = os.path.getsize(node) else: torrent_files = get_files_in_directory(node, excluded_paths=excluded_paths, excluded_regexps=excluded_regexps) torrent_size = int(sum([os.path.getsize(f) for f in torrent_files])) # Torrents for 0 byte data can't be created. if torrent_size == 0: print("Error: Can't create torrent for 0 byte data.") print("Check your files and exclusions!") return 1 # Calculate or parse the piece size. if options.piece_length == 0: piece_length = calculate_piece_length(torrent_size) elif options.piece_length > 0: piece_length = options.piece_length * KIB else: parser.error("Invalid piece size: '%d'" % options.piece_length) # Do the main work now. # -> prepare the metainfo dictionary. if os.path.isfile(node): info = create_single_file_info(node, piece_length) else: info = create_multi_file_info(node, torrent_files, piece_length) assert len(info['pieces']) % 20 == 0, "len(pieces) not a multiple of 20" # ########################### # FINISH METAINFO DICTIONARY: # - info # - piece length # - name (eventually overwrite) # - private # - announce # - announce-list (if multiple trackers) # - creation date (may be disabled as well) # - created by # - comment (may be disabled as well (if ADVERTISE = False)) # Finish sub-dict "info". info['piece length'] = piece_length # flaming - private flag should always be on (see above) if options.private: info['private'] = 1 # Construct outer metainfo dict, which contains the torrent's whole # information. metainfo = { 'info': info, 'announce': trackers[0], } # Make "announce-list" field, if there are multiple trackers. if len(trackers) > 1: metainfo['announce-list'] = [[tracker] for tracker in trackers] # Set "creation date". # The user may specify a custom creation date. He may also decide not # to include the creation date field at all. if options.date == -1: # use current time metainfo['creation date'] = int(time.time()) elif options.date >= 0: # use specified timestamp directly metainfo['creation date'] = options.date # Add the "created by" field. metainfo['created by'] = 'py3ct v%s' % VERSION # Comment field # flaming - disabled for now ## if isinstance(options.comment, str): ## if len(options.comment) > 0: ## metainfo['comment'] = options.comment # Add the name field. # By default this is the name of directory or file the torrent # is being created for. if options.name: options.name = options.name.strip() regexp = re.compile("^[A-Z0-9_\-\., ]+$", re.I) if not regexp.match(options.name): parser.error("Invalid name: '%s'. Allowed chars: A_Z, a-z, \ 0-9, any of {.,_-} plus spaces." % options.name) metainfo['info']['name'] = options.name # ################################################### # BENCODE METAINFO DICTIONARY AND WRITE TORRENT FILE: # - take into consideration the --output option # - properly handle KeyboardInterrups while writing the file # Respect the custom output location. if not options.output: # Use current directory. output_path = metainfo['info']['name'] + ".torrent" else: # Use the directory or filename specified by the user. options.output = os.path.abspath(options.output) # The user specified an output directory: if os.path.isdir(options.output): output_path = os.path.join(options.output, metainfo['info']['name']+".torrent") if os.path.isfile(output_path): if not options.force and os.path.exists(output_path): if "yes" != input("'%s' does already exist. Overwrite? \ yes/no: " % output_path): parser.error("Aborted.") # The user specified a filename: else: # Is there already a file with this path? -> overwrite?! if os.path.isfile(options.output): if not options.force and os.path.exists(options.output): if "yes" != input("'%s' does already exist. Overwrite? \ yes/no: " % options.output): parser.error("Aborted.") output_path = options.output # Actually write the torrent file now. try: fh = open(output_path,'wb') fh.write(bencode(metainfo)) fh.close() except IOError, exc: print("IOError: " + str(exc)) print("Could not write the torrent file. Check torrent name and your \ privileges.") print("Absolute output path: '%s'" % os.path.abspath(output_path)) return 1
def main(argv): # Validate the configuration. # Create OptionParser. kwargs = { "usage": "%prog [options] <file-or-directory> " "[<backup-tracker-url> ...]", "version": "%%prog v%s" % VERSION, "description": "py3createtorrent is a comprehensive command line utility for " "creating torrents.", } parser = optparse.OptionParser(**kwargs) # Add options to the OptionParser. # Note: Commonly used options are added first. parser.add_option( "-p", "--piece-length", type="int", action="store", dest="piece_length", default=0, help="piece size in KiB. 0 = automatic selection (default).", ) parser.add_option( "-P", "--private", action="store_true", dest="private", default=False, help="create private torrent" ) parser.add_option( "-c", "--comment", type="string", action="store", dest="comment", default=False, help="include comment" ) parser.add_option( "-f", "--force", action="store_true", dest="force", default=False, help="dont ask anything, just do it" ) parser.add_option( "-o", "--output", type="string", action="store", dest="output", default=None, metavar="PATH", help="custom output location (directory or complete " "path). default = current directory.", ) parser.add_option( "-e", "--exclude", type="string", action="append", dest="exclude", default=[], metavar="PATH", help="exclude path (can be repeated)", ) parser.add_option( "--exclude-pattern", type="string", action="append", dest="exclude_pattern", default=[], metavar="REGEXP", help="exclude paths matching the regular expression " "(can be repeated)", ) parser.add_option( "--exclude-pattern-ci", type="string", action="append", dest="exclude_pattern_ci", default=[], metavar="REGEXP", help="exclude paths matching the case-insensitive regular " "expression (can be repeated)", ) parser.add_option( "-d", "--date", type="int", action="store", dest="date", default=-1, metavar="TIMESTAMP", help="set creation date (unix timestamp). -1 = now " "(default). -2 = disable.", ) parser.add_option( "-n", "--name", type="string", action="store", dest="name", default=None, help="use this file (or directory) name instead of the " "real one", ) parser.add_option( "--md5", action="store_true", dest="include_md5", default=False, help="include MD5 hashes in torrent file" ) (options, args) = parser.parse_args(args=argv[1:]) # Positional arguments must have been provided: # Ask the user if he really wants to use uncommon piece lengths. # (Unless the force option has been set.) if not options.force and 0 < options.piece_length < 16: if "yes" != input( "It is strongly recommended to use a piece length " "greater or equal than 16 KiB! Do you really want " "to continue? yes/no: " ): parser.error("Aborted.") if not options.force and options.piece_length > 1024: if "yes" != input( "It is strongly recommended to use a maximum piece " "length of 1024 KiB! Do you really want to " "continue? yes/no: " ): parser.error("Aborted.") # ########################################## # CALCULATE/SET THE FOLLOWING METAINFO DATA: # - info # - pieces (concatenated 20 byte sha1 hashes of all the data) # - files (if multiple files) # - length and md5sum (if single file) # - name (may be overwritten in the next section by the --name option) node = os.path.abspath(args[0]) # Validate the given path. if not os.path.isfile(node) and not os.path.isdir(node): parser.error("'%s' neither is a file nor a directory." % node) # Parse and validate excluded paths. excluded_paths = frozenset([os.path.normcase(os.path.abspath(path)) for path in options.exclude]) # Parse exclude patterns. excluded_regexps = set(re.compile(regexp) for regexp in options.exclude_pattern) excluded_regexps |= set(re.compile(regexp, re.IGNORECASE) for regexp in options.exclude_pattern_ci) # Warn the user if he attempts to exclude any paths when creating # a torrent for a single file (makes no sense). if os.path.isfile(node) and (len(excluded_paths) > 0 or len(excluded_regexps) > 0): print("Warning: Excluding paths is not possible when creating a " "torrent for a single file.", file=sys.stderr) # Warn the user if he attempts to exclude a specific path, that does not # even exist. for path in excluded_paths: if not os.path.exists(path): print("Warning: You're excluding a path that does not exist: '%s'" % path, file=sys.stderr) # Get the torrent's files and / or calculate its size. if os.path.isfile(node): torrent_size = os.path.getsize(node) else: torrent_files = get_files_in_directory(node, excluded_paths=excluded_paths, excluded_regexps=excluded_regexps) torrent_size = sum([os.path.getsize(os.path.join(node, file)) for file in torrent_files]) # Torrents for 0 byte data can't be created. if torrent_size == 0: raise Exception("No data for torrent.") # Calculate or parse the piece size. if options.piece_length == 0: piece_length = calculate_piece_length(torrent_size) elif options.piece_length > 0: piece_length = options.piece_length * KIB else: parser.error("Invalid piece size: '%d'" % options.piece_length) # Do the main work now. # -> prepare the metainfo dictionary. if os.path.isfile(node): info = create_single_file_info(node, piece_length, options.include_md5) else: info = create_multi_file_info(node, torrent_files, piece_length, options.include_md5) assert len(info["pieces"]) % 20 == 0, "len(pieces) not a multiple of 20" # ########################### # FINISH METAINFO DICTIONARY: # - info # - piece length # - name (eventually overwrite) # - private # - announce # - announce-list (if multiple trackers) # - creation date (may be disabled as well) # - created by # Finish sub-dict "info". info["piece length"] = piece_length if options.private: info["private"] = 1 # Construct outer metainfo dict, which contains the torrent's whole # information. metainfo = {"info": info, "announce": "http://academictorrents.com/announce.php"} # Set "creation date". # The user may specify a custom creation date. He may also decide not # to include the creation date field at all. if options.date == -1: # use current time metainfo["creation date"] = int(time.time()) elif options.date >= 0: # use specified timestamp directly metainfo["creation date"] = options.date # Add the "created by" field. metainfo["created by"] = "py3createtorrent v%s" % VERSION # Add user's comment or advertise py3createtorrent (unless this behaviour # has been disabled by the user). # The user may also decide not to include the comment field at all # by specifying an empty comment. if isinstance(options.comment, str): if len(options.comment) > 0: metainfo["comment"] = options.comment # Add the name field. # By default this is the name of directory or file the torrent # is being created for. if options.name: options.name = options.name.strip() regexp = re.compile("^[A-Z0-9_\-\., ]+$", re.I) if not regexp.match(options.name): parser.error( "Invalid name: '%s'. Allowed chars: A_Z, a-z, 0-9, " "any of {.,_-} plus spaces." % options.name ) metainfo["info"]["name"] = options.name # ################################################### # BENCODE METAINFO DICTIONARY AND WRITE TORRENT FILE: # - take into consideration the --output option # - properly handle KeyboardInterrups while writing the file # Respect the custom output location. if not options.output: # Use current directory. output_path = metainfo["info"]["name"] + ".torrent" else: # Use the directory or filename specified by the user. options.output = os.path.abspath(options.output) # The user specified an output directory: if os.path.isdir(options.output): output_path = os.path.join(options.output, metainfo["info"]["name"] + ".torrent") if os.path.isfile(output_path): if not options.force and os.path.exists(output_path): if "yes" != input("'%s' does already exist. Overwrite? " "yes/no: " % output_path): parser.error("Aborted.") # The user specified a filename: else: # Is there already a file with this path? -> overwrite?! if os.path.isfile(options.output): if not options.force and os.path.exists(options.output): if "yes" != input("'%s' does already exist. Overwrite? " "yes/no: " % options.output): parser.error("Aborted.") output_path = options.output # Actually write the torrent file now. try: with open(output_path, "wb") as fh: fh.write(bencode(metainfo)) except IOError as exc: print("IOError: " + str(exc), file=sys.stderr) print("Could not write the torrent file. Check torrent name and your " "privileges.", file=sys.stderr) print("Absolute output path: '%s'" % os.path.abspath(output_path), file=sys.stderr) return 1 except KeyboardInterrupt: # Properly handle KeyboardInterrupts. # todo: open()'s context manager may already do this on his own? if os.path.exists(output_path): os.remove(output_path) return 0