Lots more work on package support.

Delegate is hooked up in the demo util, and Install forwards control to the delegate too.
This commit is contained in:
James Turner
2013-03-02 16:30:39 +00:00
parent fc64abea5c
commit 71e9548c20
10 changed files with 203 additions and 33 deletions

View File

@@ -64,7 +64,7 @@ protected:
{
if (responseCode() != 200) {
SG_LOG(SG_GENERAL, SG_ALERT, "catalog download failure:" << m_owner->url());
m_owner->refreshComplete(false);
m_owner->refreshComplete(Delegate::FAIL_DOWNLOAD);
return;
}
@@ -75,7 +75,15 @@ protected:
m_owner->parseProps(props);
} catch (sg_exception& e) {
SG_LOG(SG_GENERAL, SG_ALERT, "catalog parse failure:" << m_owner->url());
m_owner->refreshComplete(false);
m_owner->refreshComplete(Delegate::FAIL_EXTRACT);
return;
}
std::string ver(m_owner->root()->catalogVersion());
if (props->getStringValue("version") != ver) {
SG_LOG(SG_GENERAL, SG_WARN, "downloaded catalog " << m_owner->url() << ", version mismatch:\n\t"
<< props->getStringValue("version") << " vs required " << ver);
m_owner->refreshComplete(Delegate::FAIL_VERSION);
return;
}
@@ -89,7 +97,7 @@ protected:
time(&m_owner->m_retrievedTime);
m_owner->writeTimestamp();
m_owner->refreshComplete(true);
m_owner->refreshComplete(Delegate::FAIL_SUCCESS);
}
private:
@@ -142,6 +150,12 @@ Catalog* Catalog::createFromPath(Root* aRoot, const SGPath& aPath)
return NULL;
}
if (props->getStringValue("version") != aRoot->catalogVersion()) {
SG_LOG(SG_GENERAL, SG_WARN, "skipping catalog at " << aPath << ", version mismatch:\n\t"
<< props->getStringValue("version") << " vs required " << aRoot->catalogVersion());
return NULL;
}
Catalog* c = new Catalog(aRoot);
c->m_installRoot = aPath;
c->parseProps(props);
@@ -278,9 +292,9 @@ std::string Catalog::getLocalisedString(const SGPropertyNode* aRoot, const char*
return aRoot->getStringValue(aName);
}
void Catalog::refreshComplete(bool aSuccess)
void Catalog::refreshComplete(Delegate::FailureCode aReason)
{
m_root->catalogRefreshComplete(this, aSuccess);
m_root->catalogRefreshComplete(this, aReason);
}

View File

@@ -24,6 +24,8 @@
#include <simgear/misc/sg_path.hxx>
#include <simgear/props/props.hxx>
#include <simgear/package/Delegate.hxx>
namespace simgear
{
@@ -95,7 +97,7 @@ private:
void parseProps(const SGPropertyNode* aProps);
void refreshComplete(bool aSuccess);
void refreshComplete(Delegate::FailureCode aReason);
void parseTimestamp();
void writeTimestamp();

View File

@@ -1,4 +1,19 @@
// Copyright (C) 2013 James Turner - zakalawe@mac.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifndef SG_PACKAGE_DELEGATE_HXX
#define SG_PACKAGE_DELEGATE_HXX
@@ -10,26 +25,45 @@ namespace pkg
{
class Install;
class Catalog;
/**
* package delegate is the mechanism to discover progress / completion /
* errors in packaging steps asynchronously.
*/
class Delegate
{
public:
typedef enum {
FAIL_SUCCESS = 0, ///< not a failure :)
FAIL_UNKNOWN = 1,
FAIL_CHECKSUM, ///< package MD5 verificstion failed
FAIL_DOWNLOAD, ///< network issue
FAIL_EXTRACT, ///< package archive failed to extract cleanly
FAIL_FILESYSTEM, ///< unknown filesystem error occurred
FAIL_VERSION ///< version check mismatch
} FailureCode;
virtual ~Delegate() { }
/**
* invoked when all catalogs have finished refreshing - either successfully
* or with a failure.
*/
virtual void refreshComplete() = 0;
/**
* emitted when a catalog fails to refresh, due to a network issue or
* some other failure.
*/
virtual void failedRefresh(Catalog*, FailureCode aReason) = 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;
};

View File

@@ -66,8 +66,6 @@ protected:
virtual void responseHeadersComplete()
{
std::cout << "starting download of " << m_owner->package()->id() << " from "
<< url() << std::endl;
Dir d(m_extractPath);
d.create(0755);
@@ -79,13 +77,15 @@ protected:
{
m_buffer += std::string(s, n);
MD5Update(&m_md5, (unsigned char*) s, n);
m_owner->installProgress(m_buffer.size(), responseLength());
}
virtual void responseComplete()
{
if (responseCode() != 200) {
SG_LOG(SG_GENERAL, SG_ALERT, "download failure");
doFailure();
doFailure(Delegate::FAIL_DOWNLOAD);
return;
}
@@ -103,15 +103,13 @@ protected:
<< "\t" << hexMd5.str() << "\n\t"
<< m_owner->package()->md5() << "\n\t"
<< "downloading from:" << url());
doFailure();
doFailure(Delegate::FAIL_CHECKSUM);
return;
} else {
std::cout << "MD5 checksum is ok" << std::endl;
}
if (!extractUnzip()) {
SG_LOG(SG_GENERAL, SG_WARN, "zip extraction failed");
doFailure();
doFailure(Delegate::FAIL_EXTRACT);
return;
}
@@ -122,9 +120,15 @@ protected:
}
m_extractPath.append(m_owner->package()->id());
m_extractPath.rename(m_owner->path());
bool ok = m_extractPath.rename(m_owner->path());
if (!ok) {
doFailure(Delegate::FAIL_FILESYSTEM);
return;
}
m_owner->m_revision = m_owner->package()->revision();
m_owner->writeRevisionFile();
m_owner->installResult(Delegate::FAIL_SUCCESS);
}
private:
@@ -227,15 +231,18 @@ private:
return result;
}
void doFailure()
void doFailure(Delegate::FailureCode aReason)
{
Dir dir(m_extractPath);
dir.remove(true /* recursive */);
if (m_urls.size() == 1) {
std::cout << "failure:" << aReason << std::endl;
m_owner->installResult(aReason);
return;
}
std::cout << "retrying download" << std::endl;
m_urls.erase(m_urls.begin()); // pop first URL
}
@@ -300,6 +307,7 @@ void Install::startUpdate()
m_download = new PackageArchiveDownloader(this);
m_package->catalog()->root()->getHTTPClient()->makeRequest(m_download);
m_package->catalog()->root()->startInstall(this);
}
void Install::uninstall()
@@ -309,6 +317,21 @@ void Install::uninstall()
delete this;
}
void Install::installResult(Delegate::FailureCode aReason)
{
if (aReason == Delegate::FAIL_SUCCESS) {
m_package->catalog()->root()->finishInstall(this);
} else {
m_package->catalog()->root()->failedInstall(this, aReason);
}
}
void Install::installProgress(unsigned int aBytes, unsigned int aTotal)
{
m_package->catalog()->root()->installProgress(this, aBytes, aTotal);
}
} // of namespace pkg
} // of namespace simgear

View File

@@ -21,6 +21,7 @@
#include <vector>
#include <simgear/misc/sg_path.hxx>
#include <simgear/package/Delegate.hxx>
namespace simgear
{
@@ -74,6 +75,9 @@ private:
void parseRevision();
void writeRevisionFile();
void installResult(Delegate::FailureCode aReason);
void installProgress(unsigned int aBytes, unsigned int aTotal);
Package* m_package;
unsigned int m_revision; ///< revision on disk
SGPath m_path; ///< installation point on disk

View File

@@ -112,6 +112,11 @@ unsigned int Package::revision() const
return m_props->getIntValue("revision");
}
std::string Package::description() const
{
return getLocalisedProp("decription");
}
SGPropertyNode* Package::properties() const
{
return m_props.ptr();

View File

@@ -51,6 +51,11 @@ public:
std::string id() const;
/**
* syntactic sugar to get the localised description
*/
std::string description() const;
/**
* access the raw property data in the package
*/

View File

@@ -49,11 +49,12 @@ HTTP::Client* Root::getHTTPClient() const
return m_http;
}
Root::Root(const SGPath& aPath) :
Root::Root(const SGPath& aPath, const std::string& aVersion) :
m_path(aPath),
m_http(NULL),
m_maxAgeSeconds(60 * 60 * 24),
m_delegate(NULL)
m_delegate(NULL),
m_version(aVersion)
{
if (getenv("LOCALE")) {
m_locale = getenv("LOCALE");
@@ -77,7 +78,12 @@ Root::~Root()
{
}
std::string Root::catalogVersion() const
{
return m_version;
}
Catalog* Root::getCatalogById(const std::string& aId) const
{
CatalogDict::const_iterator it = m_catalogs.find(aId);
@@ -165,6 +171,11 @@ void Root::refresh(bool aForce)
}
}
void Root::setDelegate(simgear::pkg::Delegate *aDelegate)
{
m_delegate = aDelegate;
}
void Root::setLocale(const std::string& aLocale)
{
m_locale = aLocale;
@@ -190,6 +201,7 @@ void Root::scheduleToUpdate(Install* aInstall)
bool wasEmpty = m_updateDeque.empty();
m_updateDeque.push_back(aInstall);
if (wasEmpty) {
aInstall->startUpdate();
}
@@ -247,8 +259,26 @@ void Root::catalogRefreshBegin(Catalog* aCat)
m_refreshing.insert(aCat);
}
void Root::catalogRefreshComplete(Catalog* aCat, bool aSuccess)
void Root::catalogRefreshComplete(Catalog* aCat, Delegate::FailureCode aReason)
{
CatalogDict::iterator catIt = m_catalogs.find(aCat->id());
if (aReason != Delegate::FAIL_SUCCESS) {
if (m_delegate) {
m_delegate->failedRefresh(aCat, aReason);
}
// if the failure is permanent, delete the catalog from our
// list (don't touch it on disk)
bool isPermanentFailure = (aReason == Delegate::FAIL_VERSION);
if (isPermanentFailure) {
SG_LOG(SG_GENERAL, SG_WARN, "permanent failure for catalog:" << aCat->id());
m_catalogs.erase(catIt);
}
} else if (catIt == m_catalogs.end()) {
// first fresh, add to our storage now
m_catalogs.insert(catIt, CatalogDict::value_type(aCat->id(), aCat));
}
m_refreshing.erase(aCat);
if (m_refreshing.empty()) {
if (m_delegate) {

View File

@@ -49,7 +49,7 @@ typedef std::map<std::string, Catalog*> CatalogDict;
class Root
{
public:
Root(const SGPath& aPath);
Root(const SGPath& aPath, const std::string& aVersion);
virtual ~Root();
SGPath path() const
@@ -69,6 +69,12 @@ public:
HTTP::Client* getHTTPClient() const;
/**
* the version string of the root. Catalogs must match this version,
* or they will be ignored / rejected.
*/
std::string catalogVersion() 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.
@@ -98,7 +104,7 @@ private:
void catalogRefreshBegin(Catalog* aCat);
void catalogRefreshComplete(Catalog* aCat, bool aSuccess);
void catalogRefreshComplete(Catalog* aCat, Delegate::FailureCode aReason);
void startNext(Install* aCurrent);
@@ -113,6 +119,7 @@ private:
CatalogDict m_catalogs;
unsigned int m_maxAgeSeconds;
Delegate* m_delegate;
std::string m_version;
std::set<Catalog*> m_refreshing;
std::deque<Install*> m_updateDeque;

View File

@@ -31,11 +31,57 @@ using namespace std;
bool keepRunning = true;
class MyDelegate : public pkg::Delegate
{
public:
virtual void refreshComplete()
{
}
virtual void failedRefresh(pkg::Catalog* aCatalog, FailureCode aReason)
{
cerr << "failed refresh of " << aCatalog->description() << ":" << aReason << endl;
}
virtual void startInstall(pkg::Install* aInstall)
{
_lastPercent = 999;
cout << "starting install of " << aInstall->package()->description() << endl;
}
virtual void installProgress(pkg::Install* aInstall, unsigned int bytes, unsigned int total)
{
unsigned int percent = (bytes * 100) / total;
if (percent == _lastPercent) {
return;
}
_lastPercent = percent;
cout << percent << "%" << endl;
}
virtual void finishInstall(pkg::Install* aInstall)
{
cout << "done install of " << aInstall->package()->description() << endl;
}
virtual void failedInstall(pkg::Install* aInstall, FailureCode aReason)
{
cerr << "failed install of " << aInstall->package()->description() << endl;
}
private:
unsigned int _lastPercent;
};
int main(int argc, char** argv)
{
HTTP::Client* http = new HTTP::Client();
pkg::Root* root = new pkg::Root(Dir::current().path());
pkg::Root* root = new pkg::Root(Dir::current().path(), "");
MyDelegate dlg;
root->setDelegate(&dlg);
cout << "Package root is:" << Dir::current().path() << endl;
cout << "have " << pkg::Catalog::allCatalogs().size() << " catalog(s)" << endl;