]> git.mxchange.org Git - simgear.git/commitdiff
Initial work on package management.
authorJames Turner <zakalawe@mac.com>
Wed, 13 Feb 2013 13:32:19 +0000 (13:32 +0000)
committerJames Turner <zakalawe@mac.com>
Wed, 20 Feb 2013 16:17:22 +0000 (16:17 +0000)
Basic library infrastructure, catalog download/refresh, and package install,
uninstall and update. Disabled at cmake time by default, and not yet hooked
into FlightGear.

18 files changed:
CMakeLists.txt
CMakeModules/FindLibArchive.cmake [new file with mode: 0644]
simgear/CMakeLists.txt
simgear/io/HTTPClient.cxx
simgear/io/HTTPClient.hxx
simgear/package/CMakeLists.txt [new file with mode: 0644]
simgear/package/Catalog.cxx [new file with mode: 0644]
simgear/package/Catalog.hxx [new file with mode: 0644]
simgear/package/Delegate.hxx [new file with mode: 0644]
simgear/package/Install.cxx [new file with mode: 0644]
simgear/package/Install.hxx [new file with mode: 0644]
simgear/package/Package.cxx [new file with mode: 0644]
simgear/package/Package.hxx [new file with mode: 0644]
simgear/package/Root.cxx [new file with mode: 0644]
simgear/package/Root.hxx [new file with mode: 0644]
simgear/package/md5.c [new file with mode: 0644]
simgear/package/md5.h [new file with mode: 0644]
simgear/package/pkgutil.cxx [new file with mode: 0644]

index f6dd5fedb79c77c158cd19ddf2d24537cf2cda24..1a077f15f0a3bd4f4d348e838b23aaad29a5020a 100644 (file)
@@ -112,6 +112,7 @@ option(ENABLE_LIBSVN    "Set to ON to build SimGear with libsvnclient support" O
 option(ENABLE_RTI       "Set to ON to build SimGear with RTI support" OFF)
 option(ENABLE_TESTS     "Set to OFF to disable building SimGear's test applications" ON)
 option(ENABLE_SOUND     "Set to OFF to disable building SimGear's sound support" ON)
+option(ENABLE_PACKAGE   "Set to ON to build package-management support" OFF)
 
 if (MSVC)
   GET_FILENAME_COMPONENT(PARENT_DIR ${PROJECT_SOURCE_DIR} PATH)
@@ -223,6 +224,13 @@ else()
     add_definitions(-DHAVE_EXPAT_CONFIG_H)
 endif(SYSTEM_EXPAT)
 
+if (ENABLE_PACKAGE)
+    message(STATUS "package management: ENABLED: libArchive is needed")
+    find_package(LibArchive REQUIRED)
+else()
+    message(STATUS "package management: DISABLED")
+endif(ENABLE_PACKAGE)
+
 check_include_file(inttypes.h HAVE_INTTYPES_H)
 check_include_file(sys/time.h HAVE_SYS_TIME_H)
 check_include_file(sys/timeb.h HAVE_SYS_TIMEB_H)
@@ -335,8 +343,11 @@ include_directories(${PROJECT_BINARY_DIR}/simgear)
 include_directories(${PROJECT_BINARY_DIR}/simgear/xml)
 
 include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS} 
-    ${Boost_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIR} 
-    ${OPENAL_INCLUDE_DIR} )
+    ${Boost_INCLUDE_DIRS} 
+    ${ZLIB_INCLUDE_DIR} 
+    ${OPENAL_INCLUDE_DIR}
+    ${LibArchive_INCLUDE_DIRS}
+)
 
 add_definitions(-DHAVE_CONFIG_H)
 
diff --git a/CMakeModules/FindLibArchive.cmake b/CMakeModules/FindLibArchive.cmake
new file mode 100644 (file)
index 0000000..5929b24
--- /dev/null
@@ -0,0 +1,77 @@
+# - Find libarchive library and headers
+# The module defines the following variables:
+#
+#  LibArchive_FOUND        - true if libarchive was found
+#  LibArchive_INCLUDE_DIRS - include search path
+#  LibArchive_LIBRARIES    - libraries to link
+#  LibArchive_VERSION      - libarchive 3-component version number
+
+#=============================================================================
+# Copyright 2010 Kitware, Inc.
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# see accompanying file Copyright.txt for details.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+# (To distribute this file outside of CMake, substitute the full
+#  License text for the above reference.)
+
+find_path(LibArchive_INCLUDE_DIR
+  NAMES archive.h
+  PATHS
+  ${CMAKE_INSTALL_PREFIX}
+  ${ADDITIONAL_LIBRARY_PATHS}
+  "[HKEY_LOCAL_MACHINE\\SOFTWARE\\GnuWin32\\LibArchive;InstallPath]/include"
+  )
+
+# NO_DEFAULT_PATH is important on Mac - the libarchive in /usr/lib
+# is too old, and there's no matching headers :(
+find_library(LibArchive_LIBRARY
+if(APPLE)
+      NO_DEFAULT_PATH
+endif(APPLE)
+  NAMES archive libarchive
+  PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64
+  PATHS
+  ${CMAKE_INSTALL_PREFIX}
+  ${ADDITIONAL_LIBRARY_PATHS}
+  "[HKEY_LOCAL_MACHINE\\SOFTWARE\\GnuWin32\\LibArchive;InstallPath]/lib"
+  )
+
+mark_as_advanced(LibArchive_INCLUDE_DIR LibArchive_LIBRARY)
+
+# Extract the version number from the header.
+if(LibArchive_INCLUDE_DIR AND EXISTS "${LibArchive_INCLUDE_DIR}/archive.h")
+  # The version string appears in one of two known formats in the header:
+  #  #define ARCHIVE_LIBRARY_VERSION "libarchive 2.4.12"
+  #  #define ARCHIVE_VERSION_STRING "libarchive 2.8.4"
+  # Match either format.
+  set(_LibArchive_VERSION_REGEX "^#define[ \t]+ARCHIVE[_A-Z]+VERSION[_A-Z]*[ \t]+\"libarchive +([0-9]+)\\.([0-9]+)\\.([0-9]+)[^\"]*\".*$")
+  file(STRINGS "${LibArchive_INCLUDE_DIR}/archive.h" _LibArchive_VERSION_STRING LIMIT_COUNT 1 REGEX "${_LibArchive_VERSION_REGEX}")
+  if(_LibArchive_VERSION_STRING)
+    string(REGEX REPLACE "${_LibArchive_VERSION_REGEX}" "\\1.\\2.\\3" LibArchive_VERSION "${_LibArchive_VERSION_STRING}")
+  endif()
+  unset(_LibArchive_VERSION_REGEX)
+  unset(_LibArchive_VERSION_STRING)
+endif()
+
+# Handle the QUIETLY and REQUIRED arguments and set LIBARCHIVE_FOUND
+# to TRUE if all listed variables are TRUE.
+# (Use ${CMAKE_ROOT}/Modules instead of ${CMAKE_CURRENT_LIST_DIR} because CMake
+#  itself includes this FindLibArchive when built with an older CMake that does
+#  not provide it.  The older CMake also does not have CMAKE_CURRENT_LIST_DIR.)
+include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
+find_package_handle_standard_args(LibArchive
+                                  REQUIRED_VARS LibArchive_LIBRARY LibArchive_INCLUDE_DIR
+                                  VERSION_VAR LibArchive_VERSION
+  )
+set(LibArchive_FOUND ${LIBARCHIVE_FOUND})
+unset(LIBARCHIVE_FOUND)
+
+if(LibArchive_FOUND)
+  set(LibArchive_INCLUDE_DIRS ${LibArchive_INCLUDE_DIR})
+  set(LibArchive_LIBRARIES    ${LibArchive_LIBRARY})
+endif()
index b4e299ae7c4470606d66ab9b3bc9a03290d8c799..78dad8c7b80766396caf1a26f881f608a7dabd70 100644 (file)
@@ -24,6 +24,10 @@ foreach( mylibfolder
 
 endforeach( mylibfolder )
 
+if (ENABLE_PACKAGE)
+    add_subdirectory(package)
+endif(ENABLE_PACKAGE)
+
 if(NOT SIMGEAR_HEADLESS)
     add_subdirectory(canvas)
     add_subdirectory(environment)
@@ -63,6 +67,7 @@ if(SIMGEAR_SHARED)
     set_property(TARGET SimGearCore PROPERTY SOVERSION ${SIMGEAR_SOVERSION})
 
     target_link_libraries(SimGearCore ${ZLIB_LIBRARY} ${RT_LIBRARY} 
+        ${LibArchive_LIBRARIES}
         ${EXPAT_LIBRARIES}
         ${CMAKE_THREAD_LIBS_INIT})
 
index 8a48bff6386deb9e97c75cad39f44f19ae5ccca2..7a32ab4903090c0a128511416dde86b7e7a48ec2 100644 (file)
@@ -455,6 +455,11 @@ public:
     {
       return !queuedRequests.empty() && (sentRequests.size() < MAX_INFLIGHT_REQUESTS);
     }
+    
+    bool isActive() const
+    {
+        return !queuedRequests.empty() || !sentRequests.empty();
+    }
 private:
     bool connectToHost()
     {
@@ -733,6 +738,16 @@ void Client::setProxy(const string& proxy, int port, const string& auth)
     _proxyAuth = auth;
 }
 
+bool Client::hasActiveRequests() const
+{
+    ConnectionDict::const_iterator it = _connections.begin();
+    for (; it != _connections.end(); ++it) {
+        if (it->second->isActive()) return true;
+    }
+    
+    return false;
+}
+
 } // of namespace HTTP
 
 } // of namespace simgear
index f949c7bece1a33acb26adef224c5732c31cad38b..f200b9f44c9f11aed2c96734141018f269078776 100644 (file)
@@ -33,6 +33,12 @@ public:
         
     const std::string& proxyAuth() const
         { return _proxyAuth; }
+    
+    /**
+     * predicate, check if at least one connection is active, with at
+     * least one request active or queued.
+     */
+    bool hasActiveRequests() const; 
 private:
     void requestFinished(Connection* con);
     
diff --git a/simgear/package/CMakeLists.txt b/simgear/package/CMakeLists.txt
new file mode 100644 (file)
index 0000000..4c41020
--- /dev/null
@@ -0,0 +1,41 @@
+
+include (SimGearComponent)
+
+set(HEADERS 
+    Catalog.hxx
+    Package.hxx
+    Install.hxx
+    Root.hxx
+    Delegate.hxx
+    )
+
+set(SOURCES 
+    Catalog.cxx
+    Package.cxx
+    Install.cxx
+    Root.cxx
+    md5.c
+    )
+
+simgear_component(package package "${SOURCES}" "${HEADERS}")
+
+if (SIMGEAR_SHARED)
+    set(APP_LIBS SimGearCore)
+else()
+    set(APP_LIBS
+        ${LibArchive_LIBRARIES}
+        SimGearCore
+        ${CMAKE_THREAD_LIBS_INIT}
+        ${WINSOCK_LIBRARY}
+        ${ZLIB_LIBRARY}
+        ${RT_LIBRARY}
+    )
+endif()
+
+add_executable(sg_pkgutil pkgutil.cxx)
+target_link_libraries(sg_pkgutil ${APP_LIBS})
+
+if(ENABLE_TESTS)
+
+
+endif(ENABLE_TESTS)
diff --git a/simgear/package/Catalog.cxx b/simgear/package/Catalog.cxx
new file mode 100644 (file)
index 0000000..e8a1757
--- /dev/null
@@ -0,0 +1,267 @@
+
+
+#include <simgear/package/Catalog.hxx>
+
+#include <boost/foreach.hpp>
+#include <fstream>
+#include <cstring>
+
+#include <simgear/debug/logstream.hxx>
+#include <simgear/props/props_io.hxx>
+#include <simgear/io/HTTPRequest.hxx>
+#include <simgear/io/HTTPClient.hxx>
+#include <simgear/misc/sg_dir.hxx>
+#include <simgear/structure/exception.hxx>
+#include <simgear/package/Package.hxx>
+#include <simgear/package/Root.hxx>
+#include <simgear/package/Install.hxx>
+
+namespace simgear {
+    
+namespace pkg {
+
+CatalogList static_catalogs;
+
+//////////////////////////////////////////////////////////////////////////////
+
+class Catalog::Downloader : public HTTP::Request
+{
+public:
+    Downloader(Catalog* aOwner, const std::string& aUrl) :
+        HTTP::Request(aUrl),
+        m_owner(aOwner)
+    {        
+    }
+    
+protected:
+    virtual void responseHeadersComplete()
+    {
+        
+    }
+    
+    virtual void gotBodyData(const char* s, int n)
+    {
+        m_buffer += std::string(s, n);
+    }
+    
+    virtual void responseComplete()
+    {        
+        if (responseCode() != 200) {
+            SG_LOG(SG_GENERAL, SG_ALERT, "catalog download failure:" << m_owner->url());
+            m_owner->refreshComplete(false);
+            return;
+        }
+        
+        SGPropertyNode* props = new SGPropertyNode;
+        
+        try {
+            readProperties(m_buffer.data(), m_buffer.size(), props);
+            m_owner->parseProps(props);
+        } catch (sg_exception& e) {
+            SG_LOG(SG_GENERAL, SG_ALERT, "catalog parse failure:" << m_owner->url());
+            m_owner->refreshComplete(false);
+            return;
+        }
+        
+        // cache the catalog data, now we have a valid install root
+        Dir d(m_owner->installRoot());
+        SGPath p = d.file("catalog.xml");
+
+        std::ofstream f(p.c_str(), std::ios::out | std::ios::trunc);
+        f.write(m_buffer.data(), m_buffer.size());
+        f.close();
+        
+        time(&m_owner->m_retrievedTime);
+        m_owner->writeTimestamp();
+        m_owner->refreshComplete(true);
+    }
+    
+private:
+    Catalog* m_owner;  
+    std::string m_buffer;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+CatalogList Catalog::allCatalogs()
+{
+    return static_catalogs;
+}
+
+Catalog::Catalog(Root *aRoot) :
+    m_root(aRoot),
+    m_retrievedTime(0)
+{
+    static_catalogs.push_back(this);
+}
+
+Catalog::~Catalog()
+{
+    CatalogList::iterator it = std::find(static_catalogs.begin(), static_catalogs.end(), this);
+    static_catalogs.erase(it);
+}
+
+Catalog* Catalog::createFromUrl(Root* aRoot, const std::string& aUrl)
+{
+    Catalog* c = new Catalog(aRoot);
+    Downloader* dl = new Downloader(c, aUrl);
+    aRoot->getHTTPClient()->makeRequest(dl);
+    
+    return c;
+}
+    
+Catalog* Catalog::createFromPath(Root* aRoot, const SGPath& aPath)
+{
+    SGPath xml = aPath;
+    xml.append("catalog.xml");
+    if (!xml.exists()) {
+        return NULL;
+    }
+    
+    SGPropertyNode_ptr props;
+    try {
+        props = new SGPropertyNode;
+        readProperties(xml.str(), props);
+    } catch (sg_exception& e) {
+        return NULL;    
+    }
+    
+    Catalog* c = new Catalog(aRoot);
+    c->m_installRoot = aPath;
+    c->parseProps(props);
+    c->parseTimestamp();
+    
+    return c;
+}
+
+PackageList
+Catalog::packagesMatching(const SGPropertyNode* aFilter) const
+{
+    PackageList r;
+    BOOST_FOREACH(Package* p, m_packages) {
+        if (p->matches(aFilter)) {
+            r.push_back(p);
+        }
+    }
+    return r;
+}
+
+PackageList
+Catalog::packagesNeedingUpdate() const
+{
+    PackageList r;
+    BOOST_FOREACH(Package* p, m_packages) {
+        if (!p->isInstalled()) {
+            continue;
+        }
+        
+        if (p->install()->hasUpdate()) {
+            r.push_back(p);
+        }
+    }
+    return r;
+}
+
+void Catalog::refresh()
+{
+    Downloader* dl = new Downloader(this, url());
+    m_root->getHTTPClient()->makeRequest(dl);
+    m_root->catalogRefreshBegin(this);
+}
+
+void Catalog::parseProps(const SGPropertyNode* aProps)
+{
+    // copy everything except package children?
+    m_props = new SGPropertyNode;
+    
+    int nChildren = aProps->nChildren();
+    for (int i = 0; i < nChildren; i++) {
+        const SGPropertyNode* pkgProps = aProps->getChild(i);
+        if (strcmp(pkgProps->getName(), "package") == 0) {
+            Package* p = new Package(pkgProps, this);
+            m_packages.push_back(p);   
+        } else {
+            SGPropertyNode* c = m_props->getChild(pkgProps->getName(), pkgProps->getIndex(), true);
+            copyProperties(pkgProps, c);
+        }
+    } // of children iteration
+    
+    if (m_installRoot.isNull()) {
+        m_installRoot = m_root->path();
+        m_installRoot.append(id());
+        
+        Dir d(m_installRoot);
+        d.create(0755);
+    }
+}
+
+Package* Catalog::getPackageById(const std::string& aId) const
+{
+    BOOST_FOREACH(Package* p, m_packages) {
+        if (p->id() == aId) {
+            return p;
+        }
+    }
+    
+    return NULL; // not found
+}
+
+std::string Catalog::id() const
+{
+    return m_props->getStringValue("id");
+}
+
+std::string Catalog::url() const
+{
+    return m_props->getStringValue("url");
+}
+
+std::string Catalog::description() const
+{
+    return getLocalisedString(m_props, "description");
+}
+
+void Catalog::parseTimestamp()
+{
+    SGPath timestampFile = m_installRoot;
+    timestampFile.append(".timestamp");
+    std::ifstream f(timestampFile.c_str(), std::ios::in);
+    f >> m_retrievedTime;
+}
+
+void Catalog::writeTimestamp()
+{
+    SGPath timestampFile = m_installRoot;
+    timestampFile.append(".timestamp");
+    std::ofstream f(timestampFile.c_str(), std::ios::out | std::ios::trunc);
+    f << m_retrievedTime << std::endl;
+}
+
+int Catalog::ageInSeconds() const
+{
+    time_t now;
+    time(&now);
+    return ::difftime(now, m_retrievedTime);
+}
+
+std::string Catalog::getLocalisedString(const SGPropertyNode* aRoot, const char* aName) const
+{
+    if (aRoot->hasChild(m_root->getLocale())) {
+        const SGPropertyNode* localeRoot = aRoot->getChild(m_root->getLocale().c_str());
+        if (localeRoot->hasChild(aName)) {
+            return localeRoot->getStringValue(aName);
+        }
+    }
+    
+    return aRoot->getStringValue(aName);
+}
+
+void Catalog::refreshComplete(bool aSuccess)
+{
+    m_root->catalogRefreshComplete(this, aSuccess);
+}
+
+
+} // of namespace pkg
+
+} // of namespace simgear
diff --git a/simgear/package/Catalog.hxx b/simgear/package/Catalog.hxx
new file mode 100644 (file)
index 0000000..49fd2af
--- /dev/null
@@ -0,0 +1,95 @@
+#ifndef SG_PACKAGE_CATALOG_HXX
+#define SG_PACKAGE_CATALOG_HXX
+
+#include <vector>
+#include <ctime>
+
+#include <simgear/misc/sg_path.hxx>
+#include <simgear/props/props.hxx>
+
+namespace simgear
+{
+    
+namespace HTTP { class Client; }
+    
+namespace pkg
+{
+
+// forward decls
+class Package;
+class Catalog;
+class Root;
+
+typedef std::vector<Package*> PackageList;
+typedef std::vector<Catalog*> CatalogList;
+
+class Catalog
+{
+public:
+    virtual ~Catalog();
+    
+    static Catalog* createFromUrl(Root* aRoot, const std::string& aUrl);
+    
+    static Catalog* createFromPath(Root* aRoot, const SGPath& aPath);
+    
+    static CatalogList allCatalogs();
+    
+    Root* root() const
+        { return m_root;};
+    
+    /**
+     * perform a refresh of the catalog contents
+     */
+    void refresh();
+    /**
+     * retrieve packages in this catalog matching a filter.
+     * filter consists of required / minimum values, AND-ed together.
+     */
+    PackageList packagesMatching(const SGPropertyNode* aFilter) const;
+    
+    /**
+     * retrieve all the packages in the catalog which are installed
+     * and have a pendig update
+     */ 
+    PackageList packagesNeedingUpdate() const;
+     
+    SGPath installRoot() const
+         { return m_installRoot; }
+    
+    std::string id() const;
+    
+    std::string url() const;
+    
+    std::string description() const;
+    
+    Package* getPackageById(const std::string& aId) const;
+    
+    int ageInSeconds() const;
+private:
+    Catalog(Root* aRoot);
+    
+    class Downloader;
+    friend class Downloader;
+    
+    void parseProps(const SGPropertyNode* aProps);
+    
+    void refreshComplete(bool aSuccess);
+    
+    void parseTimestamp();
+    void writeTimestamp();
+    
+    std::string getLocalisedString(const SGPropertyNode* aRoot, const char* aName) const;
+    
+    Root* m_root;
+    SGPropertyNode_ptr m_props;
+    SGPath m_installRoot;
+    
+    PackageList m_packages;
+    time_t m_retrievedTime;
+};  
+    
+} // of namespace pkg
+
+} // of namespace simgear
+
+#endif // of SG_PACKAGE_CATALOG_HXX
diff --git a/simgear/package/Delegate.hxx b/simgear/package/Delegate.hxx
new file mode 100644 (file)
index 0000000..2925d24
--- /dev/null
@@ -0,0 +1,41 @@
+
+
+#ifndef SG_PACKAGE_DELEGATE_HXX
+#define SG_PACKAGE_DELEGATE_HXX
+
+namespace simgear
+{
+        
+namespace pkg
+{
+    
+class Install;
+
+class Delegate
+{
+public:
+    virtual ~Delegate() { }
+    
+    virtual void refreshComplete() = 0;
+    
+    virtual void startInstall(Install* aInstall) = 0;
+    virtual void installProgress(Install* aInstall, unsigned int aBytes, unsigned int aTotal) = 0;
+    virtual void finishInstall(Install* aInstall) = 0;
+    
+    typedef enum {
+        FAIL_UNKNOWN = 0,
+        FAIL_CHECKSUM,
+        FAIL_DOWNLOAD,
+        FAIL_EXTRACT,
+        FAIL_FILESYSTEM
+    } FailureCode;
+    
+    virtual void failedInstall(Install* aInstall, FailureCode aReason) = 0;
+    
+};  
+    
+} // of namespace pkg
+
+} // of namespace simgear
+
+#endif // of SG_PACKAGE_DELEGATE_HXX
diff --git a/simgear/package/Install.cxx b/simgear/package/Install.cxx
new file mode 100644 (file)
index 0000000..7d5b66d
--- /dev/null
@@ -0,0 +1,218 @@
+
+
+#include <simgear/package/Install.hxx>
+
+#include <boost/foreach.hpp>
+#include <fstream>
+
+// libarchive support
+#include <archive.h>
+#include <archive_entry.h>
+
+#include <simgear/package/md5.h>
+
+#include <simgear/structure/exception.hxx>
+#include <simgear/package/Catalog.hxx>
+#include <simgear/package/Package.hxx>
+#include <simgear/package/Root.hxx>
+#include <simgear/io/HTTPRequest.hxx>
+#include <simgear/io/HTTPClient.hxx>
+#include <simgear/misc/sg_dir.hxx>
+
+namespace simgear {
+    
+namespace pkg {
+
+class Install::PackageArchiveDownloader : public HTTP::Request
+{
+public:
+    PackageArchiveDownloader(Install* aOwner) :
+        HTTP::Request("" /* dummy URL */),
+        m_owner(aOwner)
+    {
+        m_urls = m_owner->package()->downloadUrls();
+        if (m_urls.empty()) {
+            throw sg_exception("no package download URLs");
+        }
+        
+        // TODO randomise order of m_urls
+        
+        m_extractPath = aOwner->path().dir();
+        m_extractPath.append("_DOWNLOAD"); // add some temporary value
+        
+    }
+    
+protected:
+    virtual std::string url() const
+    {
+        return m_urls.front();
+    }
+    
+    virtual void responseHeadersComplete()
+    {
+        std::cout << "starting download of " << m_owner->package()->id() << " from "
+            << url() << std::endl;
+        Dir d(m_extractPath);
+        d.create(0755);        
+        
+        memset(&m_md5, 0, sizeof(MD5_CTX));
+        MD5Init(&m_md5);
+    }
+    
+    virtual void gotBodyData(const char* s, int n)
+    {
+        m_buffer += std::string(s, n);
+        MD5Update(&m_md5, (unsigned char*) s, n);
+        std::cout << "got " << m_buffer.size() << " bytes" << std::endl;
+    }
+    
+    virtual void responseComplete()
+    {
+        if (responseCode() != 200) {
+            SG_LOG(SG_GENERAL, SG_ALERT, "download failure");
+            doFailure();
+            return;
+        }
+        std::cout << "content lenth:" << responseLength() << std::endl;
+        std::cout << m_buffer.size() << " total received" << std::endl;
+        MD5Final(&m_md5);
+    // convert final sum to hex
+        const char hexChar[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+        std::stringstream hexMd5;
+        for (int i=0; i<16;++i) {
+            hexMd5 << hexChar[m_md5.digest[i] >> 4];
+            hexMd5 << hexChar[m_md5.digest[i] & 0x0f];
+        }
+        
+        if (hexMd5.str() != m_owner->package()->md5()) {
+            SG_LOG(SG_GENERAL, SG_ALERT, "md5 verification failed:\n"
+                << "\t" << hexMd5.str() << "\n\t"
+                << m_owner->package()->md5() << "\n\t"
+                << "downloading from:" << url());
+            doFailure();
+            return;
+        } else {
+            std::cout << "MD5 checksum is ok" << std::endl;
+        }
+                
+        struct archive* a = archive_read_new();
+        archive_read_support_filter_all(a);
+        archive_read_support_format_all(a);
+        int result = archive_read_open_memory(a, (void*) m_buffer.data(), m_buffer.size());
+        
+        if (result != ARCHIVE_OK) {
+            doFailure();
+            return;
+        }
+        
+        struct archive_entry* entry;
+        while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
+            SGPath finalPath(m_extractPath);
+            finalPath.append(archive_entry_pathname(entry));
+         //   std::cout << "writing:" << finalPath << std::endl;
+            archive_entry_set_pathname(entry, finalPath.c_str());            
+            archive_read_extract(a, entry, 0);
+        }
+        
+        archive_read_free(a);
+                  
+        if (m_owner->path().exists()) {
+            std::cout << "removing existing path" << std::endl;
+            Dir destDir(m_owner->path());
+            destDir.remove(true /* recursive */);
+        }
+        
+        std::cout << "renaming to " << m_owner->path() << std::endl;
+        m_extractPath.append(m_owner->package()->id());
+        m_extractPath.rename(m_owner->path());
+        
+        m_owner->m_revision = m_owner->package()->revision();
+        m_owner->writeRevisionFile();
+    }
+    
+private:
+    void doFailure()
+    {
+        Dir dir(m_extractPath);
+        dir.remove(true /* recursive */);
+        if (m_urls.size() == 1) {
+            
+            return;
+        }
+        
+        m_urls.erase(m_urls.begin()); // pop first URL
+    }
+    
+    Install* m_owner;
+    string_list m_urls;
+    MD5_CTX m_md5;
+    std::string m_buffer;
+    SGPath m_extractPath;
+};
+
+////////////////////////////////////////////////////////////////////
+    
+Install::Install(Package* aPkg, const SGPath& aPath) :
+    m_package(aPkg),
+    m_path(aPath),
+    m_download(NULL)
+{
+    parseRevision();
+}
+
+Install* Install::createFromPath(const SGPath& aPath, Catalog* aCat)
+{
+    std::string id = aPath.file();
+    Package* pkg = aCat->getPackageById(id);
+    if (!pkg)
+        throw sg_exception("no package with id:" + id);
+    
+    return new Install(pkg, aPath);
+}
+
+void Install::parseRevision()
+{
+    SGPath revisionFile = m_path;
+    revisionFile.append(".revision");
+    if (!revisionFile.exists()) {
+        m_revision = 0;
+        return;
+    }
+    
+    std::ifstream f(revisionFile.c_str(), std::ios::in);
+    f >> m_revision;
+}
+
+void Install::writeRevisionFile()
+{
+    SGPath revisionFile = m_path;
+    revisionFile.append(".revision");
+    std::ofstream f(revisionFile.c_str(), std::ios::out | std::ios::trunc);
+    f << m_revision << std::endl;
+}
+
+bool Install::hasUpdate() const
+{
+    return m_package->revision() > m_revision;
+}
+
+void Install::startUpdate()
+{
+    if (m_download) {
+        return; // already active
+    }
+    
+    m_download = new PackageArchiveDownloader(this);
+    m_package->catalog()->root()->getHTTPClient()->makeRequest(m_download);
+}
+
+void Install::uninstall()
+{
+    Dir d(m_path);
+    d.remove(true);
+    delete this;
+}
+
+} // of namespace pkg
+
+} // of namespace simgear
diff --git a/simgear/package/Install.hxx b/simgear/package/Install.hxx
new file mode 100644 (file)
index 0000000..92e2c4f
--- /dev/null
@@ -0,0 +1,72 @@
+#ifndef SG_PACKAGE_INSTALL_HXX
+#define SG_PACKAGE_INSTALL_HXX
+
+#include <vector>
+
+#include <simgear/misc/sg_path.hxx>
+
+namespace simgear
+{
+    
+namespace pkg
+{
+
+// forward decls
+class Package;
+class Catalog;
+    
+/**
+ *
+ */
+class Install
+{
+public:
+    /**
+     * create from a directory on disk, or fail.
+     */
+    static Install* createFromPath(const SGPath& aPath, Catalog* aCat);
+    
+    unsigned int revsion() const
+        { return m_revision; }
+    
+    Package* package() const
+        { return m_package; } 
+    
+    SGPath path() const
+        { return m_path; }
+    
+    bool hasUpdate() const;
+    
+    void startUpdate();
+    
+    void uninstall();
+    
+// boost signals time?
+    // failure
+    // progress
+    // completed
+    
+private:
+    friend class Package;
+    
+    class PackageArchiveDownloader;
+    friend class PackageArchiveDownloader;
+    
+    Install(Package* aPkg, const SGPath& aPath);
+    
+    void parseRevision();
+    void writeRevisionFile();
+    
+    Package* m_package;
+    unsigned int m_revision; ///< revision on disk
+    SGPath m_path; ///< installation point on disk
+    
+    PackageArchiveDownloader* m_download;
+};
+    
+    
+} // of namespace pkg
+
+} // of namespace simgear
+
+#endif // of SG_PACKAGE_CATALOG_HXX
diff --git a/simgear/package/Package.cxx b/simgear/package/Package.cxx
new file mode 100644 (file)
index 0000000..1f43d3a
--- /dev/null
@@ -0,0 +1,126 @@
+
+
+#include <simgear/package/Package.hxx>
+
+#include <boost/foreach.hpp>
+
+#include <simgear/debug/logstream.hxx> 
+#include <simgear/package/Catalog.hxx>
+#include <simgear/package/Install.hxx>
+#include <simgear/package/Root.hxx>
+
+namespace simgear {
+    
+namespace pkg {
+
+Package::Package(const SGPropertyNode* aProps, Catalog* aCatalog) :
+    m_catalog(aCatalog)
+{
+    initWithProps(aProps);
+}
+
+void Package::initWithProps(const SGPropertyNode* aProps)
+{
+    m_props = const_cast<SGPropertyNode*>(aProps);
+// cache tag values
+    BOOST_FOREACH(const SGPropertyNode* c, aProps->getChildren("tag")) {
+        m_tags.insert(c->getStringValue());
+    }
+}
+
+bool Package::matches(const SGPropertyNode* aFilter) const
+{
+    int nChildren = aFilter->nChildren();
+    for (int i = 0; i < nChildren; i++) {
+        const SGPropertyNode* c = aFilter->getChild(i);
+        if (strutils::starts_with(c->getName(), "rating-")) {
+            int minRating = c->getIntValue();
+            std::string rname = c->getName() + 7;
+            int ourRating = m_props->getChild("rating")->getIntValue(rname, 0);
+            if (ourRating < minRating) {
+                return false;
+            }
+        }
+        
+        if (strcmp(c->getName(), "tag") == 0) {
+            std::string tag(c->getStringValue());
+            if (m_tags.find(tag) == m_tags.end()) {
+                return false;
+            }
+        }
+        
+        SG_LOG(SG_GENERAL, SG_WARN, "unknown filter term:" << c->getName());
+    } // of filter props iteration
+    
+    return true;
+}
+
+bool Package::isInstalled() const
+{
+    SGPath p(m_catalog->installRoot());
+    p.append("Aircraft");
+    p.append(id());
+    
+    // anything to check for? look for a valid revision file?
+    return p.exists();
+}
+
+Install* Package::install()
+{
+    SGPath p(m_catalog->installRoot());
+    p.append("Aircraft");
+    p.append(id());
+    if (p.exists()) {
+        return Install::createFromPath(p, m_catalog);
+    }
+    
+    Install* ins = new Install(this, p);
+    m_catalog->root()->scheduleToUpdate(ins);
+    return ins;
+}
+
+std::string Package::id() const
+{
+    return m_props->getStringValue("id");
+}
+
+std::string Package::md5() const
+{
+    return m_props->getStringValue("md5");
+}
+
+unsigned int Package::revision() const
+{
+    return m_props->getIntValue("revision");
+}
+
+string_list Package::downloadUrls() const
+{
+    string_list r;
+    BOOST_FOREACH(SGPropertyNode* dl, m_props->getChildren("download")) {
+        r.push_back(dl->getStringValue());
+    }
+    return r;
+}
+
+std::string Package::getLocalisedProp(const std::string& aName) const
+{
+    return getLocalisedString(m_props, aName.c_str());
+}
+
+std::string Package::getLocalisedString(const SGPropertyNode* aRoot, const char* aName) const
+{
+    std::string locale = m_catalog->root()->getLocale();
+    if (aRoot->hasChild(locale)) {
+        const SGPropertyNode* localeRoot = aRoot->getChild(locale.c_str());
+        if (localeRoot->hasChild(aName)) {
+            return localeRoot->getStringValue(aName);
+        }
+    }
+    
+    return aRoot->getStringValue(aName);
+}
+
+} // of namespace pkg
+
+} // of namespace simgear
diff --git a/simgear/package/Package.hxx b/simgear/package/Package.hxx
new file mode 100644 (file)
index 0000000..416384d
--- /dev/null
@@ -0,0 +1,68 @@
+#ifndef SG_PACKAGE_PACKAGE_HXX
+#define SG_PACKAGE_PACKAGE_HXX
+
+#include <set>
+#include <vector>
+
+#include <simgear/props/props.hxx>
+#include <simgear/misc/strutils.hxx>
+
+typedef std::set<std::string> string_set;
+
+namespace simgear
+{
+    
+namespace pkg
+{
+
+// forward decls
+class Install;
+class Catalog;
+
+class Package
+{
+public:
+    /**
+     * get or create an install for the package
+     */
+    Install* install();
+    
+    bool isInstalled() const;
+    
+    std::string id() const;
+    
+    std::string md5() const;
+    
+    std::string getLocalisedProp(const std::string& aName) const;
+
+    unsigned int revision() const;
+    
+    Catalog* catalog() const
+        { return m_catalog; }
+    
+    bool matches(const SGPropertyNode* aFilter) const;
+    
+    string_list downloadUrls() const;
+private:
+    friend class Catalog;
+    
+    Package(const SGPropertyNode* aProps, Catalog* aCatalog);
+    
+    void initWithProps(const SGPropertyNode* aProps);
+    
+    std::string getLocalisedString(const SGPropertyNode* aRoot, const char* aName) const;
+    
+    SGPropertyNode_ptr m_props;
+    string_set m_tags;
+    Catalog* m_catalog;
+};
+
+typedef std::vector<Package*> PackageList;
+
+
+} // of namespace pkg
+
+} // of namespace simgear
+
+#endif // of SG_PACKAGE_PACKAGE_HXX
+
diff --git a/simgear/package/Root.cxx b/simgear/package/Root.cxx
new file mode 100644 (file)
index 0000000..780617f
--- /dev/null
@@ -0,0 +1,236 @@
+
+
+#include <simgear/package/Root.hxx>
+
+#include <boost/foreach.hpp>
+#include <cstring>
+
+#include <simgear/debug/logstream.hxx>
+#include <simgear/props/props_io.hxx>
+#include <simgear/io/HTTPRequest.hxx>
+#include <simgear/io/HTTPClient.hxx>
+#include <simgear/misc/sg_dir.hxx>
+#include <simgear/structure/exception.hxx>
+#include <simgear/package/Package.hxx>
+#include <simgear/package/Install.hxx>
+#include <simgear/package/Catalog.hxx>
+
+namespace simgear {
+    
+namespace pkg {
+
+void Root::setMaxAgeSeconds(int seconds)
+{
+    m_maxAgeSeconds = seconds;
+}
+
+void Root::setHTTPClient(HTTP::Client* aHTTP)
+{
+    m_http = aHTTP;
+}
+
+HTTP::Client* Root::getHTTPClient() const
+{
+    return m_http;
+}
+
+Root::Root(const SGPath& aPath) :
+    m_path(aPath),
+    m_http(NULL),
+    m_maxAgeSeconds(60 * 60 * 24),
+    m_delegate(NULL)
+{
+    if (getenv("LOCALE")) {
+        m_locale = getenv("LOCALE");
+    }
+    
+    Dir d(aPath);
+    if (!d.exists()) {
+        d.create(0755);
+        return;
+    }
+    
+    BOOST_FOREACH(SGPath c, d.children(Dir::TYPE_DIR)) {
+        Catalog* cat = Catalog::createFromPath(this, c);
+        if (cat) {
+           m_catalogs[cat->id()] = cat;     
+        }
+    } // of child directories iteration
+}
+
+Root::~Root()
+{
+    
+}
+
+Catalog* Root::getCatalogById(const std::string& aId) const
+{
+    CatalogDict::const_iterator it = m_catalogs.find(aId);
+    if (it == m_catalogs.end()) {
+        return NULL;
+    }
+    
+    return it->second;
+}
+
+Package* Root::getPackageById(const std::string& aName) const
+{
+    size_t lastDot = aName.rfind('.');
+    
+    Package* pkg = NULL;
+    if (lastDot == -1) {
+        // naked package ID
+        CatalogDict::const_iterator it = m_catalogs.begin();
+        for (; it != m_catalogs.end(); ++it) {
+            pkg = it->second->getPackageById(aName);
+            if (pkg) {
+                return pkg;
+            }
+        }
+        
+        return NULL;
+    }
+    
+    std::string catalogId = aName.substr(0, lastDot);
+    std::string id = aName.substr(lastDot + 1);    
+    Catalog* catalog = getCatalogById(catalogId);
+    if (!catalog) {
+        return NULL;
+    }
+            
+    return catalog->getPackageById(id);
+}
+
+CatalogList Root::catalogs() const
+{
+    CatalogList r;
+    CatalogDict::const_iterator it = m_catalogs.begin();
+    for (; it != m_catalogs.end(); ++it) {
+        r.push_back(it->second);
+    }
+    
+    return r;
+}
+
+PackageList
+Root::packagesMatching(const SGPropertyNode* aFilter) const
+{
+    PackageList r;
+    
+    CatalogDict::const_iterator it = m_catalogs.begin();
+    for (; it != m_catalogs.end(); ++it) {
+        PackageList r2(it->second->packagesMatching(aFilter));
+        r.insert(r.end(), r2.begin(), r2.end());
+    }
+    
+    return r;
+}
+
+PackageList
+Root::packagesNeedingUpdate() const
+{
+    PackageList r;
+    
+    CatalogDict::const_iterator it = m_catalogs.begin();
+    for (; it != m_catalogs.end(); ++it) {
+        PackageList r2(it->second->packagesNeedingUpdate());
+        r.insert(r.end(), r2.begin(), r2.end());
+    }
+    
+    return r;
+}
+
+void Root::refresh(bool aForce)
+{
+    CatalogDict::iterator it = m_catalogs.begin();
+    for (; it != m_catalogs.end(); ++it) {
+        if (aForce || (it->second->ageInSeconds() > m_maxAgeSeconds)) {
+            it->second->refresh();
+        }
+    }
+}
+
+void Root::setLocale(const std::string& aLocale)
+{
+    m_locale = aLocale;
+}
+
+std::string Root::getLocale() const
+{
+    return m_locale;
+}
+
+void Root::scheduleToUpdate(Install* aInstall)
+{
+    bool wasEmpty = m_updateDeque.empty();
+    m_updateDeque.push_back(aInstall);
+    if (wasEmpty) {
+        aInstall->startUpdate();
+    }
+}
+
+void Root::startInstall(Install* aInstall)
+{
+    if (m_delegate) {
+        m_delegate->startInstall(aInstall);
+    }
+}
+
+void Root::installProgress(Install* aInstall, unsigned int aBytes, unsigned int aTotal)
+{
+    if (m_delegate) {
+        m_delegate->installProgress(aInstall, aBytes, aTotal);
+    }
+}
+
+void Root::startNext(Install* aCurrent)
+{
+    if (m_updateDeque.front() != aCurrent) {
+        SG_LOG(SG_GENERAL, SG_ALERT, "current install of package not head of the deque");
+    } else {
+        m_updateDeque.pop_front();
+    }
+    
+    if (!m_updateDeque.empty()) {
+        m_updateDeque.front()->startUpdate();
+    }
+}
+
+void Root::finishInstall(Install* aInstall)
+{
+    if (m_delegate) {
+        m_delegate->finishInstall(aInstall);
+    }
+    
+    startNext(aInstall);
+}
+
+void Root::failedInstall(Install* aInstall, Delegate::FailureCode aReason)
+{
+    SG_LOG(SG_GENERAL, SG_ALERT, "failed to install package:" 
+        << aInstall->package()->id() << ":" << aReason);
+    if (m_delegate) {
+        m_delegate->failedInstall(aInstall, aReason);
+    }
+    
+    startNext(aInstall);
+}
+
+void Root::catalogRefreshBegin(Catalog* aCat)
+{
+    m_refreshing.insert(aCat);
+}
+
+void Root::catalogRefreshComplete(Catalog* aCat, bool aSuccess)
+{
+    m_refreshing.erase(aCat);
+    if (m_refreshing.empty()) {
+        if (m_delegate) {
+            m_delegate->refreshComplete();
+        }
+    }
+}
+
+} // of namespace pkg
+
+} // of namespace simgear
diff --git a/simgear/package/Root.hxx b/simgear/package/Root.hxx
new file mode 100644 (file)
index 0000000..576b23c
--- /dev/null
@@ -0,0 +1,110 @@
+
+
+#ifndef SG_PACKAGE_ROOT_HXX
+#define SG_PACKAGE_ROOT_HXX
+
+#include <vector>
+#include <map>
+#include <deque>
+#include <set>
+
+#include <simgear/misc/sg_path.hxx>
+#include <simgear/package/Delegate.hxx>
+
+class SGPropertyNode;
+
+namespace simgear
+{
+    
+namespace HTTP { class Client; }
+    
+namespace pkg
+{
+
+// forward decls
+class Package;
+class Catalog;
+class Install;
+
+typedef std::vector<Package*> PackageList;
+typedef std::vector<Catalog*> CatalogList;
+
+typedef std::map<std::string, Catalog*> CatalogDict;
+
+class Root
+{
+public:
+    Root(const SGPath& aPath);
+    virtual ~Root();
+    
+    SGPath path() const
+        { return m_path; }
+    
+    void setLocale(const std::string& aLocale);
+        
+    void setDelegate(Delegate* aDelegate);
+        
+    std::string getLocale() const;
+    
+    CatalogList catalogs() const;
+        
+    void setMaxAgeSeconds(int seconds);
+
+    void setHTTPClient(HTTP::Client* aHTTP);
+
+    HTTP::Client* getHTTPClient() const;
+
+    /**
+     * refresh catalogs which are more than the maximum age (24 hours by default)
+     * set force to true, to download all catalogs regardless of age.
+     */
+    void refresh(bool aForce = false);
+
+    /**
+     * retrieve packages matching a filter.
+     * filter consists of required / minimum values, AND-ed together.
+     */
+    PackageList packagesMatching(const SGPropertyNode* aFilter) const;
+    
+    /**
+     * retrieve all the packages which are installed
+     * and have a pending update
+     */ 
+    PackageList packagesNeedingUpdate() const;
+     
+    Package* getPackageById(const std::string& aId) const;
+    
+    Catalog* getCatalogById(const std::string& aId) const;
+    
+    void scheduleToUpdate(Install* aInstall);
+private:
+    friend class Install;
+    friend class Catalog;    
+    
+
+    void catalogRefreshBegin(Catalog* aCat);
+    void catalogRefreshComplete(Catalog* aCat, bool aSuccess);
+        
+    void startNext(Install* aCurrent);
+    
+    void startInstall(Install* aInstall);
+    void installProgress(Install* aInstall, unsigned int aBytes, unsigned int aTotal);
+    void finishInstall(Install* aInstall);    
+    void failedInstall(Install* aInstall, Delegate::FailureCode aReason);
+    
+    SGPath m_path;
+    std::string m_locale;
+    HTTP::Client* m_http;
+    CatalogDict m_catalogs;
+    unsigned int m_maxAgeSeconds;
+    Delegate* m_delegate;
+    
+    std::set<Catalog*> m_refreshing;
+    std::deque<Install*> m_updateDeque;
+};  
+    
+} // of namespace pkg
+
+} // of namespace simgear
+
+#endif // of SG_PACKAGE_ROOT_HXX
diff --git a/simgear/package/md5.c b/simgear/package/md5.c
new file mode 100644 (file)
index 0000000..7a67e62
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ **********************************************************************
+ ** md5.c                                                            **
+ ** RSA Data Security, Inc. MD5 Message Digest Algorithm             **
+ ** Created: 2/17/90 RLR                                             **
+ ** Revised: 1/91 SRD,AJ,BSK,JT Reference C Version                  **
+ **********************************************************************
+ */
+
+/*
+ **********************************************************************
+ ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved. **
+ **                                                                  **
+ ** License to copy and use this software is granted provided that   **
+ ** it is identified as the "RSA Data Security, Inc. MD5 Message     **
+ ** Digest Algorithm" in all material mentioning or referencing this **
+ ** software or this function.                                       **
+ **                                                                  **
+ ** License is also granted to make and use derivative works         **
+ ** provided that such works are identified as "derived from the RSA **
+ ** Data Security, Inc. MD5 Message Digest Algorithm" in all         **
+ ** material mentioning or referencing the derived work.             **
+ **                                                                  **
+ ** RSA Data Security, Inc. makes no representations concerning      **
+ ** either the merchantability of this software or the suitability   **
+ ** of this software for any particular purpose.  It is provided "as **
+ ** is" without express or implied warranty of any kind.             **
+ **                                                                  **
+ ** These notices must be retained in any copies of any part of this **
+ ** documentation and/or software.                                   **
+ **********************************************************************
+ */
+
+/* -- include the following line if the md5.h header file is separate -- */
+#include "md5.h"
+
+/* forward declaration */
+static void Transform ();
+
+static unsigned char PADDING[64] = {
+  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/* F, G and H are basic MD5 functions: selection, majority, parity */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z))) 
+
+/* ROTATE_LEFT rotates x left n bits */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4 */
+/* Rotation is separate from addition to prevent recomputation */
+#define FF(a, b, c, d, x, s, ac) \
+  {(a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+#define GG(a, b, c, d, x, s, ac) \
+  {(a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+#define HH(a, b, c, d, x, s, ac) \
+  {(a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+#define II(a, b, c, d, x, s, ac) \
+  {(a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
+   (a) = ROTATE_LEFT ((a), (s)); \
+   (a) += (b); \
+  }
+
+void MD5Init (MD5_CTX *mdContext)
+{
+  mdContext->i[0] = mdContext->i[1] = (UINT4)0;
+
+  /* Load magic initialization constants.
+   */
+  mdContext->buf[0] = (UINT4)0x67452301;
+  mdContext->buf[1] = (UINT4)0xefcdab89;
+  mdContext->buf[2] = (UINT4)0x98badcfe;
+  mdContext->buf[3] = (UINT4)0x10325476;
+}
+
+void MD5Update (MD5_CTX *mdContext, unsigned char *inBuf, unsigned int inLen)
+{
+  UINT4 in[16];
+  int mdi;
+  unsigned int i, ii;
+
+  /* compute number of bytes mod 64 */
+  mdi = (int)((mdContext->i[0] >> 3) & 0x3F);
+
+  /* update number of bits */
+  if ((mdContext->i[0] + ((UINT4)inLen << 3)) < mdContext->i[0])
+    mdContext->i[1]++;
+  mdContext->i[0] += ((UINT4)inLen << 3);
+  mdContext->i[1] += ((UINT4)inLen >> 29);
+
+  while (inLen--) {
+    /* add new character to buffer, increment mdi */
+    mdContext->in[mdi++] = *inBuf++;
+
+    /* transform if necessary */
+    if (mdi == 0x40) {
+      for (i = 0, ii = 0; i < 16; i++, ii += 4)
+        in[i] = (((UINT4)mdContext->in[ii+3]) << 24) |
+                (((UINT4)mdContext->in[ii+2]) << 16) |
+                (((UINT4)mdContext->in[ii+1]) << 8) |
+                ((UINT4)mdContext->in[ii]);
+      Transform (mdContext->buf, in);
+      mdi = 0;
+    }
+  }
+}
+
+void MD5Final (MD5_CTX *mdContext)
+{
+  UINT4 in[16];
+  int mdi;
+  unsigned int i, ii;
+  unsigned int padLen;
+
+  /* save number of bits */
+  in[14] = mdContext->i[0];
+  in[15] = mdContext->i[1];
+
+  /* compute number of bytes mod 64 */
+  mdi = (int)((mdContext->i[0] >> 3) & 0x3F);
+
+  /* pad out to 56 mod 64 */
+  padLen = (mdi < 56) ? (56 - mdi) : (120 - mdi);
+  MD5Update (mdContext, PADDING, padLen);
+
+  /* append length in bits and transform */
+  for (i = 0, ii = 0; i < 14; i++, ii += 4)
+    in[i] = (((UINT4)mdContext->in[ii+3]) << 24) |
+            (((UINT4)mdContext->in[ii+2]) << 16) |
+            (((UINT4)mdContext->in[ii+1]) << 8) |
+            ((UINT4)mdContext->in[ii]);
+  Transform (mdContext->buf, in);
+
+  /* store buffer in digest */
+  for (i = 0, ii = 0; i < 4; i++, ii += 4) {
+    mdContext->digest[ii] = (unsigned char)(mdContext->buf[i] & 0xFF);
+    mdContext->digest[ii+1] =
+      (unsigned char)((mdContext->buf[i] >> 8) & 0xFF);
+    mdContext->digest[ii+2] =
+      (unsigned char)((mdContext->buf[i] >> 16) & 0xFF);
+    mdContext->digest[ii+3] =
+      (unsigned char)((mdContext->buf[i] >> 24) & 0xFF);
+  }
+}
+
+/* Basic MD5 step. Transform buf based on in.
+ */
+static void Transform (buf, in)
+UINT4 *buf;
+UINT4 *in;
+{
+  UINT4 a = buf[0], b = buf[1], c = buf[2], d = buf[3];
+
+  /* Round 1 */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+  FF ( a, b, c, d, in[ 0], S11, 3614090360); /* 1 */
+  FF ( d, a, b, c, in[ 1], S12, 3905402710); /* 2 */
+  FF ( c, d, a, b, in[ 2], S13,  606105819); /* 3 */
+  FF ( b, c, d, a, in[ 3], S14, 3250441966); /* 4 */
+  FF ( a, b, c, d, in[ 4], S11, 4118548399); /* 5 */
+  FF ( d, a, b, c, in[ 5], S12, 1200080426); /* 6 */
+  FF ( c, d, a, b, in[ 6], S13, 2821735955); /* 7 */
+  FF ( b, c, d, a, in[ 7], S14, 4249261313); /* 8 */
+  FF ( a, b, c, d, in[ 8], S11, 1770035416); /* 9 */
+  FF ( d, a, b, c, in[ 9], S12, 2336552879); /* 10 */
+  FF ( c, d, a, b, in[10], S13, 4294925233); /* 11 */
+  FF ( b, c, d, a, in[11], S14, 2304563134); /* 12 */
+  FF ( a, b, c, d, in[12], S11, 1804603682); /* 13 */
+  FF ( d, a, b, c, in[13], S12, 4254626195); /* 14 */
+  FF ( c, d, a, b, in[14], S13, 2792965006); /* 15 */
+  FF ( b, c, d, a, in[15], S14, 1236535329); /* 16 */
+
+  /* Round 2 */
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+  GG ( a, b, c, d, in[ 1], S21, 4129170786); /* 17 */
+  GG ( d, a, b, c, in[ 6], S22, 3225465664); /* 18 */
+  GG ( c, d, a, b, in[11], S23,  643717713); /* 19 */
+  GG ( b, c, d, a, in[ 0], S24, 3921069994); /* 20 */
+  GG ( a, b, c, d, in[ 5], S21, 3593408605); /* 21 */
+  GG ( d, a, b, c, in[10], S22,   38016083); /* 22 */
+  GG ( c, d, a, b, in[15], S23, 3634488961); /* 23 */
+  GG ( b, c, d, a, in[ 4], S24, 3889429448); /* 24 */
+  GG ( a, b, c, d, in[ 9], S21,  568446438); /* 25 */
+  GG ( d, a, b, c, in[14], S22, 3275163606); /* 26 */
+  GG ( c, d, a, b, in[ 3], S23, 4107603335); /* 27 */
+  GG ( b, c, d, a, in[ 8], S24, 1163531501); /* 28 */
+  GG ( a, b, c, d, in[13], S21, 2850285829); /* 29 */
+  GG ( d, a, b, c, in[ 2], S22, 4243563512); /* 30 */
+  GG ( c, d, a, b, in[ 7], S23, 1735328473); /* 31 */
+  GG ( b, c, d, a, in[12], S24, 2368359562); /* 32 */
+
+  /* Round 3 */
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+  HH ( a, b, c, d, in[ 5], S31, 4294588738); /* 33 */
+  HH ( d, a, b, c, in[ 8], S32, 2272392833); /* 34 */
+  HH ( c, d, a, b, in[11], S33, 1839030562); /* 35 */
+  HH ( b, c, d, a, in[14], S34, 4259657740); /* 36 */
+  HH ( a, b, c, d, in[ 1], S31, 2763975236); /* 37 */
+  HH ( d, a, b, c, in[ 4], S32, 1272893353); /* 38 */
+  HH ( c, d, a, b, in[ 7], S33, 4139469664); /* 39 */
+  HH ( b, c, d, a, in[10], S34, 3200236656); /* 40 */
+  HH ( a, b, c, d, in[13], S31,  681279174); /* 41 */
+  HH ( d, a, b, c, in[ 0], S32, 3936430074); /* 42 */
+  HH ( c, d, a, b, in[ 3], S33, 3572445317); /* 43 */
+  HH ( b, c, d, a, in[ 6], S34,   76029189); /* 44 */
+  HH ( a, b, c, d, in[ 9], S31, 3654602809); /* 45 */
+  HH ( d, a, b, c, in[12], S32, 3873151461); /* 46 */
+  HH ( c, d, a, b, in[15], S33,  530742520); /* 47 */
+  HH ( b, c, d, a, in[ 2], S34, 3299628645); /* 48 */
+
+  /* Round 4 */
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+  II ( a, b, c, d, in[ 0], S41, 4096336452); /* 49 */
+  II ( d, a, b, c, in[ 7], S42, 1126891415); /* 50 */
+  II ( c, d, a, b, in[14], S43, 2878612391); /* 51 */
+  II ( b, c, d, a, in[ 5], S44, 4237533241); /* 52 */
+  II ( a, b, c, d, in[12], S41, 1700485571); /* 53 */
+  II ( d, a, b, c, in[ 3], S42, 2399980690); /* 54 */
+  II ( c, d, a, b, in[10], S43, 4293915773); /* 55 */
+  II ( b, c, d, a, in[ 1], S44, 2240044497); /* 56 */
+  II ( a, b, c, d, in[ 8], S41, 1873313359); /* 57 */
+  II ( d, a, b, c, in[15], S42, 4264355552); /* 58 */
+  II ( c, d, a, b, in[ 6], S43, 2734768916); /* 59 */
+  II ( b, c, d, a, in[13], S44, 1309151649); /* 60 */
+  II ( a, b, c, d, in[ 4], S41, 4149444226); /* 61 */
+  II ( d, a, b, c, in[11], S42, 3174756917); /* 62 */
+  II ( c, d, a, b, in[ 2], S43,  718787259); /* 63 */
+  II ( b, c, d, a, in[ 9], S44, 3951481745); /* 64 */
+
+  buf[0] += a;
+  buf[1] += b;
+  buf[2] += c;
+  buf[3] += d;
+}
+
diff --git a/simgear/package/md5.h b/simgear/package/md5.h
new file mode 100644 (file)
index 0000000..d0560aa
--- /dev/null
@@ -0,0 +1,68 @@
+#ifndef SG_PACKAGE_MD5_H
+#define SG_PACKAGE_MD5_H
+
+/*
+ **********************************************************************
+ ** md5.h -- Header file for implementation of MD5                   **
+ ** RSA Data Security, Inc. MD5 Message Digest Algorithm             **
+ ** Created: 2/17/90 RLR                                             **
+ ** Revised: 12/27/90 SRD,AJ,BSK,JT Reference C version              **
+ ** Revised (for MD5): RLR 4/27/91                                   **
+ **   -- G modified to have y&~z instead of y&z                      **
+ **   -- FF, GG, HH modified to add in last register done            **
+ **   -- Access pattern: round 2 works mod 5, round 3 works mod 3    **
+ **   -- distinct additive constant for each step                    **
+ **   -- round 4 added, working mod 7                                **
+ **********************************************************************
+ */
+
+/*
+ **********************************************************************
+ ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved. **
+ **                                                                  **
+ ** License to copy and use this software is granted provided that   **
+ ** it is identified as the "RSA Data Security, Inc. MD5 Message     **
+ ** Digest Algorithm" in all material mentioning or referencing this **
+ ** software or this function.                                       **
+ **                                                                  **
+ ** License is also granted to make and use derivative works         **
+ ** provided that such works are identified as "derived from the RSA **
+ ** Data Security, Inc. MD5 Message Digest Algorithm" in all         **
+ ** material mentioning or referencing the derived work.             **
+ **                                                                  **
+ ** RSA Data Security, Inc. makes no representations concerning      **
+ ** either the merchantability of this software or the suitability   **
+ ** of this software for any particular purpose.  It is provided "as **
+ ** is" without express or implied warranty of any kind.             **
+ **                                                                  **
+ ** These notices must be retained in any copies of any part of this **
+ ** documentation and/or software.                                   **
+ **********************************************************************
+ */
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+     
+/* typedef a 32 bit type */
+typedef unsigned int UINT4;
+
+/* Data structure for MD5 (Message Digest) computation */
+typedef struct {
+  UINT4 i[2];                   /* number of _bits_ handled mod 2^64 */
+  UINT4 buf[4];                                    /* scratch buffer */
+  unsigned char in[64];                              /* input buffer */
+  unsigned char digest[16];     /* actual digest after MD5Final call */
+} MD5_CTX;
+
+void MD5Init (MD5_CTX *mdContext);
+void MD5Update (MD5_CTX *mdContext, unsigned char *inBuf, unsigned int inLen);
+void MD5Final (MD5_CTX *mdContext);
+
+#ifdef __cplusplus
+} // of extern C
+#endif
+     
+#endif // of SG_PACKAGE_MD5_H
+
\ No newline at end of file
diff --git a/simgear/package/pkgutil.cxx b/simgear/package/pkgutil.cxx
new file mode 100644 (file)
index 0000000..c72c1f1
--- /dev/null
@@ -0,0 +1,86 @@
+#include <simgear/io/HTTPClient.hxx>
+#include <simgear/package/Catalog.hxx>
+#include <simgear/package/Package.hxx>
+#include <simgear/package/Install.hxx>
+#include <simgear/package/Root.hxx>
+#include <simgear/misc/sg_dir.hxx>
+
+#include <boost/foreach.hpp>
+#include <iostream>
+#include <cstring>
+
+using namespace simgear; 
+using namespace std;
+
+bool keepRunning = true;
+
+int main(int argc, char** argv)
+{
+
+    HTTP::Client* http = new HTTP::Client();
+    pkg::Root* root = new pkg::Root(Dir::current().path());
+    
+    cout << "Package root is:" << Dir::current().path() << endl;
+    cout << "have " << pkg::Catalog::allCatalogs().size() << " catalog(s)" << endl;
+        
+    root->setHTTPClient(http);
+    
+    if (!strcmp(argv[1], "add")) {
+        std::string url(argv[2]);
+        pkg::Catalog::createFromUrl(root, url);
+    } else if (!strcmp(argv[1], "refresh")) {
+        root->refresh();
+    } else if (!strcmp(argv[1], "install")) {
+        pkg::Package* pkg = root->getPackageById(argv[2]);
+        if (!pkg) {
+            cerr << "unknown package:" << argv[2] << endl;
+            return EXIT_FAILURE;
+        }
+        
+        if (pkg->isInstalled()) {
+            cout << "package " << pkg->id() << " is already installed at " << pkg->install()->path() << endl;
+            return EXIT_SUCCESS;
+        }
+        
+        pkg::Catalog* catalog = pkg->catalog();
+        cout << "Will install:" << pkg->id() << " from " << catalog->id() <<
+                "(" << catalog->description() << ")" << endl;
+        pkg->install();
+    } else if (!strcmp(argv[1], "uninstall")) {
+        pkg::Package* pkg = root->getPackageById(argv[2]);
+        if (!pkg) {
+            cerr << "unknown package:" << argv[2] << endl;
+            return EXIT_FAILURE;
+        }
+        
+        if (!pkg->isInstalled()) {
+            cerr << "package " << argv[2] << " not installed" << endl;
+            return EXIT_FAILURE;
+        }
+        
+        cout << "Will uninstall:" << pkg->id() << endl;
+        pkg->install()->uninstall();
+    } else if (!strcmp(argv[1], "update-all")) {
+        pkg::PackageList updates = root->packagesNeedingUpdate();
+        BOOST_FOREACH(pkg::Package* p, updates) {
+            root->scheduleToUpdate(p->install());
+        }
+    } else if (!strcmp(argv[1], "list-updated")) {
+        pkg::PackageList updates = root->packagesNeedingUpdate();
+        if (updates.empty()) {
+            cout << "no packages with updates" << endl;
+            return EXIT_SUCCESS;
+        }
+        
+        cout << updates.size() << " packages have updates" << endl;
+        BOOST_FOREACH(pkg::Package* p, updates) {
+            cout << "\t" << p->id() << " " << p->getLocalisedProp("name") << endl;
+        }
+    }
+    
+    while (http->hasActiveRequests()) {
+        http->update();
+    }
+
+    return EXIT_SUCCESS;
+}