From 90b05d22c5d313c5f45e8492b25c4d0b8556db69 Mon Sep 17 00:00:00 2001
From: Athanasius <github@miggy.org>
Date: Tue, 10 Sep 2019 18:16:38 +0100
Subject: [PATCH] Now runs without console errors so far as pressing 'Update'
 is concerned.

  There's an error on the console about an iterator when doing so
though.
---
 companion.py        |   2 +-
 plug.py             |  40 +++++++--------
 plugins/coriolis.py | 117 ++++++++++++++++++++++++++++++++++++--------
 plugins/eddb.py     |   8 +--
 plugins/eddn.py     |  51 ++++++++++---------
 plugins/edsm.py     |  22 +++++----
 plugins/edsy.py     |   6 ++-
 plugins/inara.py    |  31 +++++++-----
 update.py           |  12 +++--
 9 files changed, 192 insertions(+), 97 deletions(-)

diff --git a/companion.py b/companion.py
index 8e9d484a..b5f3f67a 100644
--- a/companion.py
+++ b/companion.py
@@ -25,7 +25,7 @@ from config import appname, appversion, config
 from protocol import protocolhandler
 
 
-holdoff = 60	# be nice
+holdoff = 10	# be nice
 timeout = 10	# requests timeout
 auth_timeout = 30	# timeout for initial auth
 
diff --git a/plug.py b/plug.py
index 72514342..b4fd585b 100644
--- a/plug.py
+++ b/plug.py
@@ -1,8 +1,10 @@
 """
 Plugin hooks for EDMC - Ian Norton, Jonathan Harris
 """
+from builtins import str
+from builtins import object
 import os
-import imp
+import importlib
 import sys
 import operator
 import threading	# We don't use it, but plugins might
@@ -88,16 +90,14 @@ class Plugin(object):
         self.module = None	# None for disabled plugins.
 
         if loadfile:
-            sys.stdout.write(('loading plugin %s from "%s"\n' % (name.replace('.', '_'), loadfile)).encode('utf-8'))
-            with open(loadfile, 'rb') as plugfile:
-                module = imp.load_module('plugin_%s' % name.encode('ascii', 'replace').replace('.', '_'), plugfile, loadfile.encode(sys.getfilesystemencoding()),
-                                         ('.py', 'r', imp.PY_SOURCE))
-                if module.plugin_start.func_code.co_argcount == 0:
-                    newname = module.plugin_start()
-                else:
-                    newname = module.plugin_start(os.path.dirname(loadfile))
-                self.name = newname and unicode(newname) or name
-                self.module = module
+            sys.stdout.write('loading plugin {} from "{}"\n'.format(name.replace('.', '_'), loadfile))
+            module = importlib.machinery.SourceFileLoader('plugin_{}'.format(name.encode(encoding='ascii', errors='replace').decode('utf-8').replace('.', '_')), loadfile).load_module()
+            if module.plugin_start.__code__.co_argcount == 0:
+                newname = module.plugin_start()
+            else:
+                newname = module.plugin_start(os.path.dirname(loadfile))
+            self.name = newname and str(newname) or name
+            self.module = module
         else:
             sys.stdout.write('plugin %s disabled\n' % name)
 
@@ -143,7 +143,7 @@ class Plugin(object):
         plugin_prefs = self._get_func('plugin_prefs')
         if plugin_prefs:
             try:
-                if plugin_prefs.func_code.co_argcount == 1:
+                if plugin_prefs.__code__.co_argcount == 1:
                     frame = plugin_prefs(parent)
                 else:
                     frame = plugin_prefs(parent, cmdr, is_beta)
@@ -161,8 +161,6 @@ def load_plugins(master):
     """
     last_error['root'] = master
 
-    imp.acquire_lock()
-
     internal = []
     for name in os.listdir(config.internal_plugin_dir):
         if name.endswith('.py') and not name[0] in ['.', '_']:
@@ -195,8 +193,6 @@ def load_plugins(master):
                 print_exc()
     PLUGINS.extend(sorted(found, key = lambda p: operator.attrgetter('name')(p).lower()))
 
-    imp.release_lock()
-
 def provides(fn_name):
     """
     Find plugins that provide a function
@@ -272,7 +268,7 @@ def notify_prefs_changed(cmdr, is_beta):
         prefs_changed = plugin._get_func('prefs_changed')
         if prefs_changed:
             try:
-                if prefs_changed.func_code.co_argcount == 0:
+                if prefs_changed.__code__.co_argcount == 0:
                     prefs_changed()
                 else:
                     prefs_changed(cmdr, is_beta)
@@ -297,9 +293,9 @@ def notify_journal_entry(cmdr, is_beta, system, station, entry, state):
         if journal_entry:
             try:
                 # Pass a copy of the journal entry in case the callee modifies it
-                if journal_entry.func_code.co_argcount == 4:
+                if journal_entry.__code__.co_argcount == 4:
                     newerror = journal_entry(cmdr, system, station, dict(entry))
-                elif journal_entry.func_code.co_argcount == 5:
+                elif journal_entry.__code__.co_argcount == 5:
                     newerror = journal_entry(cmdr, system, station, dict(entry), dict(state))
                 else:
                     newerror = journal_entry(cmdr, is_beta, system, station, dict(entry), dict(state))
@@ -342,7 +338,7 @@ def notify_system_changed(timestamp, system, coordinates):
         system_changed = plugin._get_func('system_changed')
         if system_changed:
             try:
-                if system_changed.func_code.co_argcount == 2:
+                if system_changed.__code__.co_argcount == 2:
                     system_changed(timestamp, system)
                 else:
                     system_changed(timestamp, system, coordinates)
@@ -362,7 +358,7 @@ def notify_newdata(data, is_beta):
         cmdr_data = plugin._get_func('cmdr_data')
         if cmdr_data:
             try:
-                if cmdr_data.func_code.co_argcount == 1:
+                if cmdr_data.__code__.co_argcount == 1:
                     newerror = cmdr_data(data)
                 else:
                     newerror = cmdr_data(data, is_beta)
@@ -379,5 +375,5 @@ def show_error(err):
     .. versionadded:: 2.3.7
     """
     if err and last_error['root']:
-        last_error['msg'] = unicode(err)
+        last_error['msg'] = str(err)
         last_error['root'].event_generate('<<PluginError>>', when="tail")
diff --git a/plugins/coriolis.py b/plugins/coriolis.py
index 3ae23c4d..522c261d 100644
--- a/plugins/coriolis.py
+++ b/plugins/coriolis.py
@@ -1,30 +1,105 @@
-# Coriolis ship export
+#!/usr/bin/python
+#
+# build ship and module databases from https://github.com/EDCD/coriolis-data/
+#
 
+from __future__ import print_function
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+from builtins import range
+import csv
 import base64
-import gzip
+from collections import OrderedDict
+import pickle
 import json
-import StringIO
+from traceback import print_exc
 
-import companion
-import plug
-
-# Migrate settings from <= 3.01
 from config import config
-if not config.get('shipyard_provider') and config.getint('shipyard'):
-    config.set('shipyard_provider', 'Coriolis')
-config.delete('shipyard')
+import outfitting
+import companion
 
 
-def plugin_start():
-    return 'Coriolis'
+if __name__ == "__main__":
 
-# Return a URL for the current ship
-def shipyard_url(loadout, is_beta):
-    string = json.dumps(loadout, ensure_ascii=False, sort_keys=True, separators=(',', ':')).encode('utf-8')	# most compact representation
-    if not string:
-        return False
+    def add(modules, name, attributes):
+        assert name not in modules or modules[name] == attributes, '%s: %s!=%s' % (name, modules.get(name), attributes)
+        assert name not in modules, name
+        modules[name] = attributes
 
-    out = StringIO.StringIO()
-    with gzip.GzipFile(fileobj=out, mode='w') as f:
-        f.write(string)
-    return (is_beta and 'https://beta.coriolis.io/import?data=' or 'https://coriolis.io/import?data=') + base64.urlsafe_b64encode(out.getvalue()).replace('=', '%3D')
+
+    data = json.load(open('coriolis-data/dist/index.json'))
+
+    # Map Coriolis's names to names displayed in the in-game shipyard
+    coriolis_ship_map = {
+        'Cobra Mk III' : 'Cobra MkIII',
+        'Cobra Mk IV'  : 'Cobra MkIV',
+        'Krait Mk II'  : 'Krait MkII',
+        'Viper'        : 'Viper MkIII',
+        'Viper Mk IV'  : 'Viper MkIV',
+    }
+
+    # Symbolic name from in-game name
+    reverse_ship_map = {v: k for k, v in companion.ship_map.items()}
+
+    bulkheads = list(outfitting.armour_map.keys())
+
+    ships = {}
+    modules = {}
+
+    # Ship and armour masses
+    for m in list(data['Ships'].values()):
+        name = coriolis_ship_map.get(m['properties']['name'], str(m['properties']['name']))
+        assert name in reverse_ship_map, name
+        ships[name] = { 'hullMass' : m['properties']['hullMass'] }
+        for i in range(len(bulkheads)):
+            modules['_'.join([reverse_ship_map[name], 'armour', bulkheads[i]])] = { 'mass': m['bulkheads'][i]['mass'] }
+
+    ships = OrderedDict([(k,ships[k]) for k in sorted(ships)])	# sort for easier diffing
+    pickle.dump(ships, open('ships.p', 'wb'))
+
+    # Module masses
+    for cat in list(data['Modules'].values()):
+        for grp, mlist in cat.items():
+            for m in mlist:
+                assert 'symbol' in m, m
+                key = str(m['symbol'].lower())
+                if grp == 'fsd':
+                    modules[key] = {
+                        'mass'      : m['mass'],
+                        'optmass'   : m['optmass'],
+                        'maxfuel'   : m['maxfuel'],
+                        'fuelmul'   : m['fuelmul'],
+                        'fuelpower' : m['fuelpower'],
+                    }
+                elif grp == 'gfsb':
+                    modules[key] = {
+                        'mass'      : m['mass'],
+                        'jumpboost' : m['jumpboost'],
+                    }
+                else:
+                    modules[key] = { 'mass': m.get('mass', 0) }	# Some modules don't have mass
+
+    # Pre 3.3 modules
+    add(modules, 'int_stellarbodydiscoveryscanner_standard',     { 'mass': 2 })
+    add(modules, 'int_stellarbodydiscoveryscanner_intermediate', { 'mass': 2 })
+    add(modules, 'int_stellarbodydiscoveryscanner_advanced',     { 'mass': 2 })
+
+    # Missing
+    add(modules, 'hpt_mining_subsurfdispmisle_fixed_small',      { 'mass': 2 })
+    add(modules, 'hpt_mining_subsurfdispmisle_fixed_medium',     { 'mass': 4 })
+    add(modules, 'hpt_multicannon_fixed_small_advanced',         { 'mass': 2 })
+    add(modules, 'hpt_multicannon_fixed_medium_advanced',        { 'mass': 4 })
+
+    modules = OrderedDict([(k,modules[k]) for k in sorted(modules)])	# sort for easier diffing
+    pickle.dump(modules, open('modules.p', 'wb'))
+
+    # Check data is present for all modules
+    with open('outfitting.csv') as csvfile:
+        reader = csv.DictReader(csvfile, restval='')
+        for row in reader:
+            try:
+                module = outfitting.lookup({ 'id': row['id'], 'name': row['symbol'] }, companion.ship_map)
+            except:
+                print(row['symbol'])
+                print_exc()
diff --git a/plugins/eddb.py b/plugins/eddb.py
index 3fed0563..9ac96249 100644
--- a/plugins/eddb.py
+++ b/plugins/eddb.py
@@ -3,7 +3,9 @@
 # Station display and eddb.io lookup
 #
 
-import cPickle
+from future import standard_library
+standard_library.install_aliases()
+import pickle
 import csv
 import os
 from os.path import join
@@ -18,11 +20,11 @@ this = sys.modules[__name__]	# For holding module globals
 
 # (system_id, is_populated) by system_name
 with open(join(config.respath, 'systems.p'),  'rb') as h:
-    this.system_ids  = cPickle.load(h)
+    this.system_ids  = pickle.load(h)
 
 # station_id by (system_id, station_name)
 with open(join(config.respath, 'stations.p'), 'rb') as h:
-    this.station_ids = cPickle.load(h)
+    this.station_ids = pickle.load(h)
 
 
 # Main window clicks
diff --git a/plugins/eddn.py b/plugins/eddn.py
index 17170ae1..f52049c9 100644
--- a/plugins/eddn.py
+++ b/plugins/eddn.py
@@ -1,5 +1,10 @@
+from __future__ import print_function
 # Export to EDDN
 
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+from builtins import object
 from collections import OrderedDict
 import json
 from os import SEEK_SET, SEEK_CUR, SEEK_END
@@ -10,7 +15,7 @@ import requests
 import sys
 import uuid
 
-import Tkinter as tk
+import tkinter as tk
 from ttkHyperlinkLabel import HyperlinkLabel
 import myNotebook as nb
 
@@ -36,7 +41,7 @@ this.marketId = None
 this.commodities = this.outfitting = this.shipyard = None
 
 
-class EDDN:
+class EDDN(object):
 
     ### SERVER = 'http://localhost:8081'	# testing
     SERVER = 'https://eddn.edcd.io:4430'
@@ -109,10 +114,10 @@ class EDDN:
 
         r = self.session.post(self.UPLOAD, data=json.dumps(msg), timeout=self.TIMEOUT)
         if __debug__ and r.status_code != requests.codes.ok:
-            print 'Status\t%s'  % r.status_code
-            print 'URL\t%s'  % r.url
-            print 'Headers\t%s' % r.headers
-            print ('Content:\n%s' % r.text).encode('utf-8')
+            print('Status\t%s'  % r.status_code)
+            print('URL\t%s'  % r.url)
+            print('Headers\t%s' % r.headers)
+            print(('Content:\n%s' % r.text).encode('utf-8'))
         r.raise_for_status()
 
     def sendreplay(self):
@@ -135,7 +140,7 @@ class EDDN:
         except:
             # Couldn't decode - shouldn't happen!
             if __debug__:
-                print self.replaylog[0]
+                print(self.replaylog[0])
                 print_exc()
             self.replaylog.pop(0)	# Discard and continue
         else:
@@ -153,7 +158,7 @@ class EDDN:
                 return	# stop sending
             except Exception as e:
                 if __debug__: print_exc()
-                status['text'] = unicode(e)
+                status['text'] = str(e)
                 return	# stop sending
 
         self.parent.after(self.REPLAYPERIOD, self.sendreplay)
@@ -186,9 +191,9 @@ class EDDN:
                 ('commodities', commodities),
             ])
             if 'economies' in data['lastStarport']:
-                message['economies']  = sorted([x for x in (data['lastStarport']['economies']  or {}).itervalues()])
+                message['economies']  = sorted([x for x in (data['lastStarport']['economies']  or {}).values()])
             if 'prohibited' in data['lastStarport']:
-                message['prohibited'] = sorted([x for x in (data['lastStarport']['prohibited'] or {}).itervalues()])
+                message['prohibited'] = sorted([x for x in (data['lastStarport']['prohibited'] or {}).values()])
             self.send(data['commander']['name'], {
                 '$schemaRef' : 'https://eddn.edcd.io/schemas/commodity/3' + (is_beta and '/test' or ''),
                 'message'    : message,
@@ -200,10 +205,10 @@ class EDDN:
         modules = data['lastStarport'].get('modules') or {}
         ships = data['lastStarport'].get('ships') or { 'shipyard_list': {}, 'unavailable_list': [] }
         # Horizons flag - will hit at least Int_PlanetApproachSuite other than at engineer bases ("Colony"), prison or rescue Megaships, or under Pirate Attack etc
-        horizons = (any(economy['name'] == 'Colony' for economy in economies.itervalues()) or
-                    any(module.get('sku') == 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' for module in modules.itervalues()) or
-                    any(ship.get('sku') == 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' for ship in (ships['shipyard_list'] or {}).values()))
-        outfitting = sorted([self.MODULE_RE.sub(lambda m: m.group(0).capitalize(), module['name'].lower()) for module in modules.itervalues() if self.MODULE_RE.search(module['name']) and module.get('sku') in [None, 'ELITE_HORIZONS_V_PLANETARY_LANDINGS'] and module['name'] != 'Int_PlanetApproachSuite'])
+        horizons = (any(economy['name'] == 'Colony' for economy in economies.values()) or
+                    any(module.get('sku') == 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' for module in modules.values()) or
+                    any(ship.get('sku') == 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' for ship in list((ships['shipyard_list'] or {}).values())))
+        outfitting = sorted([self.MODULE_RE.sub(lambda m: m.group(0).capitalize(), module['name'].lower()) for module in modules.values() if self.MODULE_RE.search(module['name']) and module.get('sku') in [None, 'ELITE_HORIZONS_V_PLANETARY_LANDINGS'] and module['name'] != 'Int_PlanetApproachSuite'])
         if outfitting and this.outfitting != (horizons, outfitting):	# Don't send empty modules list - schema won't allow it
             self.send(data['commander']['name'], {
                 '$schemaRef' : 'https://eddn.edcd.io/schemas/outfitting/2' + (is_beta and '/test' or ''),
@@ -222,10 +227,10 @@ class EDDN:
         economies = data['lastStarport'].get('economies') or {}
         modules = data['lastStarport'].get('modules') or {}
         ships = data['lastStarport'].get('ships') or { 'shipyard_list': {}, 'unavailable_list': [] }
-        horizons = (any(economy['name'] == 'Colony' for economy in economies.itervalues()) or
-                    any(module.get('sku') == 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' for module in modules.itervalues()) or
-                    any(ship.get('sku') == 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' for ship in (ships['shipyard_list'] or {}).values()))
-        shipyard = sorted([ship['name'].lower() for ship in (ships['shipyard_list'] or {}).values() + ships['unavailable_list']])
+        horizons = (any(economy['name'] == 'Colony' for economy in economies.values()) or
+                    any(module.get('sku') == 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' for module in modules.values()) or
+                    any(ship.get('sku') == 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' for ship in list((ships['shipyard_list'] or {}).values())))
+        shipyard = sorted([ship['name'].lower() for ship in list((ships['shipyard_list'] or {}).values()) + ships['unavailable_list']])
         if shipyard and this.shipyard != (horizons, shipyard):	# Don't send empty ships list - shipyard data is only guaranteed present if user has visited the shipyard.
             self.send(data['commander']['name'], {
                 '$schemaRef' : 'https://eddn.edcd.io/schemas/shipyard/2' + (is_beta and '/test' or ''),
@@ -384,7 +389,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
     # Recursively filter '*_Localised' keys from dict
     def filter_localised(d):
         filtered = OrderedDict()
-        for k, v in d.iteritems():
+        for k, v in d.items():
             if k.endswith('_Localised'):
                 pass
             elif hasattr(v, 'iteritems'):	# dict -> recurse
@@ -420,7 +425,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
             entry.pop(thing, None)
         if 'Factions' in entry:
             # Filter faction state. `entry` is a shallow copy so replace 'Factions' value rather than modify in-place.
-            entry['Factions'] = [ {k: v for k, v in f.iteritems() if k not in ['HappiestSystem', 'HomeSystem', 'MyReputation', 'SquadronFaction']} for f in entry['Factions']]
+            entry['Factions'] = [ {k: v for k, v in f.items() if k not in ['HappiestSystem', 'HomeSystem', 'MyReputation', 'SquadronFaction']} for f in entry['Factions']]
 
         # add planet to Docked event for planetary stations if known
         if entry['event'] == 'Docked' and this.planet:
@@ -442,7 +447,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
             return _("Error: Can't connect to EDDN")
         except Exception as e:
             if __debug__: print_exc()
-            return unicode(e)
+            return str(e)
 
     elif (config.getint('output') & config.OUT_MKT_EDDN and not state['Captain'] and
           entry['event'] in ['Market', 'Outfitting', 'Shipyard']):
@@ -465,7 +470,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
             return _("Error: Can't connect to EDDN")
         except Exception as e:
             if __debug__: print_exc()
-            return unicode(e)
+            return str(e)
 
 def cmdr_data(data, is_beta):
     if data['commander'].get('docked') and config.getint('output') & config.OUT_MKT_EDDN:
@@ -492,4 +497,4 @@ def cmdr_data(data, is_beta):
 
         except Exception as e:
             if __debug__: print_exc()
-            return unicode(e)
+            return str(e)
diff --git a/plugins/edsm.py b/plugins/edsm.py
index f0ac9780..ecc24311 100644
--- a/plugins/edsm.py
+++ b/plugins/edsm.py
@@ -1,16 +1,20 @@
+from __future__ import print_function
 #
 # System display and EDSM lookup
 #
 
+from future import standard_library
+standard_library.install_aliases()
+from builtins import zip
 import json
 import requests
 import sys
 import time
-import urllib2
-from Queue import Queue
+import urllib.request, urllib.error, urllib.parse
+from queue import Queue
 from threading import Thread
 
-import Tkinter as tk
+import tkinter as tk
 from ttkHyperlinkLabel import HyperlinkLabel
 import myNotebook as nb
 
@@ -41,13 +45,13 @@ this.navbeaconscan = 0		# batch up burst of Scan events after NavBeaconScan
 
 # Main window clicks
 def system_url(system_name):
-    return 'https://www.edsm.net/en/system?systemName=%s' % urllib2.quote(system_name)
+    return 'https://www.edsm.net/en/system?systemName=%s' % urllib.parse.quote(system_name)
 
 def station_url(system_name, station_name):
     if station_name:
-        return 'https://www.edsm.net/en/system?systemName=%s&stationName=%s' % (urllib2.quote(system_name), urllib2.quote(station_name))
+        return 'https://www.edsm.net/en/system?systemName=%s&stationName=%s' % (urllib.parse.quote(system_name), urllib.parse.quote(station_name))
     else:
-        return 'https://www.edsm.net/en/system?systemName=%s&stationName=ALL' % urllib2.quote(system_name)
+        return 'https://www.edsm.net/en/system?systemName=%s&stationName=ALL' % urllib.parse.quote(system_name)
 
 
 def plugin_start():
@@ -230,9 +234,9 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
             materials = {
                 'timestamp': entry['timestamp'],
                 'event': 'Materials',
-                'Raw':          [ { 'Name': k, 'Count': v } for k,v in state['Raw'].iteritems() ],
-                'Manufactured': [ { 'Name': k, 'Count': v } for k,v in state['Manufactured'].iteritems() ],
-                'Encoded':      [ { 'Name': k, 'Count': v } for k,v in state['Encoded'].iteritems() ],
+                'Raw':          [ { 'Name': k, 'Count': v } for k,v in state['Raw'].items() ],
+                'Manufactured': [ { 'Name': k, 'Count': v } for k,v in state['Manufactured'].items() ],
+                'Encoded':      [ { 'Name': k, 'Count': v } for k,v in state['Encoded'].items() ],
             }
             materials.update(transient)
             this.queue.put((cmdr, materials))
diff --git a/plugins/edsy.py b/plugins/edsy.py
index e3b20f8b..3fda1635 100644
--- a/plugins/edsy.py
+++ b/plugins/edsy.py
@@ -1,9 +1,11 @@
 # EDShipyard ship export
 
+from future import standard_library
+standard_library.install_aliases()
 import base64
 import gzip
 import json
-import StringIO
+import io
 
 
 def plugin_start():
@@ -15,7 +17,7 @@ def shipyard_url(loadout, is_beta):
     if not string:
         return False
 
-    out = StringIO.StringIO()
+    out = io.StringIO()
     with gzip.GzipFile(fileobj=out, mode='w') as f:
         f.write(string)
 
diff --git a/plugins/inara.py b/plugins/inara.py
index 1be99c52..ab5240af 100644
--- a/plugins/inara.py
+++ b/plugins/inara.py
@@ -1,17 +1,24 @@
+from __future__ import division
+from __future__ import print_function
 #
 # Inara sync
 #
 
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+from builtins import zip
+from past.utils import old_div
 from collections import OrderedDict
 import json
 import requests
 import sys
 import time
 from operator import itemgetter
-from Queue import Queue
+from queue import Queue
 from threading import Thread
 
-import Tkinter as tk
+import tkinter as tk
 from ttkHyperlinkLabel import HyperlinkLabel
 import myNotebook as nb
 
@@ -227,14 +234,14 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
                                   ('rankName', k.lower()),
                                   ('rankValue', v[0]),
                                   ('rankProgress', v[1] / 100.0),
-                              ]) for k,v in state['Rank'].iteritems() if v is not None
+                              ]) for k,v in state['Rank'].items() if v is not None
                           ])
                 add_event('setCommanderReputationMajorFaction', entry['timestamp'],
                           [
                               OrderedDict([
                                   ('majorfactionName', k.lower()),
                                   ('majorfactionReputation', v / 100.0),
-                              ]) for k,v in state['Reputation'].iteritems() if v is not None
+                              ]) for k,v in state['Reputation'].items() if v is not None
                           ])
                 if state['Engineers']:	# Not populated < 3.3
                     add_event('setCommanderRankEngineer', entry['timestamp'],
@@ -242,7 +249,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
                                   OrderedDict([
                                       ('engineerName', k),
                                       type(v) is tuple and ('rankValue', v[0]) or ('rankStage', v),
-                                  ]) for k,v in state['Engineers'].iteritems()
+                                  ]) for k,v in state['Engineers'].items()
                               ])
 
                 # Update location
@@ -274,7 +281,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
 
             # Promotions
             elif entry['event'] == 'Promotion':
-                for k,v in state['Rank'].iteritems():
+                for k,v in state['Rank'].items():
                     if k in entry:
                         add_event('setCommanderRankPilot', entry['timestamp'],
                                   OrderedDict([
@@ -405,7 +412,7 @@ def journal_entry(cmdr, is_beta, system, station, entry, state):
 
         except Exception as e:
             if __debug__: print_exc()
-            return unicode(e)
+            return str(e)
 
         #
         # Events that don't need to be sent immediately but will be sent on the next mandatory event
@@ -712,7 +719,7 @@ def cmdr_data(data, is_beta):
         this.station_link['url'] = this.station or this.system
 
     if config.getint('inara_out') and not is_beta and not this.multicrew and credentials(this.cmdr):
-        if not (CREDIT_RATIO > this.lastcredits / data['commander']['credits'] > 1/CREDIT_RATIO):
+        if not (CREDIT_RATIO > old_div(this.lastcredits, data['commander']['credits']) > old_div(1,CREDIT_RATIO)):
             this.events = [x for x in this.events if x['eventName'] != 'setCommanderCredits']	# Remove any unsent
             add_event('setCommanderCredits', data['timestamp'],
                       OrderedDict([
@@ -724,7 +731,7 @@ def cmdr_data(data, is_beta):
 
 def make_loadout(state):
     modules = []
-    for m in state['Modules'].itervalues():
+    for m in state['Modules'].values():
         module = OrderedDict([
             ('slotName', m['Slot']),
             ('itemName', m['Item']),
@@ -816,14 +823,14 @@ def worker():
                     callback(reply)
                 elif status // 100 != 2:	# 2xx == OK (maybe with warnings)
                     # Log fatal errors
-                    print 'Inara\t%s %s' % (reply['header']['eventStatus'], reply['header'].get('eventStatusText', ''))
-                    print json.dumps(data, indent=2, separators = (',', ': '))
+                    print('Inara\t%s %s' % (reply['header']['eventStatus'], reply['header'].get('eventStatusText', '')))
+                    print(json.dumps(data, indent=2, separators = (',', ': ')))
                     plug.show_error(_('Error: Inara {MSG}').format(MSG = reply['header'].get('eventStatusText', status)))
                 else:
                     # Log individual errors and warnings
                     for data_event, reply_event in zip(data['events'], reply['events']):
                         if reply_event['eventStatus'] != 200:
-                            print 'Inara\t%s %s\t%s' % (reply_event['eventStatus'], reply_event.get('eventStatusText', ''), json.dumps(data_event))
+                            print('Inara\t%s %s\t%s' % (reply_event['eventStatus'], reply_event.get('eventStatusText', ''), json.dumps(data_event)))
                             if reply_event['eventStatus'] // 100 != 2:
                                 plug.show_error(_('Error: Inara {MSG}').format(MSG = '%s, %s' % (data_event['eventName'], reply_event.get('eventStatusText', reply_event['eventStatus']))))
                         if data_event['eventName'] in ['addCommanderTravelDock', 'addCommanderTravelFSDJump', 'setCommanderTravelLocation']:
diff --git a/update.py b/update.py
index 1cbf187a..f4ef6f02 100644
--- a/update.py
+++ b/update.py
@@ -1,3 +1,7 @@
+from future import standard_library
+standard_library.install_aliases()
+from builtins import map
+from builtins import object
 import os
 from os.path import dirname, join
 import sys
@@ -12,9 +16,9 @@ if not getattr(sys, 'frozen', False):
 
     # quick and dirty version comparison assuming "strict" numeric only version numbers
     def versioncmp(versionstring):
-        return map(int, versionstring.split('.'))
+        return list(map(int, versionstring.split('.')))
 
-    class Updater():
+    class Updater(object):
 
         def __init__(self, master):
             self.root = master
@@ -44,7 +48,7 @@ elif sys.platform=='darwin':
 
     import objc
 
-    class Updater():
+    class Updater(object):
 
         # http://sparkle-project.org/documentation/customization/
 
@@ -74,7 +78,7 @@ elif sys.platform=='win32':
     def shutdown_request():
         root.event_generate('<<Quit>>', when="tail")
 
-    class Updater():
+    class Updater(object):
 
         # https://github.com/vslavik/winsparkle/wiki/Basic-Setup