From 9c99ecfcfdbb47656227a6dcd3941a38293d3d7d Mon Sep 17 00:00:00 2001
From: Nick Draper <nick.draper@stfc.ac.uk>
Date: Fri, 3 Jul 2015 17:57:20 +0100
Subject: [PATCH] Added CheckMantidVersion Alg

Still to do documentationand wiring in to startup

re #12064
---
 .../Framework/DataHandling/CMakeLists.txt     |   3 +
 .../MantidDataHandling/CheckMantidVersion.h   |  58 +++++
 .../DataHandling/src/CheckMantidVersion.cpp   | 227 ++++++++++++++++++
 .../test/CheckMantidVersionTest.h             | 137 +++++++++++
 4 files changed, 425 insertions(+)
 create mode 100644 Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/CheckMantidVersion.h
 create mode 100644 Code/Mantid/Framework/DataHandling/src/CheckMantidVersion.cpp
 create mode 100644 Code/Mantid/Framework/DataHandling/test/CheckMantidVersionTest.h

diff --git a/Code/Mantid/Framework/DataHandling/CMakeLists.txt b/Code/Mantid/Framework/DataHandling/CMakeLists.txt
index cb40c02c93b..353a1034547 100644
--- a/Code/Mantid/Framework/DataHandling/CMakeLists.txt
+++ b/Code/Mantid/Framework/DataHandling/CMakeLists.txt
@@ -1,6 +1,7 @@
 set ( SRC_FILES
 	src/AppendGeometryToSNSNexus.cpp
 	src/AsciiPointBase.cpp
+	src/CheckMantidVersion.cpp
 	src/CompressEvents.cpp
 	src/CreateChopperModel.cpp
 	src/CreateChunkingFromInstrument.cpp
@@ -157,6 +158,7 @@ set ( SRC_FILES
 set ( INC_FILES
 	inc/MantidDataHandling/AppendGeometryToSNSNexus.h
 	inc/MantidDataHandling/AsciiPointBase.h
+	inc/MantidDataHandling/CheckMantidVersion.h
 	inc/MantidDataHandling/CompressEvents.h
 	inc/MantidDataHandling/CreateChopperModel.h
 	inc/MantidDataHandling/CreateChunkingFromInstrument.h
@@ -312,6 +314,7 @@ set ( INC_FILES
 
 set ( TEST_FILES
 	AppendGeometryToSNSNexusTest.h
+	CheckMantidVersionTest.h
 	CompressEventsTest.h
 	CreateChopperModelTest.h
 	CreateChunkingFromInstrumentTest.h
diff --git a/Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/CheckMantidVersion.h b/Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/CheckMantidVersion.h
new file mode 100644
index 00000000000..4f0aae1e4bf
--- /dev/null
+++ b/Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/CheckMantidVersion.h
@@ -0,0 +1,58 @@
+#ifndef MANTID_DATAHANDLING_CHECKMANTIDVERSION_H_
+#define MANTID_DATAHANDLING_CHECKMANTIDVERSION_H_
+
+#include "MantidKernel/System.h"
+#include "MantidAPI/Algorithm.h"
+
+namespace Mantid {
+namespace DataHandling {
+
+/** CheckMantidVersion : Checks if the current version of Mantid is the most recent
+
+  Copyright &copy; 2015 ISIS Rutherford Appleton Laboratory, NScD Oak Ridge
+  National Laboratory & European Spallation Source
+
+  This file is part of Mantid.
+
+  Mantid is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 3 of the License, or
+  (at your option) any later version.
+
+  Mantid is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  File change history is stored at: <https://github.com/mantidproject/mantid>
+  Code Documentation is available at: <http://doxygen.mantidproject.org>
+*/
+class DLLExport CheckMantidVersion : public API::Algorithm {
+public:
+  CheckMantidVersion();
+  virtual ~CheckMantidVersion();
+
+  virtual const std::string name() const;
+  virtual int version() const;
+  virtual const std::string category() const;
+  virtual const std::string summary() const;
+protected:
+  virtual std::string getVersionsFromGitHub(const std::string &url);
+  virtual std::string getCurrentVersion() const;
+private:
+  void init();
+  void exec();
+
+  std::string cleanVersionTag(const std::string& versionTag) const;
+  std::vector<int> splitVersionString(const std::string& versionString) const;
+  bool isVersionMoreRecent(const std::string& localVersion, const std::string& gitHubVersion) const;
+
+};
+
+} // namespace DataHandling
+} // namespace Mantid
+
+#endif /* MANTID_DATAHANDLING_CHECKMANTIDVERSION_H_ */
\ No newline at end of file
diff --git a/Code/Mantid/Framework/DataHandling/src/CheckMantidVersion.cpp b/Code/Mantid/Framework/DataHandling/src/CheckMantidVersion.cpp
new file mode 100644
index 00000000000..89c9946799b
--- /dev/null
+++ b/Code/Mantid/Framework/DataHandling/src/CheckMantidVersion.cpp
@@ -0,0 +1,227 @@
+#include "MantidDataHandling/CheckMantidVersion.h"
+#include "MantidKernel/InternetHelper.h"
+#include "MantidKernel/MantidVersion.h"
+#include "MantidKernel/Strings.h"
+
+#include <Poco/DateTimeFormatter.h>
+#include <Poco/DateTimeFormat.h>
+#include <Poco/DateTimeParser.h>
+#include <Poco/StringTokenizer.h>
+
+// jsoncpp
+#include <json/json.h>
+
+#include <boost/lexical_cast.hpp>
+
+namespace Mantid {
+namespace DataHandling {
+
+using namespace Mantid::Kernel;
+using Mantid::API::WorkspaceProperty;
+
+// Register the algorithm into the AlgorithmFactory
+DECLARE_ALGORITHM(CheckMantidVersion)
+
+//----------------------------------------------------------------------------------------------
+/** Constructor
+ */
+CheckMantidVersion::CheckMantidVersion() {}
+
+//----------------------------------------------------------------------------------------------
+/** Destructor
+ */
+CheckMantidVersion::~CheckMantidVersion() {}
+
+//----------------------------------------------------------------------------------------------
+
+/// Algorithms name for identification. @see Algorithm::name
+const std::string CheckMantidVersion::name() const {
+  return "CheckMantidVersion";
+}
+
+/// Algorithm's version for identification. @see Algorithm::version
+int CheckMantidVersion::version() const { return 1; }
+
+/// Algorithm's category for identification. @see Algorithm::category
+const std::string CheckMantidVersion::category() const {
+  return "Utility\\Development";
+}
+
+/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
+const std::string CheckMantidVersion::summary() const {
+  return "Checks if there is a more recent version of Mantid available using "
+         "the Github API";
+}
+
+//----------------------------------------------------------------------------------------------
+/** Initialize the algorithm's properties.
+ */
+void CheckMantidVersion::init() {
+  declareProperty("CurrentVersion", "", Direction::Output);
+  declareProperty("MostRecentVersion", "", Direction::Output);
+  declareProperty("IsNewVersionAvailable", false, Direction::Output);
+}
+
+  //----------------------------------------------------------------------------------------------
+/** Execute the algorithm.
+ */
+void CheckMantidVersion::exec() {
+  std::string currentVersion = getCurrentVersion();
+  std::string mostRecentVersion = "";
+
+  std::string gitHubReleaseUrl = ConfigService::Instance().getString(
+      "CheckMantidVersion.GitHubReleaseURL");
+  if (gitHubReleaseUrl.empty()) {
+    gitHubReleaseUrl =
+        "https://api.github.com/repos/mantidproject/mantid/releases/latest";
+  }
+  std::string downloadUrl =
+      ConfigService::Instance().getString("CheckMantidVersion.DownloadURL");
+  if (downloadUrl.empty()) {
+    downloadUrl = "http://download.mantidproject.org";
+  }
+
+  std::string json = "";
+  try {
+    json = getVersionsFromGitHub(gitHubReleaseUrl);
+  } catch (Exception::InternetError &ex) {
+    if (ex.errorCode() == InternetHelper::HTTP_NOT_MODIFIED) {
+      // No changes since last release
+      mostRecentVersion = getCurrentVersion();
+    } else {
+      throw;
+    }
+  }
+
+  if (!json.empty()) {
+    Json::Reader r;
+    Json::Value root;
+    r.parse(json, root);
+
+    std::string gitHubVersionTag = root["tag_name"].asString();
+    mostRecentVersion = cleanVersionTag(gitHubVersionTag);
+
+  }
+
+  g_log.information("Current Mantid Version: " + currentVersion);
+  g_log.information("Most Recent Mantid Version: " + mostRecentVersion);
+  bool isNewVersionAvailable = isVersionMoreRecent(currentVersion, mostRecentVersion);
+  if (isVersionMoreRecent(currentVersion, mostRecentVersion)) {
+    // output a notice level log
+    g_log.notice("A new version of Mantid(" + mostRecentVersion +
+                 ") is available for download from " + downloadUrl);
+  }
+  setProperty("CurrentVersion", currentVersion);
+  setProperty("MostRecentVersion", mostRecentVersion);
+  setProperty("IsNewVersionAvailable", isNewVersionAvailable);
+}
+
+/** Cleans the tag name from github to make it similar to that from
+* MantidVersion
+* @param versionTag the version tag that needs cleaning
+* @returns a clean string
+*/
+std::string
+CheckMantidVersion::cleanVersionTag(const std::string &versionTag) const {
+  std::string retVal = versionTag;
+
+  retVal = Strings::replaceAll(retVal, "v", "");
+  retVal = Strings::replaceAll(retVal, "V", "");
+  retVal = Strings::strip(retVal);
+
+  return retVal;
+}
+
+/** splits a . separated version string into a vector of integers
+* @param versionString Something like "2.3.4"
+* @returns a vector of [2,3,4]
+*/
+std::vector<int>
+CheckMantidVersion::splitVersionString(const std::string &versionString) const {
+  std::vector<int> retVal;
+  Poco::StringTokenizer tokenizer(versionString, ".",
+                                  Poco::StringTokenizer::TOK_TRIM |
+                                      Poco::StringTokenizer::TOK_IGNORE_EMPTY);
+  Poco::StringTokenizer::Iterator h = tokenizer.begin();
+
+  for (; h != tokenizer.end(); ++h) {
+    try {
+      int part = boost::lexical_cast<int>(*h);
+      retVal.push_back(part);
+    } catch (const boost::bad_lexical_cast &) {
+      g_log.error("Failed to convert the following string to an integer '" +
+                  *h + "' as part of CheckMantidVersion::splitVersionString");
+      retVal.push_back(0);
+    }
+  }
+  return retVal;
+}
+
+/** Compare two version strings, tests if the gitHubVersion is more recent
+* @param localVersion Something like "2.3.4"
+* @param gitHubVersion Something like "2.3.4"
+* @returns True if gitHubVersion is more recent
+*/
+bool CheckMantidVersion::isVersionMoreRecent(
+    const std::string &localVersion, const std::string &gitHubVersion) const {
+  auto localVersionParts = splitVersionString(localVersion);
+  auto gitHubVersionParts = splitVersionString(gitHubVersion);
+
+  for (int i = 0; i < gitHubVersionParts.size(); i++) {
+    // sanity check
+    if (i >= localVersionParts.size()) {
+      // ran out of items to compare
+      break;
+    }
+
+    // the revision number needs to be handled separately
+    if (i == 2) {
+      if (localVersionParts[i] > 2000) {
+        // this is a date string, nightly build
+        // state that the local version is up to date
+        return false;
+      }
+    }
+    if (gitHubVersionParts[i] > localVersionParts[i]) {
+      return true;
+    }
+    if (gitHubVersionParts[i] < localVersionParts[i]) {
+      return false;
+    }
+  }
+  return false;
+}
+
+/** Gets the version json for the most recent release from gitHub
+
+@param urlFile : The url to download the contents of
+@exception Mantid::Kernel::Exception::InternetError : For any unexpected
+behaviour.
+*/
+std::string CheckMantidVersion::getVersionsFromGitHub(const std::string &url) {
+  std::string retVal = "";
+
+  Kernel::InternetHelper inetHelper;
+  std::ostringstream os;
+  int tzd = 0;
+
+  inetHelper.headers().insert(std::make_pair(
+      "if-modified-since",
+      Poco::DateTimeFormatter::format(
+          Poco::DateTimeParser::parse(MantidVersion::releaseDate(), tzd),
+          Poco::DateTimeFormat::HTTP_FORMAT)));
+  inetHelper.sendRequest(url, os);
+  retVal = os.str();
+
+  return retVal;
+}
+/** Gets the version of this Mantid
+@returns a string of the form "1.2.3[.4]"
+*/
+std::string CheckMantidVersion::getCurrentVersion() const
+{
+  return Mantid::Kernel::MantidVersion::version();
+}
+
+} // namespace DataHandling
+} // namespace Mantid
\ No newline at end of file
diff --git a/Code/Mantid/Framework/DataHandling/test/CheckMantidVersionTest.h b/Code/Mantid/Framework/DataHandling/test/CheckMantidVersionTest.h
new file mode 100644
index 00000000000..5aff476cd75
--- /dev/null
+++ b/Code/Mantid/Framework/DataHandling/test/CheckMantidVersionTest.h
@@ -0,0 +1,137 @@
+#ifndef MANTID_DATAHANDLING_CHECKMANTIDVERSIONTEST_H_
+#define MANTID_DATAHANDLING_CHECKMANTIDVERSIONTEST_H_
+
+#include <cxxtest/TestSuite.h>
+
+#include "MantidDataHandling/CheckMantidVersion.h"
+
+using Mantid::DataHandling::CheckMantidVersion;
+using namespace Mantid::API;
+
+namespace {
+/**
+ * Mock out the version calls of this algorithm
+ */
+class MockedCheckMantidVersion : public CheckMantidVersion {
+public:
+  MockedCheckMantidVersion(std::string currentVersion, std::string gitHubVersion) : CurrentVersion(currentVersion),
+    GitHubVersion(gitHubVersion), CheckMantidVersion() {}
+
+  std::string CurrentVersion;
+  std::string GitHubVersion;
+private:
+  virtual std::string getVersionsFromGitHub(const std::string &url) {
+    std::string outputString;
+    outputString ="{\n"
+      "  \"url\": \"https://api.github.com/repos/mantidproject/mantid/releases/1308203\",\n"
+      "  \"assets_url\": \"https://api.github.com/repos/mantidproject/mantid/releases/1308203/assets\",\n"
+      "  \"upload_url\": \"https://uploads.github.com/repos/mantidproject/mantid/releases/1308203/assets{?name}\",\n"
+      "  \"html_url\": \"https://github.com/mantidproject/mantid/releases/tag/v3.4.0\",\n"
+      "  \"id\": 1308203,\n"
+      "  \"tag_name\": \"" + GitHubVersion + "\",\n"
+      "  \"target_commitish\": \"master\",\n"
+      "  \"name\": \"Release version 3.4.0\",\n"
+      "  \"draft\": false,\n"
+      "  \"author\": {\n"
+      "    \"login\": \"peterfpeterson\",\n"
+      "    \"id\": 404003,\n"
+      "    \"avatar_url\": \"https://avatars.githubusercontent.com/u/404003?v=3\",\n"
+      "    \"gravatar_id\": \"\",\n"
+      "    \"url\": \"https://api.github.com/users/peterfpeterson\",\n"
+      "    \"html_url\": \"https://github.com/peterfpeterson\",\n"
+      "    \"followers_url\": \"https://api.github.com/users/peterfpeterson/followers\",\n"
+      "    \"following_url\": \"https://api.github.com/users/peterfpeterson/following{/other_user}\",\n"
+      "    \"gists_url\": \"https://api.github.com/users/peterfpeterson/gists{/gist_id}\",\n"
+      "    \"starred_url\": \"https://api.github.com/users/peterfpeterson/starred{/owner}{/repo}\",\n"
+      "    \"subscriptions_url\": \"https://api.github.com/users/peterfpeterson/subscriptions\",\n"
+      "    \"organizations_url\": \"https://api.github.com/users/peterfpeterson/orgs\",\n"
+      "    \"repos_url\": \"https://api.github.com/users/peterfpeterson/repos\",\n"
+      "    \"events_url\": \"https://api.github.com/users/peterfpeterson/events{/privacy}\",\n"
+      "    \"received_events_url\": \"https://api.github.com/users/peterfpeterson/received_events\",\n"
+      "    \"type\": \"User\",\n"
+      "    \"site_admin\": false\n"
+      "  }";
+
+    return outputString;
+  }
+  virtual std::string getCurrentVersion() const {
+    return CurrentVersion;
+  }
+  
+
+};
+}
+
+class CheckMantidVersionTest : public CxxTest::TestSuite {
+public:
+  // This pair of boilerplate methods prevent the suite being created statically
+  // This means the constructor isn't called when running other tests
+  static CheckMantidVersionTest *createSuite() { return new CheckMantidVersionTest(); }
+  static void destroySuite( CheckMantidVersionTest *suite ) { delete suite; }
+
+
+  void test_Init()
+  {
+    CheckMantidVersion alg;
+    TS_ASSERT_THROWS_NOTHING( alg.initialize() )
+    TS_ASSERT( alg.isInitialized() )
+  }
+
+  void runTest(const std::string& localVersion, const std::string& gitHubVersion, bool expectedResult)
+  {
+    MockedCheckMantidVersion alg(localVersion,gitHubVersion);
+    TS_ASSERT_THROWS_NOTHING( alg.initialize() )
+
+    TS_ASSERT( alg.isInitialized() );
+    TS_ASSERT_THROWS_NOTHING( alg.execute(); );
+    TS_ASSERT( alg.isExecuted() );
+    
+    std::string currentVersion = alg.PropertyManagerOwner::getProperty("CurrentVersion");
+    std::string mostRecentVersion = alg.PropertyManagerOwner::getProperty("MostRecentVersion");
+    bool isNewVersionAvailable = alg.PropertyManagerOwner::getProperty("IsNewVersionAvailable");
+
+    // Check the results
+    TS_ASSERT_EQUALS(alg.CurrentVersion,currentVersion);
+    TS_ASSERT_EQUALS(alg.CurrentVersion,currentVersion);
+    TS_ASSERT_EQUALS(expectedResult,isNewVersionAvailable);
+  }
+
+  void test_execLocalNewerRevision()
+  {
+    runTest("3.4.2","v3.4.0",false);
+  }  
+  void test_execRemoteNewerRevision()
+  {
+    runTest("3.4.0","v3.4.1",true);
+  }
+  void test_execLocaldevelopRevision()
+  {
+    runTest("3.4.20150703.1043","v3.4.0",false);
+  }
+  void test_execLocaldevelopNewerRevision()
+  {
+    runTest("3.4.20150703.1043","v3.4.1",false);
+  }
+
+  void test_execLocalNewerMinor()
+  {
+    runTest("3.5.2","v3.4.7",false);
+  }  
+  void test_execRemoteNewerMinor()
+  {
+    runTest("3.3.7","v3.4.1",true);
+  }
+  void test_execLocalNewerMajor()
+  {
+    runTest("2.0.2","v1.11.7",false);
+  }  
+  void test_execRemoteNewerMajor()
+  {
+    runTest("2.3.7","v3.0.0",true);
+  }
+
+
+};
+
+
+#endif /* MANTID_DATAHANDLING_CHECKMANTIDVERSIONTEST_H_ */
\ No newline at end of file
-- 
GitLab