Package support hacking

- rename failure code to status code, and add more to handle
  cancellation.
- change caching of active Installs from Catalog to Root, to clarify
  ownership
- expose download status on Install
- adjust Delegate signatures to pass more information
This commit is contained in:
James Turner
2015-08-12 14:22:59 -07:00
parent 70c5d60564
commit 60a0c51e2b
10 changed files with 595 additions and 220 deletions

View File

@@ -77,8 +77,8 @@ public:
m_owner(aOwner)
{
// refreshing
m_owner->changeStatus(Delegate::FAIL_IN_PROGRESS);
m_owner->changeStatus(Delegate::STATUS_IN_PROGRESS);
SG_LOG(SG_GENERAL, SG_WARN, "downloading " << aUrl);
}
protected:
@@ -138,7 +138,7 @@ protected:
time(&m_owner->m_retrievedTime);
m_owner->writeTimestamp();
m_owner->refreshComplete(Delegate::CATALOG_REFRESHED);
m_owner->refreshComplete(Delegate::STATUS_REFRESHED);
}
private:
@@ -200,6 +200,8 @@ CatalogRef Catalog::createFromPath(Root* aRoot, const SGPath& aPath)
return NULL;
}
} else {
SG_LOG(SG_GENERAL, SG_INFO, "creating catalog from:" << aPath);
}
CatalogRef c = new Catalog(aRoot);
@@ -233,7 +235,7 @@ bool Catalog::uninstall()
}
changeStatus(atLeastOneFailure ? Delegate::FAIL_FILESYSTEM
: Delegate::FAIL_SUCCESS);
: Delegate::STATUS_SUCCESS);
return ok;
}
@@ -283,31 +285,25 @@ Catalog::installedPackages() const
return r;
}
InstallRef Catalog::installForPackage(PackageRef pkg) const
{
PackageInstallDict::const_iterator it = m_installed.find(pkg);
if (it == m_installed.end()) {
// check if it exists on disk, create
SGPath p(pkg->pathOnDisk());
if (p.exists()) {
return Install::createFromPath(p, CatalogRef(const_cast<Catalog*>(this)));
}
return NULL;
}
return it->second;
}
void Catalog::refresh()
{
Downloader* dl = new Downloader(this, url());
// will iupdate status to IN_PROGRESS
// will update status to IN_PROGRESS
m_root->makeHTTPRequest(dl);
m_root->catalogRefreshBegin(this);
}
struct FindById
{
FindById(const std::string &id) : m_id(id) {}
bool operator()(const PackageRef& ref) const
{
return ref->id() == m_id;
}
std::string m_id;
};
void Catalog::parseProps(const SGPropertyNode* aProps)
{
// copy everything except package children?
@@ -321,8 +317,14 @@ void Catalog::parseProps(const SGPropertyNode* aProps)
for (int i = 0; i < nChildren; i++) {
const SGPropertyNode* pkgProps = aProps->getChild(i);
if (strcmp(pkgProps->getName(), "package") == 0) {
PackageRef p = getPackageById(pkgProps->getStringValue("id"));
if (p) {
// can't use getPackageById here becuase the variant dict isn't
// built yet. Instead we need to look at m_packages directly.
PackageList::iterator pit = std::find_if(m_packages.begin(), m_packages.end(),
FindById(pkgProps->getStringValue("id")));
PackageRef p;
if (pit != m_packages.end()) {
p = *pit;
// existing package
p->updateFromProps(pkgProps);
orphans.erase(p); // not an orphan
@@ -374,7 +376,7 @@ void Catalog::parseProps(const SGPropertyNode* aProps)
}
// parsed XML ok, mark status as valid
changeStatus(Delegate::FAIL_SUCCESS);
changeStatus(Delegate::STATUS_SUCCESS);
}
PackageRef Catalog::getPackageById(const std::string& aId) const
@@ -440,6 +442,10 @@ bool Catalog::needsRefresh() const
std::string Catalog::getLocalisedString(const SGPropertyNode* aRoot, const char* aName) const
{
if (!aRoot) {
return std::string();
}
if (aRoot->hasChild(m_root->getLocale())) {
const SGPropertyNode* localeRoot = aRoot->getChild(m_root->getLocale().c_str());
if (localeRoot->hasChild(aName)) {
@@ -450,37 +456,20 @@ std::string Catalog::getLocalisedString(const SGPropertyNode* aRoot, const char*
return aRoot->getStringValue(aName);
}
void Catalog::refreshComplete(Delegate::FailureCode aReason)
void Catalog::refreshComplete(Delegate::StatusCode aReason)
{
m_root->catalogRefreshComplete(this, aReason);
m_root->catalogRefreshStatus(this, aReason);
changeStatus(aReason);
}
void Catalog::registerInstall(Install* ins)
{
if (!ins || ins->package()->catalog() != this) {
return;
}
m_installed[ins->package()] = ins;
}
void Catalog::unregisterInstall(Install* ins)
{
if (!ins || ins->package()->catalog() != this) {
return;
}
m_installed.erase(ins->package());
}
void Catalog::changeStatus(Delegate::FailureCode newStatus)
void Catalog::changeStatus(Delegate::StatusCode newStatus)
{
if (m_status == newStatus) {
return;
}
m_status = newStatus;
m_root->catalogRefreshStatus(this, newStatus);
m_statusCallbacks(this);
}
@@ -489,7 +478,7 @@ void Catalog::addStatusCallback(const Callback& cb)
m_statusCallbacks.push_back(cb);
}
Delegate::FailureCode Catalog::status() const
Delegate::StatusCode Catalog::status() const
{
return m_status;
}

View File

@@ -70,7 +70,7 @@ public:
* uninstall this catalog entirely, including all installed packages
*/
bool uninstall();
/**
* perform a refresh of the catalog contents
*/
@@ -109,8 +109,6 @@ public:
PackageRef getPackageById(const std::string& aId) const;
InstallRef installForPackage(PackageRef pkg) const;
/**
* test if the catalog data was retrieved longer ago than the
* maximum permitted age for this catalog.
@@ -123,11 +121,11 @@ public:
* access the raw property data in the catalog
*/
SGPropertyNode* properties() const;
Delegate::FailureCode status() const;
Delegate::StatusCode status() const;
typedef boost::function<void(Catalog*)> Callback;
void addStatusCallback(const Callback& cb);
template<class C>
@@ -141,38 +139,29 @@ private:
class Downloader;
friend class Downloader;
friend class Install;
void registerInstall(Install* ins);
void unregisterInstall(Install* ins);
void parseProps(const SGPropertyNode* aProps);
void refreshComplete(Delegate::FailureCode aReason);
void refreshComplete(Delegate::StatusCode aReason);
void parseTimestamp();
void writeTimestamp();
std::string getLocalisedString(const SGPropertyNode* aRoot, const char* aName) const;
void changeStatus(Delegate::FailureCode newStatus);
void changeStatus(Delegate::StatusCode newStatus);
Root* m_root;
SGPropertyNode_ptr m_props;
SGPath m_installRoot;
std::string m_url;
Delegate::FailureCode m_status;
Delegate::StatusCode m_status;
PackageList m_packages;
time_t m_retrievedTime;
typedef std::map<std::string, Package*> PackageWeakMap;
PackageWeakMap m_variantDict;
// important that this is a weak-ref to Installs,
// since it is only cleaned up in the Install destructor
typedef std::map<PackageRef, Install*> PackageInstallDict;
PackageInstallDict m_installed;
function_list<Callback> m_statusCallbacks;
};

View File

@@ -18,6 +18,8 @@
#ifndef SG_PACKAGE_DELEGATE_HXX
#define SG_PACKAGE_DELEGATE_HXX
#include <simgear/structure/SGSharedPtr.hxx>
namespace simgear
{
@@ -26,6 +28,9 @@ namespace pkg
class Install;
class Catalog;
typedef SGSharedPtr<Catalog> CatalogRef;
typedef SGSharedPtr<Install> InstallRef;
/**
* package delegate is the mechanism to discover progress / completion /
@@ -35,39 +40,35 @@ class Delegate
{
public:
typedef enum {
FAIL_SUCCESS = 0, ///< not a failure :)
STATUS_SUCCESS = 0,
FAIL_UNKNOWN = 1,
FAIL_IN_PROGRESS, ///< downloading/installation in progress (not a failure :P)
STATUS_IN_PROGRESS, ///< downloading/installation in progress
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
CATALOG_REFRESHED
} FailureCode;
STATUS_REFRESHED,
USER_CANCELLED
} StatusCode;
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.
* emitted when a catalog refesh completes, either success or failure
* If catalog is null, this means /all/ catalogs have been refreshed
*/
virtual void failedRefresh(Catalog*, FailureCode aReason) = 0;
virtual void catalogRefreshed(CatalogRef, StatusCode 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;
virtual void failedInstall(Install* aInstall, FailureCode aReason) = 0;
virtual void startInstall(InstallRef aInstall) = 0;
virtual void installProgress(InstallRef aInstall, unsigned int aBytes, unsigned int aTotal) = 0;
virtual void finishInstall(InstallRef aInstall, StatusCode aReason) = 0;
virtual void dataForThumbnail(const std::string& aThumbnailUrl,
size_t lenth, const uint8_t* bytes)
{}
};
} // of namespace pkg

View File

@@ -24,6 +24,7 @@
#include <simgear/package/md5.h>
#include <simgear/structure/exception.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/package/Catalog.hxx>
#include <simgear/package/Package.hxx>
#include <simgear/package/Root.hxx>
@@ -37,7 +38,7 @@ extern "C" {
}
namespace simgear {
namespace pkg {
class Install::PackageArchiveDownloader : public HTTP::Request
@@ -45,43 +46,63 @@ class Install::PackageArchiveDownloader : public HTTP::Request
public:
PackageArchiveDownloader(InstallRef aOwner) :
HTTP::Request("" /* dummy URL */),
m_owner(aOwner)
m_owner(aOwner),
m_downloaded(0)
{
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
m_extractPath.append("_extract_" + aOwner->package()->md5());
// clean up any existing files
Dir d(m_extractPath);
if (d.exists()) {
d.remove(true /* recursive */);
}
}
size_t downloadedBytes() const
{
return m_downloaded;
}
int percentDownloaded() const
{
if (responseLength() <= 0) {
return 0;
}
return (m_downloaded * 100) / responseLength();
}
protected:
virtual std::string url() const
{
return m_urls.front();
}
virtual void responseHeadersComplete()
{
Dir d(m_extractPath);
d.create(0755);
d.create(0755);
memset(&m_md5, 0, sizeof(SG_MD5_CTX));
SG_MD5Init(&m_md5);
}
virtual void gotBodyData(const char* s, int n)
{
m_buffer += std::string(s, n);
SG_MD5Update(&m_md5, (unsigned char*) s, n);
m_downloaded = m_buffer.size();
m_owner->installProgress(m_buffer.size(), responseLength());
}
virtual void onDone()
{
if (responseCode() != 200) {
@@ -103,65 +124,76 @@ protected:
doFailure(Delegate::FAIL_CHECKSUM);
return;
}
if (!extractUnzip()) {
SG_LOG(SG_GENERAL, SG_WARN, "zip extraction failed");
doFailure(Delegate::FAIL_EXTRACT);
return;
}
if (m_owner->path().exists()) {
//std::cout << "removing existing path" << std::endl;
Dir destDir(m_owner->path());
destDir.remove(true /* recursive */);
}
m_extractPath.append(m_owner->package()->id());
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);
m_owner->m_download.reset(); // so isDownloading reports false
m_owner->installResult(Delegate::STATUS_SUCCESS);
}
virtual void onFail()
{
if (responseCode() == -1) {
doFailure(Delegate::USER_CANCELLED);
} else {
doFailure(Delegate::FAIL_DOWNLOAD);
}
}
private:
void extractCurrentFile(unzFile zip, char* buffer, size_t bufferSize)
{
unz_file_info fileInfo;
unzGetCurrentFileInfo(zip, &fileInfo,
buffer, bufferSize,
unzGetCurrentFileInfo(zip, &fileInfo,
buffer, bufferSize,
NULL, 0, /* extra field */
NULL, 0 /* comment field */);
std::string name(buffer);
// no absolute paths, no 'up' traversals
// we could also look for suspicious file extensions here (forbid .dll, .exe, .so)
if ((name[0] == '/') || (name.find("../") != std::string::npos) || (name.find("..\\") != std::string::npos)) {
throw sg_format_exception("Bad zip path", name);
}
if (fileInfo.uncompressed_size == 0) {
// assume it's a directory for now
// since we create parent directories when extracting
// a path, we're done here
return;
}
int result = unzOpenCurrentFile(zip);
if (result != UNZ_OK) {
throw sg_io_exception("opening current zip file failed", sg_location(name));
}
std::ofstream outFile;
bool eof = false;
SGPath path(m_extractPath);
path.append(name);
// create enclosing directory heirarchy as required
Dir parentDir(path.dir());
if (!parentDir.exists()) {
@@ -170,12 +202,12 @@ private:
throw sg_io_exception("failed to create directory heirarchy for extraction", path.c_str());
}
}
outFile.open(path.c_str(), std::ios::binary | std::ios::trunc | std::ios::out);
if (outFile.fail()) {
throw sg_io_exception("failed to open output file for writing", path.c_str());
}
while (!eof) {
int bytes = unzReadCurrentFile(zip, buffer, bufferSize);
if (bytes < 0) {
@@ -186,30 +218,30 @@ private:
outFile.write(buffer, bytes);
}
}
outFile.close();
unzCloseCurrentFile(zip);
}
bool extractUnzip()
{
bool result = true;
zlib_filefunc_def memoryAccessFuncs;
fill_memory_filefunc(&memoryAccessFuncs);
char bufferName[128];
snprintf(bufferName, 128, "%p+%lx", m_buffer.data(), m_buffer.size());
unzFile zip = unzOpen2(bufferName, &memoryAccessFuncs);
const size_t BUFFER_SIZE = 32 * 1024;
void* buf = malloc(BUFFER_SIZE);
try {
int result = unzGoToFirstFile(zip);
if (result != UNZ_OK) {
throw sg_exception("failed to go to first file in archive");
}
while (true) {
extractCurrentFile(zip, (char*) buf, BUFFER_SIZE);
result = unzGoToNextFile(zip);
@@ -222,48 +254,44 @@ private:
} catch (sg_exception& e) {
result = false;
}
free(buf);
unzClose(zip);
return result;
}
void doFailure(Delegate::FailureCode aReason)
void doFailure(Delegate::StatusCode 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;
if (dir.exists()) {
dir.remove(true /* recursive */);
}
std::cout << "retrying download" << std::endl;
m_urls.erase(m_urls.begin()); // pop first URL
// TODO - try other mirrors
m_owner->m_download.reset(); // ensure we get cleaned up
m_owner->installResult(aReason);
}
InstallRef m_owner;
string_list m_urls;
SG_MD5_CTX m_md5;
std::string m_buffer;
SGPath m_extractPath;
size_t m_downloaded;
};
////////////////////////////////////////////////////////////////////
Install::Install(PackageRef aPkg, const SGPath& aPath) :
m_package(aPkg),
m_path(aPath),
m_download(NULL),
_status(Delegate::FAIL_IN_PROGRESS)
m_status(Delegate::STATUS_IN_PROGRESS)
{
parseRevision();
m_package->catalog()->registerInstall(this);
m_package->catalog()->root()->registerInstall(this);
}
Install::~Install()
{
m_package->catalog()->unregisterInstall(this);
}
InstallRef Install::createFromPath(const SGPath& aPath, CatalogRef aCat)
@@ -272,7 +300,7 @@ InstallRef Install::createFromPath(const SGPath& aPath, CatalogRef aCat)
PackageRef pkg = aCat->getPackageById(id);
if (!pkg)
throw sg_exception("no package with id:" + id);
return new Install(pkg, aPath);
}
@@ -284,7 +312,7 @@ void Install::parseRevision()
m_revision = 0;
return;
}
std::ifstream f(revisionFile.c_str(), std::ios::in);
f >> m_revision;
}
@@ -307,7 +335,7 @@ void Install::startUpdate()
if (m_download) {
return; // already active
}
m_download = new PackageArchiveDownloader(this);
m_package->catalog()->root()->makeHTTPRequest(m_download);
m_package->catalog()->root()->startInstall(this);
@@ -320,20 +348,95 @@ bool Install::uninstall()
SG_LOG(SG_GENERAL, SG_ALERT, "package uninstall failed: couldn't remove path " << m_path);
return false;
}
m_package->catalog()->unregisterInstall(this);
m_package->catalog()->root()->unregisterInstall(this);
return true;
}
bool Install::isDownloading() const
{
return (m_download != NULL);
return (m_download.valid());
}
bool Install::isQueued() const
{
return m_package->catalog()->root()->isInstallQueued(const_cast<Install*>(this));
}
int Install::downloadedPercent() const
{
if (!m_download.valid()) {
return -1;
}
PackageArchiveDownloader* dl = static_cast<PackageArchiveDownloader*>(m_download.get());
return dl->percentDownloaded();
}
size_t Install::downloadedBytes() const
{
if (!m_download.valid()) {
return -1;
}
PackageArchiveDownloader* dl = static_cast<PackageArchiveDownloader*>(m_download.get());
return dl->downloadedBytes();
}
void Install::cancelDownload()
{
if (m_download.valid()) {
m_download->abort("User cancelled download");
}
if (m_revision == 0) {
SG_LOG(SG_GENERAL, SG_INFO, "cancel install of package, will unregister");
m_package->catalog()->root()->unregisterInstall(this);
}
m_package->catalog()->root()->cancelDownload(this);
}
struct PathAppender
{
PathAppender(const SGPath& p) : m_path(p) {}
SGPath operator()(const std::string& s) const
{
SGPath p(m_path);
p.append(s);
return p;
}
SGPath m_path;
};
PathList Install::thumbnailPaths() const
{
const string_list& thumbs(m_package->thumbnails());
PathList result;
if (thumbs.empty())
return result;
std::transform(thumbs.begin(), thumbs.end(),
std::back_inserter(result),
PathAppender(m_path));
return result;
}
SGPath Install::primarySetPath() const
{
SGPath setPath(m_path);
std::string ps(m_package->id());
setPath.append(ps + "-set.xml");
return setPath;
}
//------------------------------------------------------------------------------
Install* Install::done(const Callback& cb)
{
if( _status == Delegate::FAIL_SUCCESS )
if( m_status == Delegate::STATUS_SUCCESS )
cb(this);
else
_cb_done.push_back(cb);
@@ -344,8 +447,8 @@ Install* Install::done(const Callback& cb)
//------------------------------------------------------------------------------
Install* Install::fail(const Callback& cb)
{
if( _status != Delegate::FAIL_SUCCESS
&& _status != Delegate::FAIL_IN_PROGRESS )
if( m_status != Delegate::STATUS_SUCCESS
&& m_status != Delegate::STATUS_IN_PROGRESS )
cb(this);
else
_cb_fail.push_back(cb);
@@ -356,7 +459,7 @@ Install* Install::fail(const Callback& cb)
//------------------------------------------------------------------------------
Install* Install::always(const Callback& cb)
{
if( _status != Delegate::FAIL_IN_PROGRESS )
if( m_status != Delegate::STATUS_IN_PROGRESS )
cb(this);
else
_cb_always.push_back(cb);
@@ -372,13 +475,12 @@ Install* Install::progress(const ProgressCallback& cb)
}
//------------------------------------------------------------------------------
void Install::installResult(Delegate::FailureCode aReason)
void Install::installResult(Delegate::StatusCode aReason)
{
if (aReason == Delegate::FAIL_SUCCESS) {
m_package->catalog()->root()->finishInstall(this);
m_package->catalog()->root()->finishInstall(this, aReason);
if (aReason == Delegate::STATUS_SUCCESS) {
_cb_done(this);
} else {
m_package->catalog()->root()->failedInstall(this, aReason);
_cb_fail(this);
}

View File

@@ -21,11 +21,13 @@
#include <vector>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/package/Delegate.hxx>
#include <simgear/structure/function_list.hxx>
#include <simgear/structure/SGReferenced.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
#include <simgear/io/HTTPRequest.hxx>
#include <boost/bind.hpp>
@@ -78,6 +80,31 @@ public:
bool isDownloading() const;
bool isQueued() const;
int downloadedPercent() const;
size_t downloadedBytes() const;
/**
* full path to the primary -set.xml file for this install
*/
SGPath primarySetPath() const;
/**
* if a download is in progress, cancel it. If this is the first install
* of the package (as opposed to an update), the install will be cleaned
* up once the last reference is gone.
*/
void cancelDownload();
/**
* return the thumbnails associated with this install, but as locations
* on the file system, not URLs. It is assumed the order of thumbnails
* is consistent with the URLs returned from Package::thumbnailUrls()
*/
PathList thumbnailPaths() const;
/**
* Set the handler to be called when the installation successfully
* completes.
@@ -147,16 +174,16 @@ private:
void parseRevision();
void writeRevisionFile();
void installResult(Delegate::FailureCode aReason);
void installResult(Delegate::StatusCode aReason);
void installProgress(unsigned int aBytes, unsigned int aTotal);
PackageRef m_package;
unsigned int m_revision; ///< revision on disk
SGPath m_path; ///< installation point on disk
PackageArchiveDownloader* m_download;
HTTP::Request_ptr m_download;
Delegate::FailureCode _status;
Delegate::StatusCode m_status;
function_list<Callback> _cb_done,
_cb_fail,

View File

@@ -139,7 +139,7 @@ InstallRef Package::install()
InstallRef Package::existingInstall(const InstallCallback& cb) const
{
InstallRef install = m_catalog->installForPackage(const_cast<Package*>(this));
InstallRef install = m_catalog->root()->existingInstallForPackage(const_cast<Package*>(this));
if( cb )
{
@@ -169,6 +169,10 @@ std::string Package::md5() const
unsigned int Package::revision() const
{
if (!m_props) {
return 0;
}
return m_props->getIntValue("revision");
}
@@ -200,15 +204,36 @@ SGPropertyNode* Package::properties() const
string_list Package::thumbnailUrls() const
{
string_list r;
if (!m_props) {
return r;
}
BOOST_FOREACH(SGPropertyNode* dl, m_props->getChildren("thumbnail")) {
r.push_back(dl->getStringValue());
}
return r;
}
string_list Package::thumbnails() const
{
string_list r;
if (!m_props) {
return r;
}
BOOST_FOREACH(SGPropertyNode* dl, m_props->getChildren("thumbnail-path")) {
r.push_back(dl->getStringValue());
}
return r;
}
string_list Package::downloadUrls() const
{
string_list r;
if (!m_props) {
return r;
}
BOOST_FOREACH(SGPropertyNode* dl, m_props->getChildren("url")) {
r.push_back(dl->getStringValue());
}

View File

@@ -122,6 +122,11 @@ public:
string_list thumbnailUrls() const;
/**
* thumbnail file paths within the package on disk
*/
string_list thumbnails() const;
/**
* Packages we depend upon.
* If the dependency list cannot be satisifed for some reason,
@@ -132,6 +137,7 @@ private:
SGPath pathOnDisk() const;
friend class Catalog;
friend class Root;
Package(const SGPropertyNode* aProps, CatalogRef aCatalog);

View File

@@ -38,31 +38,152 @@ namespace simgear {
namespace pkg {
typedef std::map<std::string, CatalogRef> CatalogDict;
typedef std::vector<Delegate*> DelegateVec;
typedef std::map<std::string, std::string> MemThumbnailCache;
typedef std::deque<std::string> StringDeque;
class Root::ThumbnailDownloader : public HTTP::Request
{
public:
ThumbnailDownloader(Root::RootPrivate* aOwner, const std::string& aUrl) :
HTTP::Request(aUrl),
m_owner(aOwner)
{
}
protected:
virtual void gotBodyData(const char* s, int n)
{
m_buffer += std::string(s, n);
}
virtual void onDone();
private:
Root::RootPrivate* m_owner;
std::string m_buffer;
};
class Root::RootPrivate
{
public:
RootPrivate() :
http(NULL),
maxAgeSeconds(60 * 60 * 24),
delegate(NULL)
maxAgeSeconds(60 * 60 * 24)
{
}
void fireStartInstall(InstallRef install)
{
DelegateVec::const_iterator it;
for (it = delegates.begin(); it != delegates.end(); ++it) {
(*it)->startInstall(install);
}
}
void fireInstallProgress(InstallRef install,
unsigned int aBytes, unsigned int aTotal)
{
DelegateVec::const_iterator it;
for (it = delegates.begin(); it != delegates.end(); ++it) {
(*it)->installProgress(install, aBytes, aTotal);
}
}
void fireFinishInstall(InstallRef install, Delegate::StatusCode status)
{
DelegateVec::const_iterator it;
for (it = delegates.begin(); it != delegates.end(); ++it) {
(*it)->finishInstall(install, status);
}
}
void fireRefreshStatus(CatalogRef catalog, Delegate::StatusCode status)
{
DelegateVec::const_iterator it;
for (it = delegates.begin(); it != delegates.end(); ++it) {
(*it)->catalogRefreshed(catalog, status);
}
}
void thumbnailDownloadComplete(HTTP::Request_ptr request,
Delegate::StatusCode status, const std::string& bytes)
{
std::string u(request->url());
SG_LOG(SG_IO, SG_INFO, "downloaded thumbnail:" << u);
if (status == Delegate::STATUS_SUCCESS) {
thumbnailCache[u] = bytes;
fireDataForThumbnail(u, bytes);
}
downloadNextPendingThumbnail();
}
void fireDataForThumbnail(const std::string& aUrl, const std::string& bytes)
{
DelegateVec::const_iterator it;
const uint8_t* data = reinterpret_cast<const uint8_t*>(bytes.data());
for (it = delegates.begin(); it != delegates.end(); ++it) {
(*it)->dataForThumbnail(aUrl, bytes.size(), data);
}
}
void downloadNextPendingThumbnail()
{
thumbnailDownloadRequest.clear();
if (pendingThumbnails.empty()) {
return;
}
std::string u = pendingThumbnails.front();
pendingThumbnails.pop_front();
thumbnailDownloadRequest = new Root::ThumbnailDownloader(this, u);
if (http) {
http->makeRequest(thumbnailDownloadRequest);
} else {
httpPendingRequests.push_back(thumbnailDownloadRequest);
}
}
DelegateVec delegates;
SGPath path;
std::string locale;
HTTP::Client* http;
CatalogDict catalogs;
unsigned int maxAgeSeconds;
Delegate* delegate;
std::string version;
std::set<CatalogRef> refreshing;
std::deque<InstallRef> updateDeque;
typedef std::deque<InstallRef> UpdateDeque;
UpdateDeque updateDeque;
std::deque<HTTP::Request_ptr> httpPendingRequests;
HTTP::Request_ptr thumbnailDownloadRequest;
StringDeque pendingThumbnails;
MemThumbnailCache thumbnailCache;
typedef std::map<PackageRef, InstallRef> InstallCache;
InstallCache m_installs;
};
void Root::ThumbnailDownloader::onDone()
{
if (responseCode() != 200) {
SG_LOG(SG_GENERAL, SG_ALERT, "thumbnail download failure:" << url());
m_owner->thumbnailDownloadComplete(this, Delegate::FAIL_DOWNLOAD, std::string());
return;
}
m_owner->thumbnailDownloadComplete(this, Delegate::STATUS_SUCCESS, m_buffer);
//time(&m_owner->m_retrievedTime);
//m_owner->writeTimestamp();
//m_owner->refreshComplete(Delegate::STATUS_REFRESHED);
}
SGPath Root::path() const
{
return d->path;
@@ -180,6 +301,20 @@ CatalogList Root::catalogs() const
return r;
}
PackageList
Root::allPackages() const
{
PackageList r;
CatalogDict::const_iterator it = d->catalogs.begin();
for (; it != d->catalogs.end(); ++it) {
const PackageList& r2(it->second->packages());
r.insert(r.end(), r2.begin(), r2.end());
}
return r;
}
PackageList
Root::packagesMatching(const SGPropertyNode* aFilter) const
{
@@ -210,17 +345,34 @@ Root::packagesNeedingUpdate() const
void Root::refresh(bool aForce)
{
bool didStartAny = false;
CatalogDict::iterator it = d->catalogs.begin();
for (; it != d->catalogs.end(); ++it) {
if (aForce || it->second->needsRefresh()) {
it->second->refresh();
didStartAny = true;
}
}
if (!didStartAny) {
// signal refresh complete to the delegate already
d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED);
}
}
void Root::setDelegate(simgear::pkg::Delegate *aDelegate)
void Root::addDelegate(simgear::pkg::Delegate *aDelegate)
{
d->delegate = aDelegate;
d->delegates.push_back(aDelegate);
}
void Root::removeDelegate(simgear::pkg::Delegate *aDelegate)
{
DelegateVec::iterator it = std::find(d->delegates.begin(),
d->delegates.end(), aDelegate);
if (it == d->delegates.end()) {
throw sg_exception("unknown delegate in removeDelegate");
}
d->delegates.erase(it);
}
void Root::setLocale(const std::string& aLocale)
@@ -254,18 +406,21 @@ void Root::scheduleToUpdate(InstallRef aInstall)
}
}
bool Root::isInstallQueued(InstallRef aInstall) const
{
RootPrivate::UpdateDeque::const_iterator it =
std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
return (it != d->updateDeque.end());
}
void Root::startInstall(InstallRef aInstall)
{
if (d->delegate) {
d->delegate->startInstall(aInstall.ptr());
}
d->fireStartInstall(aInstall);
}
void Root::installProgress(InstallRef aInstall, unsigned int aBytes, unsigned int aTotal)
{
if (d->delegate) {
d->delegate->installProgress(aInstall.ptr(), aBytes, aTotal);
}
d->fireInstallProgress(aInstall, aBytes, aTotal);
}
void Root::startNext(InstallRef aCurrent)
@@ -281,39 +436,49 @@ void Root::startNext(InstallRef aCurrent)
}
}
void Root::finishInstall(InstallRef aInstall)
void Root::finishInstall(InstallRef aInstall, Delegate::StatusCode aReason)
{
if (d->delegate) {
d->delegate->finishInstall(aInstall.ptr());
if (aReason != Delegate::STATUS_SUCCESS) {
SG_LOG(SG_GENERAL, SG_ALERT, "failed to install package:"
<< aInstall->package()->id() << ":" << aReason);
}
d->fireFinishInstall(aInstall, aReason);
startNext(aInstall);
}
void Root::failedInstall(InstallRef aInstall, Delegate::FailureCode aReason)
{
SG_LOG(SG_GENERAL, SG_ALERT, "failed to install package:"
<< aInstall->package()->id() << ":" << aReason);
if (d->delegate) {
d->delegate->failedInstall(aInstall.ptr(), aReason);
}
startNext(aInstall);
}
void Root::catalogRefreshBegin(CatalogRef aCat)
void Root::cancelDownload(InstallRef aInstall)
{
d->refreshing.insert(aCat);
RootPrivate::UpdateDeque::iterator it =
std::find(d->updateDeque.begin(), d->updateDeque.end(), aInstall);
if (it != d->updateDeque.end()) {
bool startNext = (aInstall == d->updateDeque.front());
d->updateDeque.erase(it);
if (startNext) {
if (!d->updateDeque.empty()) {
d->updateDeque.front()->startUpdate();
}
} // of install was front item
} // of found install in queue
}
void Root::catalogRefreshComplete(CatalogRef aCat, Delegate::FailureCode aReason)
void Root::catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason)
{
CatalogDict::iterator catIt = d->catalogs.find(aCat->id());
if (aReason != Delegate::FAIL_SUCCESS) {
if (d->delegate) {
d->delegate->failedRefresh(aCat, aReason);
d->fireRefreshStatus(aCat, aReason);
if (aReason == Delegate::STATUS_IN_PROGRESS) {
d->refreshing.insert(aCat);
if (catIt == d->catalogs.end()) {
// first fresh, add to our storage now
d->catalogs.insert(catIt, CatalogDict::value_type(aCat->id(), aCat));
}
} else {
d->refreshing.erase(aCat);
}
if ((aReason != Delegate::STATUS_REFRESHED) && (aReason != Delegate::STATUS_IN_PROGRESS)) {
// if the failure is permanent, delete the catalog from our
// list (don't touch it on disk)
bool isPermanentFailure = (aReason == Delegate::FAIL_VERSION);
@@ -323,16 +488,10 @@ void Root::catalogRefreshComplete(CatalogRef aCat, Delegate::FailureCode aReason
d->catalogs.erase(catIt);
}
}
} else if (catIt == d->catalogs.end()) {
// first fresh, add to our storage now
d->catalogs.insert(catIt, CatalogDict::value_type(aCat->id(), aCat));
}
d->refreshing.erase(aCat);
if (d->refreshing.empty()) {
if (d->delegate) {
d->delegate->refreshComplete();
}
d->fireRefreshStatus(CatalogRef(), Delegate::STATUS_REFRESHED);
}
}
@@ -357,7 +516,59 @@ bool Root::removeCatalogById(const std::string& aId)
return ok;
}
void Root::requestThumbnailData(const std::string& aUrl)
{
MemThumbnailCache::iterator it = d->thumbnailCache.find(aUrl);
if (it == d->thumbnailCache.end()) {
// insert into cache to mark as pending
d->pendingThumbnails.push_front(aUrl);
d->thumbnailCache[aUrl] = std::string();
d->downloadNextPendingThumbnail();
} else if (!it->second.empty()) {
// already loaded, fire data synchronously
d->fireDataForThumbnail(aUrl, it->second);
} else {
// in cache but empty data, still fetching
}
}
InstallRef Root::existingInstallForPackage(PackageRef p) const
{
RootPrivate::InstallCache::const_iterator it =
d->m_installs.find(p);
if (it == d->m_installs.end()) {
// check if it exists on disk, create
SGPath path(p->pathOnDisk());
if (path.exists()) {
// this will add to our cache, and hence, modify m_installs
return Install::createFromPath(path, p->catalog());
}
return InstallRef();
}
return it->second;
}
void Root::registerInstall(InstallRef ins)
{
if (!ins.valid()) {
return;
}
d->m_installs[ins->package()] = ins;
}
void Root::unregisterInstall(InstallRef ins)
{
if (!ins .valid()) {
return;
}
d->m_installs.erase(ins->package());
}
} // of namespace pkg
} // of namespace simgear

View File

@@ -62,8 +62,10 @@ public:
void setLocale(const std::string& aLocale);
void setDelegate(Delegate* aDelegate);
void addDelegate(Delegate* aDelegate);
void removeDelegate(Delegate* aDelegate);
std::string getLocale() const;
CatalogList catalogs() const;
@@ -92,6 +94,11 @@ public:
*/
void refresh(bool aForce = false);
/**
*
*/
PackageList allPackages() const;
/**
* retrieve packages matching a filter.
* filter consists of required / minimum values, AND-ed together.
@@ -115,21 +122,33 @@ public:
* from the catalog too.
*/
bool removeCatalogById(const std::string& aId);
/**
* request thumbnail data from the cache / network
*/
void requestThumbnailData(const std::string& aUrl);
bool isInstallQueued(InstallRef aInstall) const;
private:
friend class Install;
friend class Catalog;
friend class Package;
void catalogRefreshBegin(CatalogRef aCat);
void catalogRefreshComplete(CatalogRef aCat, Delegate::FailureCode aReason);
InstallRef existingInstallForPackage(PackageRef p) const;
void catalogRefreshStatus(CatalogRef aCat, Delegate::StatusCode aReason);
void startNext(InstallRef aCurrent);
void startInstall(InstallRef aInstall);
void installProgress(InstallRef aInstall, unsigned int aBytes, unsigned int aTotal);
void finishInstall(InstallRef aInstall);
void failedInstall(InstallRef aInstall, Delegate::FailureCode aReason);
void finishInstall(InstallRef aInstall, Delegate::StatusCode aReason);
void cancelDownload(InstallRef aInstall);
void registerInstall(InstallRef ins);
void unregisterInstall(InstallRef ins);
class ThumbnailDownloader;
class RootPrivate;
std::auto_ptr<RootPrivate> d;
};

View File

@@ -34,22 +34,28 @@ bool keepRunning = true;
class MyDelegate : public pkg::Delegate
{
public:
virtual void refreshComplete()
virtual void catalogRefreshed(pkg::CatalogRef aCatalog, StatusCode aReason)
{
if (aReason == STATUS_REFRESHED) {
if (aCatalog.ptr() == NULL) {
cout << "refreshed all catalogs" << endl;
} else {
cout << "refreshed catalog " << aCatalog->url() << endl;
}
} else if (aReason == STATUS_IN_PROGRESS) {
cout << "started refresh of " << aCatalog->url() << endl;
} else {
cerr << "failed refresh of " << aCatalog->url() << ":" << aReason << endl;
}
}
virtual void failedRefresh(pkg::Catalog* aCatalog, FailureCode aReason)
{
cerr << "failed refresh of " << aCatalog->description() << ":" << aReason << endl;
}
virtual void startInstall(pkg::Install* aInstall)
virtual void startInstall(pkg::InstallRef aInstall)
{
_lastPercent = 999;
cout << "starting install of " << aInstall->package()->name() << endl;
}
virtual void installProgress(pkg::Install* aInstall, unsigned int bytes, unsigned int total)
virtual void installProgress(pkg::InstallRef aInstall, unsigned int bytes, unsigned int total)
{
unsigned int percent = (bytes * 100) / total;
if (percent == _lastPercent) {
@@ -60,15 +66,15 @@ public:
cout << percent << "%" << endl;
}
virtual void finishInstall(pkg::Install* aInstall)
virtual void finishInstall(pkg::InstallRef aInstall, StatusCode aReason)
{
cout << "done install of " << aInstall->package()->name() << endl;
if (aReason == STATUS_SUCCESS) {
cout << "done install of " << aInstall->package()->name() << endl;
} else {
cerr << "failed install of " << aInstall->package()->name() << endl;
}
}
virtual void failedInstall(pkg::Install* aInstall, FailureCode aReason)
{
cerr << "failed install of " << aInstall->package()->name() << endl;
}
private:
unsigned int _lastPercent;
@@ -119,7 +125,7 @@ int main(int argc, char** argv)
pkg::Root* root = new pkg::Root(Dir::current().path(), "");
MyDelegate dlg;
root->setDelegate(&dlg);
root->addDelegate(&dlg);
cout << "Package root is:" << Dir::current().path() << endl;
cout << "have " << root->catalogs().size() << " catalog(s)" << endl;