gui/html: Finally implementing restore operation

+ modal jQueryUI-based dialogs for restore operation
+ fixing a small bug with NDN/REST API

Unrelated note: unless we do something with JSON compression, we can't request more
than 10 items at a time (I tried 20, and they already didn't fit).

Change-Id: I9206fa9ac6b02062915a6f6980ad9e69eaa8da3c
diff --git a/gui/html.qrc b/gui/html.qrc
index 737e214..1d8d379 100644
--- a/gui/html.qrc
+++ b/gui/html.qrc
@@ -1,6 +1,7 @@
 <RCC>
     <qresource prefix="/">
         <file>html/index.html</file>
+<!--
         <file>html/jquery.min.js</file>
         <file>html/load.gif</file>
         <file>html/ndn-js.js</file>
@@ -8,5 +9,6 @@
         <file>html/detect.js</file>
         <file>html/style.css</file>
         <file>html/chronoshare.js</file>
+-->
     </qresource>
 </RCC>
diff --git a/gui/html/chronoshare-helpers.js b/gui/html/chronoshare-helpers.js
index 3e33f4f..6e13caf 100644
--- a/gui/html/chronoshare-helpers.js
+++ b/gui/html/chronoshare-helpers.js
@@ -86,3 +86,73 @@
     url += "&item=" + encodeURIComponent (encodeURIComponent (fileName));
     document.location = url;
 };
+
+
+displayContent = function (newcontent, more, baseUrl) {
+
+    // if (!PARAMS.offset || PARAMS.offset==0)
+    // {
+    $("#content").fadeOut ("fast", function () {
+        $(this).replaceWith (newcontent);
+        $("#content").fadeIn ("fast");
+    });
+
+    $("#content-nav").fadeOut ("fast", function () {
+        $("#content-nav a").hide ();
+
+        if (PARAMS.offset !== undefined || more !== undefined) {
+            $("#content-nav").fadeIn ("fast");
+
+            if (more !== undefined) {
+                $("#get-more").show ();
+
+                $("#get-more").unbind ('click').click (function () {
+                    url = baseUrl;
+                    url += "&offset="+more;
+
+                    document.location = url;
+                });
+            }
+            if (PARAMS.offset > 0) {
+                $("#get-less").show ();
+
+                $("#get-less").unbind ('click').click (function () {
+                    url = baseUrl;
+                    if (PARAMS.offset > 1) {
+                        url += "&offset="+(PARAMS.offset - 1);
+                    }
+
+                    document.location = url;
+                });
+            }
+        }
+    });
+    // }
+    // else {
+    //     tbody.children ().each (function (index, row) {
+    //         $("#history-list-actions").append (row);
+    //     });
+    // }
+};
+
+
+function custom_alert (output_msg, title_msg)
+{
+    if (!title_msg)
+        title_msg = 'Alert';
+
+    if (!output_msg)
+        output_msg = 'No Message to Display';
+
+    $("<div></div>").html(output_msg).dialog({
+        title: title_msg,
+        resizable: false,
+        modal: true,
+        buttons: {
+            "Ok": function()
+            {
+                $( this ).dialog( "close" );
+            }
+        }
+    });
+}
\ No newline at end of file
diff --git a/gui/html/chronoshare-navigation.js b/gui/html/chronoshare-navigation.js
index 7dafd71..8856ec0 100644
--- a/gui/html/chronoshare-navigation.js
+++ b/gui/html/chronoshare-navigation.js
@@ -20,24 +20,33 @@
             vars[hash[0]] = decodeURIComponent (decodeURIComponent (hash[1]));
         }
 
-        if (page != PAGE)
-        {
-            PAGE = page;
-            PARAMS = vars;
-            URIPARAMS = aurl[1];
+        // if (page != PAGE)
+        // {
+        //     PAGE = page;
+        //     PARAMS = vars;
+        //     URIPARAMS = aurl[1];
 
-            if (CHRONOSHARE) {
-                CHRONOSHARE.run ();
-            }
-        }
-        else if (aurl[1] != URIPARAMS)
-        {
-            PARAMS = vars;
-            URIPARAMS = aurl[1];
+        //     if (CHRONOSHARE) {
+        //         CHRONOSHARE.run ();
+        //     }
+        // }
+        // else if (aurl[1] != URIPARAMS)
+        // {
+        //     PARAMS = vars;
+        //     URIPARAMS = aurl[1];
 
-            if (CHRONOSHARE) {
-                CHRONOSHARE.run ();
-            }
+        //     if (CHRONOSHARE) {
+        //         CHRONOSHARE.run ();
+        //     }
+        // }
+
+        // this way we can reload by just clicking on the same link
+        PAGE = page;
+        PARAMS = vars;
+        URIPARAMS = aurl[1];
+
+        if (CHRONOSHARE) {
+            CHRONOSHARE.run ();
         }
     }
 }
@@ -66,5 +75,9 @@
     $(window).on('hashchange', function() {
         nav_anchor (window.location.href);
     });
+
+    $("#reload-button").click (function() {
+        nav_anchor (window.location.href);
+    });
 });
 
diff --git a/gui/html/chronoshare.js b/gui/html/chronoshare.js
index b4c3d73..91fe02e 100644
--- a/gui/html/chronoshare.js
+++ b/gui/html/chronoshare.js
@@ -65,15 +65,35 @@
          return { request: request, callback: new HistoryClosure (this) };
      },
 
-     cmd_restore_file: function (filename, version, hash) {
+     cmd_restore_file: function (filename, version, hash, callback/*function (bool <- data received, status <- returned status)*/) {
          request = new Name ().add (this.restore)
              .add (filename)
              .addSegment (version)
              .add (hash);
          console.log (request.to_uri ());
+	 this.ndn.expressInterest (request, new CmdRestoreFileClosure (this, callback));
      }
  });
 
+$.Class ("CmdRestoreFileClosure", {}, {
+    init: function (chronoshare, callback) {
+        this.chronoshare = chronoshare;
+	this.callback = callback;
+    },
+    upcall: function(kind, upcallInfo) {
+        if (kind == Closure.UPCALL_CONTENT) {
+            convertedData = DataUtils.toString (upcallInfo.contentObject.content);
+	    this.callback (true, convertedData);
+	}
+        else if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
+	    this.callback (false, "Interest timed out");
+        }
+        else {
+	    this.callback (false, "Unknown error happened");
+        }
+    }
+});
+
 $.Class ("FilesClosure", {}, {
     init: function (chronoshare) {
         this.chronoshare = chronoshare;
@@ -91,6 +111,9 @@
 
             tbody = $("<tbody />", { "id": "file-list-files" });
 
+            /// @todo Eventually set title for other pages
+            $("title").text ("ChronoShare - List of files" + (PARAMS.item?" - "+PARAMS.item:""));
+
             // error handling?
             newcontent = $("<div />", { "id": "content" }).append (
 		$("<h2 />").append ($(document.createTextNode("List of files ")), $("<green />").text (PARAMS.item)),
@@ -126,7 +149,7 @@
 			    .prepend ($("<img />", { "src": fileExtension(file.filename) })));
 		row.append ($("<td />", { "class": "version" }).text (file.version));
 		row.append ($("<td />", { "class": "size" }).text (SegNumToFileSize (file.segNum)));
-		row.append ($("<td />", { "class": "modified" }).text (new Date (file.timestamp)));
+		row.append ($("<td />", { "class": "modified" }).text (new Date (file.timestamp+"+00:00"))); // convert from UTC
 		row.append ($("<td />", { "class": "modified-by border-right"})
 			    .append ($("<userName />").text (file.owner.userName))
 			    .append ($("<seqNo> /").text (file.owner.seqNo)));
@@ -134,60 +157,11 @@
 		tbody = tbody.append (row);
             }
 
-            // if (!PARAMS.offset || PARAMS.offset==0)
-            // {
-            $("#content").fadeOut ("fast", function () {
-                $(this).replaceWith (newcontent);
-                $("#content").fadeIn ("fast");
-            });
+	    displayContent (newcontent, data.more, this.base_url ());
 
-            self = this; // small "cheat"
-            $("#content-nav").fadeOut ("fast", function () {
-                $("#content-nav a").hide ();
-
-                if (PARAMS.offset !== undefined || data.more !== undefined) {
-                    $("#content-nav").fadeIn ("fast");
-
-                    if (data.more !== undefined) {
-                        $("#get-more").show ();
-
-                        $("#get-more").unbind ('click').click (function () {
-                            url = self.base_url ();
-                            url += "&offset="+data.more;
-
-                            document.location = url;
-                        });
-                    }
-                    if (PARAMS.offset > 0) {
-                        $("#get-less").show ();
-
-                        $("#get-less").unbind ('click').click (function () {
-                            url = self.base_url ();
-                            if (PARAMS.offset > 1) {
-                                url += "&offset="+(PARAMS.offset - 1);
-                            }
-
-                            document.location = url;
-                        });
-                    }
-                }
-            });
-            // }
-            // else {
-            //     tbody.children ().each (function (index, row) {
-            //         $("#history-list-actions").append (row);
-            //     });
-            // }
-
+	    $.contextMenu( 'destroy',  ".with-context-menu" ); // cleanup
 	    $.contextMenu({
 		selector: ".with-context-menu",
-		events: {
-		    show: function (opt) {
-			console.log(opt.$trigger.attr ("filename"));
-			opt.items.info.name = opt.$trigger.attr ("filename");
-			console.log (opt.items.info.name);
-		    },
-		},
 		items: {
 		    "info": {name: "x", type: "html", html: "<b>File operations</b>"},
 		    "sep1": "---------",
@@ -196,9 +170,7 @@
 			      callback: function(key, opt) {
 				  openHistoryForItem (opt.$trigger.attr ("filename"));
 			      }},
-		    // bar: {name: "Bar", callback: function(key, opt){ alert("Bar!") }}
 		}
-		// there's more, have a look at the demos and docs...
 	    });
         }
         else if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
@@ -240,6 +212,9 @@
 
             tbody = $("<tbody />", { "id": "history-list-actions" });
 
+            /// @todo Eventually set title for other pages
+            $("title").text ("ChronoShare - Recent actions" + (PARAMS.item?" - "+PARAMS.item:""));
+
             newcontent = $("<div />", { "id": "content" }).append (
 		$("<h2 />").append ($(document.createTextNode("Recent actions ")), $("<green />").text (PARAMS.item)),
                 $("<table />", { "class": "item-list" })
@@ -247,44 +222,42 @@
                              .append ($("<tr />")
                                       .append ($("<th />", { "class": "filename border-left", "scope": "col" }).text ("Filename"))
                                       .append ($("<th />", { "class": "version", "scope": "col" }).text ("Version"))
+                                      .append ($("<th />", { "class": "size", "scope": "col" }).text ("Size"))
                                       .append ($("<th />", { "class": "modified", "scope": "col" }).text ("Modified"))
                                       .append ($("<th />", { "class": "modified-by border-right", "scope": "col" }).text ("Modified By"))))
                     .append (tbody)
                     .append ($("<tfoot />")
                              .append ($("<tr />")
-                                      .append ($("<td />", { "colspan": "4", "class": "border-right border-left" })))));
+                                      .append ($("<td />", { "colspan": "5", "class": "border-right border-left" })))));
 
             for (var i = 0; i < data.actions.length; i++) {
                 action = data.actions[i];
 
-	        row = $("<tr />");
+		row = $("<tr />");
 	        if (i%2) { row.addClass ("odd"); }
-                if (action.action=="DELETE") { row.addClass ("delete"); }
+                if (action.action=="DELETE") {
+		    row.addClass ("delete");
+		}
+		else {
+		    row.addClass ("with-context-menu");
+                    row.attr ("file_version", action.version);
+		    row.attr ("file_hash", action.update.hash);
+		    row.attr ("file_modified_by", action.id.userName);
+		}
+
+                row.attr ("filename", action.filename);
+                row.bind('click', function (e) { openHistoryForItem ($(this).attr ("filename")) });
 
                 row.bind('mouseenter mouseleave', function() {
                     $(this).toggleClass('highlighted');
                 });
 
-                row.attr ("filename", action.filename);
-                row.attr ("file_version", action.version);
-		row.attr ("file_hash", action.hash);
-
-                // row.bind('click', function (e) {
-                //     url = "#fileHistory";
-                //     url += "&item=" + $(this).attr ("filename");
-                //     pos = URIPARAMS.indexOf ("&");
-                //     if (pos >= 0) {
-                //         url += URIPARAMS.substring (pos)
-                //     }
-
-                //     document.location = url;
-                // });
-
 		row.append ($("<td />", { "class": "filename border-left" })
 			    .text (action.filename)
 			    .prepend ($("<img />", { "src": fileExtension(action.filename) })));
 	        row.append ($("<td />", { "class": "version" }).text (action.version));
-	        row.append ($("<td />", { "class": "timestamp" }).text (new Date (action.timestamp)));
+		row.append ($("<td />", { "class": "size" }).text (action.update?SegNumToFileSize (action.update.segNum):""));
+	        row.append ($("<td />", { "class": "timestamp" }).text (new Date (action.timestamp+"+00:00"))); // conversion from UTC timezone (we store action time in UTC)
 	        row.append ($("<td />", { "class": "modified-by border-right" })
 	        	    .append ($("<userName />").text (action.id.userName))
 	        	    .append ($("<seqNo> /").text (action.id.seqNo)));
@@ -292,50 +265,58 @@
 	        tbody = tbody.append (row);
             }
 
-            // if (!PARAMS.offset || PARAMS.offset==0)
-            // {
-            $("#content").fadeOut ("fast", function () {
-                $(this).replaceWith (newcontent);
-                $("#content").fadeIn ("fast");
-            });
+	    displayContent (newcontent, data.more, this.base_url (PAGE));
 
-            self = this; // small "cheat"
-            $("#content-nav").fadeOut ("fast", function () {
-                $("#content-nav a").hide ();
+	    $.contextMenu( 'destroy',  ".with-context-menu" ); // cleanup
+	    $.contextMenu({
+		selector: ".with-context-menu",
+		items: {
+		    "sep1": "---------",
+		    restore: {name: "Restore this revision",
+			      icon: "cut", // need a better icon
+			      callback: function(key, opt) {
+				  filename = opt.$trigger.attr ("filename");
+				  version = opt.$trigger.attr ("file_version");
+				  hash = DataUtils.toNumbers (opt.$trigger.attr ("file_hash"));
+				  console.log (hash);
+				  modified_by = opt.$trigger.attr ("file_modified_by");
 
-                if (PARAMS.offset !== undefined || data.more !== undefined) {
-                    $("#content-nav").fadeIn ("fast");
+				  $("<div />", { "title": "Restore version " + version + "?" })
+				      .append ($("<p />")
+					       .append ($("<span />", { "class": "ui-icon ui-icon-alert",
+								        "style": "float: left; margin: 0 7px 50px 0;" }),
+							$(document.createTextNode ("Are you sure you want restore version ")),
+							$("<green/>").text (version),
+							$(document.createTextNode (" by ")),
+							$("<green/>").text (modified_by)))
+				      .dialog ({
+					  resizable: true,
+					  height:200,
+					  width:300,
+					  modal: true,
+					  buttons: {
+					      "Restore": function() {
+						  self = $(this);
+						  CHRONOSHARE.cmd_restore_file (filename, version, hash, function(didGetData, response) {
+						      if (!didGetData || response != "OK") {
+							  custom_alert (response);
+						      }
+						      console.log (response);
+						      self.dialog ("close");
 
-                    if (data.more !== undefined) {
-                        $("#get-more").show ();
-
-                        $("#get-more").unbind ('click').click (function () {
-                            url = self.base_url (PAGE);
-                            url += "&offset="+data.more;
-
-                            document.location = url;
-                        });
-                    }
-                    if (PARAMS.offset > 0) {
-                        $("#get-less").show ();
-
-                        $("#get-less").unbind ('click').click (function () {
-                            url = self.base_url (PAGE);
-                            if (PARAMS.offset > 1) {
-                                url += "&offset="+(PARAMS.offset - 1);
-                            }
-
-                            document.location = url;
-                        });
-                    }
-                }
-            });
-            // }
-            // else {
-            //     tbody.children ().each (function (index, row) {
-            //         $("#history-list-actions").append (row);
-            //     });
-            // }
+						      $.timer (function() {CHRONOSHARE.run ();}).once (1000);
+						  });
+					      },
+					      Cancel: function() {
+						  $(this).dialog ("close");
+					      }
+					  }
+				      });
+				  // openHistoryForItem (opt.$trigger.attr ("filename"));
+			      }},
+		    "sep2": "---------",
+		}
+	    });
         }
         else if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
             $("#error").html ("Interest timed out");
diff --git a/gui/html/css/style.css b/gui/html/css/style.css
index 62e640f..a4211ca 100644
--- a/gui/html/css/style.css
+++ b/gui/html/css/style.css
@@ -203,6 +203,7 @@
 

 .delete .filename {

     color: red;

+    text-decoration: line-through;

 }

 

 .version {

diff --git a/gui/html/css/text.css b/gui/html/css/text.css
index 2a2fd22..b115a70 100755
--- a/gui/html/css/text.css
+++ b/gui/html/css/text.css
@@ -61,17 +61,17 @@
 /* `Spacing
 ----------------------------------------------------------------------------------------------------*/
 
-ol {
-  list-style: decimal;
-}
+/* ol { */
+/*   list-style: decimal; */
+/* } */
 
-ul {
-  list-style: disc;
-}
+/* ul { */
+/*   list-style: disc; */
+/* } */
 
-li {
-  margin-left: 30px;
-}
+/* li { */
+/*   margin-left: 30px; */
+/* } */
 
 p,
 dl,
diff --git a/gui/html/favicon.ico b/gui/html/favicon.ico
new file mode 100644
index 0000000..b2991e1
--- /dev/null
+++ b/gui/html/favicon.ico
Binary files differ
diff --git a/gui/html/index.html b/gui/html/index.html
index f006cce..ab2409c 100644
--- a/gui/html/index.html
+++ b/gui/html/index.html
@@ -17,8 +17,11 @@
     <script type="text/javascript" src="js/jquery-ui.min.js"></script>
     <script type="text/javascript" src="js/jquery.class.min.js"></script>
     <script type="text/javascript" src="js/jquery.contextMenu.js"></script>
+    <script type="text/javascript" src="js/jquery.timer.js"></script>
 
     <script type="text/javascript" src="js/ndn-js.js"></script>
+
+    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
   </head>
 
   <body>
@@ -30,6 +33,7 @@
         <ul>
           <li><a class="needs-get-url" href="#fileList">All files</a></li>
           <li><a class="needs-get-url" href="#folderHistory">Recent actions</a></li>
+          <li><a class="ajax-action" id="reload-button">Reload current view</a></li>
         </ul>
       </nav>
     </header>
diff --git a/gui/html/js/jquery.timer.js b/gui/html/js/jquery.timer.js
new file mode 100755
index 0000000..0e2e5be
--- /dev/null
+++ b/gui/html/js/jquery.timer.js
@@ -0,0 +1,111 @@
+/**

+ * jquery.timer.js

+ *

+ * Copyright (c) 2011 Jason Chavannes <jason.chavannes@gmail.com>

+ *

+ * http://jchavannes.com/jquery-timer

+ *

+ * Permission is hereby granted, free of charge, to any person

+ * obtaining a copy of this software and associated documentation

+ * files (the "Software"), to deal in the Software without

+ * restriction, including without limitation the rights to use, copy,

+ * modify, merge, publish, distribute, sublicense, and/or sell copies

+ * of the Software, and to permit persons to whom the Software is

+ * furnished to do so, subject to the following conditions:

+ *

+ * The above copyright notice and this permission notice shall be

+ * included in all copies or substantial portions of the Software.

+ *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF

+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND

+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS

+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN

+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN

+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

+ * SOFTWARE.

+ */

+

+;(function($) {

+	$.timer = function(func, time, autostart) {	

+	 	this.set = function(func, time, autostart) {

+	 		this.init = true;

+	 	 	if(typeof func == 'object') {

+		 	 	var paramList = ['autostart', 'time'];

+	 	 	 	for(var arg in paramList) {if(func[paramList[arg]] != undefined) {eval(paramList[arg] + " = func[paramList[arg]]");}};

+ 	 			func = func.action;

+	 	 	}

+	 	 	if(typeof func == 'function') {this.action = func;}

+		 	if(!isNaN(time)) {this.intervalTime = time;}

+		 	if(autostart && !this.isActive) {

+			 	this.isActive = true;

+			 	this.setTimer();

+		 	}

+		 	return this;

+	 	};

+	 	this.once = function(time) {

+			var timer = this;

+	 	 	if(isNaN(time)) {time = 0;}

+			window.setTimeout(function() {timer.action();}, time);

+	 		return this;

+	 	};

+		this.play = function(reset) {

+			if(!this.isActive) {

+				if(reset) {this.setTimer();}

+				else {this.setTimer(this.remaining);}

+				this.isActive = true;

+			}

+			return this;

+		};

+		this.pause = function() {

+			if(this.isActive) {

+				this.isActive = false;

+				this.remaining -= new Date() - this.last;

+				this.clearTimer();

+			}

+			return this;

+		};

+		this.stop = function() {

+			this.isActive = false;

+			this.remaining = this.intervalTime;

+			this.clearTimer();

+			return this;

+		};

+		this.toggle = function(reset) {

+			if(this.isActive) {this.pause();}

+			else if(reset) {this.play(true);}

+			else {this.play();}

+			return this;

+		};

+		this.reset = function() {

+			this.isActive = false;

+			this.play(true);

+			return this;

+		};

+		this.clearTimer = function() {

+			window.clearTimeout(this.timeoutObject);

+		};

+	 	this.setTimer = function(time) {

+			var timer = this;

+	 	 	if(typeof this.action != 'function') {return;}

+	 	 	if(isNaN(time)) {time = this.intervalTime;}

+		 	this.remaining = time;

+	 	 	this.last = new Date();

+			this.clearTimer();

+			this.timeoutObject = window.setTimeout(function() {timer.go();}, time);

+		};

+	 	this.go = function() {

+	 		if(this.isActive) {

+	 			this.action();

+	 			this.setTimer();

+	 		}

+	 	};

+	 	

+	 	if(this.init) {

+	 		return new $.timer(func, time, autostart);

+	 	} else {

+			this.set(func, time, autostart);

+	 		return this;

+	 	}

+	};

+})(jQuery);
\ No newline at end of file
diff --git a/server/mime_types.cpp b/server/mime_types.cpp
index a2d0b15..399000d 100644
--- a/server/mime_types.cpp
+++ b/server/mime_types.cpp
@@ -27,6 +27,7 @@
   { "js", "text/javascript" },
   { "jpg", "image/jpeg" },
   { "png", "image/png" },
+  { "ico", "image/x-icon" },
   { 0, 0 } // Marks end of list.
 };
 
diff --git a/src/action-log.cc b/src/action-log.cc
index 0a05dd2..eb21b0d 100644
--- a/src/action-log.cc
+++ b/src/action-log.cc
@@ -702,10 +702,10 @@
       action.set_filename    (reinterpret_cast<const char *> (sqlite3_column_text  (stmt, 3)), sqlite3_column_bytes (stmt, 3));
       std::string directory  (reinterpret_cast<const char *> (sqlite3_column_text  (stmt, 4)), sqlite3_column_bytes (stmt, 4));
       action.set_version     (sqlite3_column_int64 (stmt, 5));
+      action.set_timestamp   (sqlite3_column_int64 (stmt, 6));
 
       if (action.action () == 0)
         {
-          action.set_timestamp   (sqlite3_column_int64 (stmt, 6));
           action.set_file_hash   (sqlite3_column_blob  (stmt, 7), sqlite3_column_bytes (stmt, 7));
           action.set_mtime       (sqlite3_column_int   (stmt, 8));
           action.set_mode        (sqlite3_column_int   (stmt, 9));
@@ -774,10 +774,10 @@
       action.set_filename    (reinterpret_cast<const char *> (sqlite3_column_text  (stmt, 3)), sqlite3_column_bytes (stmt, 3));
       std::string directory  (reinterpret_cast<const char *> (sqlite3_column_text  (stmt, 4)), sqlite3_column_bytes (stmt, 4));
       action.set_version     (sqlite3_column_int64 (stmt, 5));
+      action.set_timestamp   (sqlite3_column_int64 (stmt, 6));
 
       if (action.action () == 0)
         {
-          action.set_timestamp   (sqlite3_column_int64 (stmt, 6));
           action.set_file_hash   (sqlite3_column_blob  (stmt, 7), sqlite3_column_bytes (stmt, 7));
           action.set_mtime       (sqlite3_column_int   (stmt, 8));
           action.set_mode        (sqlite3_column_int   (stmt, 9));