git: 85e836a96d7e - main - net-p2p/py-ed2k-tools: new port had been added (+)

From: Alexey Dokuchaev <danfe_at_FreeBSD.org>
Date: Thu, 18 May 2023 06:08:50 UTC
The branch main has been updated by danfe:

URL: https://cgit.FreeBSD.org/ports/commit/?id=85e836a96d7e2647eecbc253e93e2793c3e168f5

commit 85e836a96d7e2647eecbc253e93e2793c3e168f5
Author:     Alexey Dokuchaev <danfe@FreeBSD.org>
AuthorDate: 2023-05-18 06:08:16 +0000
Commit:     Alexey Dokuchaev <danfe@FreeBSD.org>
CommitDate: 2023-05-18 06:08:16 +0000

    net-p2p/py-ed2k-tools: new port had been added (+)
    
    This is a Python framework for easy manipulation of .part.met files
    (Overnet/eDonkey2000 download meta-information).  This set of classes
    allows for rapid development of ed2k tools, using a very simple API.
---
 net-p2p/Makefile                                   |  1 +
 net-p2p/py-ed2k-tools/Makefile                     | 39 +++++++++
 net-p2p/py-ed2k-tools/distinfo                     |  3 +
 .../py-ed2k-tools/files/patch-ed2k__metutils.py    | 97 ++++++++++++++++++++++
 net-p2p/py-ed2k-tools/files/patch-fix__sofar.py    | 63 ++++++++++++++
 net-p2p/py-ed2k-tools/files/patch-make__met.py     | 79 ++++++++++++++++++
 .../files/patch-non-met_shutdown__core.py          | 29 +++++++
 .../py-ed2k-tools/files/patch-retrieve__link.py    | 32 +++++++
 net-p2p/py-ed2k-tools/files/patch-temp__summary.py | 60 +++++++++++++
 net-p2p/py-ed2k-tools/pkg-descr                    |  3 +
 10 files changed, 406 insertions(+)

diff --git a/net-p2p/Makefile b/net-p2p/Makefile
index a3360ad3b8f6..10c042fb5520 100644
--- a/net-p2p/Makefile
+++ b/net-p2p/Makefile
@@ -78,6 +78,7 @@
     SUBDIR += p5-WWW-BitTorrent
     SUBDIR += phex
     SUBDIR += prowlarr
+    SUBDIR += py-ed2k-tools
     SUBDIR += py-libtorrent-rasterbar
     SUBDIR += py-nicotine-plus
     SUBDIR += py-transmission-rpc
diff --git a/net-p2p/py-ed2k-tools/Makefile b/net-p2p/py-ed2k-tools/Makefile
new file mode 100644
index 000000000000..798eade53f98
--- /dev/null
+++ b/net-p2p/py-ed2k-tools/Makefile
@@ -0,0 +1,39 @@
+PORTNAME=	ed2k-tools
+PORTVERSION=	0.1
+CATEGORIES=	net-p2p python
+MASTER_SITES=	SF/${PORTNAME}/ed2k-python/${PORTVERSION}
+PKGNAMEPREFIX=	${PYTHON_PKGNAMEPREFIX}
+DISTNAME=	ed2k_python-${PORTVERSION}
+
+MAINTAINER=	danfe@FreeBSD.org
+COMMENT=	Python framework for manipulating eD2K metafiles
+WWW=		https://ed2k-tools.sourceforge.net/python.shtml
+
+LICENSE=	MIT
+
+USES=		python:run shebangfix
+SHEBANG_FILES=	fix_sofar.py make_met.py non-met/shutdown_core.py \
+		retrieve_link.py temp_summary.py
+WRKSRC=		${WRKDIR}/ed2k_python
+
+NO_ARCH=	yes
+NO_BUILD=	yes
+
+PLIST_FILES=	${PYTHON_SITELIBDIR}/ed2k_metutils.py \
+		${SHEBANG_FILES:T:S,^,bin/,}
+PORTDOCS=	README
+
+OPTIONS_DEFINE=	DOCS
+
+do-install:
+	@${MKDIR} ${STAGEDIR}${PYTHON_SITELIBDIR}
+	${INSTALL_DATA} ${WRKSRC}/ed2k_metutils.py \
+		${STAGEDIR}${PYTHON_SITELIBDIR}
+	${INSTALL_SCRIPT} ${SHEBANG_FILES:S,^,${WRKSRC}/,} \
+		${STAGEDIR}${PREFIX}/bin
+
+do-install-DOCS-on:
+	@${MKDIR} ${STAGEDIR}${DOCSDIR}
+	${INSTALL_DATA} ${PORTDOCS:S,^,${WRKSRC}/,} ${STAGEDIR}${DOCSDIR}
+
+.include <bsd.port.mk>
diff --git a/net-p2p/py-ed2k-tools/distinfo b/net-p2p/py-ed2k-tools/distinfo
new file mode 100644
index 000000000000..0abfe27e8726
--- /dev/null
+++ b/net-p2p/py-ed2k-tools/distinfo
@@ -0,0 +1,3 @@
+TIMESTAMP = 1052224864
+SHA256 (ed2k_python-0.1.tar.gz) = aa11b85735473319e8d0662fef60af4b777c1759456beb4aaf8545911bff761a
+SIZE (ed2k_python-0.1.tar.gz) = 10290
diff --git a/net-p2p/py-ed2k-tools/files/patch-ed2k__metutils.py b/net-p2p/py-ed2k-tools/files/patch-ed2k__metutils.py
new file mode 100644
index 000000000000..7cd01f6f60d3
--- /dev/null
+++ b/net-p2p/py-ed2k-tools/files/patch-ed2k__metutils.py
@@ -0,0 +1,97 @@
+--- ed2k_metutils.py.orig	2003-05-06 11:53:14 UTC
++++ ed2k_metutils.py
+@@ -6,7 +6,6 @@
+ #      tested on macosx 10.2.4, python 2.2
+ 
+ import struct;
+-import types;
+ import sys;
+ 
+ # Some defines.
+@@ -14,16 +13,16 @@ import sys;
+ TAG_TYPE_STRING  = 2;
+ TAG_TYPE_INTEGER = 3;
+ 
+-TAG_HANDLE_FILENAME   = chr( 1 );
+-TAG_HANDLE_FILESIZE   = chr( 2 );
+-TAG_HANDLE_FILETYPE   = chr( 3 );
+-TAG_HANDLE_FILEFORMAT = chr( 4 );
+-TAG_HANDLE_SOFAR      = chr( 8 );
+-TAG_HANDLE_GAP_START  = chr( 9 );
+-TAG_HANDLE_GAP_END    = chr( 10 );
+-TAG_HANDLE_TEMP_NAME  = chr( 18 );
+-TAG_HANDLE_PAUSED     = chr( 20 );
+-TAG_HANDLE_PRIORITY   = chr( 24 );
++TAG_HANDLE_FILENAME   = b'\x01'
++TAG_HANDLE_FILESIZE   = b'\x02'
++TAG_HANDLE_FILETYPE   = b'\x03'
++TAG_HANDLE_FILEFORMAT = b'\x04'
++TAG_HANDLE_SOFAR      = b'\x08'
++TAG_HANDLE_GAP_START  = b'\x09'
++TAG_HANDLE_GAP_END    = b'\x0a'
++TAG_HANDLE_TEMP_NAME  = b'\x12'
++TAG_HANDLE_PAUSED     = b'\x14'
++TAG_HANDLE_PRIORITY   = b'\x18'
+ 
+ class MetFile:
+ 	"""Class designed to hold the data of a .part.met file."""
+@@ -39,7 +38,7 @@ class MetFile:
+ 			# a .part file must exist, even if it's empty.  The same doesn't apply for new overnet.
+ 			self.version = 225;
+ 			self.modDate = 0;
+-			self.fileID = '\0' * 16; 
++			self.fileID = b'\0' * 16
+ 			return;
+ 			
+ 		header_struct = "<BI16sH";
+@@ -58,7 +57,7 @@ class MetFile:
+ 		dstore = dstore[ 4 : ];
+ 		
+ 		for meta in range( n_meta ):
+-			t_type, = struct.unpack( "<B", dstore[ 0 ] );
++			t_type, = struct.unpack("<B", dstore[0:1])
+ 			dstore = dstore[ 1 : ];
+ 			
+ 			name_len, = struct.unpack( "<H", dstore[ : 2 ] );
+@@ -81,14 +80,14 @@ class MetFile:
+ 		"""Return a string representation of the file MD4."""
+ 		data = "";
+ 		for i in range( len( self.fileID )  ):
+-			data += "%02x" % ord( self.fileID[ i ] );
++			data += "%02x" % self.fileID[i]
+ 		return data.upper();
+ 	
+ 	def getEd2K( self ):
+ 		"""Return the ed2k:// link associated with this met file."""
+ 		size = self.FindTags( TAG_HANDLE_FILESIZE )[ 0 ].value;
+ 		name = self.FindTags( TAG_HANDLE_FILENAME )[ 0 ].value;
+-		return "ed2k://|file|%s|%s|%s|" % ( name, size, self.getMD4() );
++		return "ed2k://|file|%s|%s|%s|" % (name.decode(), size, self.getMD4())
+ 		
+ 	def ReduceToData( self ):
+ 		"""Reduce a class instance back into a stream suitable for writing to disk."""
+@@ -109,13 +108,13 @@ class MetFile:
+ 		"""Return an array of tags matching the supplied handle.
+ 		   Tags relating to gaps do no obey the usual 'special tag' 
+ 		   semantics, so set the flag to 1 if you are dealing with them."""
+-		if gaptags: return [ x for x in self.m_tags if x.name[ 0 ] == tagHandle ];
++		if gaptags: return [ x for x in self.m_tags if x.name[0:1] == tagHandle ]
+ 		else: return [ x for x in self.m_tags if x.name == tagHandle ];
+ 			
+ 	def PurgeTags( self, tagHandle, gaptags = 0 ):
+ 		"""This is the same as FindTags, except it removes the 
+ 		   matching tags from the meta-tag store."""
+-		if gaptags: self.m_tags = [ x for x in self.m_tags if x.name[ 0 ] != tagHandle ];
++		if gaptags: self.m_tags = [ x for x in self.m_tags if x.name[0:1] != tagHandle ]
+ 		else: self.m_tags = [ x for x in self.m_tags if x.name != tagHandle ];
+ 		
+ class MetaTag:
+@@ -127,7 +126,7 @@ class MetaTag:
+ 		self.value = value;
+ 		if t_type == None:
+ 			# Rudiments of Autodetection...
+-			if type( value ) == types.IntType:
++			if isinstance(value, int):
+ 				self.tag_type = TAG_TYPE_INTEGER;
+ 			else:
+ 				self.tag_type = TAG_TYPE_STRING;
diff --git a/net-p2p/py-ed2k-tools/files/patch-fix__sofar.py b/net-p2p/py-ed2k-tools/files/patch-fix__sofar.py
new file mode 100644
index 000000000000..a7c4e293e4ab
--- /dev/null
+++ b/net-p2p/py-ed2k-tools/files/patch-fix__sofar.py
@@ -0,0 +1,63 @@
+--- fix_sofar.py.orig	2003-05-06 11:53:14 UTC
++++ fix_sofar.py
+@@ -11,26 +11,25 @@ if __name__ == "__main__":
+ 	# This will undo the damage.
+ 	
+ 	if len( sys.argv ) < 2:
+-		print "invocation: %s <x.part.met> [x.part.met ...]" % sys.argv[ 0 ];
+-		print
+-		print "Some versions of Overnet on MacOSX seem not to write the 0x08 'sofar' tag"
+-		print "on exiting, this gives the appearance that the next time you boot overnet,"
+-		print "nothing has been downloaded.  It's only cosmetic, however."
+-		print
+-		print "If you want to create new .met files with this 'bug' corrected, run this"
+-		print "program with the affected .met files as the command line arguments.  You"
+-		print "will get new .met files titled X.new, where X was the original .part.met"
+-		print "file.  Copy these over the top of your originals if you're sure thats what"
+-		print "you want to do."
+-		print
+-		print "Of course, Overnet will re-break these files on its next exit.  You'll"
+-		print "need to run this program a lot to keep everything setup."
+-		print
++		print("invocation: %s <x.part.met> [x.part.met ...]" % sys.argv[0])
++		print()
++		print("Some versions of Overnet on MacOSX seem not to write the 0x08 'sofar' tag")
++		print("on exiting, this gives the appearance that the next time you boot overnet,")
++		print("nothing has been downloaded.  It's only cosmetic, however.")
++		print()
++		print("If you want to create new .met files with this 'bug' corrected, run this")
++		print("program with the affected .met files as the command line arguments.  You")
++		print("will get new .met files titled X.new, where X was the original .part.met")
++		print("file.  Copy these over the top of your originals if you're sure thats what")
++		print("you want to do.")
++		print()
++		print("Of course, Overnet will re-break these files on its next exit.  You'll")
++		print("need to run this program a lot to keep everything setup.")
+ 		sys.exit( -1 );
+ 	
+ 	for met_file in sys.argv[ 1 : ]:
+ 		
+-		fh = open( met_file, "r" );
++		fh = open(met_file, "rb")
+ 		data = fh.read();
+ 		fh.close();
+ 		
+@@ -55,14 +54,14 @@ if __name__ == "__main__":
+ 		for gap in gaps.keys():
+ 			so_far -= gaps[ gap ];
+ 		
+-		print "%s: %s" % ( met_file, met_data.FindTags( TAG_HANDLE_FILENAME )[ 0 ].value );
+-		print "MD4: %s" % ( met_data.getMD4() );
+-		print "Obtained size / total: %i / %i" % ( so_far, length );
++		print("%s: %s" % (met_file, met_data.FindTags(TAG_HANDLE_FILENAME)[0].value.decode()))
++		print("MD4: %s" % met_data.getMD4())
++		print("Obtained size / total: %i / %i" % (so_far, length))
+ 		
+ 		met_data.PurgeTags( TAG_HANDLE_SOFAR );
+ 		met_data.AddTag( MetaTag( TAG_HANDLE_SOFAR, so_far, TAG_TYPE_INTEGER ) );
+ 			
+-		fh = open( "%s.new" % met_file, "w" );
++		fh = open("%s.new" % met_file, "wb")
+ 		fh.write( met_data.ReduceToData() );
+ 		fh.close();
+ 		del( met_data );
diff --git a/net-p2p/py-ed2k-tools/files/patch-make__met.py b/net-p2p/py-ed2k-tools/files/patch-make__met.py
new file mode 100644
index 000000000000..6bd11fea1491
--- /dev/null
+++ b/net-p2p/py-ed2k-tools/files/patch-make__met.py
@@ -0,0 +1,79 @@
+--- make_met.py.orig	2003-05-06 11:53:14 UTC
++++ make_met.py
+@@ -7,14 +7,13 @@ import re
+ if __name__ == "__main__":
+ 	
+ 	if len( sys.argv ) < 3:
+-		print "invocation: %s <temp-directory> <ed2k://...>" % sys.argv[ 0 ];
+-		print ;
+-		print "This script creates a new .part.met file in the directory of the first";
+-		print "argument, which represents the ed2k:// link provided as the second arg.";
+-		print ;
+-		print "Useful for adding things to your download list without actually opening ";
+-		print "Overnet / Donkey.";
+-		print ;
++		print("invocation: %s <temp-directory> <ed2k://...>" % sys.argv[0])
++		print()
++		print("This script creates a new .part.met file in the directory of the first")
++		print("argument, which represents the ed2k:// link provided as the second arg.")
++		print()
++		print("Useful for adding things to your download list without actually opening")
++		print("Overnet / Donkey.")
+ 		sys.exit( -1 );
+ 	
+ 	temp_dir = sys.argv[ 1 ];
+@@ -25,9 +24,9 @@ if __name__ == "__main__":
+ 	matches = ed2k_reg.findall( ed2k_link );
+ 	
+ 	if not matches:
+-		print "Oh no!  This ( %s ) doesn't feel like an ed2k link!" % ( ed2k_link );
+-		print "ed2k file links have the form:";
+-		print "   ed2k://|file|<file name>|<file size>|<md4 hash>|";
++		print("Oh no!  This (%s) doesn't look like an ed2k link!" % (ed2k_link))
++		print("ed2k file links have the form:")
++		print("  ed2k://|file|<file name>|<file size>|<md4 hash>|")
+ 		sys.exit( -1 );
+ 	
+ 	name, size, hash = ed2k_reg.findall( ed2k_link )[ 0 ];
+@@ -35,11 +34,11 @@ if __name__ == "__main__":
+ 
+ 	# Convert the printed hash into a byte representation.
+ 	# Surely there's an easier way to do this.
+-	new_hash = "";	
++	new_hash = b''
+ 	while hash:
+ 		part = hash[ 0 : 2 ];
+ 		hash = hash[ 2 : ];
+-		new_hash += chr( eval( "0x" + part ) );
++		new_hash += bytes([eval("0x" + part)])
+ 
+ 	# Find the first unused download identifier.
+ 	metfiles = [ int( x.split( "." )[ 0 ] ) for x in os.listdir( temp_dir ) if x.endswith( ".part.met" ) ];
+@@ -53,22 +52,22 @@ if __name__ == "__main__":
+ 	# Build the structure.
+ 	new = MetFile();
+ 	new.fileID = new_hash;
+-	new.AddTag( MetaTag( TAG_HANDLE_FILENAME, name ) );
++	new.AddTag(MetaTag(TAG_HANDLE_FILENAME, name.encode()))
+ 	new.AddTag( MetaTag( TAG_HANDLE_FILESIZE, size ) );	
+ 	new.AddTag( MetaTag( TAG_HANDLE_SOFAR, 0 ) );
+ 	# Now, I thought this implied an off-by-one error ( if a file is
+ 	# five bytes long, the gap should go from byte 0 to byte 4... ),
+ 	# but apparently that's not how we do it here.
+-	new.AddTag( MetaTag( "%s0" % TAG_HANDLE_GAP_START, 0 ) );
+-	new.AddTag( MetaTag( "%s0" % TAG_HANDLE_GAP_END, size ) );
++	new.AddTag(MetaTag(b"%s0" % TAG_HANDLE_GAP_START, 0))
++	new.AddTag(MetaTag(b"%s0" % TAG_HANDLE_GAP_END, size))
+ 	new.AddTag( MetaTag( TAG_HANDLE_PAUSED, 0 ) );
+ 	new.AddTag( MetaTag( TAG_HANDLE_PRIORITY, 1 ) );
+ 
+ 	# Write it out.
+-	fh = open( filename, "w" );
++	fh = open(filename, "wb")
+ 	fh.write( new.ReduceToData() );
+ 	fh.close();
+ 
+ 	del( new );
+ 
+-	print "Wrote met for %s to %s." % ( ed2k_link, filename );	
++	print("Wrote met for %s to %s." % (ed2k_link, filename))
diff --git a/net-p2p/py-ed2k-tools/files/patch-non-met_shutdown__core.py b/net-p2p/py-ed2k-tools/files/patch-non-met_shutdown__core.py
new file mode 100644
index 000000000000..ed2984ef23f6
--- /dev/null
+++ b/net-p2p/py-ed2k-tools/files/patch-non-met_shutdown__core.py
@@ -0,0 +1,29 @@
+--- non-met/shutdown_core.py.orig	2003-05-06 11:53:14 UTC
++++ non-met/shutdown_core.py
+@@ -13,7 +13,7 @@ def make_login_packet( username, password ):
+ 	"""Create a authentication packet."""
+ 	# This looks like so:
+ 	#	<length of username: 16bit><username><length of password: 16 bit><password>
+-	packet = struct.pack( "<BH%isH%is" % ( len( username ), len( password ) ), OP_LOGIN, len( username ), username, len( password ), password );
++	packet = struct.pack("<BH%isH%is" % (len(username), len(password)), OP_LOGIN, len(username), username.encode(), len(password), password.encode())
+ 	# Hss hss.
+ 	return packet;
+ 
+@@ -25,7 +25,7 @@ def make_shutdown_packet( ):
+ def make_connection( hostname, port = 4663 ):
+ 	"""Make a socket to the core."""
+ 	connection = socket.socket();
+-	connection.connect( ( hostname, port ) );
++	connection.connect((hostname, int(port)))
+ 	return connection;
+ 
+ def send_packet( connection, packet ):
+@@ -35,7 +35,7 @@ def send_packet( connection, packet ):
+ 
+ if __name__ == '__main__':
+ 	if len( sys.argv ) < 4:
+-		print "usage: %s <host[:port]> <uname> <pass>" % sys.argv[ 0 ];
++		print("usage: %s <host[:port]> <uname> <pass>" % sys.argv[0])
+ 		sys.exit( -1 );
+ 	
+ 	# Split out the hostname if necessary.
diff --git a/net-p2p/py-ed2k-tools/files/patch-retrieve__link.py b/net-p2p/py-ed2k-tools/files/patch-retrieve__link.py
new file mode 100644
index 000000000000..18f1c074cecc
--- /dev/null
+++ b/net-p2p/py-ed2k-tools/files/patch-retrieve__link.py
@@ -0,0 +1,32 @@
+--- retrieve_link.py.orig	2003-05-06 11:53:14 UTC
++++ retrieve_link.py
+@@ -8,22 +8,21 @@ if __name__ == "__main__":
+ 	# you break one so badly all you want back is the ed2k:// hash.
+ 	
+ 	if len( sys.argv ) < 2:
+-		print "invocation: %s <x.part.met> [x.part.met ...]" % sys.argv[ 0 ];
+-		print
+-		print "This program will print out the ed2k:// link responsible for"
+-		print "the formation of a given .part.met file or files, given on "
+-		print "the command line."
+-		print
++		print("invocation: %s <x.part.met> [x.part.met ...]" % sys.argv[0])
++		print()
++		print("This program will print out the ed2k:// link responsible for")
++		print("the formation of a given .part.met file or files, given on")
++		print("the command line.")
+ 		sys.exit( -1 );
+ 	
+ 	for met_file in sys.argv[ 1 : ]:
+ 		
+-		fh = open( met_file, "r" );
++		fh = open(met_file, "rb")
+ 		data = fh.read();
+ 		fh.close();
+ 		
+ 		met_data = MetFile( data );
+ 		del( data );
+ 		
+-		print "%s: %s" % ( met_file, met_data.getEd2K() );
++		print("%s: %s" % (met_file, met_data.getEd2K()))
+ 		del( met_data );
diff --git a/net-p2p/py-ed2k-tools/files/patch-temp__summary.py b/net-p2p/py-ed2k-tools/files/patch-temp__summary.py
new file mode 100644
index 000000000000..358af31af085
--- /dev/null
+++ b/net-p2p/py-ed2k-tools/files/patch-temp__summary.py
@@ -0,0 +1,60 @@
+--- temp_summary.py.orig	2003-05-06 11:53:14 UTC
++++ temp_summary.py
+@@ -15,13 +15,13 @@ if __name__ == "__main__":
+ 	# see how much data I actually got from night to night.
+ 	
+ 	if len( sys.argv ) < 2:
+-		print "invocation: %s < <x.part.met> [x.part.met ...] | <temp_dir> >" % sys.argv[ 0 ];
+-		print
+-		print "This program will show the amount downloaded vs. the total size "
+-		print "for the .part.met files listed on the command line." 
+-		print
+-		print "This program assumes an 80 column display.  You can tweak this "
+-		print "by editing the script.  Change the 'WIDTH' value."
++		print("invocation: %s < <x.part.met> [x.part.met ...] | <temp_dir> >" % sys.argv[0])
++		print()
++		print("This program will show the amount downloaded vs. the total size")
++		print("for the .part.met files listed on the command line.")
++		print()
++		print("This program assumes an 80 column display.  You can tweak this")
++		print("by editing the script.  Change the 'WIDTH' value.")
+ 		sys.exit( -1 );
+ 	
+ 	total_size = total_down = 0;
+@@ -34,7 +34,7 @@ if __name__ == "__main__":
+ 	
+ 	for met_file in mets:
+ 		
+-		fh = open( met_file, "r" );
++		fh = open(met_file, "rb")
+ 		data = fh.read();
+ 		fh.close();
+ 		
+@@ -71,19 +71,19 @@ if __name__ == "__main__":
+ 		bar = "#" * ( WIDTH - 2 );
+ 		for gap in gaps:
+ 			gap_start, gap_end = gaps[ gap ];
+-			char_gap_start = gap_start / bytes_per_char;
+-			char_gap_end = gap_end / bytes_per_char;
++			char_gap_start = int(gap_start / bytes_per_char)
++			char_gap_end = int(gap_end / bytes_per_char)
+ 			bar = bar[ : char_gap_start ] + " " * ( char_gap_end - char_gap_start ) + bar[ char_gap_end : ];
+ 				
+ 		# Print out our summary.  Limit the filenames.
+-		sizestring = " - %s - %iK of %iK" % ( met_file.split( "/" )[ -1 ], down / 1024, size / 1024 );
++		sizestring = " - %s - %.2fK of %.2fK" % (met_file.split("/")[-1], down / 1024, size / 1024)
+ 		max_name_size = WIDTH - len( sizestring );
+ 		if len( name ) < max_name_size:
+-			name += " " * ( max_name_size - len( name ) );
++			name += b" " * (max_name_size - len(name))
+ 		else:
+ 			name = name[ : max_name_size ];
+-		print "%s%s" % ( name, sizestring );
+-		print "[%s]" % bar;
+-		print 
++		print("%s%s" % (name.decode(), sizestring))
++		print("[%s]" % bar)
++		print()
+ 		del( met_data );
+-	print "Totals: %sK of %sK" % ( total_down / 1024, total_size / 1024 );
++	print("Totals: %.2fK of %.2fK" % (total_down / 1024, total_size / 1024))
diff --git a/net-p2p/py-ed2k-tools/pkg-descr b/net-p2p/py-ed2k-tools/pkg-descr
new file mode 100644
index 000000000000..76e9e05ee836
--- /dev/null
+++ b/net-p2p/py-ed2k-tools/pkg-descr
@@ -0,0 +1,3 @@
+This is a Python framework for easy manipulation of .part.met files
+(Overnet/eDonkey2000 download meta-information).  This set of classes
+allows for rapid development of ed2k tools, using a very simple API.