Allow access to PackageProvider cache
[platform/upstream/libzypp.git] / zypp / repo / PackageProvider.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/repo/PackageProvider.cc
10  *
11 */
12 #include <iostream>
13 #include <sstream>
14 #include "zypp/repo/PackageDelta.h"
15 #include "zypp/base/Logger.h"
16 #include "zypp/base/Gettext.h"
17 #include "zypp/base/UserRequestException.h"
18 #include "zypp/base/NonCopyable.h"
19 #include "zypp/repo/PackageProvider.h"
20 #include "zypp/repo/Applydeltarpm.h"
21 #include "zypp/repo/PackageDelta.h"
22
23 #include "zypp/TmpPath.h"
24 #include "zypp/ZConfig.h"
25 #include "zypp/RepoInfo.h"
26
27 using std::endl;
28
29 ///////////////////////////////////////////////////////////////////
30 namespace zypp
31 {
32   ///////////////////////////////////////////////////////////////////
33   namespace repo
34   {
35     ///////////////////////////////////////////////////////////////////
36     //  class PackageProviderPolicy
37     ///////////////////////////////////////////////////////////////////
38
39     bool PackageProviderPolicy::queryInstalled( const std::string & name_r,
40                                                 const Edition &     ed_r,
41                                                 const Arch &        arch_r ) const
42     {
43       if ( _queryInstalledCB )
44         return _queryInstalledCB( name_r, ed_r, arch_r );
45       return false;
46     }
47
48
49     ///////////////////////////////////////////////////////////////////
50     /// \class PackageProvider::Impl
51     /// \brief PackageProvider implementation.
52     ///////////////////////////////////////////////////////////////////
53     class PackageProvider::Impl : private base::NonCopyable
54     {
55     public:
56       /** Ctor taking the Package to provide. */
57       Impl( RepoMediaAccess & access_r,
58             const Package::constPtr & package_r,
59             const DeltaCandidates & deltas_r,
60             const PackageProviderPolicy & policy_r )
61       : _policy( policy_r )
62       , _package( package_r )
63       , _deltas( deltas_r )
64       , _access( access_r )
65       , _retry(false)
66       {}
67
68       virtual ~Impl() {}
69
70       /** Factory method providing the appropriate implementation.
71        * Called by PackageProvider ctor. Returned pointer should be
72        * immediately wrapped into a smartpointer.
73        */
74       static Impl * factoryMake( RepoMediaAccess & access_r,
75                                  const Package::constPtr & package_r,
76                                  const DeltaCandidates & deltas_r,
77                                  const PackageProviderPolicy & policy_r );
78
79     public:
80       /** Provide the package.
81        * The basic workflow.
82        * \throws Exception.
83        */
84       ManagedFile providePackage() const;
85
86       /** Provide the package if it is cached. */
87       ManagedFile providePackageFromCache() const
88       {
89         ManagedFile ret( doProvidePackageFromCache() );
90         if ( ! ( ret->empty() ||  _package->repoInfo().keepPackages() ) )
91           ret.setDispose( filesystem::unlink );
92         return ret;
93       }
94
95       /** Whether the package is cached. */
96       bool isCached() const
97       { return ! doProvidePackageFromCache()->empty(); }
98
99     protected:
100       typedef PackageProvider::Impl     Base;
101       typedef callback::SendReport<repo::DownloadResolvableReport>      Report;
102
103       /** Lookup the final rpm in cache.
104        *
105        * A non empty ManagedFile will be returned to the caller.
106        *
107        * \note File disposal depending on the repos keepPackages setting
108        * are not set here, but in \ref providePackage or \ref providePackageFromCache.
109        *
110        * \note The provoided default implementation returns an empty ManagedFile
111        * (cache miss).
112        */
113       virtual ManagedFile doProvidePackageFromCache() const = 0;
114
115       /** Actually provide the final rpm.
116        * Report start/problem/finish and retry loop are hadled by \ref providePackage.
117        * Here you trigger just progress and delta/plugin callbacks as needed.
118        *
119        * Proxy methods for progressPackageDownload and failOnChecksum are provided here.
120        * Create similar proxies for other progress callbacks in derived classes and link
121        * it to ProvideFilePolicy for download:
122        * \code
123        * ProvideFilePolicy policy;
124        * policy.progressCB( bind( &Base::progressPackageDownload, this, _1 ) );
125        * policy.failOnChecksumErrorCB( bind( &Base::failOnChecksumError, this ) );
126        * return _access.provideFile( _package->repoInfo(), loc, policy );
127        * \endcode
128        *
129        * \note The provoided default implementation retrieves the packages default
130        * location.
131        */
132       virtual ManagedFile doProvidePackage() const = 0;
133
134     protected:
135       /** Access to the DownloadResolvableReport */
136       Report & report() const
137       { return *_report; }
138
139       /** Redirect ProvideFilePolicy package download progress to this. */
140       bool progressPackageDownload( int value ) const
141       { return report()->progress( value, _package ); }
142
143       /** Redirect ProvideFilePolicy failOnChecksumError to this if needed. */
144       bool failOnChecksumError() const
145       {
146         std::string package_str = _package->name() + "-" + _package->edition().asString();
147
148         // TranslatorExplanation %s = package being checked for integrity
149         switch ( report()->problem( _package, repo::DownloadResolvableReport::INVALID, str::form(_("Package %s seems to be corrupted during transfer. Do you want to retry retrieval?"), package_str.c_str() ) ) )
150         {
151           case repo::DownloadResolvableReport::RETRY:
152             _retry = true;
153             break;
154           case repo::DownloadResolvableReport::IGNORE:
155             ZYPP_THROW(SkipRequestException("User requested skip of corrupted file"));
156             break;
157           case repo::DownloadResolvableReport::ABORT:
158             ZYPP_THROW(AbortRequestException("User requested to abort"));
159             break;
160           default:
161             break;
162         }
163         return true; // anyway a failure
164       }
165
166     protected:
167       PackageProviderPolicy     _policy;
168       Package::constPtr         _package;
169       DeltaCandidates           _deltas;
170       RepoMediaAccess &         _access;
171
172     private:
173       typedef shared_ptr<void>  ScopedGuard;
174
175       ScopedGuard newReport() const
176       {
177         _report.reset( new Report );
178         // Use a custom deleter calling _report.reset() when guard goes out of
179         // scope (cast required as reset is overloaded). We want report to end
180         // when leaving providePackage and not wait for *this going out of scope.
181         return shared_ptr<void>( static_cast<void*>(0),
182                                  bind( mem_fun_ref( static_cast<void (shared_ptr<Report>::*)()>(&shared_ptr<Report>::reset) ),
183                                        ref(_report) ) );
184       }
185
186       mutable bool               _retry;
187       mutable shared_ptr<Report> _report;
188     };
189     ///////////////////////////////////////////////////////////////////
190
191     /** Default implementation (cache miss). */
192     ManagedFile PackageProvider::Impl::doProvidePackageFromCache() const
193     { return ManagedFile(); }
194
195     /** Default implementation (provide full package) */
196     ManagedFile PackageProvider::Impl::doProvidePackage() const
197     {
198       ManagedFile ret;
199       OnMediaLocation loc = _package->location();
200
201       ProvideFilePolicy policy;
202       policy.progressCB( bind( &Base::progressPackageDownload, this, _1 ) );
203       policy.failOnChecksumErrorCB( bind( &Base::failOnChecksumError, this ) );
204       return _access.provideFile( _package->repoInfo(), loc, policy );
205     }
206
207     ///////////////////////////////////////////////////////////////////
208
209     ManagedFile PackageProvider::Impl::providePackage() const
210     {
211       // check for cache hit:
212       ManagedFile ret( providePackageFromCache() );
213       if ( ! ret->empty() )
214       {
215         MIL << "provided Package from cache " << _package << " at " << ret << endl;
216         return ret; // <-- cache hit
217       }
218
219       // HERE: cache misss, do download:
220       Url url;
221       RepoInfo info = _package->repoInfo();
222       // FIXME we only support the first url for now.
223       if ( info.baseUrlsEmpty() )
224         ZYPP_THROW(Exception("No url in repository."));
225       else
226         url = * info.baseUrlsBegin();
227
228       MIL << "provide Package " << _package << endl;
229       ScopedGuard guardReport( newReport() );
230       do {
231         _retry = false;
232         report()->start( _package, url );
233         try  // ELIMINATE try/catch by providing a log-guard
234           {
235             ret = doProvidePackage();
236           }
237         catch ( const UserRequestException & excpt )
238           {
239             // UserRequestException e.g. from failOnChecksumError was already reported.
240             ERR << "Failed to provide Package " << _package << endl;
241             if ( ! _retry )
242               {
243                 ZYPP_RETHROW( excpt );
244               }
245           }
246         catch ( const Exception & excpt )
247           {
248             ERR << "Failed to provide Package " << _package << endl;
249             if ( ! _retry )
250               {
251                 // Aything else gets reported
252                 std::string package_str = _package->name() + "-" + _package->edition().asString();
253
254                 // TranslatorExplanation %s = name of the package being processed.
255                 std::string detail_str( str::form(_("Failed to provide Package %s. Do you want to retry retrieval?"), package_str.c_str() ) );
256                 detail_str += str::form( "\n\n%s", excpt.asUserHistory().c_str() );
257
258                 switch ( report()->problem( _package, repo::DownloadResolvableReport::IO, detail_str.c_str() ) )
259                 {
260                       case repo::DownloadResolvableReport::RETRY:
261                         _retry = true;
262                         break;
263                       case repo::DownloadResolvableReport::IGNORE:
264                         ZYPP_THROW(SkipRequestException("User requested skip of corrupted file", excpt));
265                         break;
266                       case repo::DownloadResolvableReport::ABORT:
267                         ZYPP_THROW(AbortRequestException("User requested to abort", excpt));
268                         break;
269                       default:
270                         ZYPP_RETHROW( excpt );
271                         break;
272                 }
273               }
274           }
275       } while ( _retry );
276
277       report()->finish( _package, repo::DownloadResolvableReport::NO_ERROR, std::string() );
278       MIL << "provided Package " << _package << " at " << ret << endl;
279       return ret;
280     }
281
282
283     ///////////////////////////////////////////////////////////////////
284     /// \class RpmPackageProvider
285     /// \brief RPM PackageProvider implementation.
286     ///////////////////////////////////////////////////////////////////
287     class RpmPackageProvider : public PackageProvider::Impl
288     {
289     public:
290       RpmPackageProvider( RepoMediaAccess & access_r,
291                           const Package::constPtr & package_r,
292                           const DeltaCandidates & deltas_r,
293                           const PackageProviderPolicy & policy_r )
294       : PackageProvider::Impl( access_r, package_r, deltas_r, policy_r )
295       {}
296
297     protected:
298       virtual ManagedFile doProvidePackageFromCache() const;
299
300       virtual ManagedFile doProvidePackage() const;
301
302     private:
303       typedef packagedelta::DeltaRpm    DeltaRpm;
304
305       ManagedFile tryDelta( const DeltaRpm & delta_r ) const;
306
307       bool progressDeltaDownload( int value ) const
308       { return report()->progressDeltaDownload( value ); }
309
310       void progressDeltaApply( int value ) const
311       { return report()->progressDeltaApply( value ); }
312
313       bool queryInstalled( const Edition & ed_r = Edition() ) const
314       { return _policy.queryInstalled( _package->name(), ed_r, _package->arch() ); }
315     };
316     ///////////////////////////////////////////////////////////////////
317
318     ManagedFile RpmPackageProvider::doProvidePackageFromCache() const
319     {
320       return ManagedFile( _package->cachedLocation() );
321     }
322
323     ManagedFile RpmPackageProvider::doProvidePackage() const
324     {
325       Url url;
326       RepoInfo info = _package->repoInfo();
327       // FIXME we only support the first url for now.
328       if ( info.baseUrlsEmpty() )
329         ZYPP_THROW(Exception("No url in repository."));
330       else
331         url = * info.baseUrlsBegin();
332
333       // check whether to process patch/delta rpms
334       if ( ZConfig::instance().download_use_deltarpm()
335         && ( url.schemeIsDownloading() || ZConfig::instance().download_use_deltarpm_always() ) )
336       {
337         std::list<DeltaRpm> deltaRpms;
338         _deltas.deltaRpms( _package ).swap( deltaRpms );
339
340         if ( ! deltaRpms.empty() && queryInstalled() && applydeltarpm::haveApplydeltarpm() )
341         {
342           for_( it, deltaRpms.begin(), deltaRpms.end())
343           {
344             DBG << "tryDelta " << *it << endl;
345             ManagedFile ret( tryDelta( *it ) );
346             if ( ! ret->empty() )
347               return ret;
348           }
349         }
350       }
351
352       // no patch/delta -> provide full package
353       return Base::doProvidePackage();
354     }
355
356     ManagedFile RpmPackageProvider::tryDelta( const DeltaRpm & delta_r ) const
357     {
358       if ( delta_r.baseversion().edition() != Edition::noedition
359            && ! queryInstalled( delta_r.baseversion().edition() ) )
360         return ManagedFile();
361
362       if ( ! applydeltarpm::quickcheck( delta_r.baseversion().sequenceinfo() ) )
363         return ManagedFile();
364
365       report()->startDeltaDownload( delta_r.location().filename(),
366                                     delta_r.location().downloadSize() );
367       ManagedFile delta;
368       try
369         {
370           ProvideFilePolicy policy;
371           policy.progressCB( bind( &RpmPackageProvider::progressDeltaDownload, this, _1 ) );
372           delta = _access.provideFile( delta_r.repository().info(), delta_r.location(), policy );
373         }
374       catch ( const Exception & excpt )
375         {
376           report()->problemDeltaDownload( excpt.asUserHistory() );
377           return ManagedFile();
378         }
379       report()->finishDeltaDownload();
380
381       report()->startDeltaApply( delta );
382       if ( ! applydeltarpm::check( delta_r.baseversion().sequenceinfo() ) )
383         {
384           report()->problemDeltaApply( _("applydeltarpm check failed.") );
385           return ManagedFile();
386         }
387
388       // build the package and put it into the cache
389       Pathname destination( _package->repoInfo().packagesPath() / _package->location().filename() );
390
391       if ( ! applydeltarpm::provide( delta, destination,
392                                      bind( &RpmPackageProvider::progressDeltaApply, this, _1 ) ) )
393         {
394           report()->problemDeltaApply( _("applydeltarpm failed.") );
395           return ManagedFile();
396         }
397       report()->finishDeltaApply();
398
399       return ManagedFile( destination, filesystem::unlink );
400     }
401
402 #if 0
403     ///////////////////////////////////////////////////////////////////
404     /// \class PluginPackageProvider
405     /// \brief Plugin PackageProvider implementation.
406     ///
407     /// Basically downloads the default package and calls a
408     /// 'stem'2rpm plugin to cteate the final .rpm package.
409     ///////////////////////////////////////////////////////////////////
410     class PluginPackageProvider : public PackageProvider::Impl
411     {
412     public:
413       PluginPackageProvider( const std::string & stem_r,
414                              RepoMediaAccess & access_r,
415                              const Package::constPtr & package_r,
416                              const DeltaCandidates & deltas_r,
417                              const PackageProviderPolicy & policy_r )
418       : Base( access_r, package_r, deltas_r, policy_r )
419       {}
420
421     protected:
422       virtual ManagedFile doProvidePackageFromCache() const
423       {
424         return Base::doProvidePackageFromCache();
425       }
426
427       virtual ManagedFile doProvidePackage() const
428       {
429         return Base::doProvidePackage();
430       }
431     };
432     ///////////////////////////////////////////////////////////////////
433 #endif
434
435     ///////////////////////////////////////////////////////////////////
436     //  class PackageProvider
437     ///////////////////////////////////////////////////////////////////
438
439     PackageProvider::Impl * PackageProvider::Impl::factoryMake( RepoMediaAccess & access_r,
440                                                                 const Package::constPtr & package_r,
441                                                                 const DeltaCandidates & deltas_r,
442                                                                 const PackageProviderPolicy & policy_r )
443     {
444       return new RpmPackageProvider( access_r, package_r, deltas_r, policy_r );
445     }
446
447     PackageProvider::PackageProvider( RepoMediaAccess & access_r,
448                                       const Package::constPtr & package_r,
449                                       const DeltaCandidates & deltas_r,
450                                       const PackageProviderPolicy & policy_r )
451     : _pimpl( Impl::factoryMake( access_r, package_r, deltas_r, policy_r ) )
452     {}
453
454     PackageProvider::~PackageProvider()
455     {}
456
457     ManagedFile PackageProvider::providePackage() const
458     { return _pimpl->providePackage(); }
459
460     ManagedFile PackageProvider::providePackageFromCache() const
461     { return _pimpl->providePackageFromCache(); }
462
463     bool PackageProvider::isCached() const
464     { return _pimpl->isCached(); }
465
466   } // namespace repo
467   ///////////////////////////////////////////////////////////////////
468 } // namespace zypp
469 ///////////////////////////////////////////////////////////////////