From b988d2c4d0513809a890e3a83d5070bc982e2fda Mon Sep 17 00:00:00 2001 From: Michael Andres Date: Fri, 18 Jan 2013 17:09:28 +0100 Subject: [PATCH] Add source-download command (fate#313445) --- doc/zypper.8 | 20 ++ src/CMakeLists.txt | 2 + src/Command.cc | 2 + src/Command.h | 2 + src/Zypper.cc | 65 +++++- src/source-download.cc | 575 +++++++++++++++++++++++++++++++++++++++++++++++++ src/source-download.h | 58 +++++ 7 files changed, 722 insertions(+), 2 deletions(-) create mode 100644 src/source-download.cc create mode 100644 src/source-download.h diff --git a/doc/zypper.8 b/doc/zypper.8 index 1caf96c..c820f89 100644 --- a/doc/zypper.8 +++ b/doc/zypper.8 @@ -1510,6 +1510,26 @@ This command can be useful for companies redistributiong a custom distribution (like appliances) to figure out what licenses they are bound by. .TP +.B source-download +Download source rpms for all installed packages to a local directory. + +.TP +.I \-d, \-\-directory +Download all source rpms to this directory. Default is \fB/var/cache/zypper/source-download\fR. + +.TP +.I \-\-delete +Delete extraneous source rpms in the local directory. This is the default. + +.TP +.I \-\-no-delete +Do not delete extraneous source rpms. + +.TP +.I \-\-status +Don't download any source rpms, but show which source rpms are missing or extraneous. + +.TP .B ps After each upgrade or removal of packages, there may be running processes on the system which then use files meanwhile deleted by the upgrade. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c58e193..0d48079 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,7 @@ SET (zypper_HEADERS Table.h locks.h update.h + source-download.h solve-commit.h PackageArgs.h SolverRequester.h @@ -40,6 +41,7 @@ SET( zypper_SRCS Table.cc locks.cc update.cc + source-download.cc solve-commit.cc PackageArgs.cc RequestFeedback.cc diff --git a/src/Command.cc b/src/Command.cc index a6894d7..1a96cc3 100644 --- a/src/Command.cc +++ b/src/Command.cc @@ -78,6 +78,7 @@ namespace _T( VERSION_CMP_e ) | "versioncmp" | "vcmp"; _T( LICENSES_e ) | "licenses"; _T( PS_e ) | "ps"; + _T( SOURCE_DOWNLOAD_e ) | "source-download"; _T( HELP_e ) | "help" | "?"; _T( SHELL_e ) | "shell" | "sh"; @@ -151,6 +152,7 @@ DEF_ZYPPER_COMMAND( TARGET_OS ); DEF_ZYPPER_COMMAND( VERSION_CMP ); DEF_ZYPPER_COMMAND( LICENSES ); DEF_ZYPPER_COMMAND( PS ); +DEF_ZYPPER_COMMAND( SOURCE_DOWNLOAD ); DEF_ZYPPER_COMMAND( HELP ); DEF_ZYPPER_COMMAND( SHELL ); diff --git a/src/Command.h b/src/Command.h index 163c808..5bc19cc 100644 --- a/src/Command.h +++ b/src/Command.h @@ -68,6 +68,7 @@ struct ZypperCommand static const ZypperCommand VERSION_CMP; static const ZypperCommand LICENSES; static const ZypperCommand PS; + static const ZypperCommand SOURCE_DOWNLOAD; static const ZypperCommand HELP; static const ZypperCommand SHELL; @@ -140,6 +141,7 @@ struct ZypperCommand VERSION_CMP_e, LICENSES_e, PS_e, + SOURCE_DOWNLOAD_e, HELP_e, SHELL_e, diff --git a/src/Zypper.cc b/src/Zypper.cc index 4c9af93..e039aec 100644 --- a/src/Zypper.cc +++ b/src/Zypper.cc @@ -54,6 +54,7 @@ #include "locks.h" #include "search.h" #include "info.h" +#include "source-download.h" #include "output/OutNormal.h" #include "output/OutXML.h" @@ -269,6 +270,8 @@ void print_main_help(Zypper & zypper) "\ttargetos, tos\t\tPrint the target operating system ID string.\n" "\tlicenses\t\tPrint report about licenses and EULAs of\n" "\t\t\t\tinstalled packages.\n" + "\tsource-download\t\tDownload source rpms for all installed packages\n" + "\t\t\t\tto a local directory.\n" ); static string help_usage = _( @@ -2381,6 +2384,42 @@ void Zypper::processCommandOptions() } + case ZypperCommand::SOURCE_DOWNLOAD_e: + { + shared_ptr myOpts( new SourceDownloadOptions() ); + _commandOptions = myOpts; + static struct option options[] = + { + {"help", no_argument, 0, 'h'}, + {"directory", required_argument, 0, 'd'}, +// {"manifest", no_argument, &myOpts->_manifest, 1}, +// {"no-manifest", no_argument, &myOpts->_manifest, 0}, + {"delete", no_argument, &myOpts->_delete, 1}, + {"no-delete", no_argument, &myOpts->_delete, 0}, + {"status", no_argument, &myOpts->_dryrun, 1}, + {0, 0, 0, 0} + }; + specific_options = options; + _command_help = _( + "source-download\n" + "\n" + "Download source rpms for all installed packages to a local directory.\n" + "\n" + " Command options:\n" + "-d, --directory \n" + " Download all source rpms to this directory.\n" + " Default: /var/cache/zypper/source-download\n" + "--delete Delete extraneous source rpms in the local directory.\n" + "--no-delete Do not delete extraneous source rpms.\n" + "--status Don't download any source rpms,\n" + " but show which source rpms are missing or extraneous.\n" + ); +// "--manifest Write MANIFEST of packages and coresponding source rpms.\n" +// "--no-manifest Do not write MANIFEST.\n" + break; + } + + case ZypperCommand::SHELL_QUIT_e: { static struct option quit_options[] = { @@ -4190,8 +4229,7 @@ void Zypper::doCommand() else { SolverRequester::Options sropts; - if (copts.find("force") != -copts.end()) + if (copts.find("force") != copts.end()) sropts.force = true; sropts.best_effort = best_effort; sropts.skip_interactive = skip_interactive; // bcn #647214 @@ -4662,6 +4700,29 @@ copts.end()) break; } + case ZypperCommand::SOURCE_DOWNLOAD_e: + { + if (runningHelp()) { out().info(_command_help, Out::QUIET); return; } + + if (!_arguments.empty()) + { + report_too_many_arguments(_command_help); + setExitCode(ZYPPER_EXIT_ERR_INVALID_ARGS); + return; + } + + shared_ptr myOpts( assertCommandOptions() ); + + if ( _copts.count( "directory" ) ) + myOpts->_directory = _copts["directory"].back(); // last wins + + if ( _copts.count( "dry-run" ) ) + myOpts->_dryrun = true; + + sourceDownload( *this ); + + break; + } // -----------------------------( shell )------------------------------------ diff --git a/src/source-download.cc b/src/source-download.cc new file mode 100644 index 0000000..1bb17e3 --- /dev/null +++ b/src/source-download.cc @@ -0,0 +1,575 @@ +/*---------------------------------------------------------------------------*\ + ____ _ _ __ _ __ ___ _ _ + |_ / || | '_ \ '_ \/ -_) '_| + /__|\_, | .__/ .__/\___|_| + |__/|_| |_| +\*---------------------------------------------------------------------------*/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Zypper.h" +#include "Table.h" +#include "source-download.h" + +/////////////////////////////////////////////////////////////////// +// SourceDownloadOptions +/////////////////////////////////////////////////////////////////// + +const Pathname SourceDownloadOptions::_defaultDirectory( "/var/cache/zypper/source-download" ); +const std::string SourceDownloadOptions::_manifestName( "MANIFEST" ); + +inline std::ostream & operator<<( std::ostream & str, const SourceDownloadOptions & obj ) +{ + return str << boost::format( "{%1%|%2%%3%}" ) + % obj._directory +// % (obj._manifest ? 'M' : 'm' ) + % (obj._delete ? 'D' : 'd' ) + % (obj._dryrun ? "(dry-run)" : "" ); +} + +/////////////////////////////////////////////////////////////////// +namespace +{ + + /////////////////////////////////////////////////////////////////// + /// \class SourceDownloadImpl + /// \brief Implementation of source-download commands. + /////////////////////////////////////////////////////////////////// + class SourceDownloadImpl + { + public: + SourceDownloadImpl( Zypper & zypper_r ) + : _zypper( zypper_r ) + , _options( _zypper.commandOptionsAs() ) + , _dnlDir( Pathname::assertprefix( _zypper.globalOpts().root_dir, _options->_directory ) ) + { MIL << "SourceDownload " << _options << endl; } + + public: + + /////////////////////////////////////////////////////////////////// + /// \class SourceDownloadImpl::SourcePkg + /// \brief A Manifest entry. + /////////////////////////////////////////////////////////////////// + /** Manifest entry */ + struct SourcePkg + { + enum Status + { + S_EMPTY, //< !needed && !downloaded + S_SUPERFLUOUS, //< !needed && downloaded + S_MISSING, //< needed && !downloaded + S_OK, //< needed && downloaded + }; + + SourcePkg( const std::string & longname_r = std::string() ) + : _longname( longname_r ) {} + + bool needed() const + { return !_packages.empty(); } + + bool downloaded() const + { return !_localFile.empty(); } + + Status status() const + { + if ( needed() ) + return downloaded() ? S_OK : S_MISSING; + return downloaded() ? S_SUPERFLUOUS : S_EMPTY; + } + + PoolItem lookupSrcPackage() + { + if ( ! _srcPackage ) + { + ResPool pool( ResPool::instance() ); + Package::constPtr pkg( _packages.front()->asKind() ); + for_( it, pool.byIdentBegin( pkg->sourcePkgName() ), pool.byIdentEnd( pkg->sourcePkgName() ) ) + { + if ( (*it)->edition() == pkg->sourcePkgEdition() ) + { + _srcPackage = *it; + break; + } + } + } + return _srcPackage; + } + + std::string _longname; //< Key: name-version-release.type + std::string _localFile; //< name of srpm if downloaded + PoolItem _srcPackage; //< available SrcPackage providning the srpm + std::vector _packages; //< installed Packages built from this srpm + + static std::string makeLongname( const std::string & name_r, Edition edition_r, bool nosrc_r ) + { return str::Str() << name_r << '-' << edition_r << '.' << (nosrc_r ? "nosrc" : "src"); } + }; + /////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + /// \class SourceDownloadImpl::Manifest + /// \brief A set of SourcePkg + /////////////////////////////////////////////////////////////////// + struct Manifest : public std::map + { + typedef std::map > StatusMap; + + /** Return \ref SourcePkg for \a key_r (assert \c SourcePkg::_longname is set) */ + SourcePkg & get( const std::string & key_r ) + { + SourcePkg & ret( operator[]( key_r ) ); + if ( ret._longname.empty() ) + ret._longname = key_r; + return ret; + } + + void updateStatus( StatusMap & status_r ) const + { + StatusMap status; + for_( it, begin(), end() ) + { + const SourcePkg & src( it->second ); + ++status[src.status()]; + } + status_r.swap( status ); + } + }; + /////////////////////////////////////////////////////////////////// + + /////////////////////////////////////////////////////////////////// + /// \class SourceDownloadImpl::DownloadProgress + /// \brief Listen on media::DownloadProgressReport to feed a ProgressBar. + /// + /// Connect to media::DownloadProgressReport to feed a ProgressBar, but forward + /// callbacks to any original receiver. + /// \todo Probably move this as helper class to \ref Out. + /////////////////////////////////////////////////////////////////// + struct DownloadProgress : public callback::ReceiveReport + { + DownloadProgress( Out::ProgressBar & progressbar_r ) + : _progressbar( &progressbar_r ) + , _oldReceiver( Distributor::instance().getReceiver() ) + { + connect(); + } + + ~DownloadProgress() + { + if ( _oldReceiver ) + Distributor::instance().setReceiver( *_oldReceiver ); + else + Distributor::instance().noReceiver(); + } + + virtual void start( const Url & file, Pathname localfile ) + { + (*_progressbar)->range( 100 ); // we'll receive % + + if ( _oldReceiver ) + _oldReceiver->start( file, localfile ); + } + + virtual bool progress( int value, const Url & file, double dbps_avg = -1, double dbps_current = -1 ) + { + (*_progressbar)->set( value ); + + if ( _oldReceiver ) + return _oldReceiver->progress( value, file, dbps_avg, dbps_current ); + return true; + } + + virtual Action problem( const Url & file, Error error, const std::string & description ) + { + ERR << description << endl; + + if ( _oldReceiver ) + return _oldReceiver->problem( file, error, description ); + return Receiver::problem( file, error, description ); + } + + virtual void finish( const Url & file, Error error, const std::string & reason ) + { + if ( error == NO_ERROR ) + (*_progressbar)->toMax(); + else + { + ERR << reason << endl; + _progressbar->error(); + } + + if ( _oldReceiver ) + _oldReceiver->finish( file, error, reason ); + } + + private: + Out::ProgressBar * _progressbar; + Receiver * _oldReceiver; + }; + /////////////////////////////////////////////////////////////////// + + public: + void sourceDownload(); + + private: + /** Startup and build manifest. */ + void buildManifest(); + + std::ostream & dumpManifestSumary( std::ostream & str, Manifest::StatusMap & status ); + std::ostream & dumpManifestTable( std::ostream & str ); + + private: + Zypper & _zypper; //< my Zypper + shared_ptr _options; //< my Options + private: + Pathname _dnlDir; //< download directory (incl. root prefix) + Manifest _manifest; + DefaultIntegral _installedPkgCount; + }; + /////////////////////////////////////////////////////////////////// + + /** \relates SourceDownloadImpl::SourcePkg::Status String representation */ + inline std::string asString( SourceDownloadImpl::SourcePkg::Status obj ) + { + switch ( obj ) + { + case SourceDownloadImpl::SourcePkg::S_EMPTY: return "?"; break; + case SourceDownloadImpl::SourcePkg::S_SUPERFLUOUS:return "+"; break; + case SourceDownloadImpl::SourcePkg::S_MISSING: return "-"; break; + case SourceDownloadImpl::SourcePkg::S_OK: return " "; break; + } + return "?"; + } + + /** \relates SourceDownloadImpl::SourcePkg::Status Stream output */ + inline std::ostream & operator<<( std::ostream & str, SourceDownloadImpl::SourcePkg::Status obj ) + { return str << asString( obj ); } + + /** \relates SourceDownloadImpl::SourcePkg Stream output */ + inline std::ostream & operator<<( std::ostream & str, const SourceDownloadImpl::SourcePkg & obj ) + { + str << obj.status() << " " << obj._longname << endl; + str << " spkg: " << obj._srcPackage << endl; + str << " pkgs: " << obj._packages.size(); + return str; + } + + /////////////////////////////////////////////////////////////////// + /// class SourceDownloadImpl + /////////////////////////////////////////////////////////////////// + + void SourceDownloadImpl::buildManifest() + { + PathInfo pi( _dnlDir ); + if ( pi.error() == ENOENT ) + { + // try to create it on the fly + filesystem::assert_dir( pi.path() ); + pi(); + } + DBG << "Download directory: " << pi << endl; + + if ( ! pi.isDir() ) + { + ERR << "Can't create or access download directory" << endl; + throw( Out::Error( ZYPPER_EXIT_ERR_BUG, + boost::format(_("Can't create or access download directory '%s'.")) % pi.asString(), + Errno( pi.error() ).asString() ) ); + return; + } + + if ( ! ( pi.userMayR() && ( _options->_dryrun || pi.userMayW() ) ) ) + { + ERR << "Download directory is not readable." << endl; + throw( Out::Error( ZYPPER_EXIT_ERR_PRIVILEGES, + boost::format(_("Insufficient privileges to use download directory '%s'.")) % pi.asString() ) ); + return; + } + + _zypper.out().info( + boost::format(_("Using download directory at '%s'.")) % pi.asString() + ); + + // scan download directory to manifest + { + std::list todolist; + int ret = readdir( todolist, pi.path(), /*dots*/false ); + if ( ret != 0 ) + { + ERR << "Failed to read download directory." << endl; + throw( Out::Error( ZYPPER_EXIT_ERR_BUG, + _("Failed to read download directory"), + Errno().asString() ) ); + return; + } + + Out::ProgressBar report( _zypper.out(), _("Scanning download directory") ); + report->range( todolist.size() ); + for ( const auto & file : todolist ) + { + report->incr(); // fast enough to count in advance. + + if ( file == _options->_manifestName ) + continue; + + using target::rpm::RpmHeader; + Pathname path( pi.path() / file ); + RpmHeader::constPtr pkg( RpmHeader::readPackage( path, RpmHeader::NOVERIFY ) ); + + if ( ! ( pkg && pkg->isSrc() ) ) + continue; + + SourcePkg & spkg( _manifest.get( SourcePkg::makeLongname( pkg->tag_name(), pkg->tag_edition(), pkg->isNosrc() ) ) ); + spkg._localFile = file; + } + } + + // scan installed packages to manifest + { + if ( _zypper.defaultLoadSystem( _options->_dryrun ? Zypper::NO_REPOS : Zypper::LoadSystemFlags() ) != ZYPPER_EXIT_OK ) + { + ERR << "Startup returns " << _zypper.exitCode() << endl; + throw( Out::Error(_("Failed to read download directory"), + Errno().asString() ) ); + } + + Out::ProgressBar report( _zypper.out(), _("Scanning installed packages") ); + ResPool pool( ResPool::instance() ); + for_( it, pool.byKindBegin(), pool.byKindEnd() ) + { + if ( ! it->status().isInstalled() ) + continue; + + SourcePkg & spkg( _manifest.get( (*it)->asKind()->sourcePkgLongName() ) ); + spkg._packages.push_back( *it ); + ++_installedPkgCount; // on the fly count installed packages + } + } + } + + std::ostream & SourceDownloadImpl::dumpManifestSumary( std::ostream & str, Manifest::StatusMap & status ) + { + { + Table t; + t.lineStyle( none ); + { TableRow tr; tr << _("Installed packages:") + << str::numstring( _installedPkgCount, 5 ) + ; t << tr; } + { TableRow tr; tr << _("Required source packages:") + << str::numstring( _manifest.size() - status[SourcePkg::S_SUPERFLUOUS] - status[SourcePkg::S_EMPTY], 5 ) + ; t << tr; } + str << t << endl; + } + { + Table t; + t.lineStyle( none ); + { TableRow tr; tr << '[' + asString( SourcePkg::S_OK ) + ']' + << _("Required source packages available in download directory:") + << str::numstring( status[SourcePkg::S_OK], 5 ); + t << tr; } + { TableRow tr; tr << '[' + asString( SourcePkg::S_MISSING ) + ']' + << _("Required source packages to be downloaded:") + << str::numstring( status[SourcePkg::S_MISSING], 5 ); + t << tr; } + { TableRow tr; tr << '[' + asString( SourcePkg::S_SUPERFLUOUS ) + ']' + << _("Superfluous source packages in download directory:") + << str::numstring( status[SourcePkg::S_SUPERFLUOUS], 5 ); + t << tr; } + str << t << endl; + } + return str; + } + + + std::ostream & SourceDownloadImpl::dumpManifestTable( std::ostream & str ) + { + Table t; + TableHeader th; + // translators: table headers + th << "#" << _("Source package") << _("Installed package"); + t << th; + + for ( const auto & item : _manifest ) + { + const SourcePkg & spkg( item.second ); + std::string l1( asString( spkg.status() ) ); + std::string l2( spkg._longname ); + if ( spkg._packages.empty() ) + { + TableRow tr; + tr << l1 << l2 << "-----"; + t << tr; + } + else + { + for ( const auto & pkg : spkg._packages ) + { + TableRow tr; + tr << l1 << l2 << pkg.satSolvable(); + t << tr; + if ( ! l1.empty() ) + { + l1.clear(); + l2 = " --\"--"; + } + } + } + } + str << t << endl; + return str; + } + + + void SourceDownloadImpl::sourceDownload() + { + buildManifest(); + Manifest::StatusMap status; + _manifest.updateStatus( status ); + + cout << endl; + dumpManifestSumary( cout, status ); + + if ( _options->_dryrun ) + { + if ( _zypper.out().verbosity() > Out::NORMAL ) + { + dumpManifestTable( cout ); + } + else + { + _zypper.out().info(_("Use '--verbose' option for a full list of required source packages.") ); + } + return; // --> dry run ends here. + } + + // delete superfluous source packages + + if ( status[SourcePkg::S_SUPERFLUOUS] && _options->_delete ) + { + Out::ProgressBar report( _zypper.out(), _("Deleting superfluous source packages") ); + report->range( status[SourcePkg::S_SUPERFLUOUS] ); + for ( auto & item : _manifest ) + { + SourcePkg & spkg( item.second ); + if ( spkg.status() != SourcePkg::S_SUPERFLUOUS ) + continue; + + int res = filesystem::unlink( _dnlDir / spkg._localFile ); + if ( res != 0 ) + { + throw( Out::Error( boost::format(_("Failed to remove source package '%s'") ) % (_dnlDir / spkg._localFile), + Errno().asString() ) ); + } + MIL << spkg << endl; + spkg._localFile.clear(); + DBG << spkg << endl; + report->incr(); + } + } + else + { + std::string msg(_("No superfluous source packages to delete.") ); + if ( status[SourcePkg::S_SUPERFLUOUS] ) + msg += " (--no-delete)"; + _zypper.out().info( msg ); + } + + // download missing packages + + if ( status[SourcePkg::S_MISSING] ) + { + _zypper.out().info(_("Downloading required source packages...") ); + repo::RepoMediaAccess access; + repo::SrcPackageProvider prov( access ); + unsigned current = 0; + for ( auto & item : _manifest ) + { + SourcePkg & spkg( item.second ); + if ( spkg.status() != SourcePkg::S_MISSING ) + continue; + ++current; + + try + { + Out::ProgressBar report( _zypper.out(), spkg._longname, current, status[SourcePkg::S_MISSING] ); + + if ( ! spkg.lookupSrcPackage() ) + { + report.error(); + throw( Out::Error( ZYPPER_EXIT_ERR_BUG, + boost::format(_("Source package '%s' is not provided by any repository.") ) % spkg._longname ) ); + } + report.print( str::form( "%s (%s)", spkg._longname.c_str(), spkg._srcPackage->repository().name().c_str() ) ); + MIL << spkg._srcPackage << endl; + + ManagedFile localfile; + { + report.error(); // error if provideSrcPackage throws + DownloadProgress redirect( report ); + localfile = prov.provideSrcPackage( spkg._srcPackage->asKind() ); + DBG << localfile << endl; + report.error( false ); + } + + if ( filesystem::hardlinkCopy( localfile, _dnlDir / (spkg._longname+".rpm") ) != 0 ) + { + ERR << "Can't hardlink/copy " << localfile << " to " << (_dnlDir / spkg._longname) << endl; + report.error(); + throw( Out::Error( ZYPPER_EXIT_ERR_BUG, + boost::format(_("Error downloading source package '%s'.") ) % spkg._longname ), + Errno().asString() ); + } + spkg._localFile = spkg._longname; + } + catch ( const Out::Error & error_r ) + { + error_r.report( _zypper ); + } + catch ( const Exception & exp ) + { + // TODO: Need class Out::Error support for exceptions + ERR << exp << endl; + _zypper.out().error( exp, + boost::str( boost::format(_("Error downloading source package '%s'.") ) % spkg._longname ) ); + + //throw( Out::Error( ZYPPER_EXIT_ERR_BUG ) ); + } + + if ( _zypper.exitRequested() ) + throw( Out::Error( ZYPPER_EXIT_ON_SIGNAL ) ); + } + } + else + { + _zypper.out().info(_("No source packages to download.") ); + } + + // finished + cout << endl; + if ( _zypper.exitCode() != ZYPPER_EXIT_OK ) + _zypper.out().info(_("Finished with error.") ); + else + _zypper.out().info(_("Done.") ); + } + +} // namespace +/////////////////////////////////////////////////////////////////// + +int sourceDownload( Zypper & zypper_r ) +{ + try + { + SourceDownloadImpl( zypper_r ).sourceDownload(); + } + catch ( const Out::Error & error_r ) + { + return error_r.report( zypper_r ); + } + return zypper_r.exitCode(); +} diff --git a/src/source-download.h b/src/source-download.h new file mode 100644 index 0000000..200418f --- /dev/null +++ b/src/source-download.h @@ -0,0 +1,58 @@ +/*---------------------------------------------------------------------------*\ + ____ _ _ __ _ __ ___ _ _ + |_ / || | '_ \ '_ \/ -_) '_| + /__|\_, | .__/ .__/\___|_| + |__/|_| |_| +\*---------------------------------------------------------------------------*/ + +#ifndef ZYPPER_SOURCE_DOWNLOAD_H +#define ZYPPER_SOURCE_DOWNLOAD_H + +#include "zypp/Pathname.h" + +class Zypper; + +/* + "source-download\n" + "\n" + "Download source rpms for all installed packages to a local directory.\n" + "\n" + " Command options:\n" + "-d, --directory \n" + " Download all source rpms to this directory.\n" + " Default: /var/cache/zypper/source-download\n" + "--manifest Write MANIFEST of packages and coresponding source rpms.\n" + "--no-manifest Do not write MANIFEST.\n" + "--delete Delete extraneous source rpms in the local directory.\n" + "--no-delete Do not delete extraneous source rpms.\n" + "--dry-run Don't download any source rpms nor write a MANIFEST,\n" + " but show which source rpms are missing or extraneous.\n" + + TBD: maybe write manifest file to download directory. +*/ + +/** source-download specific options */ +struct SourceDownloadOptions : public Options +{ + static const Pathname _defaultDirectory; + static const std::string _manifestName; + + SourceDownloadOptions() + : _directory( _defaultDirectory ) +// , _manifest( true ) + , _delete( true ) + , _dryrun( false ) + {} + + Pathname _directory; //< Download all source rpms to this directory. +// int _manifest; //< Whether to write a MANIFEST file. + int _delete; //< Whether to delete extranous source rpms. + int _dryrun; //< Dryrun mode. +}; + +/** Download source rpms for all installed packages to a local directory. + * \returns zypper.exitCode + */ +int sourceDownload( Zypper & zypper_r ); + +#endif // ZYPPER_SOURCE_DOWNLOAD_H \ No newline at end of file -- 2.7.4