Merge feature branch 'feature-auto-update'
diff --git a/gui/chronosharegui.cpp b/gui/chronosharegui.cpp
index bbd0286..898f6c4 100644
--- a/gui/chronosharegui.cpp
+++ b/gui/chronosharegui.cpp
@@ -27,6 +27,7 @@
 #include "ccnx-wrapper.h"
 #include <QValidator>
 #include <QDir>
+#include <QFileInfo>
 #include <QDesktopServices>
 
 #include <boost/make_shared.hpp>
@@ -51,7 +52,7 @@
   , m_executor (1)
 #endif
 #ifdef SPARKLE_SUPPORTED
-  , m_autoUpdate(new SparkleAutoUpdate(tr("http://no-url.org")))
+  , m_autoUpdate(new SparkleAutoUpdate(tr("http://irl.cs.ucla.edu")))
 #endif
 {
   setWindowTitle("Settings");
@@ -93,7 +94,7 @@
   setLayout(mainLayout);
 
   // create actions that result from clicking a menu option
-  createActions();
+  createActionsAndMenu();
 
   // create tray icon
   createTrayIcon();
@@ -163,6 +164,7 @@
 
   delete m_watcher; // stop filewatching ASAP
   delete m_dispatcher; // stop dispatcher ASAP, but after watcher (to prevent triggering callbacks on deleted object)
+  m_httpServer->handle_stop();
   delete m_httpServer;
 
   // cleanup
@@ -212,7 +214,7 @@
   messageBox.exec();
 }
 
-void ChronoShareGui::createActions()
+void ChronoShareGui::createActionsAndMenu()
 {
   _LOG_DEBUG ("Create actions");
 
@@ -220,6 +222,19 @@
   m_openFolder = new QAction(tr("&Open Folder"), this);
   connect(m_openFolder, SIGNAL(triggered()), this, SLOT(openSharedFolder()));
 
+  m_openWeb = new QAction(tr("Open in &Browser"), this);
+  connect(m_openWeb, SIGNAL(triggered()), this, SLOT(openInWebBrowser()));
+
+  m_recentFilesMenu = new QMenu(tr("Recently Changed Files"), this);
+  for (int i = 0; i < 5; i++)
+  {
+    m_fileActions[i] = new QAction(this);
+    m_fileActions[i]->setVisible(false);
+    connect(m_fileActions[i], SIGNAL(triggered()), this, SLOT(openFile()));
+    m_recentFilesMenu->addAction(m_fileActions[i]);
+  }
+  connect(m_recentFilesMenu, SIGNAL(aboutToShow()), this, SLOT(updateRecentFilesMenu()));
+
   // create the "view settings" action
   m_viewSettings = new QAction(tr("&View Settings"), this);
   connect(m_viewSettings, SIGNAL(triggered()), this, SLOT(viewSettings()));
@@ -253,10 +268,14 @@
 
   // add actions to the menu
   m_trayIconMenu->addAction(m_openFolder);
+  m_trayIconMenu->addAction(m_openWeb);
+  m_trayIconMenu->addMenu(m_recentFilesMenu);
   m_trayIconMenu->addSeparator();
   m_trayIconMenu->addAction(m_viewSettings);
   m_trayIconMenu->addAction(m_changeFolder);
   m_trayIconMenu->addSeparator();
+  m_trayIconMenu->addAction(m_checkForUpdates);
+  m_trayIconMenu->addSeparator();
   m_trayIconMenu->addAction(m_wifiAction);
   m_trayIconMenu->addSeparator();
   m_trayIconMenu->addAction(m_quitProgram);
@@ -312,13 +331,15 @@
 #endif
 }
 
-#ifdef SPARKLE_SUPPORTED
 void
 ChronoShareGui::onCheckForUpdates()
 {
+#ifdef SPARKLE_SUPPORTED
+  cout << "+++++++++++ trying to update +++++++ " << endl;
   m_autoUpdate->checkForUpdates();
-}
+  cout << "+++++++++++ end trying to update +++++++ " << endl;
 #endif
+}
 
 void ChronoShareGui::setIcon()
 {
@@ -332,6 +353,90 @@
   QDesktopServices::openUrl(QUrl("file:///" + path));
 }
 
+void ChronoShareGui::openInWebBrowser()
+{
+  QDesktopServices::openUrl(QUrl("http://localhost:9001"));
+}
+
+void ChronoShareGui::openFile()
+{
+  // figure out who sent the signal
+  QAction *pAction = qobject_cast<QAction*>(sender());
+  if (pAction->isEnabled())
+  {
+    // we stored full path of the file in this toolTip field
+#ifdef Q_WS_MAC
+    // we do some hack so we could show the file in Finder highlighted
+    QStringList args;
+    args << "-e";
+    args << "tell application \"Finder\"";
+    args << "-e";
+    args << "activate";
+    args << "-e";
+    args << "select POSIX file \"" + pAction->toolTip() + "/" + pAction->text() + "\"";
+    args << "-e";
+    args << "end tell";
+    QProcess::startDetached("osascript", args);
+#else
+    // too bad qt couldn't do highlighting for Linux (or Mac)
+    QDesktopServices::openUrl(QUrl("file:///" + pAction->toolTip()));
+#endif
+  }
+}
+
+void ChronoShareGui::updateRecentFilesMenu()
+{
+  for (int i = 0; i < 5; i++)
+  {
+    m_fileActions[i]->setVisible(false);
+  }
+  m_dispatcher->LookupRecentFileActions(boost::bind(&ChronoShareGui::checkFileAction, this, _1, _2, _3), 5);
+}
+
+void ChronoShareGui::checkFileAction (const std::string &filename, int action, int index)
+{
+  QString fullPath = m_dirPath + "/" + filename.c_str();
+  QFileInfo fileInfo(fullPath);
+  if (fileInfo.exists())
+  {
+    // This is a hack, we just use some field to store the path
+    m_fileActions[index]->setToolTip(fileInfo.absolutePath());
+    m_fileActions[index]->setEnabled(true);
+  }
+  else
+  {
+    // after half an hour frustrating test and search around,
+    // I think it's the problem of Qt.
+    // According to the Qt doc, the action cannot be clicked
+    // and the area would be grey, but it didn't happen
+    // User can still trigger the action, and not greyed
+    // added check in SLOT to see if the action is "enalbed"
+    // as a remedy
+    // Give up at least for now
+    m_fileActions[index]->setEnabled(false);
+    // UPDATE, file not fetched yet
+    if (action == 0)
+    {
+      QFont font;
+      // supposed by change the font, didn't happen
+      font.setWeight(QFont::Light);
+      m_fileActions[index]->setFont(font);
+      m_fileActions[index]->setToolTip(tr("Fetching..."));
+    }
+    // DELETE
+    else
+    {
+      QFont font;
+      // supposed by change the font, didn't happen
+      font.setStrikeOut(true);
+      m_fileActions[index]->setFont(font);
+      m_fileActions[index]->setToolTip(tr("Deleted..."));
+    }
+  }
+  m_fileActions[index]->setText(fileInfo.fileName());
+  m_fileActions[index]->setVisible(true);
+}
+
 void ChronoShareGui::changeSettings()
 {
   if(!editUsername->text().isEmpty())
diff --git a/gui/chronosharegui.h b/gui/chronosharegui.h
index c094f3e..fffaca7 100644
--- a/gui/chronosharegui.h
+++ b/gui/chronosharegui.h
@@ -63,6 +63,12 @@
   // open the shared folder
   void openSharedFolder();
 
+  void openFile();
+
+  void openInWebBrowser();
+
+  void updateRecentFilesMenu();
+
   // open file dialog
   void openFileDialog();
 
@@ -77,13 +83,13 @@
 
   // click on adhoc button
   void onAdHocChange (bool state); // cannot be protected with #ifdef. otherwise something fishy with QT
-#ifdef SPARKLE_SUPPORTED
+
   void onCheckForUpdates();
-#endif
 
 private:
+  void checkFileAction(const std::string &, int, int);
   // create actions that result from clicking a menu option
-  void createActions();
+  void createActionsAndMenu();
 
   // create tray icon
   void createTrayIcon();
@@ -119,6 +125,9 @@
   QAction* m_changeFolder; // change the shared folder action
   QAction* m_quitProgram; // quit program action
   QAction *m_checkForUpdates;
+  QAction *m_openWeb;
+  QMenu *m_recentFilesMenu;
+  QAction *m_fileActions[5];
 
   QAction *m_wifiAction;
 
diff --git a/osx/auto-update/sparkle-auto-update.mm b/osx/auto-update/sparkle-auto-update.mm
index 5b35a76..9429892 100644
--- a/osx/auto-update/sparkle-auto-update.mm
+++ b/osx/auto-update/sparkle-auto-update.mm
@@ -22,6 +22,7 @@
 #include "sparkle-auto-update.h"
 #import <Foundation/Foundation.h>
 #import <Sparkle/Sparkle.h>
+#include <stdio.h>
 
 class SparkleAutoUpdate::Private
 {
@@ -32,14 +33,15 @@
 SparkleAutoUpdate::SparkleAutoUpdate(const QString &updateUrl)
 {
   d = new Private;
-  d->updater = [SUUpdater sharedUpdater];
-  [d->updater setAutomaticallyChecksForUpdates:YES];
+  d->updater = [[SUUpdater sharedUpdater] retain];
+  // [d->updater setAutomaticallyChecksForUpdates:YES];
   NSURL *url = [NSURL URLWithString: [NSString stringWithUTF8String: updateUrl.toUtf8().data()]];
   [d->updater setFeedURL: url];
 }
 
 SparkleAutoUpdate::~SparkleAutoUpdate()
 {
+  [d->updater release];
   delete d;
   // presummably SUUpdater handles garbage collection
 }
@@ -47,4 +49,5 @@
 void SparkleAutoUpdate::checkForUpdates()
 {
   [d->updater checkForUpdatesInBackground];
+  printf("++++++++ checking update ++++++\n");
 }
diff --git a/server/server.hpp b/server/server.hpp
index 0cb3476..408f7b9 100644
--- a/server/server.hpp
+++ b/server/server.hpp
@@ -34,6 +34,9 @@
   /// Run the server's io_service loop.
   void run();
 
+  /// Handle a request to stop the server.
+  void handle_stop();
+
 private:
   /// Initiate an asynchronous accept operation.
   void start_accept();
@@ -41,9 +44,6 @@
   /// Handle completion of an asynchronous accept operation.
   void handle_accept(const boost::system::error_code& e);
 
-  /// Handle a request to stop the server.
-  void handle_stop();
-
   /// The io_service used to perform asynchronous operations.
   boost::asio::io_service io_service_;
 
diff --git a/src/action-log.cc b/src/action-log.cc
index 9d96efc..740430f 100644
--- a/src/action-log.cc
+++ b/src/action-log.cc
@@ -727,6 +727,38 @@
   return (limit == 1); // more data is available
 }
 
+void
+ActionLog::LookupRecentFileActions(const boost::function<void (const string &, int, int)> &visitor, int limit)
+{
+  sqlite3_stmt *stmt;
+
+  sqlite3_prepare_v2 (m_db,
+                          "SELECT AL.filename, AL.action"
+                          "   FROM ActionLog AL"
+                          "   JOIN "
+                          "   (SELECT filename, MAX(action_timestamp) AS action_timestamp "
+                          "       FROM ActionLog "
+                          "       GROUP BY filename ) AS GAL"
+                          "   ON AL.filename = GAL.filename AND AL.action_timestamp = GAL.action_timestamp "
+                          "   ORDER BY AL.action_timestamp DESC "
+                          "   LIMIT ?;",
+                           -1, &stmt, 0);
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_OK, sqlite3_errmsg (m_db));
+  sqlite3_bind_int(stmt, 1, limit);
+  int index = 0;
+  while (sqlite3_step(stmt) == SQLITE_ROW)
+  {
+    std::string filename(reinterpret_cast<const char *> (sqlite3_column_text  (stmt, 0)), sqlite3_column_bytes (stmt, 0));
+    int action = sqlite3_column_int (stmt, 1);
+    visitor(filename, action, index);
+    index++;
+  }
+
+  _LOG_DEBUG_COND (sqlite3_errcode (m_db) != SQLITE_DONE, sqlite3_errmsg (m_db));
+
+  sqlite3_finalize (stmt);
+}
+
 
 ///////////////////////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////////////////////
diff --git a/src/action-log.h b/src/action-log.h
index cef0a65..6345281 100644
--- a/src/action-log.h
+++ b/src/action-log.h
@@ -109,6 +109,9 @@
   LookupActionsInFolderRecursively (const boost::function<void (const Ccnx::Name &name, sqlite3_int64 seq_no, const ActionItem &)> &visitor,
                                     const std::string &folder, int offset=0, int limit=-1);
 
+  void
+  LookupRecentFileActions(const boost::function<void (const std::string &, int, int)> &visitor, int limit = 5);
+
   //
   inline FileStatePtr
   GetFileState ();
diff --git a/src/dispatcher.h b/src/dispatcher.h
index 0455ff0..d3b6bc3 100644
--- a/src/dispatcher.h
+++ b/src/dispatcher.h
@@ -75,6 +75,9 @@
   HashPtr
   SyncRoot() { return m_core->root(); }
 
+  inline void
+  LookupRecentFileActions(const boost::function<void(const std::string &, int, int)> &visitor, int limit) { m_actionLog->LookupRecentFileActions(visitor, limit); }
+
 private:
   void
   Did_LocalFile_AddOrModify_Execute (boost::filesystem::path relativeFilepath); // cannot be const & for Execute event!!! otherwise there will be segfault
diff --git a/wscript b/wscript
index b9b9410..2c30e6c 100644
--- a/wscript
+++ b/wscript
@@ -219,6 +219,8 @@
     <string>APPL</string>
     <key>CFBundleGetInfoString</key>
     <string>Created by Waf</string>
+    <key>CFBundleIdentifier</key>
+    <string>edu.ucla.cs.irl.Chronoshare</string>
     <key>CFBundleSignature</key>
     <string>????</string>
     <key>NOTE</key>