TPView: Python server for loading/saving logs on the workstation

This CL extends tpview by a python server which can read/save log files
on the workstation. By accessing /edit/logname TPView will load the log
file tpview/logs/logfile.log and after shrinking save it as
/tpview/logs/logfile.log.trimmed.

TEST=python server.py
BUG=chromium-os:38304

Change-Id: I219f8950273e4027f1501d223362a25c02a9674a
Reviewed-on: https://gerrit.chromium.org/gerrit/42180
Reviewed-by: Dennis Kempin <denniskempin@chromium.org>
Commit-Queue: Dennis Kempin <denniskempin@chromium.org>
Tested-by: Dennis Kempin <denniskempin@chromium.org>
diff --git a/tpview/server.py b/tpview/server.py
new file mode 100644
index 0000000..d91b957
--- /dev/null
+++ b/tpview/server.py
@@ -0,0 +1,108 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from SimpleHTTPServer import SimpleHTTPRequestHandler
+import cgi
+import json
+import os
+import shutil
+import SimpleHTTPServer
+import socket
+import SocketServer
+import sys
+import threading
+
+# change into script in order to serve tpview files
+script_dir = os.path.dirname(os.path.realpath(__file__))
+os.chdir(script_dir)
+
+class ServerData(object):
+  def __init__(self, log):
+    self.log = log
+    self.loaded = False
+    self.saved = False
+    self.result = None
+
+class TPViewHTTPRequestHandler(SimpleHTTPRequestHandler):
+  """
+    serves static files and provides three dynamic URLs:
+    /edit serving view.html
+    /load/* serving the log file provided when starting the server
+    /save/* for POST'ing trimmed log files
+
+    The latter two are only to be used with AJAX commands from
+    /edit
+  """
+  def respond(self, data, type="text/html"):
+      # send text response to browser
+      self.send_response(200)
+      self.send_header("Content-type", type)
+      self.end_headers()
+      self.wfile.write(data)
+
+  def do_GET(self):
+    data = self.server.user_data
+
+    if self.path.startswith("/load/"):
+      self.respond(data.log, "text/plain")
+      data.loaded = True
+
+    elif self.path.startswith("/edit"):
+      self.path = "view.html"
+      SimpleHTTPRequestHandler.do_GET(self)
+
+    else:
+      SimpleHTTPRequestHandler.do_GET(self)
+
+  def do_POST(self):
+    data = self.server.user_data
+
+    if self.path.startswith("/save/"):
+      name = os.path.basename(self.path)
+      length = int(self.headers.getheader('content-length'))
+      data.result = self.rfile.read(length)
+      data.saved = True
+      self.respond("Success")
+
+
+def View(port, log=None, persistent=False):
+  """
+    Serve TPView viewing 'log'. The server will exit after serving TPView
+    unless persistent is set to True.
+  """
+  data = ServerData(log)
+  httpd = SocketServer.TCPServer(("", port), TPViewHTTPRequestHandler)
+  httpd.user_data = data
+
+  while True:
+    httpd.handle_request()
+    if not persistent and data.loaded:
+      return
+
+
+def Edit(port, log=None):
+  """
+    Serve TPView for editing 'log'. Blocks until the file has been trimmed
+    and returns the trimmed log.
+  """
+  data = ServerData(log)
+  httpd = SocketServer.TCPServer(("", port), TPViewHTTPRequestHandler)
+  httpd.user_data = data
+
+  while True:
+    httpd.handle_request()
+    if data.saved:
+      return data.result
+
+
+def Serve(port):
+  """
+    Serve TPView without any log data. The server will serve until killed
+    externally (e.g. via keyboard interrupt).
+  """
+  data = ServerData("")
+  httpd = SocketServer.TCPServer(("", port), TPViewHTTPRequestHandler)
+  httpd.user_data = data
+  while True:
+    httpd.handle_request()
diff --git a/tpview/view.html b/tpview/view.html
index 7da5d93..50e053f 100644
--- a/tpview/view.html
+++ b/tpview/view.html
@@ -2,26 +2,30 @@
 <head>
   <meta http-equiv="Content-type" content="text/html; charset=utf-8">
   <title>Touchpad Log Viewer</title>
-<link type="text/css" href="css/smoothness/jquery-ui-1.8.16.custom.css" rel="stylesheet" />	
+<link type="text/css" href="css/smoothness/jquery-ui-1.8.16.custom.css" rel="stylesheet" />
 <script src="js/jquery-1.6.4.min.js"></script>
 <script src="js/jquery.mousewheel.js"></script>
 <script src="js/jquery-ui-1.8.16.custom.min.js"></script>
 <script src="js/bzip2.js"></script>
 <script src="js/base64-binary.js"></script>
 <script src="js/jsxcompressor.js"></script>
+<script src="js/purl.js"></script>
 
 <script src="finger_view_controller.js"></script>
 <script src="graph_controller.js"></script>
 <script src="secret_entries.js"></script>
 
 
-<link rel="stylesheet" href="style.css" type="text/css" media="all" />
+<link rel="stylesheet" href="/style.css" type="text/css" media="all" />
 <script type="text/javascript" charset="utf-8">
 
 var finger_view_controller;
 var current_layer = 0;
 var json_obj;
 
+var use_server = false;
+var logname = undefined;
+
 function update_range(event, ui) {
   finger_view_controller.setRange(ui.values[0], ui.values[1]);
   var begin_event = finger_view_controller.getEvent(ui.values[0]);
@@ -371,6 +375,12 @@
   var snippet = finger_view_controller.getSnippet(values[0], values[1]);
   $('#text_box').val(JSON.stringify(snippet, null, 2));
   loadLogObj(snippet, true);
+
+  if (use_server) {
+    $.post('/save/' + logname, JSON.stringify(snippet, null, 2), function () {
+      alert("Saved");
+    });
+  }
 }
 
 function generateRadioButtons(obj) {
@@ -613,7 +623,17 @@
     };
     reader.readAsArrayBuffer(document.getElementById('bzipupload').files[0]);
   });
-  loadLog(secret_obj);
+
+  logname = $.url().fparam('id');
+  use_server = logname !== '';
+
+  if (use_server) {
+    $.get('/load/' + logname, function(data) {
+      loadLog(data);
+    }, 'json');
+  } else {
+    loadLog(secret_obj);
+  }
 });
 
 </script>