Merge "fix packaging script"
diff --git a/gui/chronosharegui.cpp b/gui/chronosharegui.cpp
index 898f6c4..cdc5dc0 100644
--- a/gui/chronosharegui.cpp
+++ b/gui/chronosharegui.cpp
@@ -355,7 +355,13 @@
 
 void ChronoShareGui::openInWebBrowser()
 {
-  QDesktopServices::openUrl(QUrl("http://localhost:9001"));
+  QUrl url ("http://localhost:9001/");
+  url.setFragment ("fileList&"
+                   "user=" + QUrl::toPercentEncoding (m_username) + "&"
+                   "folder=" + QUrl::toPercentEncoding (m_sharedFolderName));
+
+  // i give up. there is double encoding and I have no idea how to fight it...
+  QDesktopServices::openUrl (url);
 }
 
 void ChronoShareGui::openFile()
diff --git a/gui/html/chronoshare.js b/gui/html/chronoshare.js
index c8b8ae0..d94bb5f 100644
--- a/gui/html/chronoshare.js
+++ b/gui/html/chronoshare.js
@@ -1,5 +1,104 @@
+var CHRONOSHARE;
+
+var PAGE = "folderHistory";
+var PARAMS = [ ];
+var URIPARAMS = "";
+
+function nav_anchor (aurl) {
+    aurl = aurl.split('#');
+    if (aurl[1])
+    {
+        aurl_split = aurl[1].split ('&');
+        page = aurl_split[0];
+
+        vars = [ ];
+        for (var i = 1; i < aurl_split.length; i++)
+        {
+            hash = aurl_split[i].split('=');
+            vars.push(hash[0]);
+            // there is strange double-encoding problem...
+            vars[hash[0]] = decodeURIComponent (decodeURIComponent (hash[1]));
+        }
+
+        if (page != PAGE)
+        {
+            PAGE = page;
+            PARAMS = vars;
+            URIPARAMS = aurl[1];
+
+            if (CHRONOSHARE) {
+                CHRONOSHARE.run ();
+            }
+        }
+        else if (aurl != URIPARAMS)
+        {
+            PARAMS = vars;
+            URIPARAMS = aurl[1];
+
+            if (CHRONOSHARE) {
+                CHRONOSHARE.run ();
+            }
+        }
+    }
+}
+
+$(document).ready (function () {
+    nav_anchor (window.location.href);
+
+    if (!PARAMS.user || !PARAMS.folder)
+    {
+        $("#error").html ("user and folder must be be specified in the URL");
+        $("#error").removeClass ("hidden");
+        return;
+    }
+    else {
+        // update in-page URLs
+        $(".needs-get-url").each (function (index,element) {
+            this.href += "&user="+encodeURIComponent (encodeURIComponent (PARAMS.user))
+                + "&folder="+encodeURIComponent (encodeURIComponent (PARAMS.folder));
+        });
+        $(".needs-get-url").removeClass ("needs-get-url");
+    }
+
+    CHRONOSHARE = new ChronoShare (PARAMS.user, PARAMS.folder);
+    CHRONOSHARE.run ();
+
+    $("a").click (function () {
+        nav_anchor (this.href)
+    });
+
+    $(window).on('hashchange', function() {
+        nav_anchor (window.location.href);
+    });
+});
+
+/**
+ * @brief Convert binary data represented as non-escaped hex string to Uint8Array
+ * @param str String like ba0cb43e4b9639c114a0487d5faa7c70452533963fc8beb37d1b67c09a48a21d
+ *
+ * Note that if string length is odd, null will be returned
+ */
+StringHashToUint8Array = function (str) {
+    if (str.length % 2 != 0) {
+        return null;
+    }
+
+    var buf = new Uint8Array (str.length / 2);
+
+    for (var i = 0; i < str.length; i+=2) {
+        value = parseInt (str.substring (i, i+2), 16);
+        buf[i/2] = value;
+    }
+
+    return buf;
+};
+
 
 $.Class ("FilesClosure", {}, {
+    init: function (chronoshare) {
+        this.chronoshare = chronoshare;
+    },
+
     upcall: function(kind, upcallInfo) {
         $("#loader").fadeOut (500); // ("hidden");
         if (kind == Closure.UPCALL_CONTENT) {
@@ -9,21 +108,56 @@
             data = JSON.parse (convertedData);
 
             // error handling?
-            // if (data.files instanceof Array) {
-            //     alert (data.files);
-            // }
+            table = $("#content").append (
+                $("<table />", { "class": "item-list" })
+                    .append ($("<thead />")
+                             .append ($("<tr />")
+                                      .append ($("<th />", { "class": "filename border-left", "scope": "col" }).text ("Filename"))
+                                      .append ($("<th />", { "class": "version", "scope": "col" }).text ("Version"))
+                                      .append ($("<th />", { "class": "modified", "scope": "col" }).text ("Modified"))
+                                      .append ($("<th />", { "class": "modified-by border-right", "scope": "col" }).text ("Modified By"))))
+                    .append ($("<tbody />", { "id": "file-list-files" }))
+                    .append ($("<tfoot />")
+                             .append ($("<tr />")
+                                      .append ($("<td />", { "colspan": "4", "class": "border-right border-left" })))));
 
-            var html = $("tbody#files");
+            var html = $("#file-list-files");
             for (var i = 0; i < data.files.length; i++) {
                 file = data.files[i];
-                html = html.append ([
-                    "<tr"+(i%2?" class=\"odd\"":"")+">",
-                    "<td class=\"border-left\">" + file.filename + "</td>",
-                    "<td>" + file.version + "</td>",
-                    "<td>" + file.timestamp + "</td>",
-                    "<td class=\"border-right\">" + "</td>",
-                    "</tr>"
-                ].join());
+
+		row = $("<tr />");
+		if (i%2) { row.addClass ("odd"); }
+
+                row.bind('mouseenter mouseleave', function() {
+                    $(this).toggleClass('highlighted');
+                });
+
+                // fileHistoryUrl = new Name ()
+                //     .add (this.chronoshare.actions)
+                //     .add (file.filename)
+                //     .add ("nonce")
+                //     .addSegment (0);
+                // row.attr ("history", fileHistoryUrl.to_uri ());
+                row.bind('click', function () {
+                    url = "#fileHistory";
+                    url += "&item=" + encodeURIComponent(encodeURIComponent(file.filename));
+                    pos = URIPARAMS.indexOf ("&");
+                    if (pos >= 0) {
+                        url += URIPARAMS.substring (pos)
+                    }
+
+                    document.location = url;
+                    nav_anchor (document.location.href);
+                });
+
+		row.append ($("<td />", {"class": "border-left"}).text (file.filename));
+		row.append ($("<td />").text (file.version));
+		row.append ($("<td />").text (new Date (file.timestamp)));
+		row.append ($("<td />")
+			    .append ($("<userName />").text (file.owner.userName))
+			    .append ($("<seqNo> /").text (file.owner.seqNo)));
+
+		html = html.append (row);
             }
         }
         else if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
@@ -33,7 +167,76 @@
         else {
             $("#error").html ("Unknown error happened");
             $("#error").removeClass ("hidden");
-            // some kind of error
+        }
+    }
+});
+
+
+$.Class ("HistoryClosure", {}, {
+    init: function (chronoshare) {
+        this.chronoshare = chronoshare;
+    },
+
+    upcall: function(kind, upcallInfo) {
+        $("#loader").fadeOut (500); // ("hidden");
+        if (kind == Closure.UPCALL_CONTENT) {
+            convertedData = DataUtils.toString (upcallInfo.contentObject.content);
+            $("#json").text (convertedData);
+            $("#json").removeClass ("hidden");
+            data = JSON.parse (convertedData);
+
+            // error handling?
+            table = $("#content").append (
+                $("<table />", { "class": "item-list" })
+                    .append ($("<thead />")
+                             .append ($("<tr />")
+                                      .append ($("<th />", { "class": "filename border-left", "scope": "col" }).text ("Filename"))
+                                      .append ($("<th />", { "class": "version", "scope": "col" }).text ("Version"))
+                                      .append ($("<th />", { "class": "modified", "scope": "col" }).text ("Modified"))
+                                      .append ($("<th />", { "class": "modified-by border-right", "scope": "col" }).text ("Modified By"))))
+                    .append ($("<tbody />", { "id": "history-list-actions" }))
+                    .append ($("<tfoot />")
+                             .append ($("<tr />")
+                                      .append ($("<td />", { "colspan": "4", "class": "border-right border-left" })))));
+
+            var html = $("#history-list-actions");
+            for (var i = 0; i < data.actions.length; i++) {
+                action = data.actions[i];
+
+	        row = $("<tr />");
+	        if (i%2) { row.addClass ("odd"); }
+
+                row.bind('mouseenter mouseleave', function() {
+                    $(this).toggleClass('highlighted');
+                });
+
+                fileHistoryUrl = new Name ()
+                    .add (this.chronoshare.actions)
+                    .add (action.filename)
+                    .add ("nonce")
+                    .addSegment (0);
+                // row.attr ("history", fileHistoryUrl.to_uri ());
+                row.bind('click', function () {
+                    // alert (fileHistoryUrl.to_uri ());
+                });
+
+	        row.append ($("<td />", {"class": "border-left"}).text (action.filename));
+	        row.append ($("<td />").text (action.version));
+	        row.append ($("<td />").text (new Date (action.timestamp)));
+	        row.append ($("<td />")
+	        	    .append ($("<userName />").text (action.id.userName))
+	        	    .append ($("<seqNo> /").text (action.id.seqNo)));
+
+	        html = html.append (row);
+            }
+        }
+        else if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
+            $("#error").html ("Interest timed out");
+            $("#error").removeClass ("hidden");
+        }
+        else {
+            $("#error").html ("Unknown error happened");
+            $("#error").removeClass ("hidden");
         }
     }
 });
@@ -49,113 +252,38 @@
 
          this.restore = new Name ("/localhost").add (this.username).add ("chronoshare").add (foldername).add ("cmd").add ("restore").add ("file");
 
-         this.ndn = new NDN ({host:"127.0.0.1", getHostAndPort: function() { return {host: "127.0.0.1", port: 9696}}});
-         // this.ndn.transport.connect (this.ndn);
+         // this.ndn = new NDN ({host:"127.0.0.1", getHostAndPort: function() { return {host: "127.0.0.1", port: 9696}}});
+         this.ndn = new NDN ({host:"127.0.0.1"});
      },
 
-     run: function () {
-         request = new Name ().add (this.files)./*add (folder_in_question).*/add ("nonce").addSegment (0);
-         console.log (request.to_uri ());
-         $("#files").empty ();
-         // $("#loader").removeClass ("hidden");
-         $("#loader").fadeIn (500);
-         this.ndn.expressInterest (request, new FilesClosure ());
-     }
-// ,
 
-     // sendRequest: function () {
-     //     alert ('bla');
-     // }
+     run: function () {
+         $("#content").empty ();
+         $("#loader").fadeIn (500);
+         $("#error").addClass ("hidden");
+
+         if (PAGE == "fileList") {
+             request = new Name ().add (this.files)./*add (folder_in_question).*/add ("nonce").addSegment (0);
+             console.log (request.to_uri ());
+             this.ndn.expressInterest (request, new FilesClosure (this));
+         }
+         else if (PAGE == "folderHistory") {
+             request = new Name ().add (this.actions)./*add (folder_in_question).*/add ("nonce").addSegment (0);
+             console.log (request.to_uri ());
+             this.ndn.expressInterest (request, new HistoryClosure (this));
+         }
+         else if (PAGE == "fileHistory") {
+             if (!PARAMS.item) {
+                 $("#loader").fadeOut (500); // ("hidden");
+                 $("#error").html ("incorrect input for fileHistory command");
+                 $("#error").removeClass ("hidden");
+                 return;
+             }
+             request = new Name ().add (this.actions).add (PARAMS.item).add ("nonce").addSegment (0);
+             console.log (request.to_uri ());
+             this.ndn.expressInterest (request, new HistoryClosure (this));
+         }
+     }
  });
 
 
-// $.Class ("onFiles",
-//  {
-//  },
-
-//  {
-//      upcall: function(kind, upcallInfo, tmp) {
-//          if (kind
-//      }
-//  });
-
-// var AsyncGetClosure = function AsyncGetClosure() {
-//     Closure.call(this);
-// };
-
-// AsyncGetClosure.prototype.upcall = function(kind, upcallInfo, tmp) {
-//     if (kind == Closure.UPCALL_FINAL) {
-//         // Do nothing.
-//     } else if (kind == Closure.UPCALL_CONTENT) {
-//         var content = upcallInfo.contentObject;
-// 	var nameStr = content.name.getName().split("/").slice(5,6);
-
-// 	if (nameStr == "prefix") {
-// 	    document.getElementById('prefixcontent').innerHTML = DataUtils.toString(content.content);
-// 	    prefix();
-// 	} else if (nameStr == "link") {
-// 	    document.getElementById('linkcontent').innerHTML = DataUtils.toString(content.content);
-// 	    link();
-// 	} else {
-// 	    var data = DataUtils.toString(content.content);
-// 	    var obj = jQuery.parseJSON(data);
-// 	    document.getElementById("lastupdated").innerHTML = obj.lastupdated;
-// 	    document.getElementById("lastlog").innerHTML = obj.lastlog;
-// 	    document.getElementById("lasttimestamp").innerHTML = obj.lasttimestamp;
-// 	}
-//     } else if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
-//         console.log("Closure.upcall called with interest time out.");
-//     }
-//     return Closure.RESULT_OK;
-// };
-
-// function getStatus(name) {
-//     // Template interest to get the latest content.
-//     var interest = new Interest("/tmp/");
-//     interest.childSelector = 1;
-//     interest.interestLifetime = 4000;
-
-//     ndn.expressInterest(new Name("/ndn/memphis.edu/netlab/status/" + name), new AsyncGetClosure(), interest);
-// }
-
-// // Calls to get the content data.
-// function begin() {
-//     getStatus("metadata");
-//     getStatus("prefix");
-//     getStatus("link");
-// }
-
-// var ndn;
-// $(document).ready(function() {
-//     $("#all").fadeIn(500);
-//     var res = detect();
-
-//     if (!res) {
-// 	$("#base").fadeOut(50);
-// 	$("#nosupport").fadeIn(500);
-//     } else {
-//         //$("#all").fadeIn(500);
-
-//         $.get("test.php", function() {
-// 	    openHandle = function() { begin() };
-//             ndn = new NDN({host:hostip, onopen:openHandle});
-//             ndn.transport.connectWebSocket(ndn);
-// 	    $("#base").fadeOut(500, function () {
-//                 $("#status").fadeIn(1000);
-//             });
-//         });
-//     }
-// });
-
-// $("#continue").click(function() {
-//     $("#nosupport").fadeOut(50);
-//     $("#base").fadeIn(500);
-//     $.get("test.php", function() {
-//         openHandle = function() { begin() };
-//         ndn = new NDN({host:hostip, onopen:openHandle});
-//         ndn.transport.connectWebSocket(ndn);
-//         $("#base").fadeOut(500, function () {
-//             $("#status").fadeIn(1000);
-//         });
-//     });
-// });
diff --git a/gui/html/index.html b/gui/html/index.html
index 21842a8..0b21c61 100644
--- a/gui/html/index.html
+++ b/gui/html/index.html
@@ -22,8 +22,8 @@
 
       <nav>
         <ul>
-          <li><a href="#files">File list</a></li>
-          <li><a href="#history">History</a></li>
+          <li><a class="needs-get-url" href="#fileList">File list</a></li>
+          <li><a class="needs-get-url" href="#folderHistory">Folder history</a></li>
         </ul>
       </nav>
     </header>
@@ -31,35 +31,18 @@
     <article>
       <!-- <img id="loader" src="load.gif" /> -->
       <div id="content" class="hidden">
-
-        <table class="file-list">
-          <thead>
-            <tr>
-              <th class="filename border-left" scope="col">Filename</th>
-              <th class="version" scope="col">Version</th>
-              <th class="modified" scope="col">Modified</th>
-              <th class="modified-by border-right" scope="col">Modified By</th>
-            </tr>
-          </thead>
-
-          <tbody id="files">
-          </tbody>
-
-          <tfoot><tr><td colspan="4" class="border-right border-left"></td></tr></tfoot>
-        </table>
-
-        <div class="hidden" id="loader">
-          <img src="load.gif" />
-        </div>
-
-
-        <red class="hidden" id="error">
-        </red>
-
-        <pre class="hidden" id="json">
-        </pre>
       </div>
 
+      <div class="hidden" id="loader">
+        <img src="load.gif" />
+      </div>
+
+      <red class="hidden" id="error">
+      </red>
+
+      <pre class="hidden" id="json">
+      </pre>
+
 
 
       <div id="no-support">
@@ -80,9 +63,6 @@
          if (detect ()) {
              $("#no-support").remove ();
              $("#content").removeClass ("hidden");
-
-             folder=new ChronoShare ("/ndn/ucla.edu/alex/macbook", "testing7");
-             folder.run ();
          }
       });
     </script>
diff --git a/gui/html/style.css b/gui/html/style.css
index 198e2ef..4b7322d 100644
--- a/gui/html/style.css
+++ b/gui/html/style.css
@@ -116,7 +116,7 @@
 }

 

 /* */

-.file-list

+.item-list

 {

     border-radius: 6px;

 

@@ -130,7 +130,7 @@
     /* border-collapse: collapse; */

 }

 

-.file-list th

+.item-list th

 {

     /* -moz-border-radius: 10px; */

     /* border-radius: 10px; */

@@ -144,13 +144,13 @@
     text-align: left;

 }

 

-.file-list th.border-left {

+.item-list th.border-left {

     /* -moz-border-radius-topleft:10px; */

     /* -webkit-border-top-left-radius:10px; */

     /* border-top-left-radius:10px; */

 }

 

-.file-list td

+.item-list td

 {

     padding: 8px;

     border-bottom: 1px solid #fff;

@@ -172,7 +172,7 @@
     border-bottom: 2px solid #99CCB2;

     background: #EAF4EF;

 }

-.file-list tfoot td {

+.item-list tfoot td {

     padding: 0;

 }

 

@@ -181,6 +181,11 @@
     background-color: #eeeeee;

 }

 

+.highlighted {

+    background-color: #cccccc;

+    cursor: pointer;

+}

+

 .filename {

     width: 50%;

 }

@@ -207,3 +212,13 @@
     margin-top: -64px; /* Half the height */

     margin-left: -64px; /* Half the width */

 }

+

+userName {

+    display: inline-block;

+    /* ? how format user name? */

+}

+

+seqNo {

+    display: inline-block;

+    margin-left: 5px;

+}
\ No newline at end of file
diff --git a/server/mime_types.cpp b/server/mime_types.cpp
index eb86fdf..a2d0b15 100644
--- a/server/mime_types.cpp
+++ b/server/mime_types.cpp
@@ -24,6 +24,7 @@
   { "htm", "text/html" },
   { "html", "text/html" },
   { "css", "text/css" },
+  { "js", "text/javascript" },
   { "jpg", "image/jpeg" },
   { "png", "image/png" },
   { 0, 0 } // Marks end of list.
diff --git a/src/state-server.cc b/src/state-server.cc
index e70b891..fa8517c 100644
--- a/src/state-server.cc
+++ b/src/state-server.cc
@@ -164,15 +164,16 @@
 
   json.push_back (Pair ("id", id));
 
-  json.push_back (Pair ("timestamp", to_iso_string (from_time_t (action.timestamp ()))));
+  json.push_back (Pair ("timestamp", to_iso_extended_string (from_time_t (action.timestamp ()))));
   json.push_back (Pair ("filename",  action.filename ()));
+  json.push_back (Pair ("version",  action.version ()));
   json.push_back (Pair ("action", (action.action () == 0) ? "UPDATE" : "DELETE"));
 
   if (action.action () == 0)
     {
       Object update;
       update.push_back (Pair ("hash", boost::lexical_cast<string> (Hash (action.file_hash ().c_str (), action.file_hash ().size ()))));
-      update.push_back (Pair ("timestamp", to_iso_string (from_time_t (action.mtime ()))));
+      update.push_back (Pair ("timestamp", to_iso_extended_string (from_time_t (action.mtime ()))));
 
       ostringstream chmod;
       chmod << setbase (8) << setfill ('0') << setw (4) << action.mode ();
@@ -304,7 +305,7 @@
   }
 
   json.push_back (Pair ("hash", boost::lexical_cast<string> (Hash (file.file_hash ().c_str (), file.file_hash ().size ()))));
-  json.push_back (Pair ("timestamp", to_iso_string (from_time_t (file.mtime ()))));
+  json.push_back (Pair ("timestamp", to_iso_extended_string (from_time_t (file.mtime ()))));
 
   ostringstream chmod;
   chmod << setbase (8) << setfill ('0') << setw (4) << file.mode ();