Enhance exception histories
[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/repo/PackageProvider.h"
19 #include "zypp/repo/RepoProvideFile.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     ///////////////////////////////////////////////////////////////////
37     //
38     //  CLASS NAME : PackageProviderPolicy
39     //
40     ///////////////////////////////////////////////////////////////////
41
42     bool PackageProviderPolicy::queryInstalled( const std::string & name_r,
43                                                 const Edition &     ed_r,
44                                                 const Arch &        arch_r ) const
45     {
46       if ( _queryInstalledCB )
47         return _queryInstalledCB( name_r, ed_r, arch_r );
48       return false;
49     }
50
51     ///////////////////////////////////////////////////////////////////
52     //
53     //  CLASS NAME : PackageProvider
54     //
55     ///////////////////////////////////////////////////////////////////
56
57     ///////////////////////////////////////////////////////////////////
58     namespace
59     { /////////////////////////////////////////////////////////////////
60
61       inline std::string defRpmFileName( const Package::constPtr & package )
62       {
63         std::ostringstream ret;
64         ret << package->name() << '-' << package->edition() << '.' << package->arch() << ".rpm";
65         return ret.str();
66       }
67
68       /////////////////////////////////////////////////////////////////
69     } // namespace source
70     ///////////////////////////////////////////////////////////////////
71     PackageProvider::PackageProvider(  RepoMediaAccess &access,
72                                       const Package::constPtr & package,
73                                       const DeltaCandidates & deltas,
74                                       const PackageProviderPolicy & policy_r )
75     : _policy( policy_r )
76     , _package( package )
77     , _deltas(deltas)
78     , _access(access)
79     {}
80
81     PackageProvider::~PackageProvider()
82     {}
83
84     ManagedFile PackageProvider::providePackage() const
85     {
86       Url url;
87       RepoInfo info = _package->repoInfo();
88       // FIXME we only support the first url for now.
89       if ( info.baseUrlsEmpty() )
90         ZYPP_THROW(Exception("No url in repository."));
91       else
92         url = * info.baseUrlsBegin();
93
94       { // check for cache hit:
95         OnMediaLocation loc( _package->location() );
96         PathInfo cachepath( info.packagesPath() / loc.filename() );
97
98         if ( cachepath.isFile() && ! loc.checksum().empty() ) // accept cache hit with matching checksum only!
99              // Tempting to do a quick check for matching .rpm-filesize before computing checksum,
100              // but real life shows that loc.downloadSize() and the .rpm-filesize frequently do not
101              // match, even if loc.checksum() and the .rpm-files checksum do. Blame the metadata generator(s).
102         {
103           CheckSum cachechecksum( loc.checksum().type(), filesystem::checksum( cachepath.path(), loc.checksum().type() ) );
104           USR << cachechecksum << endl;
105           if ( cachechecksum == loc.checksum() )
106           {
107             ManagedFile ret( cachepath.path() );
108             if ( ! info.keepPackages() )
109             {
110               ret.setDispose( filesystem::unlink );
111             }
112             MIL << "provided Package from cache " << _package << " at " << ret << endl;
113             return ret; // <-- cache hit
114           }
115         }
116       }
117
118       // HERE: cache misss, do download:
119       MIL << "provide Package " << _package << endl;
120       ScopedGuard guardReport( newReport() );
121       ManagedFile ret;
122       do {
123         _retry = false;
124         report()->start( _package, url );
125         try  // ELIMINATE try/catch by providing a log-guard
126           {
127             ret = doProvidePackage();
128           }
129         catch ( const UserRequestException & excpt )
130           {
131             // UserRequestException e.g. from failOnChecksumError was already reported.
132             ERR << "Failed to provide Package " << _package << endl;
133             if ( ! _retry )
134               {
135                 ZYPP_RETHROW( excpt );
136               }
137           }
138         catch ( const Exception & excpt )
139           {
140             ERR << "Failed to provide Package " << _package << endl;
141             if ( ! _retry )
142               {
143                 // Aything else gets reported
144                 std::string package_str = _package->name() + "-" + _package->edition().asString();
145
146                 // TranslatorExplanation %s = name of the package being processed.
147                 std::string detail_str( str::form(_("Failed to provide Package %s. Do you want to retry retrieval?"), package_str.c_str() ) );
148                 detail_str += str::form( "\n\n%s", excpt.asUserHistory().c_str() );
149
150                 switch ( report()->problem( _package, repo::DownloadResolvableReport::IO, detail_str.c_str() ) )
151                 {
152                       case repo::DownloadResolvableReport::RETRY:
153                         _retry = true;
154                         break;
155                       case repo::DownloadResolvableReport::IGNORE:
156                         ZYPP_THROW(SkipRequestException("User requested skip of corrupted file", excpt));
157                         break;
158                       case repo::DownloadResolvableReport::ABORT:
159                         ZYPP_THROW(AbortRequestException("User requested to abort", excpt));
160                         break;
161                       default:
162                         ZYPP_RETHROW( excpt );
163                         break;
164                 }
165               }
166           }
167       } while ( _retry );
168
169       report()->finish( _package, repo::DownloadResolvableReport::NO_ERROR, std::string() );
170       MIL << "provided Package " << _package << " at " << ret << endl;
171       return ret;
172     }
173
174     ManagedFile PackageProvider::doProvidePackage() const
175     {
176       Url url;
177       RepoInfo info = _package->repoInfo();
178       // FIXME we only support the first url for now.
179       if ( info.baseUrlsEmpty() )
180         ZYPP_THROW(Exception("No url in repository."));
181       else
182         url = * info.baseUrlsBegin();
183
184       // check whether to process patch/delta rpms
185       if ( url.schemeIsDownloading() || ZConfig::instance().download_use_deltarpm_always() )
186         {
187           std::list<DeltaRpm> deltaRpms;
188           if ( ZConfig::instance().download_use_deltarpm() )
189           {
190             _deltas.deltaRpms( _package ).swap( deltaRpms );
191           }
192
193           if ( ! ( deltaRpms.empty() )
194                && queryInstalled() )
195             {
196               if ( ! deltaRpms.empty() && applydeltarpm::haveApplydeltarpm() )
197                 {
198                   for( std::list<DeltaRpm>::const_iterator it = deltaRpms.begin();
199                        it != deltaRpms.end(); ++it )
200                     {
201                       DBG << "tryDelta " << *it << endl;
202                       ManagedFile ret( tryDelta( *it ) );
203                       if ( ! ret->empty() )
204                         return ret;
205                     }
206                 }
207             }
208         }
209
210       // no patch/delta -> provide full package
211       ManagedFile ret;
212       OnMediaLocation loc = _package->location();
213
214       ProvideFilePolicy policy;
215       policy.progressCB( bind( &PackageProvider::progressPackageDownload, this, _1 ) );
216       policy.failOnChecksumErrorCB( bind( &PackageProvider::failOnChecksumError, this ) );
217       return _access.provideFile( _package->repoInfo(), loc, policy );
218     }
219
220     ManagedFile PackageProvider::tryDelta( const DeltaRpm & delta_r ) const
221     {
222       if ( delta_r.baseversion().edition() != Edition::noedition
223            && ! queryInstalled( delta_r.baseversion().edition() ) )
224         return ManagedFile();
225
226       if ( ! applydeltarpm::quickcheck( delta_r.baseversion().sequenceinfo() ) )
227         return ManagedFile();
228
229       report()->startDeltaDownload( delta_r.location().filename(),
230                                     delta_r.location().downloadSize() );
231       ManagedFile delta;
232       try
233         {
234           ProvideFilePolicy policy;
235           policy.progressCB( bind( &PackageProvider::progressDeltaDownload, this, _1 ) );
236           delta = _access.provideFile( delta_r.repository().info(), delta_r.location(), policy );
237         }
238       catch ( const Exception & excpt )
239         {
240           report()->problemDeltaDownload( excpt.asUserHistory() );
241           return ManagedFile();
242         }
243       report()->finishDeltaDownload();
244
245       report()->startDeltaApply( delta );
246       if ( ! applydeltarpm::check( delta_r.baseversion().sequenceinfo() ) )
247         {
248           report()->problemDeltaApply( _("applydeltarpm check failed.") );
249           return ManagedFile();
250         }
251
252       // build the package and put it into the cache
253       Pathname destination( _package->repoInfo().packagesPath() / _package->location().filename() );
254
255       if ( ! applydeltarpm::provide( delta, destination,
256                                      bind( &PackageProvider::progressDeltaApply, this, _1 ) ) )
257         {
258           report()->problemDeltaApply( _("applydeltarpm failed.") );
259           return ManagedFile();
260         }
261       report()->finishDeltaApply();
262
263       return ManagedFile( destination, filesystem::unlink );
264     }
265
266     PackageProvider::ScopedGuard PackageProvider::newReport() const
267     {
268       _report.reset( new Report );
269       return shared_ptr<void>( static_cast<void*>(0),
270                                // custom deleter calling _report.reset()
271                                // (cast required as reset is overloaded)
272                                bind( mem_fun_ref( static_cast<void (shared_ptr<Report>::*)()>(&shared_ptr<Report>::reset) ),
273                                      ref(_report) ) );
274     }
275
276     PackageProvider::Report & PackageProvider::report() const
277     { return *_report; }
278
279     bool PackageProvider::progressDeltaDownload( int value ) const
280     { return report()->progressDeltaDownload( value ); }
281
282     void PackageProvider::progressDeltaApply( int value ) const
283     { return report()->progressDeltaApply( value ); }
284
285     bool PackageProvider::progressPackageDownload( int value ) const
286     { return report()->progress( value, _package ); }
287
288     bool PackageProvider::failOnChecksumError() const
289     {
290       std::string package_str = _package->name() + "-" + _package->edition().asString();
291
292       // TranslatorExplanation %s = package being checked for integrity
293       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() ) ) )
294         {
295         case repo::DownloadResolvableReport::RETRY:
296           _retry = true;
297           break;
298           case repo::DownloadResolvableReport::IGNORE:
299           ZYPP_THROW(SkipRequestException("User requested skip of corrupted file"));
300           break;
301           case repo::DownloadResolvableReport::ABORT:
302           ZYPP_THROW(AbortRequestException("User requested to abort"));
303           break;
304         default:
305           break;
306         }
307       return true; // anyway a failure
308     }
309
310     bool PackageProvider::queryInstalled( const Edition & ed_r ) const
311     { return _policy.queryInstalled( _package->name(), ed_r, _package->arch() ); }
312
313
314     /////////////////////////////////////////////////////////////////
315   } // namespace repo
316   ///////////////////////////////////////////////////////////////////
317   /////////////////////////////////////////////////////////////////
318 } // namespace zypp
319 ///////////////////////////////////////////////////////////////////