Send the canvas image when the client quits.

When a client quits, it is helpful to save the canvas image
and sends it back to the server for further examination. Saving
the canvas image from the client is useful because the python
server might not run on the same machine as the client.

To save the image, the client strips off the header of the base64
encoding of the png image, and the server converts it into binary
format before saving the file on the server side.

BUG=chromium:443539
TEST=Manually execute webplot.py on a chrome os machine.
Press 'q' on chrome to quit, or press ctrl-c to interrupt
the webplot server. The whole thing should be terminated
correctly in both ways.

Change-Id: I2546c37501987b29ef5b5e888ff14d59ecfc5e99
Reviewed-on: https://chromium-review.googlesource.com/242510
Reviewed-by: Charlie Mooney <[email protected]>
Commit-Queue: Shyh-In Hwang <[email protected]>
Tested-by: Shyh-In Hwang <[email protected]>
diff --git a/webplot.js b/webplot.js
index 48d4982..d89a0e4 100644
--- a/webplot.js
+++ b/webplot.js
@@ -194,6 +194,17 @@
 
 
 /**
+ * Capture the canvas image.
+ * @return {string} the image represented in base64 text
+ */
+Webplot.prototype.captureCanvasImage = function() {
+  var imageData = this.canvas.toDataURL('image/png');
+  // Strip off the header.
+  return imageData.replace(/^data:image\/png;base64,/, '');
+}
+
+
+/**
  * Process an incoming snapshot.
  * @param {object} snapshot
  *
@@ -283,7 +294,10 @@
   var startY = 100;
   var font = '30px Verdana';
 
-  window.ws.send('quit');
+  // Capture the image before clearing the canvas and send it to the server,
+  // and notify the server that this client quits.
+  window.ws.send('quit:' + webplot.captureCanvasImage());
+
   canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
   if (closed_by_server) {
     webplot.fillText('The python server has quit.', startX, startY, font);
diff --git a/webplot.py b/webplot.py
index bb07da3..a38405b 100755
--- a/webplot.py
+++ b/webplot.py
@@ -7,6 +7,7 @@
 """
 
 import argparse
+import base64
 import json
 import logging
 import os
@@ -31,6 +32,7 @@
 
 # The touch events are saved in this file as default.
 SAVED_FILE = '/tmp/webplot.dat'
+SAVED_IMAGE = '/tmp/webplot.png'
 
 
 def KeyboardInterruptHandler():
@@ -38,8 +40,9 @@
 
   The stop procedure triggered is as follows:
   1. This handler sends a 'quit' message to the listening client.
-  2. The client responds by sending back a 'quit' message.
-  3. WebplotWSHandler.received_message() handles the 'quit' message.
+  2. The client sends the canvas image back to the server in its quit message.
+  3. WebplotWSHandler.received_message() saves the image.
+  4. WebplotWSHandler.received_message() handles the 'quit' message.
      The cherrypy engine exits if this is the last client.
   """
   cherrypy.log('Cherrypy engine is sending quit message to clients.')
@@ -61,6 +64,8 @@
     content = data[1] if len(data) == 2 else None
     if mtype == 'quit':
       # A shutdown message requested by the user.
+      cherrypy.log('Save the image to %s' % SAVED_IMAGE)
+      self.SaveImage(content, SAVED_IMAGE)
       cherrypy.log('The user requests to shutdown the cherrypy server....')
       state.DecCount()
     elif mtype == 'save':
@@ -73,6 +78,12 @@
     cherrypy.log('A client requests to close WS.')
     cherrypy.engine.publish('websocket-broadcast', TextMessage(reason))
 
+  @staticmethod
+  def SaveImage(image_data, image_file):
+    """Decoded the base64 image data and save it in the file."""
+    with open(image_file, 'w') as f:
+      f.write(base64.b64decode(image_data))
+
 
 class TouchDeviceWrapper(object):
   """This is a wrapper of remote.RemoteTouchDevice.