namespace {
struct ProgressData
{
- ProgressData( CURL *_curl, time_t _timeout = 0, const Url & _url = Url(),
- ByteCount expectedFileSize_r = 0,
- callback::SendReport<DownloadProgressReport> *_report = nullptr )
- : curl( _curl )
- , url( _url )
- , timeout( _timeout )
- , reached( false )
- , fileSizeExceeded ( false )
- , report( _report )
- , _expectedFileSize( expectedFileSize_r )
+ ProgressData(CURL *_curl, const long _timeout, const zypp::Url &_url = zypp::Url(),
+ callback::SendReport<DownloadProgressReport> *_report=NULL)
+ : curl(_curl)
+ , timeout(_timeout)
+ , reached(false)
+ , report(_report)
+ , drate_period(-1)
+ , dload_period(0)
+ , secs(0)
+ , drate_avg(-1)
+ , ltime( time(NULL))
+ , dload( 0)
+ , uload( 0)
+ , url(_url)
{}
-
- CURL *curl;
- Url url;
- time_t timeout;
- bool reached;
- bool fileSizeExceeded;
+ CURL *curl;
+ long timeout;
+ bool reached;
callback::SendReport<DownloadProgressReport> *report;
- ByteCount _expectedFileSize;
-
- time_t _timeStart = 0; ///< Start total stats
- time_t _timeLast = 0; ///< Start last period(~1sec)
- time_t _timeRcv = 0; ///< Start of no-data timeout
- time_t _timeNow = 0; ///< Now
-
- double _dnlTotal = 0.0; ///< Bytes to download or 0 if unknown
- double _dnlLast = 0.0; ///< Bytes downloaded at period start
- double _dnlNow = 0.0; ///< Bytes downloaded now
-
- int _dnlPercent= 0; ///< Percent completed or 0 if _dnlTotal is unknown
-
- double _drateTotal= 0.0; ///< Download rate so far
- double _drateLast = 0.0; ///< Download rate in last period
-
- void updateStats( double dltotal = 0.0, double dlnow = 0.0 )
- {
- time_t now = _timeNow = time(0);
-
- // If called without args (0.0), recompute based on the last values seen
- if ( dltotal && dltotal != _dnlTotal )
- _dnlTotal = dltotal;
-
- if ( dlnow && dlnow != _dnlNow )
- {
- _timeRcv = now;
- _dnlNow = dlnow;
- }
- else if ( !_dnlNow && !_dnlTotal )
- {
- // Start time counting as soon as first data arrives.
- // Skip the connection / redirection time at begin.
- return;
- }
-
- // init or reset if time jumps back
- if ( !_timeStart || _timeStart > now )
- _timeStart = _timeLast = _timeRcv = now;
-
- // timeout condition
- if ( timeout )
- reached = ( (now - _timeRcv) > timeout );
-
- // check if the downloaded data is already bigger than what we expected
- fileSizeExceeded = _expectedFileSize > 0 && _expectedFileSize < static_cast<ByteCount::SizeType>(_dnlNow);
-
- // percentage:
- if ( _dnlTotal )
- _dnlPercent = int(_dnlNow * 100 / _dnlTotal);
-
- // download rates:
- _drateTotal = _dnlNow / std::max( int(now - _timeStart), 1 );
-
- if ( _timeLast < now )
- {
- _drateLast = (_dnlNow - _dnlLast) / int(now - _timeLast);
- // start new period
- _timeLast = now;
- _dnlLast = _dnlNow;
- }
- else if ( _timeStart == _timeLast )
- _drateLast = _drateTotal;
- }
-
- int reportProgress() const
- {
- if ( fileSizeExceeded )
- return 1;
- if ( reached )
- return 1; // no-data timeout
- if ( report && !(*report)->progress( _dnlPercent, url, _drateTotal, _drateLast ) )
- return 1; // user requested abort
- return 0;
- }
-
-
// download rate of the last period (cca 1 sec)
double drate_period;
// bytes downloaded at the start of the last period
double dload;
// bytes uploaded at the moment the progress was last reported
double uload;
+ zypp::Url url;
};
///////////////////////////////////////////////////////////////////
ZYPP_THROW(MediaBadUrlException(_url));
checkProtocol(_url);
- if( !isUseableAttachPoint(attachPoint()))
+ if( !isUseableAttachPoint( attachPoint() ) )
{
- std::string mountpoint = createAttachPoint().asString();
-
- if( mountpoint.empty())
- ZYPP_THROW( MediaBadAttachPointException(url()));
-
- setAttachPoint( mountpoint, true);
+ setAttachPoint( createAttachPoint(), true );
}
disconnectFrom(); // clean _curl if needed
///////////////////////////////////////////////////////////////////
-void MediaCurl::getFile(const Pathname & filename , const ByteCount &expectedFileSize_r) const
+void MediaCurl::getFile( const Pathname & filename ) const
{
// Use absolute file name to prevent access of files outside of the
// hierarchy below the attach point.
- getFileCopy(filename, localPath(filename).absolutename(), expectedFileSize_r);
+ getFileCopy(filename, localPath(filename).absolutename());
}
///////////////////////////////////////////////////////////////////
-void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target, const ByteCount &expectedFileSize_r ) const
+void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target) const
{
callback::SendReport<DownloadProgressReport> report;
{
try
{
- doGetFileCopy(filename, target, report, expectedFileSize_r);
+ doGetFileCopy(filename, target, report);
retry = false;
}
// retry with proper authentication data
///////////////////////////////////////////////////////////////////
-void MediaCurl::evaluateCurlCode(const Pathname &filename,
+void MediaCurl::evaluateCurlCode( const Pathname &filename,
CURLcode code,
- bool timeout_reached) const
+ bool timeout_reached ) const
{
if ( code != 0 )
{
url = _url;
else
url = getFileUrl(filename);
-
std::string err;
try
{
));
}
- case 502: // bad gateway (bnc #1070851)
case 503: // service temporarily unavailable (bnc #462545)
ZYPP_THROW(MediaTemporaryProblemException(url));
case 504: // gateway timeout
ZYPP_THROW(MediaForbiddenException(url, msg403));
}
case 404:
- case 410:
ZYPP_THROW(MediaFileNotFoundException(_url, filename));
}
ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
}
- AutoFILE file { ::fopen( "/dev/null", "w" ) };
+ FILE *file = ::fopen( "/dev/null", "w" );
if ( !file ) {
ERR << "fopen failed for /dev/null" << endl;
curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
ZYPP_THROW(MediaWriteException("/dev/null"));
}
- ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, (*file) );
+ ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
if ( ret != 0 ) {
+ ::fclose(file);
std::string err( _curlError);
curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
}
}
+ // if the code is not zero, close the file
+ if ( ok != 0 )
+ ::fclose(file);
+
// as we are not having user interaction, the user can't cancel
// the file existence checking, a callback or timeout return code
// will be always a timeout.
///////////////////////////////////////////////////////////////////
-void MediaCurl::doGetFileCopy(const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report, const ByteCount &expectedFileSize_r, RequestOptions options ) const
+void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const
{
Pathname dest = target.absolutename();
if( assert_dir( dest.dirname() ) )
{
DBG << "assert_dir " << dest.dirname() << " failed" << endl;
- ZYPP_THROW( MediaSystemException(getFileUrl(filename), "System error on " + dest.dirname().asString()) );
+ Url url(getFileUrl(filename));
+ ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
}
-
- ManagedFile destNew { target.extend( ".new.zypp.XXXXXX" ) };
- AutoFILE file;
+ string destNew = target.asString() + ".new.zypp.XXXXXX";
+ char *buf = ::strdup( destNew.c_str());
+ if( !buf)
{
- AutoFREE<char> buf { ::strdup( (*destNew).c_str() ) };
- if( ! buf )
- {
- ERR << "out of memory for temp file name" << endl;
- ZYPP_THROW(MediaSystemException(getFileUrl(filename), "out of memory for temp file name"));
- }
-
- AutoFD tmp_fd { ::mkostemp( buf, O_CLOEXEC ) };
- if( tmp_fd == -1 )
- {
- ERR << "mkstemp failed for file '" << destNew << "'" << endl;
- ZYPP_THROW(MediaWriteException(destNew));
- }
- destNew = ManagedFile( (*buf), filesystem::unlink );
+ ERR << "out of memory for temp file name" << endl;
+ Url url(getFileUrl(filename));
+ ZYPP_THROW(MediaSystemException(url, "out of memory for temp file name"));
+ }
- file = ::fdopen( tmp_fd, "we" );
- if ( ! file )
- {
- ERR << "fopen failed for file '" << destNew << "'" << endl;
- ZYPP_THROW(MediaWriteException(destNew));
- }
- tmp_fd.resetDispose(); // don't close it here! ::fdopen moved ownership to file
+ int tmp_fd = ::mkostemp( buf, O_CLOEXEC );
+ if( tmp_fd == -1)
+ {
+ free( buf);
+ ERR << "mkstemp failed for file '" << destNew << "'" << endl;
+ ZYPP_THROW(MediaWriteException(destNew));
+ }
+ destNew = buf;
+ free( buf);
+
+ FILE *file = ::fdopen( tmp_fd, "we" );
+ if ( !file ) {
+ ::close( tmp_fd);
+ filesystem::unlink( destNew );
+ ERR << "fopen failed for file '" << destNew << "'" << endl;
+ ZYPP_THROW(MediaWriteException(destNew));
}
DBG << "dest: " << dest << endl;
}
try
{
- doGetFileCopyFile(filename, dest, file, report, expectedFileSize_r, options);
+ doGetFileCopyFile(filename, dest, file, report, options);
}
catch (Exception &e)
{
+ ::fclose( file );
+ filesystem::unlink( destNew );
curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
ZYPP_RETHROW(e);
{
ERR << "Failed to chmod file " << destNew << endl;
}
-
- file.resetDispose(); // we're going to close it manually here
- if ( ::fclose( file ) )
+ if (::fclose( file ))
{
ERR << "Fclose failed for file '" << destNew << "'" << endl;
ZYPP_THROW(MediaWriteException(destNew));
}
-
// move the temp file into dest
if ( rename( destNew, dest ) != 0 ) {
ERR << "Rename failed" << endl;
ZYPP_THROW(MediaWriteException(dest));
}
- destNew.resetDispose(); // no more need to unlink it
+ }
+ else
+ {
+ // close and remove the temp file
+ ::fclose( file );
+ filesystem::unlink( destNew );
}
DBG << "done: " << PathInfo(dest) << endl;
///////////////////////////////////////////////////////////////////
-void MediaCurl::doGetFileCopyFile(const Pathname & filename , const Pathname & dest, FILE *file, callback::SendReport<DownloadProgressReport> & report, const ByteCount &expectedFileSize_r, RequestOptions options ) const
+void MediaCurl::doGetFileCopyFile( const Pathname & filename , const Pathname & dest, FILE *file, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const
{
DBG << filename.asString() << endl;
}
// Set callback and perform.
- ProgressData progressData(_curl, _settings.timeout(), url, expectedFileSize_r, &report);
+ ProgressData progressData(_curl, _settings.timeout(), url, &report);
if (!(options & OPTION_NO_REPORT_START))
report->start(url, dest);
if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
// which holds whether the timeout was reached or not,
// otherwise it would be a user cancel
try {
-
- if ( progressData.fileSizeExceeded )
- ZYPP_THROW(MediaFileSizeExceededException(url, progressData._expectedFileSize));
-
- evaluateCurlCode( filename, ret, progressData.reached );
+ evaluateCurlCode( filename, ret, progressData.reached);
}
catch ( const MediaException &e ) {
// some error, we are not sure about file existence, rethrw
switch ( it->type ) {
case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
case filesystem::FT_FILE:
- getFile( filename, 0 );
+ getFile( filename );
break;
case filesystem::FT_DIR: // newer directory.yast contain at least directory info
if ( recurse_r ) {
}
///////////////////////////////////////////////////////////////////
-//
-int MediaCurl::aliveCallback( void *clientp, double /*dltotal*/, double dlnow, double /*ultotal*/, double /*ulnow*/ )
-{
- ProgressData *pdata = reinterpret_cast<ProgressData *>( clientp );
- if( pdata )
- {
- // Do not propagate dltotal in alive callbacks. MultiCurl uses this to
- // prevent a percentage raise while downloading a metalink file. Download
- // activity however is indicated by propagating the download rate (via dlnow).
- pdata->updateStats( 0.0, dlnow );
- return pdata->reportProgress();
- }
- return 0;
-}
-int MediaCurl::progressCallback( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow )
+int MediaCurl::progressCallback( void *clientp,
+ double dltotal, double dlnow,
+ double ultotal, double ulnow)
{
- ProgressData *pdata = reinterpret_cast<ProgressData *>( clientp );
- if( pdata )
+ ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp);
+ if( pdata)
{
// work around curl bug that gives us old data
long httpReturnCode = 0;
- if ( curl_easy_getinfo( pdata->curl, CURLINFO_RESPONSE_CODE, &httpReturnCode ) != CURLE_OK || httpReturnCode == 0 )
- return aliveCallback( clientp, dltotal, dlnow, ultotal, ulnow );
+ if (curl_easy_getinfo(pdata->curl, CURLINFO_RESPONSE_CODE, &httpReturnCode) != CURLE_OK || httpReturnCode == 0)
+ return 0;
+
+ time_t now = time(NULL);
+ if( now > 0)
+ {
+ // reset time of last change in case initial time()
+ // failed or the time was adjusted (goes backward)
+ if( pdata->ltime <= 0 || pdata->ltime > now)
+ {
+ pdata->ltime = now;
+ }
+
+ // start time counting as soon as first data arrives
+ // (skip the connection / redirection time at begin)
+ time_t dif = 0;
+ if (dlnow > 0 || ulnow > 0)
+ {
+ dif = (now - pdata->ltime);
+ dif = dif > 0 ? dif : 0;
+
+ pdata->secs += dif;
+ }
+
+ // update the drate_avg and drate_period only after a second has passed
+ // (this callback is called much more often than a second)
+ // otherwise the values would be far from accurate when measuring
+ // the time in seconds
+ //! \todo more accurate download rate computationn, e.g. compute average value from last 5 seconds, or work with milliseconds instead of seconds
+
+ if ( pdata->secs > 1 && (dif > 0 || dlnow == dltotal ))
+ pdata->drate_avg = (dlnow / pdata->secs);
+
+ if ( dif > 0 )
+ {
+ pdata->drate_period = ((dlnow - pdata->dload_period) / dif);
+ pdata->dload_period = dlnow;
+ }
+ }
- pdata->updateStats( dltotal, dlnow );
- return pdata->reportProgress();
+ // send progress report first, abort transfer if requested
+ if( pdata->report)
+ {
+ if (!(*(pdata->report))->progress(int( dltotal ? dlnow * 100 / dltotal : 0 ),
+ pdata->url,
+ pdata->drate_avg,
+ pdata->drate_period))
+ {
+ return 1; // abort transfer
+ }
+ }
+
+ // check if we there is a timeout set
+ if( pdata->timeout > 0)
+ {
+ if( now > 0)
+ {
+ bool progress = false;
+
+ // update download data if changed, mark progress
+ if( dlnow != pdata->dload)
+ {
+ progress = true;
+ pdata->dload = dlnow;
+ pdata->ltime = now;
+ }
+ // update upload data if changed, mark progress
+ if( ulnow != pdata->uload)
+ {
+ progress = true;
+ pdata->uload = ulnow;
+ pdata->ltime = now;
+ }
+
+ if( !progress && (now >= (pdata->ltime + pdata->timeout)))
+ {
+ pdata->reached = true;
+ return 1; // aborts transfer
+ }
+ }
+ }
}
return 0;
}
return "";
}
-/**
- * MediaMultiCurl needs to reset the expected filesize in case a metalink file is downloaded
- * otherwise this function should not be called
- */
-void MediaCurl::resetExpectedFileSize(void *clientp, const ByteCount &expectedFileSize)
-{
- ProgressData *data = reinterpret_cast<ProgressData *>(clientp);
- if ( data ) {
- data->_expectedFileSize = expectedFileSize;
- }
-}
-
///////////////////////////////////////////////////////////////////
bool MediaCurl::authenticate(const string & availAuthTypes, bool firstTry) const