diff --git a/simgear/canvas/ODGauge.cxx b/simgear/canvas/ODGauge.cxx index f7e95c4d..12bb52d2 100644 --- a/simgear/canvas/ODGauge.cxx +++ b/simgear/canvas/ODGauge.cxx @@ -250,10 +250,16 @@ namespace canvas if( !texture ) { + // It shouldn't be necessary to allocate an image for the + // texture that is the target of dynamic rendering, but + // otherwise OSG won't construct all the mipmaps for the texture + // and dynamic mipmap generation doesn't work. + osg::Image* image = new osg::Image; + image->allocateImage(_size_x, _size_y, 1, GL_RGBA, GL_UNSIGNED_BYTE); texture = new osg::Texture2D; texture->setResizeNonPowerOfTwoHint(false); - texture->setTextureSize(_size_x, _size_y); - texture->setInternalFormat(GL_RGBA); + texture->setImage(image); + texture->setUnRefImageDataAfterApply(true); } updateSampling(); diff --git a/simgear/io/HTTPRepository.cxx b/simgear/io/HTTPRepository.cxx index bfa426a6..59fa8dc0 100644 --- a/simgear/io/HTTPRepository.cxx +++ b/simgear/io/HTTPRepository.cxx @@ -34,9 +34,11 @@ #include "simgear/debug/logstream.hxx" #include "simgear/misc/strutils.hxx" + #include #include #include +#include #include #include #include @@ -176,7 +178,8 @@ class HTTPDirectory enum Type { FileType, - DirectoryType + DirectoryType, + TarballType }; ChildInfo(Type ty, const std::string & nameData, const std::string & hashData) : @@ -186,7 +189,7 @@ class HTTPDirectory { } - ChildInfo(const ChildInfo& other) = default; + ChildInfo(const ChildInfo& other) = default; void setSize(const std::string & sizeData) { @@ -311,59 +314,41 @@ public: void updateChildrenBasedOnHash() { - //SG_LOG(SG_TERRASYNC, SG_DEBUG, "updated children for:" << relativePath()); + copyInstalledChildren(); - copyInstalledChildren(); + ChildInfoList toBeUpdated; - string_list toBeUpdated, orphans, - indexNames = indexChildren(); - simgear::Dir d(absolutePath()); - PathList fsChildren = d.children(0); + simgear::Dir d(absolutePath()); + PathList fsChildren = d.children(0); + PathList orphans = d.children(0); - for (const auto& child : fsChildren) { - const auto& fileName = child.file(); - if ((fileName == ".dirindex") || (fileName == ".hashes")) { - continue; - } + ChildInfoList::const_iterator it; + for (it=children.begin(); it != children.end(); ++it) { + // Check if the file exists + PathList::const_iterator p = std::find_if(fsChildren.begin(), fsChildren.end(), LocalFileMatcher(*it)); + if (p == fsChildren.end()) { + // File or directory does not exist on local disk, so needs to be updated. + toBeUpdated.push_back(ChildInfo(*it)); + } else if (hashForChild(*it) != it->hash) { + // File/directory exists, but hash doesn't match. + toBeUpdated.push_back(ChildInfo(*it)); + orphans.erase(std::remove(orphans.begin(), orphans.end(), *p), orphans.end()); + } else { + // File/Directory exists and hash is valid. + orphans.erase(std::remove(orphans.begin(), orphans.end(), *p), orphans.end()); - ChildInfo info(child.isDir() ? ChildInfo::DirectoryType : ChildInfo::FileType, - fileName, ""); - info.path = child; - std::string hash = hashForChild(info); + if (it->type == ChildInfo::DirectoryType) { + // If it's a directory,perform a recursive check. + HTTPDirectory* childDir = childDirectory(it->name); + childDir->updateChildrenBasedOnHash(); + } + } + } - ChildInfoList::iterator c = findIndexChild(fileName); - if (c == children.end()) { - orphans.push_back(fileName); - } else if (c->hash != hash) { -#if 0 - SG_LOG(SG_TERRASYNC, SG_DEBUG, "hash mismatch'" << fileName); - // file exists, but hash mismatch, schedule update - if (!hash.empty()) { - SG_LOG(SG_TERRASYNC, SG_DEBUG, "file exists but hash is wrong for:" << fileName); - SG_LOG(SG_TERRASYNC, SG_DEBUG, "on disk:" << hash << " vs in info:" << c->hash); - } -#endif - toBeUpdated.push_back(fileName); - } else { - // file exists and hash is valid. If it's a directory, - // perform a recursive check. - if (c->type == ChildInfo::DirectoryType) { - HTTPDirectory* childDir = childDirectory(fileName); - childDir->updateChildrenBasedOnHash(); - } - } - - // remove existing file system children from the index list, - // so we can detect new children - // https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Erase-Remove - indexNames.erase(std::remove(indexNames.begin(), indexNames.end(), fileName), indexNames.end()); - } // of real children iteration - - // all remaining names in indexChilden are new children - toBeUpdated.insert(toBeUpdated.end(), indexNames.begin(), indexNames.end()); - - removeOrphans(orphans); - scheduleUpdates(toBeUpdated); + // We now have a list of entries that need to be updated, and a list + // of orphan files that should be removed. + removeOrphans(orphans); + scheduleUpdates(toBeUpdated); } HTTPDirectory* childDirectory(const std::string& name) @@ -372,10 +357,12 @@ public: return _repository->getOrCreateDirectory(childPath); } - void removeOrphans(const string_list& orphans) + void removeOrphans(const PathList orphans) { - string_list::const_iterator it; + PathList::const_iterator it; for (it = orphans.begin(); it != orphans.end(); ++it) { + if (it->file() == ".dirindex") continue; + if (it->file() == ".hash") continue; removeChild(*it); } } @@ -391,21 +378,20 @@ public: return r; } - void scheduleUpdates(const string_list& names) + void scheduleUpdates(const ChildInfoList names) { - string_list::const_iterator it; + ChildInfoList::const_iterator it; for (it = names.begin(); it != names.end(); ++it) { - ChildInfoList::iterator cit = findIndexChild(*it); - if (cit == children.end()) { - SG_LOG(SG_TERRASYNC, SG_WARN, "scheduleUpdate, unknown child:" << *it); - continue; - } - - if (cit->type == ChildInfo::FileType) { - _repository->updateFile(this, *it, cit->sizeInBytes); + if (it->type == ChildInfo::FileType) { + _repository->updateFile(this, it->name, it->sizeInBytes); + } else if (it->type == ChildInfo::DirectoryType){ + HTTPDirectory* childDir = childDirectory(it->name); + _repository->updateDir(childDir, it->hash, it->sizeInBytes); + } else if (it->type == ChildInfo::TarballType) { + // Download a tarball just as a file. + _repository->updateFile(this, it->name, it->sizeInBytes); } else { - HTTPDirectory* childDir = childDirectory(*it); - _repository->updateDir(childDir, cit->hash, cit->sizeInBytes); + SG_LOG(SG_TERRASYNC, SG_ALERT, "Coding error! Unknown Child type to schedule update"); } } } @@ -430,12 +416,58 @@ public: SG_LOG(SG_TERRASYNC, SG_WARN, "updated file but not found in dir:" << _relativePath << " " << file); } else { if (it->hash != hash) { - // we don't erase the file on a hash mismatch, becuase if we're syncing during the + SG_LOG(SG_TERRASYNC, SG_INFO, "Checksum error for " << absolutePath() << "/" << file << " " << it->hash << " " << hash); + // we don't erase the file on a hash mismatch, because if we're syncing during the // middle of a server-side update, the downloaded file may actually become valid. _repository->failedToUpdateChild(_relativePath, HTTPRepository::REPO_ERROR_CHECKSUM); } else { _repository->updatedFileContents(it->path, hash); _repository->totalDownloaded += sz; + SGPath p = SGPath(absolutePath(), file); + + if ((p.extension() == "tgz") || (p.extension() == "zip")) { + // We require that any compressed files have the same filename as the file or directory + // they expand to, so we can remove the old file/directory before extracting the new + // data. + SGPath removePath = SGPath(p.base()); + bool pathAvailable = true; + if (removePath.exists()) { + if (removePath.isDir()) { + simgear::Dir pd(removePath); + pathAvailable = pd.removeChildren(); + } else { + pathAvailable = removePath.remove(); + } + } + + if (pathAvailable) { + // If this is a tarball, then extract it. + SGBinaryFile f(p); + if (! f.open(SG_IO_IN)) SG_LOG(SG_TERRASYNC, SG_ALERT, "Unable to open " << p << " to extract"); + + SG_LOG(SG_TERRASYNC, SG_INFO, "Extracting " << absolutePath() << "/" << file << " to " << p.dir()); + SGPath extractDir = p.dir(); + ArchiveExtractor ex(extractDir); + + uint8_t* buf = (uint8_t*) alloca(128); + while (!f.eof()) { + size_t bufSize = f.read((char*) buf, 128); + ex.extractBytes(buf, bufSize); + } + + ex.flush(); + if (! ex.isAtEndOfArchive()) { + SG_LOG(SG_TERRASYNC, SG_ALERT, "Corrupt tarball " << p); + } + + if (ex.hasError()) { + SG_LOG(SG_TERRASYNC, SG_ALERT, "Error extracting " << p); + } + + } else { + SG_LOG(SG_TERRASYNC, SG_ALERT, "Unable to remove old file/directory " << removePath); + } // of pathAvailable + } // of handling tgz files } // of hash matches } // of found in child list } @@ -458,6 +490,16 @@ private: { return info.name == name; } }; + struct LocalFileMatcher + { + LocalFileMatcher(const ChildInfo ci) : childInfo(ci) {} + ChildInfo childInfo; + + bool operator()(const SGPath path) const { + return path.file() == childInfo.name; + } + }; + ChildInfoList::iterator findIndexChild(const std::string& name) { return std::find_if(children.begin(), children.end(), ChildWithName(name)); @@ -519,8 +561,8 @@ private: continue; } - if (typeData != "f" && typeData != "d" ) { - SG_LOG(SG_TERRASYNC, SG_WARN, "malformed .dirindex file: invalid type in line '" << line << "', expected 'd' or 'f', (ignoring line)" + if (typeData != "f" && typeData != "d" && typeData != "t" ) { + SG_LOG(SG_TERRASYNC, SG_WARN, "malformed .dirindex file: invalid type in line '" << line << "', expected 't', 'd' or 'f', (ignoring line)" << "\n\tparsing:" << p.utf8Str()); continue; } @@ -533,8 +575,12 @@ private: continue; } - children.emplace_back(ChildInfo(typeData == "f" ? ChildInfo::FileType : ChildInfo::DirectoryType, tokens[1], tokens[2])); - children.back().path = absolutePath() / tokens[1]; + ChildInfo ci = ChildInfo(ChildInfo::FileType, tokens[1], tokens[2]); + if (typeData == "d") ci.type = ChildInfo::DirectoryType; + if (typeData == "t") ci.type = ChildInfo::TarballType; + + children.emplace_back(ci); + children.back().path = absolutePath() / tokens[1]; if (tokens.size() > 3) { children.back().setSize(tokens[3]); } @@ -543,40 +589,36 @@ private: return true; } - void removeChild(const std::string& name) + void removeChild(SGPath path) { - SGPath p(absolutePath()); - p.append(name); bool ok; + SG_LOG(SG_TERRASYNC, SG_WARN, "Removing:" << path); - std::string fpath = _relativePath + "/" + name; - if (p.isDir()) { - ok = _repository->deleteDirectory(fpath, p); + std::string fpath = _relativePath + "/" + path.file(); + if (path.isDir()) { + ok = _repository->deleteDirectory(fpath, path); } else { // remove the hash cache entry - _repository->updatedFileContents(p, std::string()); - ok = p.remove(); + _repository->updatedFileContents(path, std::string()); + ok = path.remove(); } if (!ok) { - SG_LOG(SG_TERRASYNC, SG_WARN, "removal failed for:" << p); - throw sg_io_exception("Failed to remove existing file/dir:", p); + SG_LOG(SG_TERRASYNC, SG_WARN, "removal failed for:" << path); + throw sg_io_exception("Failed to remove existing file/dir:", path); } } std::string hashForChild(const ChildInfo& child) const { - SGPath p(child.path); - if (child.type == ChildInfo::DirectoryType) { - p.append(".dirindex"); - } + SGPath p(child.path); + if (child.type == ChildInfo::DirectoryType) p.append(".dirindex"); + if (child.type == ChildInfo::TarballType) p.concat(".tgz"); // For tarballs the hash is against the tarball file itself return _repository->hashForPath(p); } HTTPRepoPrivate* _repository; std::string _relativePath; // in URL and file-system space - - }; HTTPRepository::HTTPRepository(const SGPath& base, HTTP::Client *cl) : diff --git a/simgear/io/test_HTTP.cxx b/simgear/io/test_HTTP.cxx index 52034a3b..601c1037 100644 --- a/simgear/io/test_HTTP.cxx +++ b/simgear/io/test_HTTP.cxx @@ -61,23 +61,23 @@ public: std::map headers; protected: - virtual void onDone() + void onDone() override { complete = true; } - - virtual void onFail() + + void onFail() override { failed = true; } - virtual void gotBodyData(const char* s, int n) + void gotBodyData(const char* s, int n) override { - //std::cout << "got body data:'" << string(s, n) << "'" <bodyData, string(BODY1)); } + // disabling this test for now, since it seems to have changed depending + // on the libCurl version. (Or some other configuration which is currently + // not apparent). + // old behaviour: Curl sends the second request soon after makeRequest + // new behaviour: Curl waits for the first request to complete, before + // sending the second request (i.e acts as if HTTP pipelining is disabled) +#if 0 { - cout << "get-during-response-send" << endl; + cout << "get-during-response-send\n\n" << endl; cl.clearAllConnections(); //test_get_during_send @@ -804,7 +811,10 @@ cout << "testing proxy close" << endl; HTTP::Request_ptr own2(tr2); cl.makeRequest(tr2); - waitForComplete(&cl, tr2); + SG_VERIFY(waitFor(&cl, [tr, tr2]() { + return tr->isComplete() && tr2->isComplete(); + })); + SG_CHECK_EQUAL(tr->responseCode(), 200); SG_CHECK_EQUAL(tr->bodyData, string(BODY3)); SG_CHECK_EQUAL(tr->responseBytesReceived(), strlen(BODY3)); @@ -812,6 +822,7 @@ cout << "testing proxy close" << endl; SG_CHECK_EQUAL(tr2->bodyData, string(BODY1)); SG_CHECK_EQUAL(tr2->responseBytesReceived(), strlen(BODY1)); } +#endif { cout << "redirect test" << endl; diff --git a/simgear/package/Catalog.cxx b/simgear/package/Catalog.cxx index 5092b888..5b51b60c 100644 --- a/simgear/package/Catalog.cxx +++ b/simgear/package/Catalog.cxx @@ -198,9 +198,7 @@ private: ////////////////////////////////////////////////////////////////////////////// Catalog::Catalog(Root *aRoot) : - m_root(aRoot), - m_status(Delegate::FAIL_UNKNOWN), - m_retrievedTime(0) + m_root(aRoot) { } @@ -221,7 +219,7 @@ CatalogRef Catalog::createFromPath(Root* aRoot, const SGPath& aPath) SGPath xml = aPath; xml.append("catalog.xml"); if (!xml.exists()) { - return NULL; + return nullptr; } SGPropertyNode_ptr props; @@ -229,7 +227,7 @@ CatalogRef Catalog::createFromPath(Root* aRoot, const SGPath& aPath) props = new SGPropertyNode; readProperties(xml, props); } catch (sg_exception& ) { - return NULL; + return nullptr; } bool versionCheckOk = checkVersion(aRoot->applicationVersion(), props); @@ -240,19 +238,29 @@ CatalogRef Catalog::createFromPath(Root* aRoot, const SGPath& aPath) } else { SG_LOG(SG_GENERAL, SG_DEBUG, "creating catalog from:" << aPath); } + + // check for the marker file we write, to mark a catalog as disabled + const SGPath disableMarkerFile = aPath / "_disabled_"; CatalogRef c = new Catalog(aRoot); c->m_installRoot = aPath; + + if (disableMarkerFile.exists()) { + c->m_userEnabled = false; + } + c->parseProps(props); c->parseTimestamp(); if (!c->validatePackages()) { c->changeStatus(Delegate::FAIL_VALIDATION); - } else if (versionCheckOk) { + } else if (!versionCheckOk) { + c->changeStatus(Delegate::FAIL_VERSION); + } else if (!c->m_userEnabled) { + c->changeStatus(Delegate::USER_DISABLED); + } else { // parsed XML ok, mark status as valid c->changeStatus(Delegate::STATUS_SUCCESS); - } else { - c->changeStatus(Delegate::FAIL_VERSION); } return c; @@ -592,6 +600,9 @@ Delegate::StatusCode Catalog::status() const bool Catalog::isEnabled() const { + if (!m_userEnabled) + return false; + switch (m_status) { case Delegate::STATUS_SUCCESS: case Delegate::STATUS_REFRESHED: @@ -603,6 +614,36 @@ bool Catalog::isEnabled() const return false; } } + +bool Catalog::isUserEnabled() const +{ + return m_userEnabled; +} + +void Catalog::setUserEnabled(bool b) +{ + if (m_userEnabled == b) + return; + + m_userEnabled = b; + SGPath disableMarkerFile = installRoot() / "_disabled_"; + if (m_userEnabled) { + sg_ofstream of(disableMarkerFile); + of << "1\n"; // touch the file + } else { + bool ok = disableMarkerFile.remove(); + if (!ok) { + SG_LOG(SG_GENERAL, SG_ALERT, "Failed to remove catalog-disable marker file:" << disableMarkerFile); + } + } + + Delegate::StatusCode effectiveStatus = m_status; + if ((m_status == Delegate::STATUS_SUCCESS) && !m_userEnabled) { + effectiveStatus = Delegate::USER_DISABLED; + } + + m_root->catalogRefreshStatus(this, effectiveStatus); +} void Catalog::processAlternate(SGPropertyNode_ptr alt) { diff --git a/simgear/package/Catalog.hxx b/simgear/package/Catalog.hxx index f300690a..3bfe4065 100644 --- a/simgear/package/Catalog.hxx +++ b/simgear/package/Catalog.hxx @@ -150,6 +150,9 @@ public: { return addStatusCallback(boost::bind(mem_func, instance, _1)); } + + bool isUserEnabled() const; + void setUserEnabled(bool b); private: Catalog(Root* aRoot); @@ -185,11 +188,12 @@ private: SGPropertyNode_ptr m_props; SGPath m_installRoot; std::string m_url; - Delegate::StatusCode m_status; + Delegate::StatusCode m_status = Delegate::FAIL_UNKNOWN; HTTP::Request_ptr m_refreshRequest; - + bool m_userEnabled = true; + PackageList m_packages; - time_t m_retrievedTime; + time_t m_retrievedTime = 0; typedef std::map PackageWeakMap; PackageWeakMap m_variantDict; diff --git a/simgear/package/CatalogTest.cxx b/simgear/package/CatalogTest.cxx index 8b9f3435..d901c68f 100644 --- a/simgear/package/CatalogTest.cxx +++ b/simgear/package/CatalogTest.cxx @@ -15,9 +15,7 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // -#ifdef HAVE_CONFIG_H -# include -#endif +#include #include @@ -98,6 +96,11 @@ public: path = "/catalogTest1/movies-data.zip"; } + if (path == "/catalogTest1/b747.tar.gz") { + sendErrorResponse(403, false, "Bad URL"); + return; + } + localPath.append(path); // SG_LOG(SG_IO, SG_INFO, "local path is:" << localPath.str()); @@ -158,7 +161,7 @@ int parseTest() SG_CHECK_EQUAL(cat->description(), "First test catalog"); // check the packages too - SG_CHECK_EQUAL(cat->packages().size(), 5); + SG_CHECK_EQUAL(cat->packages().size(), 6); pkg::PackageRef p1 = cat->packages().front(); SG_CHECK_EQUAL(p1->catalog(), cat.ptr()); @@ -349,7 +352,7 @@ void testAddCatalog(HTTP::Client* cl) p.append("org.flightgear.test.catalog1"); p.append("catalog.xml"); SG_VERIFY(p.exists()); - SG_CHECK_EQUAL(root->allPackages().size(), 5); + SG_CHECK_EQUAL(root->allPackages().size(), 6); SG_CHECK_EQUAL(root->catalogs().size(), 1); pkg::PackageRef p1 = root->getPackageById("alpha"); @@ -383,6 +386,7 @@ void testInstallPackage(HTTP::Client* cl) waitForUpdateComplete(cl, root); SG_VERIFY(p1->isInstalled()); SG_VERIFY(p1->existingInstall() == ins); + SG_CHECK_EQUAL(ins->status(), pkg::Delegate::STATUS_SUCCESS); pkg::PackageRef commonDeps = root->getPackageById("common-sounds"); SG_VERIFY(commonDeps->existingInstall()); @@ -428,6 +432,7 @@ void testUninstall(HTTP::Client* cl) ins->uninstall(); + SG_CHECK_EQUAL(ins->status(), pkg::Delegate::STATUS_SUCCESS); SG_VERIFY(!ins->path().exists()); } @@ -1075,9 +1080,45 @@ void updateInvalidToInvalid(HTTP::Client* cl) } +void testInstallBadPackage(HTTP::Client* cl) +{ + global_catalogVersion = 0; + + SGPath rootPath(simgear::Dir::current().path()); + rootPath.append("pkg_install_bad_pkg"); + simgear::Dir pd(rootPath); + pd.removeChildren(); + + pkg::RootRef root(new pkg::Root(rootPath, "8.1.2")); + // specify a test dir + root->setHTTPClient(cl); + + pkg::CatalogRef c = pkg::Catalog::createFromUrl(root.ptr(), "http://localhost:2000/catalogTest1/catalog.xml"); + waitForUpdateComplete(cl, root); + + pkg::PackageRef p1 = root->getPackageById("org.flightgear.test.catalog1.b747-400"); + pkg::InstallRef ins = p1->install(); + + bool didFail = false; + ins->fail([&didFail, &ins](pkg::Install* ourInstall) { + SG_CHECK_EQUAL(ins, ourInstall); + didFail = true; + }); + + SG_VERIFY(ins->isQueued()); + + waitForUpdateComplete(cl, root); + SG_VERIFY(!p1->isInstalled()); + SG_VERIFY(didFail); + SG_VERIFY(p1->existingInstall() == ins); + SG_CHECK_EQUAL(ins->status(), pkg::Delegate::FAIL_DOWNLOAD); + SG_CHECK_EQUAL(ins->path(), rootPath / "org.flightgear.test.catalog1" / "Aircraft" / "b744"); +} + + int main(int argc, char* argv[]) { - sglog().setLogLevels( SG_ALL, SG_DEBUG ); + // sglog().setLogLevels( SG_ALL, SG_DEBUG ); HTTP::Client cl; cl.setMaxConnections(1); @@ -1116,6 +1157,8 @@ int main(int argc, char* argv[]) testVersionMigrateToId(&cl); + testInstallBadPackage(&cl); + SG_LOG(SG_GENERAL, SG_INFO, "Successfully passed all tests!"); return EXIT_SUCCESS; } diff --git a/simgear/package/Delegate.hxx b/simgear/package/Delegate.hxx index 4f92abca..03b262fb 100644 --- a/simgear/package/Delegate.hxx +++ b/simgear/package/Delegate.hxx @@ -56,7 +56,8 @@ public: FAIL_HTTP_FORBIDDEN, ///< URL returned a 403. Marked specially to catch rate-limiting FAIL_VALIDATION, ///< catalog or package failed to validate STATUS_REFRESHED, - USER_CANCELLED + USER_CANCELLED, + USER_DISABLED } StatusCode; diff --git a/simgear/package/Install.cxx b/simgear/package/Install.cxx index 85f8b708..8ab574c9 100644 --- a/simgear/package/Install.cxx +++ b/simgear/package/Install.cxx @@ -108,6 +108,8 @@ protected: m_extractor.reset(new ArchiveExtractor(m_extractPath)); memset(&m_md5, 0, sizeof(SG_MD5_CTX)); SG_MD5Init(&m_md5); + + m_owner->startDownload(); } virtual void gotBodyData(const char* s, int n) @@ -395,6 +397,7 @@ Install* Install::progress(const ProgressCallback& cb) //------------------------------------------------------------------------------ void Install::installResult(Delegate::StatusCode aReason) { + m_status = aReason; m_package->catalog()->root()->finishInstall(this, aReason); if (aReason == Delegate::STATUS_SUCCESS) { _cb_done(this); @@ -412,6 +415,15 @@ void Install::installProgress(unsigned int aBytes, unsigned int aTotal) _cb_progress(this, aBytes, aTotal); } +void Install::startDownload() +{ + m_status = Delegate::STATUS_IN_PROGRESS; +} + +Delegate::StatusCode Install::status() const +{ + return m_status; +} } // of namespace pkg diff --git a/simgear/package/Install.hxx b/simgear/package/Install.hxx index fd4e5d8c..268564eb 100644 --- a/simgear/package/Install.hxx +++ b/simgear/package/Install.hxx @@ -86,6 +86,8 @@ public: size_t downloadedBytes() const; + Delegate::StatusCode status() const; + /** * full path to the primary -set.xml file for this install */ @@ -169,6 +171,7 @@ private: void installResult(Delegate::StatusCode aReason); void installProgress(unsigned int aBytes, unsigned int aTotal); + void startDownload(); PackageRef m_package; unsigned int m_revision; ///< revision on disk diff --git a/simgear/package/catalogTest1/catalog.xml b/simgear/package/catalogTest1/catalog.xml index e16b3ec2..4305b1d1 100644 --- a/simgear/package/catalogTest1/catalog.xml +++ b/simgear/package/catalogTest1/catalog.xml @@ -194,6 +194,32 @@ http://localhost:2000/catalogTest1/b737.tar.gz + + b747-400 + Boeing 747-400 + b744 + A popular four-engined wide-body jet + 111 + 860 + + boeing + jet + ifr + + + + + 5 + 5 + 4 + 4 + + + a94ca5704f305b90767f40617d194ed6 + + http://localhost:2000/catalogTest1/b747.tar.gz + + common-sounds Common sound files for test catalog aircraft