Imported Upstream version 17.22.1
[platform/upstream/libzypp.git] / zypp / zyppng / media / network / downloader.cc
1 #include <zypp/zyppng/media/network/private/downloader_p.h>
2 #include <zypp/zyppng/media/network/private/request_p.h>
3 #include <zypp/zyppng/media/network/private/networkrequesterror_p.h>
4 #include <zypp/zyppng/media/network/networkrequestdispatcher.h>
5 #include <zypp/zyppng/media/network/request.h>
6 #include <zypp/zyppng/base/Timer>
7 #include <zypp/zyppng/base/EventDispatcher>
8 #include <zypp/Pathname.h>
9 #include <zypp/media/TransferSettings.h>
10 #include <zypp/media/MetaLinkParser.h>
11 #include <zypp/ByteCount.h>
12 #include <zypp/base/String.h>
13 #include <zypp/PathInfo.h>
14 #include <zypp/media/CurlHelper.h>
15 #include <zypp/media/CredentialManager.h>
16 #include <zypp/ZConfig.h>
17 #include <zypp/base/Logger.h>
18
19 #include <queue>
20 #include <fcntl.h>
21 #include <iostream>
22 #include <fstream>
23
24 #define BLKSIZE         131072
25
26 namespace  {
27   bool looks_like_metalink_data( const std::vector<char> &data )
28   {
29     if ( data.empty() )
30       return false;
31
32     const char *p = data.data();
33     while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')
34       p++;
35
36     if (!strncasecmp(p, "<?xml", 5))
37     {
38       while (*p && *p != '>')
39         p++;
40       if (*p == '>')
41         p++;
42       while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')
43         p++;
44     }
45     bool ret = !strncasecmp( p, "<metalink", 9 ) ? true : false;
46     return ret;
47   }
48
49   bool looks_like_metalink_file( const zypp::Pathname &file )
50   {
51     std::unique_ptr<FILE, decltype(&fclose)> fd( fopen( file.c_str(), "r" ), &fclose );
52     if ( !fd )
53       return false;
54     return looks_like_metalink_data( zyppng::peek_data_fd( fd.get(), 0, 256 ) );
55   }
56 }
57
58 namespace zyppng {
59
60   DownloadPrivate::DownloadPrivate(Downloader &parent, std::shared_ptr<NetworkRequestDispatcher> requestDispatcher, Url &&file, zypp::filesystem::Pathname &&targetPath, zypp::ByteCount &&expectedFileSize )
61     : _requestDispatcher ( requestDispatcher )
62     , _url( std::move(file) )
63     , _targetPath( std::move(targetPath) )
64     , _expectedFileSize( std::move(expectedFileSize) )
65     , _parent( &parent )
66   { }
67
68   void DownloadPrivate::Request::connectSignals(DownloadPrivate &dl)
69   {
70     _sigStartedConn  = sigStarted().connect ( sigc::mem_fun( dl, &DownloadPrivate::onRequestStarted) );
71     _sigProgressConn = sigProgress().connect( sigc::mem_fun( dl, &DownloadPrivate::onRequestProgress) );
72     _sigFinishedConn = sigFinished().connect( sigc::mem_fun( dl, &DownloadPrivate::onRequestFinished) );
73   }
74
75   void DownloadPrivate::Request::disconnectSignals()
76   {
77     _sigStartedConn.disconnect();
78     _sigProgressConn.disconnect();
79     _sigFinishedConn.disconnect();
80   }
81
82   void DownloadPrivate::start()
83   {
84     if ( _state == Download::Initializing ||
85          _state == Download::Running ||
86          _state == Download::RunningMulti )
87       return;
88
89     //reset state variables
90     _isMultiDownload = false;
91     _downloadedMultiByteCount = 0;
92     _multiPartMirrors.clear();
93     _blockList    = zypp::media::MediaBlockList ();
94     _blockIter    = 0;
95     _errorString  = std::string();
96     _requestError = NetworkRequestError();
97
98     setState( Download::Initializing );
99
100     _requestError = safeFillSettingsFromURL( _url , _transferSettings );
101     if ( _requestError.type() != NetworkRequestError::NoError ) {
102       setFailed( "Failed to read settings from URL " );
103       return;
104     }
105
106     std::shared_ptr<Request> initialRequest = std::make_shared<Request>( internal::clearQueryString(_url), _targetPath );
107
108     initialRequest->_originalUrl = _url;
109     initialRequest->transferSettings() = _transferSettings;
110
111     if ( _isMultiPartEnabled ) {
112       if ( !_checkExistsOnly )
113         initialRequest->transferSettings().addHeader("Accept: */*, application/metalink+xml, application/metalink4+xml");
114       else
115         WAR << "Ignoring enabled multi part option for check only download of : " << internal::clearQueryString(_url) << std::endl;
116     }
117
118     if ( _checkExistsOnly )
119       initialRequest->setOptions( initialRequest->options() | NetworkRequest::HeadRequest );
120
121     addNewRequest( initialRequest );
122   }
123
124   void DownloadPrivate::setState(Download::State newState)
125   {
126     if ( _state == newState )
127       return;
128     _state = newState;
129     _sigStateChanged.emit( *z_func(), newState );
130   }
131
132   void DownloadPrivate::onRequestStarted( NetworkRequest & )
133   {
134     if ( _state == Download::Initializing )
135       _sigStarted.emit( *z_func() );
136   }
137
138   void DownloadPrivate::onRequestProgress( NetworkRequest &req, off_t dltotal, off_t dlnow, off_t , off_t  )
139   {
140     //we are not sure yet if we are downloading a Metalink, for now just send alive messages
141     if ( _state == Download::Initializing ) {
142       if ( !_isMultiPartEnabled ) {
143         setState( Download::Running );
144       } else {
145         if ( !_isMultiDownload ) {
146           std::string cType = req.contentType();
147           if ( cType.find("application/metalink+xml") == 0 || cType.find("application/metalink4+xml") == 0 )
148             _isMultiDownload = true;
149         }
150
151         if ( !_isMultiDownload && dlnow < 256 ) {
152           // can't tell yet, ...
153           return _sigAlive.emit( *z_func(), dlnow );
154         }
155
156         if ( !_isMultiDownload )
157         {
158           _isMultiDownload = looks_like_metalink_data( req.peekData( 0, 256 ) );
159         }
160
161         if ( _isMultiDownload )
162         {
163           // this is a metalink file change the expected filesize
164           if ( zypp::ByteCount( 2, zypp::ByteCount::MB) < static_cast<zypp::ByteCount::SizeType>( dlnow ) ) {
165             _requestDispatcher->cancel( req, NetworkRequestErrorPrivate::customError( NetworkRequestError::ExceededMaxLen ) );
166             return;
167           }
168           return _sigAlive.emit( *z_func(), dlnow );
169         }
170
171         //if we reach here we have a normal file ( or a multi download with more than 256 byte of comment in the beginning )
172         setState( Download::Running );
173       }
174     }
175
176     if ( _state == Download::Running ) {
177       if ( _expectedFileSize > 0 && _expectedFileSize < dlnow ) {
178         _requestDispatcher->cancel( req, NetworkRequestErrorPrivate::customError( NetworkRequestError::ExceededMaxLen ) );
179         return;
180       }
181       return _sigProgress.emit( *z_func(), dltotal, dlnow );
182
183     } else if ( _state == Download::RunningMulti ) {
184       off_t dlnowMulti = _downloadedMultiByteCount;
185       for( const auto &req : _runningRequests ) {
186         dlnowMulti += req->downloadedByteCount();
187       }
188       _sigProgress.emit( *z_func(), _blockList.getFilesize(), dlnowMulti );
189     }
190   }
191
192   void DownloadPrivate::onRequestFinished( NetworkRequest &req, const zyppng::NetworkRequestError &err )
193   {
194
195     auto it = std::find_if( _runningRequests.begin(), _runningRequests.end(), [ &req ]( const std::shared_ptr<Request> &r ) {
196       return ( r.get() == &req );
197     });
198     if ( it == _runningRequests.end() )
199       return;
200
201     auto reqLocked = *it;
202
203     //remove from running
204     _runningRequests.erase( it );
205
206     if ( err.isError() ) {
207
208       bool retry = false;
209
210       //Handle the auth errors explicitely, we need to give the user a way to put in new credentials
211       //if we get valid new credentials we can retry the request
212       if ( err.type() == NetworkRequestError::Unauthorized || err.type() == NetworkRequestError::AuthFailed ) {
213
214         zypp::media::CredentialManager cm( zypp::media::CredManagerOptions( zypp::ZConfig::instance().repoManagerRoot()) );
215         auto authDataPtr = cm.getCred( req.url() );
216
217         // get stored credentials
218         NetworkAuthData_Ptr cmcred( authDataPtr ? new NetworkAuthData( *authDataPtr ) : new NetworkAuthData() );
219         TransferSettings &ts = req.transferSettings();
220
221         // We got credentials from store, _triedCredFromStore makes sure we just try the auth from store once
222         if ( cmcred && !reqLocked->_triedCredFromStore ) {
223           DBG << "got stored credentials:" << std::endl << *cmcred << std::endl;
224           ts.setUsername( cmcred->username() );
225           ts.setPassword( cmcred->password() );
226           retry = true;
227           reqLocked->_triedCredFromStore = true;
228         } else {
229
230           //we did not get credentials from the store, emit a signal that allows
231           //setting new auth data
232
233           NetworkAuthData credFromUser;
234           credFromUser.setUrl( req.url() );
235
236           //in case we got a auth hint from the server the error object will contain it
237           auto authHintIt = err.extraInfo().find("authHint");
238           std::string authHint;
239
240           if ( authHintIt != err.extraInfo().end() ){
241             try {
242               authHint = boost::any_cast<std::string>( authHintIt->second );
243             } catch ( const boost::bad_any_cast &) { }
244           }
245
246           //preset from store if we found something
247           if ( cmcred && !cmcred->username().empty() )
248             credFromUser.setUsername( cmcred->username() );
249
250           _sigAuthRequired.emit( *z_func(), credFromUser, authHint );
251           if ( credFromUser.valid() ) {
252             ts.setUsername( credFromUser.username() );
253             ts.setPassword( credFromUser.password() );
254
255             // set available authentication types from the error
256             if ( credFromUser.authType() == CURLAUTH_NONE )
257               credFromUser.setAuthType( authHint );
258
259             // set auth type (seems this must be set _after_ setting the userpwd)
260             if ( credFromUser.authType()  != CURLAUTH_NONE ) {
261               // FIXME: only overwrite if not empty?
262               req.transferSettings().setAuthType(credFromUser.authTypeAsString());
263             }
264
265             cm.addCred( credFromUser );
266             cm.save();
267
268             retry = true;
269           }
270         }
271       } else if ( _state == Download::RunningMulti ) {
272
273         //if a error happens during a multi download we try to use another mirror to download the failed block
274         DBG << "Request failed " << reqLocked->_myBlock << " " << reqLocked->extendedErrorString() << std::endl;
275         NetworkRequestError dummyErr;
276
277         //try to init a new multi request, if we have leftover mirrors we get a valid one
278         auto newReq = initMultiRequest( reqLocked->_myBlock, dummyErr );
279         if ( newReq ) {
280           newReq->_retryCount = reqLocked->_retryCount + 1;
281           addNewRequest( newReq );
282           return;
283         } else {
284           //no mirrors left but if we still have running requests, there is hope to finish the block
285           if ( !_runningRequests.empty() ) {
286             DBG << "Adding to failed blocklist " << reqLocked->_myBlock <<std::endl;
287             _failedBlocks.push_back( FailedBlock{ reqLocked->_myBlock, reqLocked->_retryCount, err } );
288             return;
289           }
290         }
291       }
292
293       //if rety is true we just enqueue the request again, usually this means authentication was updated
294       if ( retry ) {
295         //make sure this request will run asap
296         reqLocked->setPriority( NetworkRequest::High );
297
298         //this is not a new request, only add to queues but do not connect signals again
299         _runningRequests.push_back( reqLocked );
300         _requestDispatcher->enqueue( reqLocked );
301         return;
302       }
303
304       //we do not have more mirrors left we can try or we do not have a multi download, abort
305       while( _runningRequests.size() ) {
306         auto req = _runningRequests.back();
307         req->disconnectSignals();
308         _runningRequests.pop_back();
309         _requestDispatcher->cancel( *req, err );
310       }
311
312       //not all hope is lost, maybe a normal download can work out?
313       if ( _state == Download::RunningMulti ) {
314         //fall back to normal download
315         DBG << "Falling back to download from initial URL." << std::endl;
316         _isMultiDownload = false;
317         _isMultiPartEnabled = false;
318         setState( Download::Running );
319
320         auto req = std::make_shared<Request>( internal::clearQueryString(_url), _targetPath ) ;
321         req->transferSettings() = _transferSettings;
322         addNewRequest( req );
323         return;
324       }
325
326       _requestError = err;
327       setFailed( "Download failed ");
328       return;
329     }
330
331     if ( _state == Download::Initializing || _state == Download::Running ) {
332       if ( _isMultiPartEnabled && !_isMultiDownload )
333         _isMultiDownload = looks_like_metalink_file( req.targetFilePath() );
334       if ( !_isMultiDownload ) {
335         setFinished();
336         return;
337       }
338
339       DBG << " Upgrading request for URL: "<< req.url() << " to multipart download " << std::endl;
340
341       //we have a metalink download, lets parse it and see what we got
342       _multiPartMirrors.clear();
343
344       try {
345         zypp::media::MetaLinkParser parser;
346         parser.parse( req.targetFilePath() );
347
348         _blockList = parser.getBlockList();
349
350         //migrate some settings from the base url to the mirror
351         std::vector<Url> urllist = parser.getUrls();
352         for (std::vector<Url>::iterator urliter = urllist.begin(); urliter != urllist.end(); ++urliter) {
353           try {
354             std::string scheme = urliter->getScheme();
355             if (scheme == "http" || scheme == "https" || scheme == "ftp" || scheme == "tftp") {
356               if ( !_requestDispatcher->supportsProtocol( *urliter ))
357                 continue;
358               _multiPartMirrors.push_back(internal::propagateQueryParams(*urliter, _url));
359             }
360           }
361           catch (...) {  }
362         }
363
364         if ( _multiPartMirrors.empty() )
365           _multiPartMirrors.push_back( _url );
366
367       } catch ( const zypp::Exception &ex ) {
368         setFailed( zypp::str::Format("Failed to parse metalink information.(%1%)" ) % ex.asUserString() );
369         return;
370       }
371
372       if ( _multiPartMirrors.size() == 0 ) {
373         setFailed( zypp::str::Format("Invalid metalink information.( No mirrors in metalink file)" ) );
374         return;
375       }
376
377       if ( !_blockList.haveBlocks() ) {
378
379         //if we have no filesize we can not generate a blocklist, we need to fall back to normal download
380         if ( !_blockList.haveFilesize() ) {
381           DBG << "No blocklist and no filesize, falling back to normal download for URL " << _url << std::endl;
382
383           //fall back to normal download but use a mirror from the mirror list
384           //otherwise we get HTTPS to HTTP redirect errors
385           _isMultiDownload = false;
386           _isMultiPartEnabled = false;
387
388           Url url;
389           TransferSettings set;
390           NetworkRequestError dummyErr;
391           if ( !findNextMirror( url, set, dummyErr ) ) {
392             url = _url;
393             set = _transferSettings;
394           }
395
396           auto req = std::make_shared<Request>( internal::clearQueryString(url), _targetPath ) ;
397
398           if ( _blockList.haveFileChecksum() ) {
399             std::shared_ptr<zypp::Digest> fileDigest = std::make_shared<zypp::Digest>();
400             if ( _blockList.createFileDigest( *fileDigest ) ) {
401               req->setDigest( fileDigest );
402               req->setExpectedChecksum( _blockList.getFileChecksum() );
403             }
404           }
405
406           req->transferSettings() = set;
407           addNewRequest( req );
408           return;
409
410         } else {
411           //we generate a blocklist on the fly based on the filesize
412
413           DBG << "Generate blocklist, since there was none in the metalink file." << _url  << std::endl;
414
415           off_t currOff = 0;
416           off_t filesize = _blockList.getFilesize();
417           while ( currOff <  filesize )  {
418
419             size_t blksize = static_cast<size_t>( filesize - currOff );
420             if ( blksize > BLKSIZE)
421               blksize = BLKSIZE;
422
423             _blockList.addBlock( currOff, blksize );
424             currOff += blksize;
425           }
426
427           XXX << "Generated blocklist: " << std::endl << _blockList << std::endl << " End blocklist " << std::endl;
428         }
429       }
430
431       //remove the metalink file
432       zypp::filesystem::unlink( _targetPath );
433
434       if ( !_deltaFilePath.empty() ) {
435         zypp::PathInfo dFileInfo ( _deltaFilePath );
436         if ( dFileInfo.isFile() && dFileInfo.isR() ) {
437           FILE *f = fopen( _targetPath.asString().c_str(), "w+b" );
438           if ( !f ) {
439             setFailed( zypp::str::Format("Failed to open target file.(errno %1%)" ) % errno );
440             return;
441           }
442
443           try {
444             _blockList.reuseBlocks ( f, _deltaFilePath.asString() );
445           } catch ( ... ) { }
446
447           fclose( f );
448         }
449       }
450
451       setState( Download::RunningMulti );
452       _blockIter = 0;
453
454     } else if ( _state == Download::RunningMulti ) {
455       _downloadedMultiByteCount += req.downloadedByteCount();
456
457       DBG << "Request finished " << reqLocked->_myBlock <<std::endl;
458
459       auto restartReqWithBlock = [ this ]( std::shared_ptr<Request> &req, size_t block, int retryCount ) {
460         zypp::media::MediaBlock blk = _blockList.getBlock( block );
461         req->_myBlock = block;
462         req->_retryCount = retryCount;
463         req->setRequestRange( blk.off, static_cast<off_t>( blk.size ) );
464         req->setExpectedChecksum( _blockList.getChecksum( block ) );
465
466         //this is not a new request, only add to queues but do not connect signals again
467         _runningRequests.push_back( req );
468         _requestDispatcher->enqueue( req );
469       };
470
471       //check if we already have enqueued all blocks if not reuse the request
472       if ( _blockIter  < _blockList.numBlocks() ) {
473
474         DBG << "Reusing to download block: " << _blockIter <<std::endl;
475         restartReqWithBlock( reqLocked, _blockIter, 0 );
476         _blockIter++;
477         return;
478
479       } else {
480         //if we have failed blocks, try to download them with this mirror
481         if ( !_failedBlocks.empty() ) {
482
483           FailedBlock blk = std::move( _failedBlocks.front() );
484           _failedBlocks.pop_front();
485
486           DBG << "Reusing to download failed block: " << blk._block <<std::endl;
487
488           restartReqWithBlock( reqLocked, blk._block, blk._retryCount+1 );
489           return;
490         }
491
492         //feed the working URL back into the mirrors in case there are still running requests that might fail
493         _multiPartMirrors.push_front( reqLocked->_originalUrl );
494       }
495     }
496
497     //check if there is still work to do
498     if ( _runningRequests.size() < 10 ) {
499
500       NetworkRequestError lastErr = _requestError;
501
502       //we try to allocate as many requests as possible but stop if we cannot find a valid mirror for one
503       for ( ; _blockIter < _blockList.numBlocks(); _blockIter++ ){
504
505         if ( _runningRequests.size() >= 10 )
506           break;
507
508         std::shared_ptr<Request> req = initMultiRequest( _blockIter, lastErr );
509         if ( !req )
510           break;
511
512         addNewRequest( req );
513       }
514
515       while ( _failedBlocks.size() ) {
516
517         if ( _runningRequests.size() >= 10 )
518           break;
519
520         FailedBlock blk = std::move( _failedBlocks.front() );
521         _failedBlocks.pop_front();
522
523         auto req = initMultiRequest( blk._block, lastErr );
524         if ( !req )
525           break;
526
527         addNewRequest( req );
528       }
529
530       if ( _runningRequests.empty() && lastErr.type()!= NetworkRequestError::NoError )  {
531         //we found no mirrors -> fail
532         _requestError = lastErr;
533         setFailed( "Unable to use mirror" );
534       }
535     }
536
537     if ( _runningRequests.empty() ) {
538
539       if ( _failedBlocks.size() || ( _blockIter < _blockList.numBlocks() )) {
540         setFailed( "Unable to download all blocks." );
541         return;
542       }
543
544       if ( _state == Download::RunningMulti && _blockList.haveFileChecksum() ) {
545         //TODO move this into a external application so we do not need to block on it
546         //need to check file digest
547         zypp::Digest dig;
548         _blockList.createFileDigest( dig );
549
550         std::ifstream istrm( _targetPath.asString(), std::ios::binary);
551         if ( !istrm.is_open() ) {
552           setFailed( "Failed to verify file digest (Could not open target file)." );
553           return;
554         }
555         if ( !dig.update( istrm ) ) {
556           setFailed( "Failed to verify file digest (Could not read target file)." );
557           return;
558         }
559         if ( !_blockList.verifyFileDigest( dig ) ) {
560           setFailed( "Failed to verify file digest (Checksum did not match)." );
561           return;
562         }
563       }
564       //TODO implement digest check for non multi downloads
565       setFinished();
566     }
567   }
568
569   void DownloadPrivate::addNewRequest( std::shared_ptr<Request> req )
570   {
571     auto slot = _sigStarted.slots().front();
572     req->connectSignals( *this );
573     _runningRequests.push_back( req );
574     _requestDispatcher->enqueue( req );
575   }
576
577   std::shared_ptr<DownloadPrivate::Request> DownloadPrivate::initMultiRequest( size_t block, NetworkRequestError &err )
578   {
579     zypp::media::MediaBlock blk = _blockList.getBlock( block );
580
581     Url myUrl;
582     TransferSettings settings;
583     if ( !findNextMirror( myUrl, settings, err ) )
584       return nullptr;
585
586     DBG << "Starting block " << block << std::endl;
587
588     std::shared_ptr<Request> req = std::make_shared<Request>( internal::clearQueryString( myUrl ), _targetPath, blk.off, blk.size, NetworkRequest::WriteShared );
589     req->_originalUrl = myUrl;
590     req->_myBlock = block;
591     req->setPriority( NetworkRequest::High );
592     req->transferSettings() = settings;
593
594     if ( _blockList.haveChecksum( block ) ) {
595       std::shared_ptr<zypp::Digest> dig = std::make_shared<zypp::Digest>();
596       _blockList.createDigest( *dig );
597       req->setDigest( dig );
598       std::vector<unsigned char> checksumVec = _blockList.getChecksum( block );
599       req->setExpectedChecksum( checksumVec );
600       DBG << "Starting block  " << block << " with checksum " << zypp::Digest::digestVectorToString( checksumVec ) << std::endl;
601     } else {
602       DBG << "Block " << block << " has no checksum." << std::endl;
603     }
604     return req;
605   }
606
607   bool DownloadPrivate::findNextMirror( Url &url, TransferSettings &set, NetworkRequestError &err )
608   {
609     Url myUrl;
610     bool foundMirror = false;
611     TransferSettings settings;
612     while ( _multiPartMirrors.size() ) {
613       myUrl = _multiPartMirrors.front();
614       _multiPartMirrors.pop_front();
615
616       settings = _transferSettings;
617       //if this is a different host than the initial request, we reset username/password
618       if ( myUrl.getHost() != _url.getHost() ) {
619         settings.setUsername( std::string() );
620         settings.setPassword( std::string() );
621         settings.setAuthType( std::string() );
622       }
623
624       err = safeFillSettingsFromURL( myUrl, settings );
625       if ( err.type() != NetworkRequestError::NoError ) {
626         continue;
627       }
628
629       foundMirror = true;
630       break;
631     }
632
633     if ( !foundMirror )
634       return false;
635
636     url = myUrl;
637     set = settings;
638     return true;
639   }
640
641   void DownloadPrivate::setFailed(std::string &&reason)
642   {
643     _errorString = reason;
644     //zypp::filesystem::unlink( _targetPath );
645     setFinished( false );
646   }
647
648   void DownloadPrivate::setFinished(bool success)
649   {
650     setState( success ? Download::Success : Download::Failed );
651     _sigFinished.emit( *z_func() );
652   }
653
654   NetworkRequestError DownloadPrivate::safeFillSettingsFromURL( const Url &url, TransferSettings &set)
655   {
656     auto buildExtraInfo = [this, &url](){
657       std::map<std::string, boost::any> extraInfo;
658       extraInfo.insert( {"requestUrl", url } );
659       extraInfo.insert( {"filepath", _targetPath } );
660       return extraInfo;
661     };
662
663     NetworkRequestError res;
664     try {
665       internal::fillSettingsFromUrl( url, set );
666       if ( _transferSettings.proxy().empty() )
667         internal::fillSettingsSystemProxy( url, set );
668     } catch ( const zypp::media::MediaBadUrlException & e ) {
669       res = NetworkRequestErrorPrivate::customError( NetworkRequestError::MalformedURL, e.asString(), buildExtraInfo() );
670     } catch ( const zypp::media::MediaUnauthorizedException & e ) {
671       res = NetworkRequestErrorPrivate::customError( NetworkRequestError::AuthFailed, e.asString(), buildExtraInfo() );
672     } catch ( const zypp::Exception & e ) {
673       res = NetworkRequestErrorPrivate::customError( NetworkRequestError::InternalError, e.asString(), buildExtraInfo() );
674     }
675     return res;
676   }
677
678   Download::Download(zyppng::DownloadPrivate &&prv)
679   : Base( prv )
680   { }
681
682   Url Download::url() const
683   {
684     return d_func()->_url;
685   }
686
687   zypp::filesystem::Pathname Download::targetPath() const
688   {
689     return d_func()->_targetPath;
690   }
691
692   Download::State Download::state() const
693   {
694     return d_func()->_state;
695   }
696
697   NetworkRequestError Download::lastRequestError() const
698   {
699     return d_func()->_requestError;
700   }
701
702   std::string Download::errorString() const
703   {
704     Z_D();
705     if (! d->_requestError.isError() ) {
706       return d->_errorString;
707     }
708
709     return ( zypp::str::Format("%1%(%2% %3%)") % d->_errorString % d->_requestError.toString() % d->_requestError.nativeErrorString() );
710   }
711
712   TransferSettings &Download::settings()
713   {
714     return d_func()->_transferSettings;
715   }
716
717   void Download::start()
718   {
719     d_func()->start();
720   }
721
722   void Download::setMultiPartHandlingEnabled( bool enable )
723   {
724     d_func()->_isMultiPartEnabled = enable;
725   }
726
727   void Download::setCheckExistsOnly(bool set)
728   {
729     Z_D();
730     if ( d->_checkExistsOnly != set ) {
731       d_func()->_checkExistsOnly = set;
732       if ( set == true )
733         d_func()->_isMultiPartEnabled = false;
734     }
735   }
736
737   void Download::setDeltaFile( const zypp::filesystem::Pathname &file )
738   {
739     d_func()->_deltaFilePath = file;
740   }
741
742   zyppng::NetworkRequestDispatcher &Download::dispatcher() const
743   {
744     return *d_func()->_requestDispatcher;
745   }
746
747   SignalProxy<void (Download &req)> Download::sigStarted()
748   {
749     return d_func()->_sigStarted;
750   }
751
752   SignalProxy<void (Download &req, Download::State state)> Download::sigStateChanged()
753   {
754     return d_func()->_sigStateChanged;
755   }
756
757   SignalProxy<void (zyppng::Download &req, off_t dlnow)> zyppng::Download::sigAlive()
758   {
759     return d_func()->_sigAlive;
760   }
761
762   SignalProxy<void (Download &req, off_t dltotal, off_t dlnow)> Download::sigProgress()
763   {
764     return d_func()->_sigProgress;
765   }
766
767   SignalProxy<void (Download &req)> Download::sigFinished()
768   {
769     return d_func()->_sigFinished;
770   }
771
772   SignalProxy<void (zyppng::Download &req, zyppng::NetworkAuthData &auth, const std::string &availAuth)> Download::sigAuthRequired()
773   {
774     return d_func()->_sigAuthRequired;
775   }
776
777   DownloaderPrivate::DownloaderPrivate( )
778   {
779     _requestDispatcher = std::make_shared<NetworkRequestDispatcher>(  );
780   }
781
782   void DownloaderPrivate::onDownloadStarted(Download &download)
783   {
784     _sigStarted.emit( *z_func(), download );
785   }
786
787   void DownloaderPrivate::onDownloadFinished( Download &download )
788   {
789     _sigFinished.emit( *z_func(), download );
790
791     auto it = std::find_if( _runningDownloads.begin(), _runningDownloads.end(), [ &download ]( const std::shared_ptr<Download> &dl){
792       return dl.get() == &download;
793     });
794
795     if ( it != _runningDownloads.end() ) {
796       //make sure this is not deleted before all user code was done
797       EventDispatcher::unrefLater( *it );
798       _runningDownloads.erase( it );
799     }
800
801     if ( _runningDownloads.empty() )
802       _queueEmpty.emit( *z_func() );
803   }
804
805   Downloader::Downloader( )
806     : Base ( *new DownloaderPrivate( ) )
807   {
808
809   }
810
811   std::shared_ptr<Download> Downloader::downloadFile(zyppng::Url file, zypp::filesystem::Pathname targetPath, zypp::ByteCount expectedFileSize )
812   {
813     Z_D();
814     std::shared_ptr<Download> dl = std::make_shared<Download>( std::move( *new DownloadPrivate( *this, d->_requestDispatcher, std::move(file), std::move(targetPath), std::move(expectedFileSize) ) ) );
815
816     d->_runningDownloads.push_back( dl );
817     dl->sigFinished().connect( sigc::mem_fun( *d , &DownloaderPrivate::onDownloadFinished ) );
818
819     d->_requestDispatcher->run();
820
821     return dl;
822   }
823
824   std::shared_ptr<NetworkRequestDispatcher> Downloader::requestDispatcher() const
825   {
826     return d_func()->_requestDispatcher;
827   }
828
829   SignalProxy<void (Downloader &parent, Download &download)> Downloader::sigStarted()
830   {
831     return d_func()->_sigStarted;
832   }
833
834   SignalProxy<void (Downloader &parent, Download &download)> Downloader::sigFinished()
835   {
836     return d_func()->_sigFinished;
837   }
838
839   SignalProxy<void (Downloader &parent)> Downloader::queueEmpty()
840   {
841     return d_func()->_queueEmpty;
842   }
843
844 }