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));