Make mtedit Python 3 compatible

Due to the way relative imports work in Python 3, mtedit/main.py has to
be moved outside of the mtedit package, as when run using its hashbang
line it is package `__main__`, and therefore cannot use relative
imports.

The other manual changes are:

* to properly distinguish between strings and binary data in
  mtlib/util.py, mtlib/log.py, and mtedit/server.py;
* to switch between methods for getting headers in mtedit/server.py; and
* updating some `import` statements (as well as removing unused ones).

The rest of the changes were done using `futurize`:

    $ futurize --stage1 --all-imports --write --nobackups mtedit/**/*.py

Two features remain broken:

* retrieving logs over SSH fails to retrieve
  /var/log/xorg/cmt_input_events.dat, but that was broken already; and
* editing logs by adding the `-o` switch and clicking the shrink button
  in the Web UI fails to import `MTReplay`, presumably because I haven't
  updated that module yet.

TEST=run `mtedit` with the path of a downloaded ZIP file, choose a log
     to view, and check that you can view the data properly in the
     browser.
BUG=none

Cq-Depend: chromium:2024460
Change-Id: Ie59c191d3bc2827d3e94e45a9a8a38e3d524a1a2
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/mttools/+/2025612
Tested-by: Harry Cutts <hcutts@chromium.org>
Reviewed-by: Sean O'Brien <seobrien@chromium.org>
Commit-Queue: Harry Cutts <hcutts@chromium.org>
diff --git a/Makefile b/Makefile
index 49b4e1a..5f28881 100644
--- a/Makefile
+++ b/Makefile
@@ -52,7 +52,7 @@
 	echo "$(SRC)" > $(PYTHON_SITE)/mttools.pth
 
 	# install symlinks to command line tools
-	ln -sf $(SRC)/mtedit/main.py /usr/bin/mtedit
+	ln -sf $(SRC)/mtedit.py /usr/bin/mtedit
 	ln -sf $(SRC)/mtreplay/main.py /usr/bin/mtreplay
 	ln -sf $(SRC)/mtstat/main.py /usr/bin/mtstat
 	ln -sf $(SRC)/mtbringup/main.py /usr/bin/mtbringup
diff --git a/mtedit/main.py b/mtedit.py
similarity index 96%
rename from mtedit/main.py
rename to mtedit.py
index b4c385a..4e72a5e 100755
--- a/mtedit/main.py
+++ b/mtedit.py
@@ -3,6 +3,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+from __future__ import absolute_import
+from __future__ import division
 from __future__ import print_function
 
 from mtedit import MTEdit
diff --git a/mtedit/__init__.py b/mtedit/__init__.py
index b865368..8d0a07e 100644
--- a/mtedit/__init__.py
+++ b/mtedit/__init__.py
@@ -1 +1,3 @@
-from mtedit import MTEdit
\ No newline at end of file
+from __future__ import absolute_import
+
+from .mtedit import MTEdit
diff --git a/mtedit/mtedit.py b/mtedit/mtedit.py
index 219dadf..554ef9e 100644
--- a/mtedit/mtedit.py
+++ b/mtedit/mtedit.py
@@ -2,12 +2,14 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+from __future__ import absolute_import
+from __future__ import division
 from __future__ import print_function
 
 import os.path
 import random
 import socket
-import server
+from . import server
 
 # path to current script directory
 script_dir = os.path.dirname(os.path.realpath(__file__))
diff --git a/mtedit/server.py b/mtedit/server.py
index 2f12340..db05148 100644
--- a/mtedit/server.py
+++ b/mtedit/server.py
@@ -2,20 +2,13 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+from __future__ import absolute_import
+from __future__ import division
 from __future__ import print_function
 
-from SimpleHTTPServer import SimpleHTTPRequestHandler
-import cgi
-import json
+from http.server import SimpleHTTPRequestHandler
 import os
-import posixpath
-import shutil
-import SimpleHTTPServer
-import socket
-import SocketServer
-import sys
-import threading
-import urllib
+import socketserver
 
 # change into script in order to serve tpview files
 script_dir = os.path.dirname(os.path.realpath(__file__))
@@ -42,7 +35,8 @@
     self.send_response(200)
     self.send_header('Content-type', type)
     self.end_headers()
-    self.wfile.write(data)
+    output_data = data.encode('utf-8') if isinstance(data, str) else data
+    self.wfile.write(output_data)
 
   def end_headers(self):
     # Override end_headers for additional header to be sent with every response
@@ -66,7 +60,14 @@
 
     if self.path.startswith('/save/'):
       name = os.path.basename(self.path)
-      length = int(self.headers.getheader('content-length'))
+      # getallmatchingheaders would be great here, if it hadn't been broken for
+      # over 10 years (https://bugs.python.org/issue5053).
+      try:
+          # Python 3 version
+          length = int(self.headers.get_all('Content-Length')[0])
+      except AttributeError:
+          # Python 2.7 version
+          length = int(self.headers.getheader('Content-Length'))
       data.result = self.rfile.read(length)
       data.saved = True
       self.respond('Success')
@@ -89,7 +90,7 @@
     unless persistent is set to True.
   """
   data = ServerData(log)
-  httpd = SocketServer.TCPServer(('', port), MTEditHTTPRequestHandler)
+  httpd = socketserver.TCPServer(('', port), MTEditHTTPRequestHandler)
   httpd.user_data = data
 
   while True:
@@ -105,7 +106,7 @@
     and returns the trimmed log.
   """
   data = ServerData(log)
-  httpd = SocketServer.TCPServer(('', port), MTEditHTTPRequestHandler)
+  httpd = socketserver.TCPServer(('', port), MTEditHTTPRequestHandler)
   httpd.user_data = data
 
   while True:
@@ -121,7 +122,7 @@
     externally (e.g. via keyboard interrupt).
   """
   data = ServerData('')
-  httpd = SocketServer.TCPServer(('', port), MTEditHTTPRequestHandler)
+  httpd = socketserver.TCPServer(('', port), MTEditHTTPRequestHandler)
   httpd.user_data = data
   while True:
     httpd.handle_request()
diff --git a/mtlib/log.py b/mtlib/log.py
index 2835d11..d0a6797 100644
--- a/mtlib/log.py
+++ b/mtlib/log.py
@@ -8,21 +8,15 @@
 
 from gzip import GzipFile
 from mtlib.feedback import FeedbackDownloader
-try:
-    from StringIO import StringIO
-except ImportError:
-    from io import StringIO
+from io import BytesIO
 from .cros_remote import CrOSRemote
 import base64
 import bz2
-import imghdr
 import os
 import os.path
 import re
-import subprocess
 import sys
 import tarfile
-import tempfile
 import zipfile
 
 # path for temporary screenshot
@@ -135,7 +129,7 @@
     self.downloader = downloader
 
     if id_or_filename.endswith('.zip') or id_or_filename.endswith('.bz2'):
-      self.report = open(id_or_filename).read()
+      self.report = open(id_or_filename, 'rb').read()
       screenshot_filename = id_or_filename[:-4] + '.jpg'
       try:
         self.image = open(screenshot_filename).read()
@@ -169,17 +163,19 @@
     return tar.extractfile(names[-1])
 
   def _ExtractSystemLog(self):
-    if self.report[0:2] == 'BZ':
-      self.system = bz2.decompress(self.report)
-    elif self.report[0:2] == 'PK':
-      io = StringIO(self.report)
-      zip = zipfile.ZipFile(io, 'r')
-      self.system = zip.read(zip.namelist()[0])
+    if self.report[0:2] == b'BZ':
+      system_log = bz2.decompress(self.report)
+    elif self.report[0:2] == b'PK':
+      bytes_io = BytesIO(self.report)
+      zip = zipfile.ZipFile(bytes_io, 'r')
+      system_log = zip.read(zip.namelist()[0])
       zip.extractall('./')
     else:
       print('Cannot download logs file')
       sys.exit(-1)
 
+    self.system = system_log.decode('utf-8', 'strict')
+
   def _ExtractLogFiles(self):
     # Find embedded and uuencoded activity.tar in system log
 
@@ -219,7 +215,7 @@
         return []
 
       # untar
-      activity_tar_file = tarfile.open(fileobj=StringIO(activity_tar_data))
+      activity_tar_file = tarfile.open(fileobj=BytesIO(activity_tar_data))
 
       def ExtractPadFiles(name):
         # find matching evdev file
diff --git a/mtlib/util.py b/mtlib/util.py
index 052a3f4..6ca628c 100644
--- a/mtlib/util.py
+++ b/mtlib/util.py
@@ -16,6 +16,7 @@
 import re
 import shlex
 import shutil
+from six import string_types
 import subprocess
 import sys
 import time
@@ -211,7 +212,7 @@
   This method behaves the same as Execute, but throws an ExecuteException
   if the command fails.
   """
-  if isinstance(command, basestring):
+  if isinstance(command, string_types):
     command = shlex.split(command)
   (code, out) = __Execute(command, cwd, verbose, interactive)
   if code != 0:
@@ -219,7 +220,7 @@
   return out
 
 def __Execute(command, cwd=None, verbose=False, interactive=False):
-  if isinstance(command, basestring):
+  if isinstance(command, string_types):
     command = shlex.split(command)
 
   if cwd:
@@ -251,7 +252,8 @@
 
   out = None
   if not interactive:
-    out = process.stdout.read().replace("\r", "").strip()
+    out = process.stdout.read().decode('utf-8', 'strict')
+    out = out.replace("\r", "").strip()
 
   return (process.returncode, out)