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>
24 #define BLKSIZE 131072
27 bool looks_like_metalink_data( const std::vector<char> &data )
32 const char *p = data.data();
33 while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')
36 if (!strncasecmp(p, "<?xml", 5))
38 while (*p && *p != '>')
42 while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')
45 bool ret = !strncasecmp( p, "<metalink", 9 ) ? true : false;
49 bool looks_like_metalink_file( const zypp::Pathname &file )
51 std::unique_ptr<FILE, decltype(&fclose)> fd( fopen( file.c_str(), "r" ), &fclose );
54 return looks_like_metalink_data( zyppng::peek_data_fd( fd.get(), 0, 256 ) );
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) )
68 void DownloadPrivate::Request::connectSignals(DownloadPrivate &dl)
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) );
75 void DownloadPrivate::Request::disconnectSignals()
77 _sigStartedConn.disconnect();
78 _sigProgressConn.disconnect();
79 _sigFinishedConn.disconnect();
82 void DownloadPrivate::start()
84 if ( _state == Download::Initializing ||
85 _state == Download::Running ||
86 _state == Download::RunningMulti )
89 //reset state variables
90 _isMultiDownload = false;
91 _downloadedMultiByteCount = 0;
92 _multiPartMirrors.clear();
93 _blockList = zypp::media::MediaBlockList ();
95 _errorString = std::string();
96 _requestError = NetworkRequestError();
98 setState( Download::Initializing );
100 _requestError = safeFillSettingsFromURL( _url , _transferSettings );
101 if ( _requestError.type() != NetworkRequestError::NoError ) {
102 setFailed( "Failed to read settings from URL " );
106 std::shared_ptr<Request> initialRequest = std::make_shared<Request>( internal::clearQueryString(_url), _targetPath );
108 initialRequest->_originalUrl = _url;
109 initialRequest->transferSettings() = _transferSettings;
111 if ( _isMultiPartEnabled ) {
112 if ( !_checkExistsOnly )
113 initialRequest->transferSettings().addHeader("Accept: */*, application/metalink+xml, application/metalink4+xml");
115 WAR << "Ignoring enabled multi part option for check only download of : " << internal::clearQueryString(_url) << std::endl;
118 if ( _checkExistsOnly )
119 initialRequest->setOptions( initialRequest->options() | NetworkRequest::HeadRequest );
121 addNewRequest( initialRequest );
124 void DownloadPrivate::setState(Download::State newState)
126 if ( _state == newState )
129 _sigStateChanged.emit( *z_func(), newState );
132 void DownloadPrivate::onRequestStarted( NetworkRequest & )
134 if ( _state == Download::Initializing )
135 _sigStarted.emit( *z_func() );
138 void DownloadPrivate::onRequestProgress( NetworkRequest &req, off_t dltotal, off_t dlnow, off_t , off_t )
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 );
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;
151 if ( !_isMultiDownload && dlnow < 256 ) {
152 // can't tell yet, ...
153 return _sigAlive.emit( *z_func(), dlnow );
156 if ( !_isMultiDownload )
158 _isMultiDownload = looks_like_metalink_data( req.peekData( 0, 256 ) );
161 if ( _isMultiDownload )
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 ) );
168 return _sigAlive.emit( *z_func(), dlnow );
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 );
176 if ( _state == Download::Running ) {
177 if ( _expectedFileSize > 0 && _expectedFileSize < dlnow ) {
178 _requestDispatcher->cancel( req, NetworkRequestErrorPrivate::customError( NetworkRequestError::ExceededMaxLen ) );
181 return _sigProgress.emit( *z_func(), dltotal, dlnow );
183 } else if ( _state == Download::RunningMulti ) {
184 off_t dlnowMulti = _downloadedMultiByteCount;
185 for( const auto &req : _runningRequests ) {
186 dlnowMulti += req->downloadedByteCount();
188 _sigProgress.emit( *z_func(), _blockList.getFilesize(), dlnowMulti );
192 void DownloadPrivate::onRequestFinished( NetworkRequest &req, const zyppng::NetworkRequestError &err )
195 auto it = std::find_if( _runningRequests.begin(), _runningRequests.end(), [ &req ]( const std::shared_ptr<Request> &r ) {
196 return ( r.get() == &req );
198 if ( it == _runningRequests.end() )
201 auto reqLocked = *it;
203 //remove from running
204 _runningRequests.erase( it );
206 if ( err.isError() ) {
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 ) {
214 zypp::media::CredentialManager cm( zypp::media::CredManagerOptions( zypp::ZConfig::instance().repoManagerRoot()) );
215 auto authDataPtr = cm.getCred( req.url() );
217 // get stored credentials
218 NetworkAuthData_Ptr cmcred( authDataPtr ? new NetworkAuthData( *authDataPtr ) : new NetworkAuthData() );
219 TransferSettings &ts = req.transferSettings();
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() );
227 reqLocked->_triedCredFromStore = true;
230 //we did not get credentials from the store, emit a signal that allows
231 //setting new auth data
233 NetworkAuthData credFromUser;
234 credFromUser.setUrl( req.url() );
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;
240 if ( authHintIt != err.extraInfo().end() ){
242 authHint = boost::any_cast<std::string>( authHintIt->second );
243 } catch ( const boost::bad_any_cast &) { }
246 //preset from store if we found something
247 if ( cmcred && !cmcred->username().empty() )
248 credFromUser.setUsername( cmcred->username() );
250 _sigAuthRequired.emit( *z_func(), credFromUser, authHint );
251 if ( credFromUser.valid() ) {
252 ts.setUsername( credFromUser.username() );
253 ts.setPassword( credFromUser.password() );
255 // set available authentication types from the error
256 if ( credFromUser.authType() == CURLAUTH_NONE )
257 credFromUser.setAuthType( authHint );
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());
265 cm.addCred( credFromUser );
271 } else if ( _state == Download::RunningMulti ) {
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;
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 );
280 newReq->_retryCount = reqLocked->_retryCount + 1;
281 addNewRequest( newReq );
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 } );
293 //if rety is true we just enqueue the request again, usually this means authentication was updated
295 //make sure this request will run asap
296 reqLocked->setPriority( NetworkRequest::High );
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 );
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 );
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 );
320 auto req = std::make_shared<Request>( internal::clearQueryString(_url), _targetPath ) ;
321 req->transferSettings() = _transferSettings;
322 addNewRequest( req );
327 setFailed( "Download failed ");
331 if ( _state == Download::Initializing || _state == Download::Running ) {
332 if ( _isMultiPartEnabled && !_isMultiDownload )
333 _isMultiDownload = looks_like_metalink_file( req.targetFilePath() );
334 if ( !_isMultiDownload ) {
339 DBG << " Upgrading request for URL: "<< req.url() << " to multipart download " << std::endl;
341 //we have a metalink download, lets parse it and see what we got
342 _multiPartMirrors.clear();
345 zypp::media::MetaLinkParser parser;
346 parser.parse( req.targetFilePath() );
348 _blockList = parser.getBlockList();
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) {
354 std::string scheme = urliter->getScheme();
355 if (scheme == "http" || scheme == "https" || scheme == "ftp" || scheme == "tftp") {
356 if ( !_requestDispatcher->supportsProtocol( *urliter ))
358 _multiPartMirrors.push_back(internal::propagateQueryParams(*urliter, _url));
364 if ( _multiPartMirrors.empty() )
365 _multiPartMirrors.push_back( _url );
367 } catch ( const zypp::Exception &ex ) {
368 setFailed( zypp::str::Format("Failed to parse metalink information.(%1%)" ) % ex.asUserString() );
372 if ( _multiPartMirrors.size() == 0 ) {
373 setFailed( zypp::str::Format("Invalid metalink information.( No mirrors in metalink file)" ) );
377 if ( !_blockList.haveBlocks() ) {
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;
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;
389 TransferSettings set;
390 NetworkRequestError dummyErr;
391 if ( !findNextMirror( url, set, dummyErr ) ) {
393 set = _transferSettings;
396 auto req = std::make_shared<Request>( internal::clearQueryString(url), _targetPath ) ;
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() );
406 req->transferSettings() = set;
407 addNewRequest( req );
411 //we generate a blocklist on the fly based on the filesize
413 DBG << "Generate blocklist, since there was none in the metalink file." << _url << std::endl;
416 off_t filesize = _blockList.getFilesize();
417 while ( currOff < filesize ) {
419 size_t blksize = static_cast<size_t>( filesize - currOff );
420 if ( blksize > BLKSIZE)
423 _blockList.addBlock( currOff, blksize );
427 XXX << "Generated blocklist: " << std::endl << _blockList << std::endl << " End blocklist " << std::endl;
431 //remove the metalink file
432 zypp::filesystem::unlink( _targetPath );
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" );
439 setFailed( zypp::str::Format("Failed to open target file.(errno %1%)" ) % errno );
444 _blockList.reuseBlocks ( f, _deltaFilePath.asString() );
451 setState( Download::RunningMulti );
454 } else if ( _state == Download::RunningMulti ) {
455 _downloadedMultiByteCount += req.downloadedByteCount();
457 DBG << "Request finished " << reqLocked->_myBlock <<std::endl;
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 ) );
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 );
471 //check if we already have enqueued all blocks if not reuse the request
472 if ( _blockIter < _blockList.numBlocks() ) {
474 DBG << "Reusing to download block: " << _blockIter <<std::endl;
475 restartReqWithBlock( reqLocked, _blockIter, 0 );
480 //if we have failed blocks, try to download them with this mirror
481 if ( !_failedBlocks.empty() ) {
483 FailedBlock blk = std::move( _failedBlocks.front() );
484 _failedBlocks.pop_front();
486 DBG << "Reusing to download failed block: " << blk._block <<std::endl;
488 restartReqWithBlock( reqLocked, blk._block, blk._retryCount+1 );
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 );
497 //check if there is still work to do
498 if ( _runningRequests.size() < 10 ) {
500 NetworkRequestError lastErr = _requestError;
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++ ){
505 if ( _runningRequests.size() >= 10 )
508 std::shared_ptr<Request> req = initMultiRequest( _blockIter, lastErr );
512 addNewRequest( req );
515 while ( _failedBlocks.size() ) {
517 if ( _runningRequests.size() >= 10 )
520 FailedBlock blk = std::move( _failedBlocks.front() );
521 _failedBlocks.pop_front();
523 auto req = initMultiRequest( blk._block, lastErr );
527 addNewRequest( req );
530 if ( _runningRequests.empty() && lastErr.type()!= NetworkRequestError::NoError ) {
531 //we found no mirrors -> fail
532 _requestError = lastErr;
533 setFailed( "Unable to use mirror" );
537 if ( _runningRequests.empty() ) {
539 if ( _failedBlocks.size() || ( _blockIter < _blockList.numBlocks() )) {
540 setFailed( "Unable to download all blocks." );
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
548 _blockList.createFileDigest( dig );
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)." );
555 if ( !dig.update( istrm ) ) {
556 setFailed( "Failed to verify file digest (Could not read target file)." );
559 if ( !_blockList.verifyFileDigest( dig ) ) {
560 setFailed( "Failed to verify file digest (Checksum did not match)." );
564 //TODO implement digest check for non multi downloads
569 void DownloadPrivate::addNewRequest( std::shared_ptr<Request> req )
571 auto slot = _sigStarted.slots().front();
572 req->connectSignals( *this );
573 _runningRequests.push_back( req );
574 _requestDispatcher->enqueue( req );
577 std::shared_ptr<DownloadPrivate::Request> DownloadPrivate::initMultiRequest( size_t block, NetworkRequestError &err )
579 zypp::media::MediaBlock blk = _blockList.getBlock( block );
582 TransferSettings settings;
583 if ( !findNextMirror( myUrl, settings, err ) )
586 DBG << "Starting block " << block << std::endl;
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;
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;
602 DBG << "Block " << block << " has no checksum." << std::endl;
607 bool DownloadPrivate::findNextMirror( Url &url, TransferSettings &set, NetworkRequestError &err )
610 bool foundMirror = false;
611 TransferSettings settings;
612 while ( _multiPartMirrors.size() ) {
613 myUrl = _multiPartMirrors.front();
614 _multiPartMirrors.pop_front();
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() );
624 err = safeFillSettingsFromURL( myUrl, settings );
625 if ( err.type() != NetworkRequestError::NoError ) {
641 void DownloadPrivate::setFailed(std::string &&reason)
643 _errorString = reason;
644 //zypp::filesystem::unlink( _targetPath );
645 setFinished( false );
648 void DownloadPrivate::setFinished(bool success)
650 setState( success ? Download::Success : Download::Failed );
651 _sigFinished.emit( *z_func() );
654 NetworkRequestError DownloadPrivate::safeFillSettingsFromURL( const Url &url, TransferSettings &set)
656 auto buildExtraInfo = [this, &url](){
657 std::map<std::string, boost::any> extraInfo;
658 extraInfo.insert( {"requestUrl", url } );
659 extraInfo.insert( {"filepath", _targetPath } );
663 NetworkRequestError res;
665 internal::fillSettingsFromUrl( url, set );
666 if ( _transferSettings.proxy().empty() )
667 internal::fillSettingsSystemProxy( url, set );
669 /* Fixes bsc#1174011 "auth=basic ignored in some cases"
670 * We should proactively add the password to the request if basic auth is configured
671 * and a password is available in the credentials but not in the URL.
673 * We will be a bit paranoid here and require that the URL has a user embedded, otherwise we go the default route
674 * and ask the server first about the auth method
676 if ( set.authType() == "basic"
677 && set.username().size()
678 && !set.password().size() ) {
679 zypp::media::CredentialManager cm( zypp::media::CredManagerOptions( zypp::ZConfig::instance().repoManagerRoot()) );
680 const auto cred = cm.getCred( url );
681 if ( cred && cred->valid() ) {
682 if ( !set.username().size() )
683 set.setUsername(cred->username());
684 set.setPassword(cred->password());
688 } catch ( const zypp::media::MediaBadUrlException & e ) {
689 res = NetworkRequestErrorPrivate::customError( NetworkRequestError::MalformedURL, e.asString(), buildExtraInfo() );
690 } catch ( const zypp::media::MediaUnauthorizedException & e ) {
691 res = NetworkRequestErrorPrivate::customError( NetworkRequestError::AuthFailed, e.asString(), buildExtraInfo() );
692 } catch ( const zypp::Exception & e ) {
693 res = NetworkRequestErrorPrivate::customError( NetworkRequestError::InternalError, e.asString(), buildExtraInfo() );
698 Download::Download(zyppng::DownloadPrivate &&prv)
702 Url Download::url() const
704 return d_func()->_url;
707 zypp::filesystem::Pathname Download::targetPath() const
709 return d_func()->_targetPath;
712 Download::State Download::state() const
714 return d_func()->_state;
717 NetworkRequestError Download::lastRequestError() const
719 return d_func()->_requestError;
722 std::string Download::errorString() const
725 if (! d->_requestError.isError() ) {
726 return d->_errorString;
729 return ( zypp::str::Format("%1%(%2% %3%)") % d->_errorString % d->_requestError.toString() % d->_requestError.nativeErrorString() );
732 TransferSettings &Download::settings()
734 return d_func()->_transferSettings;
737 void Download::start()
742 void Download::setMultiPartHandlingEnabled( bool enable )
744 d_func()->_isMultiPartEnabled = enable;
747 void Download::setCheckExistsOnly(bool set)
750 if ( d->_checkExistsOnly != set ) {
751 d_func()->_checkExistsOnly = set;
753 d_func()->_isMultiPartEnabled = false;
757 void Download::setDeltaFile( const zypp::filesystem::Pathname &file )
759 d_func()->_deltaFilePath = file;
762 zyppng::NetworkRequestDispatcher &Download::dispatcher() const
764 return *d_func()->_requestDispatcher;
767 SignalProxy<void (Download &req)> Download::sigStarted()
769 return d_func()->_sigStarted;
772 SignalProxy<void (Download &req, Download::State state)> Download::sigStateChanged()
774 return d_func()->_sigStateChanged;
777 SignalProxy<void (zyppng::Download &req, off_t dlnow)> zyppng::Download::sigAlive()
779 return d_func()->_sigAlive;
782 SignalProxy<void (Download &req, off_t dltotal, off_t dlnow)> Download::sigProgress()
784 return d_func()->_sigProgress;
787 SignalProxy<void (Download &req)> Download::sigFinished()
789 return d_func()->_sigFinished;
792 SignalProxy<void (zyppng::Download &req, zyppng::NetworkAuthData &auth, const std::string &availAuth)> Download::sigAuthRequired()
794 return d_func()->_sigAuthRequired;
797 DownloaderPrivate::DownloaderPrivate( )
799 _requestDispatcher = std::make_shared<NetworkRequestDispatcher>( );
802 void DownloaderPrivate::onDownloadStarted(Download &download)
804 _sigStarted.emit( *z_func(), download );
807 void DownloaderPrivate::onDownloadFinished( Download &download )
809 _sigFinished.emit( *z_func(), download );
811 auto it = std::find_if( _runningDownloads.begin(), _runningDownloads.end(), [ &download ]( const std::shared_ptr<Download> &dl){
812 return dl.get() == &download;
815 if ( it != _runningDownloads.end() ) {
816 //make sure this is not deleted before all user code was done
817 EventDispatcher::unrefLater( *it );
818 _runningDownloads.erase( it );
821 if ( _runningDownloads.empty() )
822 _queueEmpty.emit( *z_func() );
825 Downloader::Downloader( )
826 : Base ( *new DownloaderPrivate( ) )
831 std::shared_ptr<Download> Downloader::downloadFile(zyppng::Url file, zypp::filesystem::Pathname targetPath, zypp::ByteCount expectedFileSize )
834 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) ) ) );
836 d->_runningDownloads.push_back( dl );
837 dl->sigFinished().connect( sigc::mem_fun( *d , &DownloaderPrivate::onDownloadFinished ) );
839 d->_requestDispatcher->run();
844 std::shared_ptr<NetworkRequestDispatcher> Downloader::requestDispatcher() const
846 return d_func()->_requestDispatcher;
849 SignalProxy<void (Downloader &parent, Download &download)> Downloader::sigStarted()
851 return d_func()->_sigStarted;
854 SignalProxy<void (Downloader &parent, Download &download)> Downloader::sigFinished()
856 return d_func()->_sigFinished;
859 SignalProxy<void (Downloader &parent)> Downloader::queueEmpty()
861 return d_func()->_queueEmpty;