diff -u -r plugins/artsearch/lastfm.py plugins/artsearch/lastfm.py
--- plugins/artsearch/lastfm.py	(original)
+++ plugins/artsearch/lastfm.py	(refactored)
@@ -24,10 +24,10 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 
-import urllib
+import urllib.request, urllib.parse, urllib.error
 import xml.dom.minidom as dom
 import re
-import ConfigParser
+import configparser
 import os
 import time
 
@@ -65,7 +65,7 @@
 	if os.path.exists(session_file) == False:
 		return False
 
-	sessions = ConfigParser.RawConfigParser()
+	sessions = configparser.RawConfigParser()
 	sessions.read(session_file)
 	try:
 		return (sessions.get('Last.fm', 'username') != "")
@@ -85,23 +85,23 @@
 
 		album.strip()
 
-		print "searching for (%s, %s, %s)" % (artist, album, album_mbid)
+		print("searching for (%s, %s, %s)" % (artist, album, album_mbid))
 		url = API_URL + "?method=album.getinfo&"
 		if artist != None:
-			url = url + "artist=%s&" % (urllib.quote_plus(artist))
+			url = url + "artist=%s&" % (urllib.parse.quote_plus(artist))
 		if album != None:
-			url = url + "album=%s&" % (urllib.quote_plus(album))
+			url = url + "album=%s&" % (urllib.parse.quote_plus(album))
 		if album_mbid != None:
-			url = url + "mbid=%s&" % (urllib.quote_plus(album_mbid))
+			url = url + "mbid=%s&" % (urllib.parse.quote_plus(album_mbid))
 
 		url = url + "api_key=%s" % API_KEY
-		print "last.fm query url = %s" % url
+		print("last.fm query url = %s" % url)
 		return url
 
 
 	def album_info_cb (self, data):
 		if data is None:
-			print "last.fm query returned nothing"
+			print("last.fm query returned nothing")
 			self.search_next()
 			return
 
@@ -111,13 +111,13 @@
 		image_urls = []
 		for tag in parsed.getElementsByTagName('image'):
 			if tag.firstChild is None:
-				print "got useless image tag"
+				print("got useless image tag")
 				continue
 
 			url = tag.firstChild.data
 			url.strip()
 			if url != "":
-				print "found image url: %s" % url
+				print("found image url: %s" % url)
 				image_urls.append(url)
 
 		if len(image_urls) > 0:
@@ -147,12 +147,12 @@
 
 	def search(self, key, last_time, store, callback, args):
 		if last_time > (time.time() - REPEAT_SEARCH_PERIOD):
-			print "we already tried this one"
+			print("we already tried this one")
 			callback (args)
 			return
 
 		if user_has_account() == False:
-			print "can't search: no last.fm account details"
+			print("can't search: no last.fm account details")
 			callback (args)
 			return
 
@@ -160,12 +160,12 @@
 		artists = key.get_field_values("artist")
 		album_mbid = key.get_info("musicbrainz-albumid")
 
-		artists = filter(lambda x: x not in (None, "", _("Unknown")), artists)
+		artists = [x for x in artists if x not in (None, "", _("Unknown"))]
 		if album in ("", _("Unknown")):
 			album = None
 
 		if album == None or len(artists) == 0:
-			print "can't search: no useful details"
+			print("can't search: no useful details")
 			callback (args)
 			return
 
diff -u -r plugins/artsearch/local.py plugins/artsearch/local.py
--- plugins/artsearch/local.py	(original)
+++ plugins/artsearch/local.py	(refactored)
@@ -72,7 +72,7 @@
 					nkey = RB.ExtDBKey.create_storage("album", album)
 					nkey.add_field("artist", artist)
 					uri = parent.resolve_relative_path(f_name).get_uri()
-					print "found album+artist match " + uri
+					print("found album+artist match " + uri)
 					self.store.store_uri(nkey, RB.ExtDBSourceType.USER, uri)
 
 		# if that didn't work, look for the longest shared prefix
@@ -87,7 +87,7 @@
 
 		if match is not None:
 			uri = parent.resolve_relative_path(match).get_uri()
-			print "found prefix match " + uri
+			print("found prefix match " + uri)
 			self.store.store_uri(key, RB.ExtDBSourceType.USER, uri)
 
 		self.callback(self.callback_args)
@@ -95,15 +95,15 @@
 	def _close_enum_cb(self, fileenum, result, results):
 		try:
 			fileenum.close_finish(result)
-		except Exception, e:
-			print "couldn't close file enumerator: %s" % e
+		except Exception as e:
+			print("couldn't close file enumerator: %s" % e)
 		
 
 	def _enum_dir_cb(self, fileenum, result, results):
 		try:
 			files = fileenum.next_files_finish(result)
 			if files is None or len(files) == 0:
-				print "okay, done; got %d files" % len(results)
+				print("okay, done; got %d files" % len(results))
 				fileenum.close_async(GLib.PRIORITY_DEFAULT, None, self._close_enum_cb, None)
 				self.finished(results)
 				return
@@ -118,8 +118,8 @@
 					results.append(f.get_name())
 
 			fileenum.next_files_async(ITEMS_PER_NOTIFICATION, GLib.PRIORITY_DEFAULT, None, self._enum_dir_cb, results)
-		except Exception, e:
-			print "okay, probably done: %s" % e
+		except Exception as e:
+			print("okay, probably done: %s" % e)
 			import sys
 			sys.excepthook(*sys.exc_info())
 			self.finished(results)
@@ -130,8 +130,8 @@
 		try:
 			enumfiles = parent.enumerate_children_finish(result)
 			enumfiles.next_files_async(ITEMS_PER_NOTIFICATION, GLib.PRIORITY_DEFAULT, None, self._enum_dir_cb, [])
-		except Exception, e:
-			print "okay, probably done: %s" % e
+		except Exception as e:
+			print("okay, probably done: %s" % e)
 			if not isinstance(e, GLib.GError):
 				import sys
 				sys.excepthook(*sys.exc_info())
@@ -143,13 +143,13 @@
 
 		location = key.get_info("location")
 		if location is None:
-			print "not searching, we don't have a location"
+			print("not searching, we don't have a location")
 			callback(args)
 			return
 
 		self.file = Gio.file_new_for_uri(location)
 		if self.file.get_uri_scheme() in IGNORED_SCHEMES:
-			print 'not searching for local art for %s' % (self.file.get_uri())
+			print('not searching for local art for %s' % (self.file.get_uri()))
 			callback(args)
 			return
 
@@ -159,6 +159,6 @@
 		self.callback = callback
 		self.callback_args = args
 
-		print 'searching for local art for %s' % (self.file.get_uri())
+		print('searching for local art for %s' % (self.file.get_uri()))
 		parent = self.file.get_parent()
 		enumfiles = parent.enumerate_children_async("standard::content-type,access::can-read,standard::name", 0, 0, None, self._enum_children_cb, None)
diff -u -r plugins/artsearch/musicbrainz.py plugins/artsearch/musicbrainz.py
--- plugins/artsearch/musicbrainz.py	(original)
+++ plugins/artsearch/musicbrainz.py	(refactored)
@@ -26,7 +26,7 @@
 
 import xml.dom.minidom as dom
 
-import rb, urllib
+import rb, urllib.request, urllib.parse, urllib.error
 from gi.repository import RB
 
 # musicbrainz URLs
@@ -48,7 +48,7 @@
 	def get_release_cb (self, data, args):
 		(key, store, callback, cbargs) = args
 		if data is None:
-			print "musicbrainz release request returned nothing"
+			print("musicbrainz release request returned nothing")
 			callback(*cbargs)
 			return
 
@@ -66,7 +66,7 @@
 					nametags = artist_tags[0].getElementsByTagName('name')
 					if len(nametags) > 0:
 						artistname = nametags[0].firstChild.data
-						print "got musicbrainz artist name %s" % artistname
+						print("got musicbrainz artist name %s" % artistname)
 						storekey.add_field('artist', artistname)
 
 
@@ -75,16 +75,16 @@
 			if len(asin_tags) > 0:
 				asin = asin_tags[0].firstChild.data
 
-				print "got ASIN %s" % asin
+				print("got ASIN %s" % asin)
 				image_url = AMAZON_IMAGE_URL % asin
 
 				store.store_uri(storekey, RB.ExtDBSourceType.SEARCH, image_url)
 			else:
-				print "no ASIN for this release"
+				print("no ASIN for this release")
 
 			callback(*cbargs)
-		except Exception, e:
-			print "exception parsing musicbrainz response: %s" % e
+		except Exception as e:
+			print("exception parsing musicbrainz response: %s" % e)
 			callback(*cbargs)
 
 	def try_search_artist_album (self, key, store, callback, *args):
@@ -92,12 +92,12 @@
 		artist = key.get_field("artist")
 
 		if not album or not artist:
-			print "artist or album information missing"
+			print("artist or album information missing")
 			callback(*args)
 			return
 
 		query = MUSICBRAINZ_SEARCH_QUERY % (artist.lower(), album.lower())
-		url = MUSICBRAINZ_SEARCH_URL % (urllib.quote(query, safe=':'),)
+		url = MUSICBRAINZ_SEARCH_URL % (urllib.parse.quote(query, safe=':'),)
 
 		loader = rb.Loader()
 		loader.get_url(url, self.get_release_cb, (key, store, callback, args))
@@ -106,7 +106,7 @@
 		key = key.copy()	# ugh
 		album_id = key.get_info("musicbrainz-albumid")
 		if album_id is None:
-			print "no musicbrainz release ID for this track"
+			print("no musicbrainz release ID for this track")
 			self.try_search_artist_album(key, store, callback, args)
 			return
 
@@ -116,7 +116,7 @@
 		if album_id.endswith(MUSICBRAINZ_RELEASE_SUFFIX):
 			album_id = album_id[:-len(MUSICBRAINZ_RELEASE_SUFFIX)]
 
-		print "stripped release ID: %s" % album_id
+		print("stripped release ID: %s" % album_id)
 
 		url = MUSICBRAINZ_RELEASE_URL % (album_id)
 		loader = rb.Loader()
diff -u -r plugins/artsearch/oldcache.py plugins/artsearch/oldcache.py
--- plugins/artsearch/oldcache.py	(original)
+++ plugins/artsearch/oldcache.py	(refactored)
@@ -25,7 +25,7 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 
 import os.path
-import urllib
+import urllib.request, urllib.parse, urllib.error
 
 import rb
 from gi.repository import RB
@@ -49,13 +49,13 @@
 		album = key.get_field("album")
 		artists = key.get_field_values("artist") or []
 
-		print "looking for %s by %s" % (album, str(artists))
+		print("looking for %s by %s" % (album, str(artists)))
 		for artist in artists:
 			for ext in ('jpg', 'png'):
 				path = self.filename(album, artist, ext)
 				if os.path.exists(path):
-					print "found %s" % path
-					uri = "file://" + urllib.pathname2url(path)
+					print("found %s" % path)
+					uri = "file://" + urllib.request.pathname2url(path)
 					storekey = RB.ExtDBKey.create_storage('album', album)
 					storekey.add_field("artist", artist)
 					store.store_uri(storekey, RB.ExtDBSourceType.SEARCH, uri)
diff -u -r plugins/context/AlbumTab.py plugins/context/AlbumTab.py
--- plugins/context/AlbumTab.py	(original)
+++ plugins/context/AlbumTab.py	(refactored)
@@ -26,7 +26,7 @@
 
 import os
 import cgi
-import urllib
+import urllib.request, urllib.parse, urllib.error
 from mako.template import Template
 import xml.dom.minidom as dom
 
@@ -102,7 +102,7 @@
         self.file    = ""
 
         plugindir = plugin.plugin_info.get_data_dir()
-        self.basepath = "file://" + urllib.pathname2url (plugindir)
+        self.basepath = "file://" + urllib.request.pathname2url (plugindir)
 
         self.load_tmpl ()
         self.connect_signals ()
@@ -160,7 +160,7 @@
         """
         try:
             return data[position].firstChild.data
-        except Exception, e:
+        except Exception as e:
             return None
 
     def get_artist (self):
@@ -176,7 +176,7 @@
             return
 
         self.artist = artist
-        qartist = urllib.quote_plus (artist)
+        qartist = urllib.parse.quote_plus (artist)
         self.error  = None
         url = "%sartist.gettopalbums&artist=%s&api_key=%s" % (LastFM.URL_PREFIX,
                                                               qartist,
@@ -186,13 +186,13 @@
 
     def parse_album_list (self, data, artist):
         if data is None:
-            print "Nothing fetched for %s top albums" % artist
+            print("Nothing fetched for %s top albums" % artist)
             return
 
         try:
             parsed = dom.parseString (data)
-        except Exception, e:
-            print "Error parsing album list: %s" % e
+        except Exception as e:
+            print("Error parsing album list: %s" % e)
             return False
 
         lfm = parsed.getElementsByTagName ('lfm')[0]
@@ -203,7 +203,7 @@
 
         self.albums = []
         album_nodes = parsed.getElementsByTagName ('album') 
-        print "num albums: %d" % len(album_nodes)
+        print("num albums: %d" % len(album_nodes))
         if len(album_nodes) == 0:
             self.error = "No albums found for %s" % artist
             self.emit('albums-ready')
@@ -225,8 +225,8 @@
         return self.albums
 
     def fetch_album_info (self, artist, album, index):
-        qartist = urllib.quote_plus (artist)
-        qalbum = urllib.quote_plus (album.encode('utf-8'))
+        qartist = urllib.parse.quote_plus (artist)
+        qalbum = urllib.parse.quote_plus (album.encode('utf-8'))
         cachekey = "lastfm:album:getinfo:%s:%s" % (qartist, qalbum)
         url = "%salbum.getinfo&artist=%s&album=%s&api_key=%s" % (LastFM.URL_PREFIX,
                                                                  qartist,
@@ -241,8 +241,8 @@
         try:
             parsed = dom.parseString (data)
             self.albums[index]['id'] = parsed.getElementsByTagName ('id')[0].firstChild.data
-        except Exception, e:
-            print "Error parsing album tracklist: %s" % e
+        except Exception as e:
+            print("Error parsing album tracklist: %s" % e)
             return False
 
         self.albums[index]['releasedate'] = self.extract(parsed.getElementsByTagName ('releasedate'),0)
@@ -257,7 +257,7 @@
     def assemble_info (self, data, album, index):
         rv = True
         if data is None:
-            print "nothing fetched for %s tracklist" % album
+            print("nothing fetched for %s tracklist" % album)
         else:
             try:
                 parsed = dom.parseString (data)
@@ -271,12 +271,12 @@
                     tracklist.append ((i, title, duration))
                 self.albums[index]['tracklist'] = tracklist
                 self.albums[index]['duration']  = album_length
-            except Exception, e:
-                print "Error parsing album playlist: %s" % e
+            except Exception as e:
+                print("Error parsing album playlist: %s" % e)
                 rv = False
 
         self.album_info_fetched -= 1
-        print "%s albums left to process" % self.album_info_fetched
+        print("%s albums left to process" % self.album_info_fetched)
         if self.album_info_fetched == 0:
             self.emit('albums-ready')
 
diff -u -r plugins/context/ArtistTab.py plugins/context/ArtistTab.py
--- plugins/context/ArtistTab.py	(original)
+++ plugins/context/ArtistTab.py	(refactored)
@@ -26,7 +26,7 @@
 
 import re, os
 import cgi
-import urllib
+import urllib.request, urllib.parse, urllib.error
 import xml.dom.minidom as dom
 
 from mako.template import Template
@@ -69,20 +69,20 @@
         buttons.pack_start (self.button, True, True, 0)
 
     def activate (self):
-        print "activating Artist Tab"
+        print("activating Artist Tab")
         self.button.set_active(True)
         self.active = True
         self.reload ()
 
     def deactivate (self):
-        print "deactivating Artist Tab"
+        print("deactivating Artist Tab")
         self.button.set_active(False)
         self.active = False
 
     def reload (self):
         entry = self.sp.get_playing_entry ()
         if entry is None:
-            print "Nothing playing"
+            print("Nothing playing")
             return None
         artist = entry.get_string (RB.RhythmDBPropType.ARTIST)
 
@@ -104,7 +104,7 @@
         self.file     = ""
 
 	plugindir = plugin.plugin_info.get_data_dir()
-        self.basepath = "file://" + urllib.pathname2url (plugindir)
+        self.basepath = "file://" + urllib.request.pathname2url (plugindir)
 
         self.load_tmpl ()
         self.connect_signals ()
@@ -145,8 +145,8 @@
                                               datasource = LastFM.datasource_link (self.basepath),
                                               stylesheet = self.styles )
             self.load_view ()
-        except Exception, e:
-            print "Problem in info ready: %s" % e
+        except Exception as e:
+            print("Problem in info ready: %s" % e)
     
 
 class ArtistDataSource (GObject.GObject):
@@ -205,14 +205,14 @@
         
         try:
             return data[position].firstChild.data
-        except Exception, e:
+        except Exception as e:
             return None
 
     def fetch_top_tracks (self, artist):
         if LastFM.user_has_account() is False:
             return
 
-        artist = urllib.quote_plus (artist)
+        artist = urllib.parse.quote_plus (artist)
         function = self.artist['top_tracks']['function']
         cache = self.artist['top_tracks']['cache']
         cachekey = "lastfm:artist:%s:%s" % (function, artist)
@@ -234,8 +234,8 @@
             return
 
         self.error = None
-        artist = urllib.quote_plus (artist)
-        for key, value in self.artist.items():
+        artist = urllib.parse.quote_plus (artist)
+        for key, value in list(self.artist.items()):
             cachekey = "lastfm:artist:%s:%s" % (value['function'], artist)
             url = '%sartist.%s&artist=%s&api_key=%s' % (LastFM.URL_PREFIX,
                 value['function'], artist, LastFM.API_KEY)
@@ -243,15 +243,15 @@
 
     def fetch_artist_data_cb (self, data, category):
         if data is None:
-            print "no data fetched for artist %s" % category['function']
+            print("no data fetched for artist %s" % category['function'])
             return
 
         try:
             category['data'] = dom.parseString (data)
             category['parsed'] = False
             self.emit (category['signal'])
-        except Exception, e:
-            print "Error parsing artist %s: %s" % (category['function'], e)
+        except Exception as e:
+            print("Error parsing artist %s: %s" % (category['function'], e))
             return False
 
     def get_current_artist (self):
diff -u -r plugins/context/ContextView.py plugins/context/ContextView.py
--- plugins/context/ContextView.py	(original)
+++ plugins/context/ContextView.py	(refactored)
@@ -133,7 +133,7 @@
         self.tab_cb_ids = []
 
         # Listen for switch-tab signal from each tab
-        for key, value in self.tab.items():
+        for key, value in list(self.tab.items()):
             self.tab_cb_ids.append((key, self.tab[key].connect ('switch-tab', self.change_tab)))
 
     def disconnect_signals (self):
@@ -154,7 +154,7 @@
             self.visible = False
 
     def change_tab (self, tab, newtab):
-        print "swapping tab from %s to %s" % (self.current, newtab)
+        print("swapping tab from %s to %s" % (self.current, newtab))
         if (self.current != newtab):
             self.tab[self.current].deactivate()
             self.tab[newtab].activate()
@@ -219,7 +219,7 @@
     def navigation_request_cb(self, view, frame, request):
         # open HTTP URIs externally.  this isn't a web browser.
         if request.get_uri().startswith('http'):
-            print "opening uri %s" % request.get_uri()
+            print("opening uri %s" % request.get_uri())
 	    Gtk.show_uri(self.shell.props.window.get_screen(), request.get_uri(), Gdk.CURRENT_TIME)
 
             return 1        # WEBKIT_NAVIGATION_RESPONSE_IGNORE
@@ -239,7 +239,7 @@
             font_size /= Pango.SCALE
         self.websettings.props.default_font_size = font_size
         self.websettings.props.default_font_family = style.font_desc.get_family()
-        print "web view font settings: %s, %d" % (style.font_desc.get_family(), font_size)
+        print("web view font settings: %s, %d" % (style.font_desc.get_family(), font_size))
 
     def init_gui(self):
         self.vbox = Gtk.VBox()
diff -u -r plugins/context/LastFM.py plugins/context/LastFM.py
--- plugins/context/LastFM.py	(original)
+++ plugins/context/LastFM.py	(refactored)
@@ -25,7 +25,7 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 
 import os
-import ConfigParser
+import configparser
 
 from gi.repository import RB
 
@@ -47,7 +47,7 @@
     if os.path.exists(session_file) == False:
 	return False
 
-    sessions = ConfigParser.RawConfigParser()
+    sessions = configparser.RawConfigParser()
     sessions.read(session_file)
     try:
 	return (sessions.get('Last.fm', 'username') != "")
diff -u -r plugins/context/LinksTab.py plugins/context/LinksTab.py
--- plugins/context/LinksTab.py	(original)
+++ plugins/context/LinksTab.py	(refactored)
@@ -26,7 +26,7 @@
 
 import os
 import cgi
-import urllib
+import urllib.request, urllib.parse, urllib.error
 import rb
 
 from gi.repository import GObject, Gtk
@@ -66,12 +66,12 @@
         buttons.pack_start (self.button, True, True, 0)
 
     def activate (self):
-        print "activating Links Tab"
+        print("activating Links Tab")
         self.button.set_active(True)
         self.reload ()
 
     def deactivate (self):
-        print "deactivating Links Tab"
+        print("deactivating Links Tab")
         self.button.set_active(False)
 
     def reload (self):
@@ -100,10 +100,10 @@
         self.file     = ""
 
         plugindir = plugin.plugin_info.get_data_dir()
-        self.basepath = "file://" + urllib.pathname2url (plugindir)
+        self.basepath = "file://" + urllib.request.pathname2url (plugindir)
 
     def load_links (self, ds):
-        print "Loading links into webview"
+        print("Loading links into webview")
         self.path = rb.find_plugin_file(self.plugin, 'tmpl/links-tmpl.html')
         self.images = self.basepath + '/img/links/'
         self.styles = self.basepath + '/tmpl/main.css'
@@ -152,7 +152,7 @@
         artist = self.get_artist()
         if artist is not "" and artist is not None:
             wpartist = artist.replace(" ", "_")
-            artist = urllib.quote_plus (artist)
+            artist = urllib.parse.quote_plus (artist)
             artist_links = {
                 "Wikipedia" : "http://www.wikipedia.org/wiki/%s" % wpartist,
                 "Discogs"  : "http://www.discogs.com/artist/%s" % artist,
@@ -169,7 +169,7 @@
         album = self.get_album()
         if album is not "" and album is not None:
             wpalbum = album.replace(" ", "_")
-            album = urllib.quote_plus (album)
+            album = urllib.parse.quote_plus (album)
             album_links = {
                 "Wikipedia" : "http://www.wikipedia.org/wiki/%s" % wpalbum,
                 "Discogs" : "http://www.discogs.com/search?type=album&q=%s&f=html" % album,
diff -u -r plugins/context/LyricsTab.py plugins/context/LyricsTab.py
--- plugins/context/LyricsTab.py	(original)
+++ plugins/context/LyricsTab.py	(refactored)
@@ -24,7 +24,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 
-import urllib
+import urllib.request, urllib.parse, urllib.error
 import re, os
 import cgi
 from mako.template import Template
@@ -62,12 +62,12 @@
         toolbar.pack_start (self.button, True, True, 0)
 
     def activate (self):
-        print "activating Lyrics Tab"
+        print("activating Lyrics Tab")
         self.button.set_active(True)
         self.reload ()
 
     def deactivate (self):
-        print "deactivating Lyrics Tab"
+        print("deactivating Lyrics Tab")
         self.button.set_active(False)
 
     def reload (self):
@@ -87,7 +87,7 @@
         self.plugin  = plugin
         self.file    = ""
         plugindir = plugin.plugin_info.get_data_dir()
-        self.basepath = "file://" + urllib.pathname2url (plugindir)
+        self.basepath = "file://" + urllib.request.pathname2url (plugindir)
 
         self.load_tmpl ()
         self.connect_signals ()
@@ -105,7 +105,7 @@
             song     = song,
             basepath = self.basepath)
         self.webview.load_string (self.loading_file, 'text/html', 'utf-8', self.basepath)
-        print "loading screen loaded"
+        print("loading screen loaded")
 
     def load_tmpl (self):
         self.path = rb.find_plugin_file(self.plugin, 'tmpl/lyrics-tmpl.html')
@@ -115,7 +115,7 @@
         self.styles = self.basepath + '/tmpl/main.css'
 
     def lyrics_ready (self, ds, entry, lyrics):
-        print "loading lyrics into webview"
+        print("loading lyrics into webview")
         if lyrics is None:
             lyrics = _("Lyrics not found")
         else:
diff -u -r plugins/im-status/im-status.py plugins/im-status/im-status.py
--- plugins/im-status/im-status.py	(original)
+++ plugins/im-status/im-status.py	(refactored)
@@ -32,7 +32,7 @@
 from gi.repository import RB
 
 import gettext
-gettext.install('rhythmbox', RB.locale_dir(), unicode=True)
+gettext.install('rhythmbox', RB.locale_dir(), str=True)
 
 NORMAL_SONG_ARTIST = 'artist'
 NORMAL_SONG_TITLE  = 'title'
@@ -159,31 +159,31 @@
 
   def set_status (self):
     subs = {
-        'artist': unicode (self.current_artist, encoding='utf-8'),
-        'album': unicode (self.current_album, encoding='utf-8'),
-        'title': unicode (self.current_title, encoding='utf-8')
+        'artist': str (self.current_artist, encoding='utf-8'),
+        'album': str (self.current_album, encoding='utf-8'),
+        'title': str (self.current_title, encoding='utf-8')
     }
     if self.current_artist:
       if self.current_title:
         # Translators: do not translate %(artist)s or %(title)s, they are
         # string substitution markers (like %s) for the artist and title of
         # the current playing song.  They can be reordered if necessary.
-        new_status = _(u"♫ %(artist)s - %(title)s ♫") % subs
+        new_status = _("♫ %(artist)s - %(title)s ♫") % subs
       elif self.current_album:
         # Translators: do not translate %(artist)s or %(album)s, they are
         # string substitution markers (like %s) for the artist and album name
         # of the current playing song.  They can be reordered if necessary.
-        new_status = _(u"♫ %(artist)s - %(album)s ♫") % subs
+        new_status = _("♫ %(artist)s - %(album)s ♫") % subs
     elif self.current_album:
       # Translators: do not translate %(album)s, it is a string substitution
       # marker (like %s) for the album name of the current playing song.
-      new_status = _(u"♫ %(album)s ♫") % subs
+      new_status = _("♫ %(album)s ♫") % subs
     elif self.current_title:
       # Translators: do not translate %(title)s, it is a string substitution
       # marker (like %s) for the title of the current playing song.
-      new_status = _(u"♫ %(title)s ♫") % subs
+      new_status = _("♫ %(title)s ♫") % subs
     else:
-      new_status = _(u"♫ Listening to music... ♫")
+      new_status = _("♫ Listening to music... ♫")
 
     self.set_mc5_status (new_status)
     self.set_purple_status (new_status)
@@ -213,7 +213,7 @@
         # Set the status!
         acct_proxy.Set("(ssv)", MC5_ACCT_IFACE_NAME, "RequestedPresence", vstatus)
     except gi._glib.GError as e:
-      print ("GError while setting status: " + str(e))
+      print(("GError while setting status: " + str(e)))
 
   def get_mc5_status (self):
     try:
@@ -235,7 +235,7 @@
       if got_status:
         return ""
     except gi._glib.GError as e:
-      print ("GError while setting status: " + str(e))
+      print(("GError while setting status: " + str(e)))
     return None
 
   def set_purple_status (self, new_status):
@@ -245,7 +245,7 @@
       proxy.PurpleSavedstatusSetMessage("(is)", status, new_status)
       proxy.PurpleSavedstatusActivate("(i)", status)
     except gi._glib.GError as e:
-      print ("GError while setting status: " + str(e))
+      print(("GError while setting status: " + str(e)))
 
   def get_purple_status (self):
     try:
@@ -253,5 +253,5 @@
       status = proxy.PurpleSavedstatusGetCurrent()
       return proxy.PurpleSavedstatusGetMessage("(i)", status)
     except gi._glib.GError as e:
-      print ("GError while setting status: " + str(e))
+      print(("GError while setting status: " + str(e)))
     return None
diff -u -r plugins/lyrics/AstrawebParser.py plugins/lyrics/AstrawebParser.py
--- plugins/lyrics/AstrawebParser.py	(original)
+++ plugins/lyrics/AstrawebParser.py	(refactored)
@@ -25,7 +25,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 
-import urllib
+import urllib.request, urllib.parse, urllib.error
 import re
 import rb
 
@@ -39,8 +39,8 @@
 		self.title = title
 		
 	def search(self, callback, *data):
-		wartist = re.sub('%20', '+', urllib.quote(self.artist))
-		wtitle = re.sub('%20', '+', urllib.quote(self.title))
+		wartist = re.sub('%20', '+', urllib.parse.quote(self.artist))
+		wtitle = re.sub('%20', '+', urllib.parse.quote(self.title))
 
 		wurl = 'http://search.lyrics.astraweb.com/?word=%s+%s' % (wartist, wtitle)
 
@@ -58,7 +58,7 @@
 			body = re.split('(<tr><td bgcolor="#BBBBBB".*)(More Songs &gt)', results)[1]
 			entries = re.split('<tr><td bgcolor="#BBBBBB"', body)
 			entries.pop(0)
-			print "found %d entries; looking for [%s,%s]" % (len(entries), self.title, self.artist)
+			print("found %d entries; looking for [%s,%s]" % (len(entries), self.title, self.artist))
 			for entry in entries:
 				url = re.split('(\/display[^"]*)', entry)[1]
 				artist = re.split('(Artist:.*html">)([^<]*)', entry)[2].strip()
@@ -71,7 +71,7 @@
 
 				title_str = rb.string_match(self.title, title)
 
-				print "checking [%s,%s]: match strengths [%f,%f]" % (title.strip(), artist.strip(), title_str, artist_str)
+				print("checking [%s,%s]: match strengths [%f,%f]" % (title.strip(), artist.strip(), title_str, artist_str))
 				if title_str > title_match and artist_str > artist_match:
 					loader = rb.Loader()
 					loader.get_url ('http://display.lyrics.astraweb.com' + url, self.parse_lyrics, callback, *data)
diff -u -r plugins/lyrics/LyricsConfigureDialog.py plugins/lyrics/LyricsConfigureDialog.py
--- plugins/lyrics/LyricsConfigureDialog.py	(original)
+++ plugins/lyrics/LyricsConfigureDialog.py	(refactored)
@@ -84,7 +84,7 @@
 			if check.get_active():
 				sites.append(s['id'])
 
-		print "setting lyrics sites: " + str(sites)
+		print("setting lyrics sites: " + str(sites))
 		self.settings['sites'] = sites
 
 
@@ -112,11 +112,11 @@
 	def get_prefs (self):
 		try:
 			sites = self.settings['sites']
-		except GObject.GError, e:
-			print e
+		except GObject.GError as e:
+			print(e)
 			engines = []
 		folder = self.settings['folder']
 
-		print "lyric sites: " + str (sites)
-		print "lyric folder: " + folder
+		print("lyric sites: " + str (sites))
+		print("lyric folder: " + folder)
 		return (sites, folder)
diff -u -r plugins/lyrics/LyricsParse.py plugins/lyrics/LyricsParse.py
--- plugins/lyrics/LyricsParse.py	(original)
+++ plugins/lyrics/LyricsParse.py	(refactored)
@@ -24,7 +24,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 
-import urllib
+import urllib.request, urllib.parse, urllib.error
 import re
 
 import rb
@@ -40,19 +40,19 @@
 		try:
 			settings = Gio.Settings("org.gnome.rhythmbox.plugins.lyrics")
 			self.sites = settings['sites']
-		except GObject.GError, e:
-			print e
+		except GObject.GError as e:
+			print(e)
 			self.sites = []
 
 	def searcher(self, plexer, callback, *data):
 		for site in lyrics_sites:
 			if site['id'] not in self.sites:
-				print site['id'] + " search is disabled"
+				print(site['id'] + " search is disabled")
 				continue
 
 			plexer.clear()
 			parser = site['class'] (self.artist, self.title)
-			print "searching " + site['id'] + " for lyrics"
+			print("searching " + site['id'] + " for lyrics")
 
 			parser.search(plexer.send())
 			yield None
diff -u -r plugins/lyrics/lyrics.py plugins/lyrics/lyrics.py
--- plugins/lyrics/lyrics.py	(original)
+++ plugins/lyrics/lyrics.py	(refactored)
@@ -27,7 +27,7 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 
 import os, re
-import urllib
+import urllib.request, urllib.parse, urllib.error
 
 import rb
 from gi.repository import Gtk, Gio, GObject, Peas
@@ -159,7 +159,7 @@
 		
 		if status:
 			l = rb.Loader()
-			l.get_url('file://' + urllib.pathname2url(self.cache_path), callback)
+			l.get_url('file://' + urllib.request.pathname2url(self.cache_path), callback)
 		elif cache_only:
 			self.callback(_("No lyrics found"))
 		elif self.artist == "" and self.title == "":
@@ -411,7 +411,7 @@
 			self.action.set_sensitive (False)
 
 	def window_deleted (self, window):
-		print "lyrics window destroyed"
+		print("lyrics window destroyed")
 		self.window = None
 	
 	def create_song_info (self, shell, song_info, is_multiple):
diff -u -r plugins/lyrics/LyricWikiParser.py plugins/lyrics/LyricWikiParser.py
--- plugins/lyrics/LyricWikiParser.py	(original)
+++ plugins/lyrics/LyricWikiParser.py	(refactored)
@@ -25,7 +25,7 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 
 
-import urllib
+import urllib.request, urllib.parse, urllib.error
 import rb
 from xml.dom import minidom
 
@@ -35,8 +35,8 @@
 		self.title = title
 	
 	def search(self, callback, *data):
-		artist = urllib.quote(self.artist.replace(' ', '_'))
-		title = urllib.quote(self.title.replace(' ', '_'))
+		artist = urllib.parse.quote(self.artist.replace(' ', '_'))
+		title = urllib.parse.quote(self.title.replace(' ', '_'))
 
 		htstring = 'http://lyricwiki.org/api.php?artist=%s&song=%s&fmt=text' % (artist, title)
 			
diff -u -r plugins/lyrics/TerraParser.py plugins/lyrics/TerraParser.py
--- plugins/lyrics/TerraParser.py	(original)
+++ plugins/lyrics/TerraParser.py	(refactored)
@@ -26,7 +26,7 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 
 
-import urllib
+import urllib.request, urllib.parse, urllib.error
 import rb
 import re
 import sys
@@ -34,25 +34,25 @@
 # Deal with html entitys and utf-8
 # code taken from django/utils/text.py
 
-from htmlentitydefs import name2codepoint
+from html.entities import name2codepoint
 
 pattern = re.compile("&(#?\w+?);")
 
 def _replace_entity(match):
 	text = match.group(1)
-	if text[0] == u'#':
+	if text[0] == '#':
 		text = text[1:]
 		try:
-			if text[0] in u'xX':
+			if text[0] in 'xX':
 				c = int(text[1:], 16)
 			else:
 				c = int(text)
-			return unichr(c)
+			return chr(c)
 		except ValueError:
 			return match.group(0)
 	else:
 		try:
-			return unichr(name2codepoint[text])
+			return chr(name2codepoint[text])
 		except (ValueError, KeyError):
 			return match.group(0)
 
@@ -67,12 +67,12 @@
 	def search(self, callback, *data):
 		path = 'http://letras.terra.com.br/'
 
-		artist = urllib.quote(self.artist)
-		title = urllib.quote(self.title)
-		join = urllib.quote(' - ')
+		artist = urllib.parse.quote(self.artist)
+		title = urllib.parse.quote(self.title)
+		join = urllib.parse.quote(' - ')
 
 		wurl = 'winamp.php?t=%s%s%s' % (artist, join, title)
-		print "search URL: " + wurl
+		print("search URL: " + wurl)
 
 		loader = rb.Loader()
 		loader.get_url (path + wurl, self.got_lyrics, callback, *data)
@@ -85,7 +85,7 @@
 		if result is not None:
 			result = result.decode('iso-8859-1').encode('UTF-8')
 			if re.search('M&uacute;sica n&atilde;o encontrada', result):
-				print "not found"
+				print("not found")
 				callback (None, *data)
 			elif re.search('<div id="letra">', result):
 				callback(self.parse_lyrics(result), *data)
diff -u -r plugins/lyrics/WinampcnParser.py plugins/lyrics/WinampcnParser.py
--- plugins/lyrics/WinampcnParser.py	(original)
+++ plugins/lyrics/WinampcnParser.py	(refactored)
@@ -25,7 +25,7 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 
 import sys
-import urllib
+import urllib.request, urllib.parse, urllib.error
 import re
 import rb
 from xml.dom import minidom
@@ -34,7 +34,7 @@
 	charsets = ('iso-8859-1', 'gbk', 'utf-8')
 	for charset in charsets:
 		try:
-			return unicode(unicode(s, 'utf-8').encode(charset), 'gbk')
+			return str(str(s, 'utf-8').encode(charset), 'gbk')
 		except:
 			continue
 	return s
@@ -47,8 +47,8 @@
 	def search(self, callback, *data):
 
 		# encode search string
-                title_encode = urllib.quote(detect_charset(self.title).encode('gbk').replace(' ', ''))
-		artist_encode = urllib.quote(detect_charset(self.artist).encode('gbk').replace(' ',''))
+                title_encode = urllib.parse.quote(detect_charset(self.title).encode('gbk').replace(' ', ''))
+		artist_encode = urllib.parse.quote(detect_charset(self.artist).encode('gbk').replace(' ',''))
 		url = 'http://www.winampcn.com/lyrictransfer/get.aspx?song=%s&artist=%s&lsong=%s&Datetime=20060601' % (title_encode, artist_encode, title_encode)
 		
 		loader = rb.Loader()
@@ -57,7 +57,7 @@
 	def got_lyrics(self, xmltext, callback, *data):
 		# retrieve xml content
 		if xmltext is None:
-			print "no response"
+			print("no response")
 			callback (None, *data)
 			return
 
@@ -69,13 +69,13 @@
 
 			lrcurl = root.getElementsByTagName('LyricUrl')[0].childNodes[0].data
 			if lrcurl is None:
-				print "no lyric urls"
+				print("no lyric urls")
 				callback (xmltext, *data)
 				return
 
 			# download the lyrics file
 			lrcurl = lrcurl.replace('%3A', ':');
-			print "url: %s" % lrcurl
+			print("url: %s" % lrcurl)
 
 			loader = rb.Loader()
 			loader.get_url (lrcurl, self.parse_lyrics, callback, *data)
@@ -86,7 +86,7 @@
 	def parse_lyrics(self, lyrics, callback, *data):
 
 		if lyrics is None:
-			print "no lyrics"
+			print("no lyrics")
 			callback (None, *data)
 			return
 
@@ -96,7 +96,7 @@
 			lrcplaintext = re.sub('\[.*?\]', '', lrcplaintext)
 			lrcplaintext = lrcplaintext.decode('gbk').encode('UTF-8')
 		except:
-			print "unable to decode lyrics"
+			print("unable to decode lyrics")
 			callback (lrcplaintext, *data)
 			return
 
diff -u -r plugins/magnatune/MagnatuneAccount.py plugins/magnatune/MagnatuneAccount.py
--- plugins/magnatune/MagnatuneAccount.py	(original)
+++ plugins/magnatune/MagnatuneAccount.py	(refactored)
@@ -50,9 +50,9 @@
 			if result == GnomeKeyring.Result.OK:
 				self.keyring_item = item
 			else:
-				print "Couldn't get keyring item: " + GnomeKeyring.result_to_message(result)
+				print("Couldn't get keyring item: " + GnomeKeyring.result_to_message(result))
 		else:
-			print "couldn't search keyring items: " + GnomeKeyring.result_to_message(result)
+			print("couldn't search keyring items: " + GnomeKeyring.result_to_message(result))
 
 	def get(self):
 		if self.keyring_item is None:
@@ -69,7 +69,7 @@
 		secret = '\n'.join((username, password))
 		if self.keyring_item is not None:
 			if secret == self.keyring_item.get_secret():
-				print "account details not changed"
+				print("account details not changed")
 				return
 
 		(result, id) = GnomeKeyring.item_create_sync(None,
@@ -84,6 +84,6 @@
 				if result == GnomeKeyring.Result.OK:
 					self.keyring_item = item
 				else:
-					print "couldn't fetch keyring itme: " + GnomeKeyring.result_to_message(result)
+					print("couldn't fetch keyring itme: " + GnomeKeyring.result_to_message(result))
 		else:
-			print "couldn't create keyring item: " + GnomeKeyring.result_to_message(result)
+			print("couldn't create keyring item: " + GnomeKeyring.result_to_message(result))
diff -u -r plugins/magnatune/magnatune.py plugins/magnatune/magnatune.py
--- plugins/magnatune/magnatune.py	(original)
+++ plugins/magnatune/magnatune.py	(refactored)
@@ -25,7 +25,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 
-import urllib
+import urllib.request, urllib.parse, urllib.error
 import zipfile
 import sys, os.path
 import xml
@@ -84,7 +84,7 @@
 		if account_type != "none":
 			uri = self.URIre.sub("http://%s:%s@%s.magnatune.com/" % (username, password, account_type), uri)
 			uri = self.nsre.sub(r"_nospeech.\1", uri)
-			print "converted track uri: %s" % uri
+			print("converted track uri: %s" % uri)
 
 		return uri
 
@@ -221,7 +221,7 @@
 
 
 		def account_type_toggled(button):
-			print "account type radiobutton toggled: " + button.get_name()
+			print("account type radiobutton toggled: " + button.get_name())
 			account_type = {"no_account_radio": 'none', "stream_account_radio": 'stream', "download_account_radio": 'download'} 
 			if button.get_active():
 				self.settings['account-type'] = account_type[button.get_name()]
@@ -232,7 +232,7 @@
 			password = builder.get_object("password_entry").get_text()
 
 			if username == "" or password == "":
-				print "missing something"
+				print("missing something")
 				return
 
 			# should actually try a request to http://username:password@account-type.magnatune.com/
diff -u -r plugins/magnatune/MagnatuneSource.py plugins/magnatune/MagnatuneSource.py
--- plugins/magnatune/MagnatuneSource.py	(original)
+++ plugins/magnatune/MagnatuneSource.py	(refactored)
@@ -28,8 +28,8 @@
 import os
 import sys
 import xml
-import urllib
-import urlparse
+import urllib.request, urllib.parse, urllib.error
+import urllib.parse
 import threading
 import zipfile
 
@@ -107,7 +107,7 @@
 				progress = -1.0
 			return (_("Loading Magnatune catalog"), None, progress)
 		elif len(self.__downloads) > 0:
-			complete, total = map(sum, zip(*self.__downloads.itervalues()))
+			complete, total = list(map(sum, list(zip(*iter(self.__downloads.values())))))
 			if total > 0:
 				progress = min(float(complete) / total, 1.0)
 			else:
@@ -125,11 +125,11 @@
 
 			if not magnatune_in_progress_dir.query_exists(None):
 				magnatune_in_progress_path = magnatune_in_progress_dir.get_path()
-				os.mkdir(magnatune_in_progress_path, 0700)
+				os.mkdir(magnatune_in_progress_path, 0o700)
 
 			if not magnatune_cache_dir.query_exists(None):
 				magnatune_cache_path = magnatune_cache_dir.get_path()
-				os.mkdir(magnatune_cache_path, 0700)
+				os.mkdir(magnatune_cache_path, 0o700)
 
 			self.__activated = True
 			self.__show_loading_screen(True)
@@ -189,7 +189,7 @@
 
 		for tr in tracks:
 			sku = self.__sku_dict[tr.get_string(RB.RhythmDBPropType.LOCATION)]
-			url = magnatune_buy_album_uri + urllib.urlencode({ 'sku': sku, 'ref': magnatune_partner_id })
+			url = magnatune_buy_album_uri + urllib.parse.urlencode({ 'sku': sku, 'ref': magnatune_partner_id })
 			if url not in urls:
 				Gtk.show_uri(screen, url, Gdk.CURRENT_TIME)
 				urls.add(url)
@@ -204,7 +204,7 @@
 			# Just use the first library location
 			library = Gio.Settings("org.gnome.rhythmbox.rhythmdb")
 			library_location = library['locations'][0]
-		except IndexError, e:
+		except IndexError as e:
 			RB.error_dialog(title = _("Couldn't download album"),
 				        message = _("You must have a library location set to download an album."))
 			return
@@ -233,14 +233,14 @@
 				local_changes = ""
 
 			remote_changes = remote_changes.strip()
-			print "local checksum %s, remote checksum %s" % (local_changes, remote_changes)
+			print("local checksum %s, remote checksum %s" % (local_changes, remote_changes))
 			if local_changes != remote_changes:
 				try:
 					f = open(magnatune_changes, 'w')
 					f.write(remote_changes + "\n")
 					f.close()
-				except Exception, e:
-					print "unable to write local change id: %s" % str(e)
+				except Exception as e:
+					print("unable to write local change id: %s" % str(e))
 
 				download_catalogue()
 			elif self.__has_loaded is False:
@@ -259,11 +259,11 @@
 
 			def download_finished(copy, success, self):
 				if not success:
-					print "catalog download failed"
-					print copy.get_error()
+					print("catalog download failed")
+					print(copy.get_error())
 					return
 
-				print "catalog download successful"
+				print("catalog download successful")
 				# done downloading, unzip to real location
 				catalog_zip = zipfile.ZipFile(magnatune_song_info_temp)
 				catalog = open(magnatune_song_info, 'w')
@@ -303,13 +303,13 @@
 					error = loader.get_error()
 					if error:
 						# report error somehow?
-						print "error loading catalogue: %s" % error
+						print("error loading catalogue: %s" % error)
 
 					try:
 						parser.close()
-					except xml.sax.SAXParseException, e:
+					except xml.sax.SAXParseException as e:
 						# there isn't much we can do here
-						print "error parsing catalogue: %s" % e
+						print("error parsing catalogue: %s" % e)
 
 					self.__show_loading_screen(False)
 					self.__updating = False
@@ -322,7 +322,7 @@
 						if not name.startswith("in_progress_"):
 							continue
 						(result, uri, etag) = magnatune_in_progress_dir.resolve_relative_path(name).load_contents(None)
-						print "restarting download from %s" % uri
+						print("restarting download from %s" % uri)
 						self.__download_album(uri, name[12:])
 				else:
 					# hack around some weird chars that show up in the catalogue for some reason
@@ -335,8 +335,8 @@
 
 					try:
 						parser.feed(data)
-					except xml.sax.SAXParseException, e:
-						print "error parsing catalogue: %s" % e
+					except xml.sax.SAXParseException as e:
+						print("error parsing catalogue: %s" % e)
 
 					load_size['size'] += len(data)
 					self.__load_progress = (load_size['size'], total)
@@ -391,7 +391,8 @@
 
 	def __auth_download(self, sku): # http://magnatune.com/info/api
 
-		def auth_data_cb(data, (username, password)):
+		def auth_data_cb(data, xxx_todo_changeme):
+			(username, password) = xxx_todo_changeme
 			dl_album_handler = DownloadAlbumHandler(self.__settings['format'])
 			auth_parser = xml.sax.make_parser()
 			auth_parser.setContentHandler(dl_album_handler)
@@ -408,28 +409,28 @@
 				auth_parser.close()
 
 				# process the URI: add authentication info, quote the filename component for some reason
-				parsed = urlparse.urlparse(dl_album_handler.url)
+				parsed = urllib.parse.urlparse(dl_album_handler.url)
 				netloc = "%s:%s@%s" % (username, password, parsed.hostname)
 
-				spath = os.path.split(urllib.url2pathname(parsed.path))
+				spath = os.path.split(urllib.request.url2pathname(parsed.path))
 				basename = spath[1]
-				path = urllib.pathname2url(os.path.join(spath[0], urllib.quote(basename)))
+				path = urllib.request.pathname2url(os.path.join(spath[0], urllib.parse.quote(basename)))
 
 				authed = (parsed[0], netloc, path) + parsed[3:]
-				audio_dl_uri = urlparse.urlunparse(authed)
-
-				print "download uri for %s is %s" % (sku, audio_dl_uri)
+				audio_dl_uri = urllib.parse.urlunparse(authed)
+
+				print("download uri for %s is %s" % (sku, audio_dl_uri))
 				self.__download_album(audio_dl_uri, sku)
 
-			except MagnatuneDownloadError, e:
+			except MagnatuneDownloadError as e:
 				RB.error_dialog(title = _("Download Error"),
 						message = _("An error occurred while trying to authorize the download.\nThe Magnatune server returned:\n%s") % str(e))
-			except Exception, e:
+			except Exception as e:
 				sys.excepthook(*sys.exc_info())
 				RB.error_dialog(title = _("Error"),
 						message = _("An error occurred while trying to download the album.\nThe error text is:\n%s") % str(e))
 
-		print "downloading album: " + sku
+		print("downloading album: " + sku)
 		account = MagnatuneAccount.instance()
 		(account_type, username, password) = account.get()
 		url_dict = {
@@ -437,7 +438,7 @@
 			'sku':	sku
 		}
 		url = magnatune_api_download_uri % (username, password)
-		url = url + urllib.urlencode(url_dict)
+		url = url + urllib.parse.urlencode(url_dict)
 
 		l = rb.Loader()
 		l.get_url(url, auth_data_cb, (username, password))
@@ -452,7 +453,7 @@
 			del self.__downloads[audio_dl_uri]
 			del self.__copies[audio_dl_uri]
 
-			print "download of %s finished: %s" % (audio_dl_uri, success)
+			print("download of %s finished: %s" % (audio_dl_uri, success))
 			if success:
 				threading.Thread(target=unzip_album).start()
 			else:
@@ -472,11 +473,11 @@
 			library = Gio.Settings("org.gnome.rhythmbox.rhythmdb")
 			library_location = Gio.file_new_for_uri(library['locations'][0])
 
-			print "unzipping %s" % dest.get_path()
+			print("unzipping %s" % dest.get_path())
 			album = zipfile.ZipFile(dest.get_path())
 			for track in album.namelist():
 				track_uri = library_location.resolve_relative_path(track).get_uri()
-				print "zip file entry: %s => %s" % (track, track_uri)
+				print("zip file entry: %s => %s" % (track, track_uri))
 
 				track_uri = RB.sanitize_uri_for_filesystem(track_uri)
 				RB.uri_create_parent_dirs(track_uri)
@@ -485,14 +486,14 @@
 				if track_out is not None:
 					track_out.write(album.read(track), None)
 					track_out.close(None)
-					print "adding %s to library" % track_uri
+					print("adding %s to library" % track_uri)
 					self.__db.add_uri(track_uri)
 
 			album.close()
 			remove_download_files()
 
 		def remove_download_files():
-			print "removing download files"
+			print("removing download files")
 			in_progress.delete(None)
 			dest.delete(None)
 
@@ -523,7 +524,7 @@
 
 
 	def cancel_downloads(self):
-		for download in self.__copies.values():
+		for download in list(self.__copies.values()):
 			download.cancel()
 
 		shell = self.props.shell
diff -u -r plugins/magnatune/TrackListHandler.py plugins/magnatune/TrackListHandler.py
--- plugins/magnatune/TrackListHandler.py	(original)
+++ plugins/magnatune/TrackListHandler.py	(refactored)
@@ -26,10 +26,11 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 
 import xml.sax, xml.sax.handler
-import datetime, re, urllib
+import datetime, re, urllib.request, urllib.parse, urllib.error
 
 import rb
 from gi.repository import RB
+import sys
 
 class TrackListHandler(xml.sax.handler.ContentHandler):
 	def __init__(self, db, entry_type, sku_dict, home_dict, art_dict):
@@ -82,21 +83,21 @@
 				self.__db.entry_set(entry, RB.RhythmDBPropType.ALBUM, str(self.__track['albumname']))
 				self.__db.entry_set(entry, RB.RhythmDBPropType.TITLE, str(self.__track['trackname']))
 				self.__db.entry_set(entry, RB.RhythmDBPropType.GENRE, str(self.__track['magnatunegenres']))
-				self.__db.entry_set(entry, RB.RhythmDBPropType.TRACK_NUMBER, long(tracknum))
-				self.__db.entry_set(entry, RB.RhythmDBPropType.DATE, long(date))
-				self.__db.entry_set(entry, RB.RhythmDBPropType.DURATION, long(duration))
+				self.__db.entry_set(entry, RB.RhythmDBPropType.TRACK_NUMBER, int(tracknum))
+				self.__db.entry_set(entry, RB.RhythmDBPropType.DATE, int(date))
+				self.__db.entry_set(entry, RB.RhythmDBPropType.DURATION, int(duration))
 
 				key = str(trackurl)
-				sku = intern(str(self.__track['albumsku']))
+				sku = sys.intern(str(self.__track['albumsku']))
 				self.__sku_dict[key] = sku
 				self.__home_dict[sku] = str(self.__track['home'])
 				self.__art_dict[sku] = str(self.__track['cover_small'])
 
 				self.__db.commit()
-			except Exception,e: # This happens on duplicate uris being added
+			except Exception as e: # This happens on duplicate uris being added
 				import sys
 				sys.excepthook(*sys.exc_info())
-				print "Couldn't add %s - %s" % (self.__track['artist'], self.__track['trackname']), e
+				print("Couldn't add %s - %s" % (self.__track['artist'], self.__track['trackname']), e)
 
 			self.__track = {}
 		elif name == "AllSongs":
diff -u -r plugins/pythonconsole/pythonconsole.py plugins/pythonconsole/pythonconsole.py
--- plugins/pythonconsole/pythonconsole.py	(original)
+++ plugins/pythonconsole/pythonconsole.py	(refactored)
@@ -415,9 +415,9 @@
 			try:
 				r = eval(command, self.namespace, self.namespace)
 				if r is not None:
-					print `r`
+					print(repr(r))
 			except SyntaxError:
-				exec command in self.namespace
+				exec(command, self.namespace)
 		except:
 			if hasattr(sys, 'last_type') and sys.last_type == SystemExit:
 				self.destroy()
@@ -448,8 +448,8 @@
 	def readlines(self):     return []
 	def write(self, s):      self.console.write(s, self.tag)
 	def writelines(self, l): self.console.write(l, self.tag)
-	def seek(self, a):       raise IOError, (29, 'Illegal seek')
-	def tell(self):          raise IOError, (29, 'Illegal seek')
+	def seek(self, a):       raise IOError(29, 'Illegal seek')
+	def tell(self):          raise IOError(29, 'Illegal seek')
 	truncate = tell
 
 # ex:noet:ts=8:
diff -u -r plugins/rb/Coroutine.py plugins/rb/Coroutine.py
--- plugins/rb/Coroutine.py	(original)
+++ plugins/rb/Coroutine.py	(refactored)
@@ -43,9 +43,9 @@
 			self._executing = True
 			try:
 				try:
-					self._continuation.next ()
+					next(self._continuation)
 					while self._data:
-						self._continuation.next ()
+						next(self._continuation)
 				except StopIteration:
 					pass
 			finally:
diff -u -r plugins/rb/Loader.py plugins/rb/Loader.py
--- plugins/rb/Loader.py	(original)
+++ plugins/rb/Loader.py	(refactored)
@@ -33,7 +33,7 @@
 		v = callback(data, *args)
 		Gdk.threads_leave()
 		return v
-	except Exception, e:
+	except Exception as e:
 		sys.excepthook(*sys.exc_info())
 		Gdk.threads_leave()
 
@@ -49,7 +49,7 @@
 				callback_with_gdk_lock(self.callback, contents, self.args)
 			else:
 				callback_with_gdk_lock(self.callback, None, self.args)
-		except Exception, e:
+		except Exception as e:
 			sys.excepthook(*sys.exc_info())
 			callback_with_gdk_lock(self.callback, None, self.args)
 
@@ -60,7 +60,7 @@
 		try:
 			file = Gio.file_new_for_uri(url)
 			file.load_contents_async(self._cancel, self._contents_cb, None)
-		except Exception, e:
+		except Exception as e:
 			sys.excepthook(*sys.exc_info())
 			callback(None, *args)
 
@@ -78,7 +78,7 @@
 			rfi = file.query_info_finish(result)
 			remote_mod = rfi.get_attribute_uint64(Gio.FILE_ATTRIBUTE_TIME_MODIFIED)
 			callback_with_gdk_lock(self.callback, remote_mod != self.local_mod, self.args)
-		except Exception, e:
+		except Exception as e:
 			sys.excepthook(*sys.exc_info())
 			callback_with_gdk_lock(self.callback, False, self.args)
 
@@ -95,7 +95,7 @@
 
 			rf = Gio.file_new_for_uri(remote)
 			rf.query_info_async(Gio.FILE_ATTRIBUTE_TIME_MODIFIED, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, self._cancel, self._file_info_cb, None)
-		except Exception, e:
+		except Exception as e:
 			sys.excepthook(*sys.exc_info())
 			self.callback(True, *self.args)
 
diff -u -r plugins/rb/rb.py plugins/rb/rb.py
--- plugins/rb/rb.py	(original)
+++ plugins/rb/rb.py	(refactored)
@@ -30,7 +30,7 @@
 import os.path
 import os
 import time
-import thread
+import thread
 
 from gi.repository import RB
 
@@ -69,7 +69,7 @@
 	info = plugin.plugin_info
 	data_dir = info.get_data_dir()
 	path = os.path.join(data_dir, filename)
-	print "looking for " + filename + " in " + data_dir
+	print("looking for " + filename + " in " + data_dir)
 	if os.path.exists(path):
 		return path
 
@@ -97,7 +97,7 @@
 
 		# add the class name to the method, if 'self' exists
 		methodname = co.co_name
-		if fr.f_locals.has_key('self'):
+		if 'self' in fr.f_locals:
 			methodname = '%s.%s' % (fr.f_locals['self'].__class__.__name__, methodname)
 
 		ln = co.co_firstlineno + fr.f_lineno
@@ -111,8 +111,8 @@
 	def readline(self):      return ''
 	def readlines(self):     return []
 	writelines = write
-	def seek(self, a):       raise IOError, (29, 'Illegal seek')
-	def tell(self):          raise IOError, (29, 'Illegal seek')
+	def seek(self, a):       raise IOError(29, 'Illegal seek')
+	def tell(self):          raise IOError(29, 'Illegal seek')
 	truncate = tell
 
 sys.stdout = _rbdebugfile(sys.stdout.fileno())
diff -u -r plugins/rb/stringmatch.py plugins/rb/stringmatch.py
--- plugins/rb/stringmatch.py	(original)
+++ plugins/rb/stringmatch.py	(refactored)
@@ -31,7 +31,7 @@
 def pairs(str):
 	pairs = []
 	for word in str.split(' '):
-		wpairs = map(lambda x: word[x:x+2], range(len(word)-1))
+		wpairs = [word[x:x+2] for x in range(len(word)-1)]
 		pairs.extend(wpairs)
 
 	return pairs
diff -u -r plugins/rb/URLCache.py plugins/rb/URLCache.py
--- plugins/rb/URLCache.py	(original)
+++ plugins/rb/URLCache.py	(refactored)
@@ -65,11 +65,11 @@
         """
         now = time.time()
         if os.path.exists(self.path) == False:
-            print "cache directory %s does not exist" % self.path
+            print("cache directory %s does not exist" % self.path)
             return
             
 
-        print "cleaning cache directory %s" % self.path
+        print("cleaning cache directory %s" % self.path)
         for f in os.listdir(self.path):
             try:
                 path = os.path.join(self.path, f)
@@ -77,20 +77,20 @@
 
                 if self.lifetime != -1:
                     if stat.st_ctime + (self.lifetime * SECS_PER_DAY) < now:
-                        print "removing stale cache file %s:%s: age %s (past lifetime limit)" % (self.name, f, int(now - stat.st_ctime))
+                        print("removing stale cache file %s:%s: age %s (past lifetime limit)" % (self.name, f, int(now - stat.st_ctime)))
                         os.unlink(path)
                         continue
 
                 if self.discard != -1:
                     # hmm, noatime mounts will break this, probably
                     if stat.st_atime + (self.discard * SECS_PER_DAY) < now:
-                        print "removing stale cache file %s:%s: age %s (past discard limit)" % (self.name, f, int(now - stat.st_atime))
+                        print("removing stale cache file %s:%s: age %s (past discard limit)" % (self.name, f, int(now - stat.st_atime)))
                         os.unlink(path)
                         continue
 
-            except Exception, e:
-                print "error while checking cache entry %s:%s: %s" % (self.name, f, str(e))
-        print "finished cleaning cache directory %s" % self.path
+            except Exception as e:
+                print("error while checking cache entry %s:%s: %s" % (self.name, f, str(e)))
+        print("finished cleaning cache directory %s" % self.path)
 
     def cachefile(self, key):
         """
@@ -129,15 +129,15 @@
                     stale = True
 
             if stale:
-                print "removing stale cache entry %s:%s" % (self.name, key)
+                print("removing stale cache entry %s:%s" % (self.name, key))
                 os.unlink(path)
                 return None
 
             return path
 
-        except Exception, e:
+        except Exception as e:
             if hasattr(e, 'errno') is False or (e.errno != errno.ENOENT):
-                print "error checking cache for %s:%s: %s" % (self.name, key, e)
+                print("error checking cache for %s:%s: %s" % (self.name, key, e))
             return None
 
 
@@ -148,7 +148,7 @@
         try:
             # construct cache filename
             if not os.path.exists(self.path):
-                os.makedirs(self.path, mode=0700)
+                os.makedirs(self.path, mode=0o700)
             path = self.cachefile(key)
 
             # consider using gio set contents async?
@@ -156,9 +156,9 @@
             f.write(data)
             f.close()
 
-            print "stored cache data %s:%s" % (self.name, key)
-        except Exception, e:
-            print "exception storing cache data %s:%s: %s" % (self.name, key, e)
+            print("stored cache data %s:%s" % (self.name, key))
+        except Exception as e:
+            print("exception storing cache data %s:%s: %s" % (self.name, key, e))
     
 
     def __fetch_cb(self, data, url, key, callback, args):
@@ -169,13 +169,13 @@
                 data = f.read()
                 f.close()
                 if callback(data, *args) is False:
-                    print "cache entry %s:%s invalidated by callback" % (self.name, key)
+                    print("cache entry %s:%s invalidated by callback" % (self.name, key))
                     os.unlink(cachefile)
             else:
                 callback(None, *args)
         else:
             if callback(data, *args) is False:
-                print "cache entry %s:%s invalidated by callback" % (self.name, key)
+                print("cache entry %s:%s invalidated by callback" % (self.name, key))
             else:
                 self.store(key, data)
 
@@ -190,7 +190,7 @@
         from the origin site will result in valid data.
         """
         # check if we've got a fresh entry in the cache
-        print "fetching cache entry %s:%s [%s]" % (self.name, key, url)
+        print("fetching cache entry %s:%s [%s]" % (self.name, key, url))
         cachefile = self.check(key, True)
         if cachefile is not None:
             # could use a loader here, maybe
@@ -200,7 +200,7 @@
             if callback(data, *args) is not False:
                 return
 
-            print "cache entry %s:%s invalidated by callback" % (self.name, key)
+            print("cache entry %s:%s invalidated by callback" % (self.name, key))
             os.unlink(cachefile)
 
         ld = rb.Loader()
diff -u -r plugins/rbzeitgeist/rbzeitgeist.py plugins/rbzeitgeist/rbzeitgeist.py
--- plugins/rbzeitgeist/rbzeitgeist.py	(original)
+++ plugins/rbzeitgeist/rbzeitgeist.py	(refactored)
@@ -37,8 +37,8 @@
 
 try:
     IFACE = ZeitgeistClient()
-except RuntimeError, e:
-    print "Unable to connect to Zeitgeist, won't send events. Reason: '%s'" %e
+except RuntimeError as e:
+    print("Unable to connect to Zeitgeist, won't send events. Reason: '%s'" %e)
     IFACE = None
 
 class ZeitgeistPlugin(GObject.Object, Peas.Activatable):
@@ -49,7 +49,7 @@
 	GObject.Object.__init__(self)
 
     def do_activate(self):
-        print "Loading Zeitgeist plugin..."
+        print("Loading Zeitgeist plugin...")
         if IFACE is not None:
 	    shell = self.object
             shell_player = shell.props.shell_player
@@ -131,16 +131,16 @@
 
             subject = Subject.new_for_values(
                 uri=song["location"],
-                interpretation=unicode(Interpretation.AUDIO),
-                manifestation=unicode(Manifestation.FILE_DATA_OBJECT),
+                interpretation=str(Interpretation.AUDIO),
+                manifestation=str(Manifestation.FILE_DATA_OBJECT),
                 origin=song["location"].rpartition("/")[0],
                 mimetype=uri_mimetype,
                 text=" - ".join([song["title"], song["artist"], song["album"]])
             )
             event = Event.new_for_values(
                 timestamp=int(time.time()*1000),
-                interpretation=unicode(event_type),
-                manifestation=unicode(manifest),
+                interpretation=str(event_type),
+                manifestation=str(manifest),
                 actor="application://rhythmbox.desktop",
                 subjects=[subject,]
             )
@@ -150,7 +150,7 @@
         f.query_info_async(Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, None, file_info_complete, None)
 
     def do_deactivate(self):
-        print "Deactivating Zeitgeist plugin..."
+        print("Deactivating Zeitgeist plugin...")
         if IFACE is not None:
 	    shell = self.object
             shell_player = shell.props.shell_player
diff -u -r plugins/replaygain/config.py plugins/replaygain/config.py
--- plugins/replaygain/config.py	(original)
+++ plugins/replaygain/config.py	(refactored)
@@ -79,7 +79,7 @@
 
 	def sync_preamp(self, settings, preamp):
 		v = preamp.get_value()
-		print "preamp gain changed to %f" % v
+		print("preamp gain changed to %f" % v)
 		settings['preamp'] = v
 
 GObject.type_register(ReplayGainConfig)
diff -u -r plugins/replaygain/player.py plugins/replaygain/player.py
--- plugins/replaygain/player.py	(original)
+++ plugins/replaygain/player.py	(refactored)
@@ -94,18 +94,18 @@
 		# set calculated fallback gain
 		rgvolume.props.fallback_gain = self.fallback_gain
 
-		print "updated rgvolume settings: preamp %f, album-mode %s, fallback gain %f" % (
-			rgvolume.props.pre_amp, str(rgvolume.props.album_mode), rgvolume.props.fallback_gain)
+		print("updated rgvolume settings: preamp %f, album-mode %s, fallback gain %f" % (
+			rgvolume.props.pre_amp, str(rgvolume.props.album_mode), rgvolume.props.fallback_gain))
 
 
 	def update_fallback_gain(self, rgvolume):
 		gain = rgvolume.props.target_gain - rgvolume.props.pre_amp
 		# filter out bogus notifications
 		if abs(gain - self.fallback_gain) < EPSILON:
-			print "ignoring gain %f (current fallback gain)" % gain
+			print("ignoring gain %f (current fallback gain)" % gain)
 			return False
 		if abs(gain) < EPSILON:
-			print "ignoring zero gain (pretty unlikely)"
+			print("ignoring zero gain (pretty unlikely)")
 			return False
 
 		# update the running average
@@ -113,7 +113,7 @@
 			self.previous_gain.pop(0)
 		self.previous_gain.append(gain)
 		self.fallback_gain = sum(self.previous_gain) / len(self.previous_gain)
-		print "got target gain %f; running average of previous gain values is %f" % (gain, self.fallback_gain)
+		print("got target gain %f; running average of previous gain values is %f" % (gain, self.fallback_gain))
 		return True
 
 
@@ -137,11 +137,11 @@
 		# do something clever probably
 
 	def rgvolume_reset_done(self, pad, blocked, rgvolume):
-		print "rgvolume reset done"
+		print("rgvolume reset done")
 		self.set_rgvolume(rgvolume)
 
 	def rgvolume_blocked_cb(self, pad, blocked, rgvolume):
-		print "bouncing rgvolume state to reset tags"
+		print("bouncing rgvolume state to reset tags")
 		# somehow need to decide whether we've already got a gain value for the new track
 		#self.resetting_rgvolume = True
 		rgvolume.set_state(Gst.State.READY)
@@ -154,14 +154,14 @@
 			return
 
 		if self.got_replaygain is False:
-			print "blocking rgvolume to reset it"
+			print("blocking rgvolume to reset it")
 			pad = self.rgvolume.get_static_pad("sink").get_peer()
 			pad.set_blocked_async(True, self.rgvolume_blocked_cb, self.rgvolume)
 		else:
-			print "no need to reset rgvolume"
+			print("no need to reset rgvolume")
 
 	def setup_playbin2_mode(self):
-		print "using output filter for rgvolume and rglimiter"
+		print("using output filter for rgvolume and rglimiter")
 		self.rgvolume = Gst.ElementFactory.make("rgvolume", None)
 		self.rgvolume.connect("notify::target-gain", self.playbin2_target_gain_cb)
 		self.rglimiter = Gst.ElementFactory.make("rglimiter", None)
@@ -203,7 +203,7 @@
 			rgvolume.disconnect_by_func(self.xfade_target_gain_cb)
 
 	def create_stream_filter_cb(self, player, uri):
-		print "creating rgvolume instance for stream %s" % uri
+		print("creating rgvolume instance for stream %s" % uri)
 		rgvolume = Gst.ElementFactory.make("rgvolume", None)
 		rgvolume.connect("notify::target-gain", self.xfade_target_gain_cb)
 		self.set_rgvolume(rgvolume)
@@ -212,11 +212,11 @@
 	def limiter_changed_cb(self, settings, key):
 		if self.rglimiter is not None:
 			limiter = settings['limiter']
-			print "limiter setting is now %s" % str(limiter)
+			print("limiter setting is now %s" % str(limiter))
 			self.rglimiter.props.enabled = limiter
 
 	def setup_xfade_mode(self):
-		print "using per-stream filter for rgvolume"
+		print("using per-stream filter for rgvolume")
 		self.stream_filter_id = self.player.connect("get-stream-filters", self.create_stream_filter_cb)
 
 		# and add rglimiter as an output filter
diff -u -r remote/dbus/rb-print-playing.py remote/dbus/rb-print-playing.py
--- remote/dbus/rb-print-playing.py	(original)
+++ remote/dbus/rb-print-playing.py	(refactored)
@@ -12,12 +12,12 @@
 rbplayer = dbus.Interface(rbplayerobj, 'org.gnome.Rhythmbox.Player')
 
 def playing_uri_changed(uri):
-    print "Now playing: %s" % (uri,)
+    print("Now playing: %s" % (uri,))
     props = rbshell.getSongProperties(uri)
     interesting = ['title', 'artist', 'album', 'location']
     for prop in props:
         if prop in interesting:
-            print "%s: %s" % (prop, props[prop])
+            print("%s: %s" % (prop, props[prop]))
 
 rbplayer.connect_to_signal('playingUriChanged', playing_uri_changed)
