1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaAria2c.cc
17 #include <boost/lexical_cast.hpp>
19 #include "zypp/base/Logger.h"
20 #include "zypp/ExternalProgram.h"
21 #include "zypp/ProgressData.h"
22 #include "zypp/base/String.h"
23 #include "zypp/base/Gettext.h"
24 #include "zypp/base/Sysconfig.h"
25 #include "zypp/base/Gettext.h"
26 #include "zypp/ZYppCallbacks.h"
28 #include "zypp/Edition.h"
29 #include "zypp/Target.h"
30 #include "zypp/ZYppFactory.h"
31 #include "zypp/ZConfig.h"
33 #include "zypp/TmpPath.h"
35 #include "zypp/media/MediaAria2c.h"
36 #include "zypp/media/proxyinfo/ProxyInfos.h"
37 #include "zypp/media/ProxyInfo.h"
38 #include "zypp/media/MediaUserAuth.h"
39 #include "zypp/media/MediaCurl.h"
40 #include "zypp/thread/Once.h"
42 #include <sys/types.h>
44 #include <sys/mount.h>
48 #include <boost/format.hpp>
50 #define DETECT_DIR_INDEX 0
51 #define CONNECT_TIMEOUT 60
52 #define TRANSFER_TIMEOUT 60 * 3
53 #define TRANSFER_TIMEOUT_MAX 60 * 60
55 #define ARIA_BINARY "aria2c"
58 using namespace zypp::base;
65 Pathname MediaAria2c::_cookieFile = "/var/lib/YaST2/cookies";
66 std::string MediaAria2c::_aria2cVersion = "WE DON'T KNOW ARIA2C VERSION";
68 //check if aria2c is present in the system
70 MediaAria2c::existsAria2cmd()
72 static const char* argv[] =
78 ExternalProgram aria( argv, ExternalProgram::Stderr_To_Stdout );
79 return( aria.close() == 0 );
83 * comannd line for aria.
84 * The argument list gets passed as reference
87 void fillAriaCmdLine( const string &ariaver,
88 const TransferSettings &s,
89 filesystem::TmpPath &credentials,
91 const Pathname &destination,
92 ExternalProgram::Arguments &args )
95 // options that are not passed in the command line
96 // like credentials, every string is in the
98 list<string> file_options;
100 args.push_back(ARIA_BINARY);
101 args.push_back(str::form("--user-agent=%s", s.userAgentString().c_str()));
102 args.push_back("--summary-interval=1");
103 args.push_back("--follow-metalink=mem");
104 args.push_back("--check-integrity=true");
105 args.push_back("--file-allocation=none");
107 // save the stats of the mirrors and use them as input later
108 Pathname statsFile = ZConfig::instance().repoCachePath() / "aria2.stats";
109 args.push_back(str::form("--server-stat-of=%s", statsFile.c_str()));
110 args.push_back(str::form("--server-stat-if=%s", statsFile.c_str()));
111 args.push_back("--uri-selector=adaptive");
113 // only present in recent aria lets find out the aria version
114 vector<string> fields;
115 // "aria2c version x.x"
116 str::split( ariaver, std::back_inserter(fields));
117 if ( fields.size() == 3 )
119 if ( Edition(fields[2]) >= Edition("1.1.2") )
120 args.push_back( "--use-head=false");
123 if ( s.maxDownloadSpeed() > 0 )
124 args.push_back(str::form("--max-download-limit=%ld", s.maxDownloadSpeed()));
125 if ( s.minDownloadSpeed() > 0 )
126 args.push_back(str::form("--lowest-speed-limit=%ld", s.minDownloadSpeed()));
128 args.push_back(str::form("--max-tries=%ld", s.maxSilentTries()));
130 if ( Edition(fields[2]) < Edition("1.2.0") )
131 WAR << "aria2c is older than 1.2.0, some features may be disabled" << endl;
133 // TODO make this one configurable
134 args.push_back(str::form("--max-concurrent-downloads=%ld", s.maxConcurrentConnections()));
136 // add the anonymous id.
137 for ( TransferSettings::Headers::const_iterator it = s.headersBegin();
138 it != s.headersEnd();
140 args.push_back(str::form("--header=%s", it->c_str() ));
142 args.push_back( str::form("--connect-timeout=%ld", s.timeout()));
144 if ( s.username().empty() )
146 if ( url.getScheme() == "ftp" )
149 args.push_back(str::form("--ftp-user=%s", "suseuser" ));
150 args.push_back(str::form("--ftp-passwd=%s", VERSION ));
154 DBG << "Anonymous FTP identification: '" << id << "'" << endl;
159 if ( url.getScheme() == "ftp" )
160 file_options.push_back(str::form("ftp-user=%s", s.username().c_str() ));
161 else if ( url.getScheme() == "http" ||
162 url.getScheme() == "https" )
163 file_options.push_back(str::form("http-user=%s", s.username().c_str() ));
165 if ( s.password().size() )
167 if ( url.getScheme() == "ftp" )
168 file_options.push_back(str::form("ftp-passwd=%s", s.password().c_str() ));
169 else if ( url.getScheme() == "http" ||
170 url.getScheme() == "https" )
171 file_options.push_back(str::form("http-passwd=%s", s.password().c_str() ));
175 if ( s.proxyEnabled() )
177 args.push_back(str::form("--http-proxy=%s", s.proxy().c_str() ));
178 if ( ! s.proxyUsername().empty() )
180 file_options.push_back(str::form("http-proxy-user=%s", s.proxyUsername().c_str() ));
181 if ( ! s.proxyPassword().empty() )
182 file_options.push_back(str::form("http-proxy-passwd=%s", s.proxyPassword().c_str() ));
186 if ( ! destination.empty() )
187 args.push_back(str::form("--dir=%s", destination.c_str()));
189 // now append the file if there are hidden options
190 if ( ! file_options.empty() )
192 filesystem::TmpFile tmp;
193 ofstream outs( tmp.path().c_str() );
194 for_( it, file_options.begin(), file_options.end() )
199 args.push_back(str::form("--conf-path=%s", credentials.path().c_str()));
202 args.push_back(url.asString().c_str());
205 const char *const MediaAria2c::agentString()
207 // we need to add the release and identifier to the
209 // The target could be not initialized, and then this information
211 Target_Ptr target = zypp::getZYpp()->getTarget();
213 static const std::string _value(
217 , MediaAria2c::_aria2cVersion.c_str()
218 , Target::targetDistribution( Pathname()/*guess root*/ ).c_str()
221 return _value.c_str();
226 MediaAria2c::MediaAria2c( const Url & url_r,
227 const Pathname & attach_point_hint_r )
228 : MediaCurl( url_r, attach_point_hint_r )
230 MIL << "MediaAria2c::MediaAria2c(" << url_r << ", " << attach_point_hint_r << ")" << endl;
232 _aria2cVersion = getAria2cVersion();
235 void MediaAria2c::attachTo (bool next)
237 MediaCurl::attachTo(next);
238 _settings.setUserAgentString(agentString());
242 MediaAria2c::checkAttachPoint(const Pathname &apoint) const
244 return MediaCurl::checkAttachPoint( apoint );
247 void MediaAria2c::disconnectFrom()
249 MediaCurl::disconnectFrom();
252 void MediaAria2c::releaseFrom( const std::string & ejectDev )
254 MediaCurl::releaseFrom(ejectDev);
257 static Url getFileUrl(const Url & url, const Pathname & filename)
260 string path = url.getPathName();
261 if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
262 filename.absolute() )
264 // If url has a path with trailing slash, remove the leading slash from
265 // the absolute file name
266 path += filename.asString().substr( 1, filename.asString().size() - 1 );
268 else if ( filename.relative() )
270 // Add trailing slash to path, if not already there
271 if (path.empty()) path = "/";
272 else if (*path.rbegin() != '/' ) path += "/";
273 // Remove "./" from begin of relative file name
274 path += filename.asString().substr( 2, filename.asString().size() - 2 );
278 path += filename.asString();
281 newurl.setPathName(path);
285 void MediaAria2c::getFile( const Pathname & filename ) const
287 // Use absolute file name to prevent access of files outside of the
288 // hierarchy below the attach point.
289 getFileCopy(filename, localPath(filename).absolutename());
292 void MediaAria2c::getFileCopy( const Pathname & filename , const Pathname & target) const
294 callback::SendReport<DownloadProgressReport> report;
296 Url fileurl(getFileUrl(_url, filename));
300 ExternalProgram::Arguments args;
302 filesystem::TmpPath credentials;
303 fillAriaCmdLine(_aria2cVersion, _settings, credentials, fileurl, target.dirname(), args);
309 report->start(fileurl, target.asString() );
311 ExternalProgram aria(args, ExternalProgram::Stderr_To_Stdout);
313 // progress line like: [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:6899.88KiB/s]
314 // but since 1.4.0: [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:899.8KiBs]
315 // (bnc #513944) [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:3.8MiBs]
316 // we save it until we find a string with FILE: later
319 // file line, which tell which file is the previous progress
320 // ie: FILE: ./packages.FL.gz
321 double average_speed = 0;
322 long average_speed_count = 0;
324 // here we capture aria output exceptions
325 vector<string> ariaExceptions;
327 // TODO: Detect partial downloads!
328 bool partialDownload = false; // Whether it makes sense to retry with --continue!
331 for(std::string ariaResponse( aria.receiveLine());
332 ariaResponse.length();
333 ariaResponse = aria.receiveLine())
335 //cout << ariaResponse;
336 string line = str::trim(ariaResponse);
338 // look for the progress line and save it for later
339 if ( str::hasPrefix(line, "[#") )
343 // save error messages for later
344 else if ( str::hasPrefix(line, "Exception: ") )
346 // for auth exception, we throw
347 if (!line.substr(0,31).compare("Exception: Authorization failed") )
349 ZYPP_THROW(MediaUnauthorizedException(
350 _url, "Login failed.", "Login failed", "auth hint"
353 // otherwise, remember the error
354 string excpMsg = line.substr(10, line.size());
355 DBG << "aria2c reported: '" << excpMsg << "'" << endl;
356 ariaExceptions.push_back(excpMsg);
358 else if ( str::hasPrefix(line, "FILE: ") )
361 string theFile(line.substr(6, line.size()));
362 // is the report about the filename we are downloading?
363 // aria may report progress about metalinks, torrent and
364 // other stuff which is not the main transfer
365 // the reported file is the url before the server emits a response
366 // and then is reported as the target file
367 if ( Pathname(theFile) == target || theFile == fileurl.asCompleteString() )
369 // once we find the FILE: line, progress has to be
371 if ( ! progressLine.empty() )
373 // get the percentage (progress) data
375 size_t left_bound = progressLine.find('(',0) + 1;
376 size_t count = progressLine.find('%',left_bound) - left_bound;
377 string progressStr = progressLine.substr(left_bound, count);
379 if ( count != string::npos )
380 progress = std::atoi(progressStr.c_str());
382 ERR << "Can't parse progress from '" << progressStr << "'" << endl;
385 double current_speed = 0;
386 left_bound = progressLine.find("SPD:",0) + 4;
387 count = progressLine.find("KiB",left_bound);
388 bool kibs = true; // KiBs ? (MiBs if false)
389 if ( count == string::npos ) // try MiBs
391 count = progressLine.find("MiBs",left_bound);
394 if ( count != string::npos )
395 { // convert the string to a double
397 string speedStr = progressLine.substr(left_bound, count);
399 current_speed = boost::lexical_cast<double>(speedStr);
401 catch (const std::exception&) {
402 ERR << "Can't parse speed from '" << speedStr << "'" << endl;
407 // we have a new average speed
408 average_speed_count++;
410 // this is basically A: average
411 // ((n-1)A(n-1) + Xn)/n = A(n)
413 (((average_speed_count - 1 )*average_speed) + current_speed)
414 / average_speed_count;
416 // note that aria report speed in kBps or MBps, while the report takes Bps
417 report->progress ( progress, fileurl,
418 average_speed * (kibs ? 0x400 : 0x10000),
419 current_speed * (kibs ? 0x400 : 0x10000));
420 // clear the progress line to detect mismatches between
421 // [# and FILE: lines
422 progressLine.clear();
426 WAR << "aria2c reported a file, but no progress data available" << endl;
432 DBG << "Progress is not about '" << target << "' but '" << theFile << "'" << endl;
437 // other line type, just ignore it.
441 int code = aria.close();
450 MediaTimeoutException e(_url);
451 for_(it, ariaExceptions.begin(), ariaExceptions.end())
456 case 4: // max notfound reached
458 MediaFileNotFoundException e(_url, filename);
459 for_(it, ariaExceptions.begin(), ariaExceptions.end())
464 case 6: // network problem
465 case 7: // unfinished downloads (ctr-c)
469 if ( partialDownload )
471 // Ask for retry on partial downloads, when it makes sense to retry with --continue!
472 // Other errors are handled by the layers above.
473 MediaException e(str::form(_("Download interrupted at %d%%"), progress ));
474 for_(it, ariaExceptions.begin(), ariaExceptions.end())
477 DownloadProgressReport::Action action = report->problem( _url, DownloadProgressReport::ERROR, e.asUserHistory() );
478 if ( action == DownloadProgressReport::RETRY )
485 // TranslatorExplanation: Failed to download <FILENAME> from <SERVERURL>.
486 MediaException e(str::form(_("Failed to download %s from %s"), filename.c_str(), _url.asString().c_str()));
487 for_(it, ariaExceptions.begin(), ariaExceptions.end())
496 // retry with proper authentication data
497 catch (MediaUnauthorizedException & ex_r)
499 if(authenticate(ex_r.hint(), !retry))
503 report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
508 // unexpected exception
509 catch (MediaException & excpt_r)
511 // FIXME: error number fix
512 report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
513 ZYPP_RETHROW(excpt_r);
518 report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
521 bool MediaAria2c::getDoesFileExist( const Pathname & filename ) const
523 return MediaCurl::getDoesFileExist(filename);
526 bool MediaAria2c::doGetDoesFileExist( const Pathname & filename ) const
528 return MediaCurl::doGetDoesFileExist(filename);
531 void MediaAria2c::getDir( const Pathname & dirname, bool recurse_r ) const
533 MediaCurl::getDir(dirname, recurse_r);
536 bool MediaAria2c::authenticate(const std::string & availAuthTypes, bool firstTry) const
541 void MediaAria2c::getDirInfo( std::list<std::string> & retlist,
542 const Pathname & dirname, bool dots ) const
544 getDirectoryYast( retlist, dirname, dots );
547 void MediaAria2c::getDirInfo( filesystem::DirContent & retlist,
548 const Pathname & dirname, bool dots ) const
550 getDirectoryYast( retlist, dirname, dots );
553 std::string MediaAria2c::getAria2cVersion()
555 static const char* argv[] =
561 ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
562 std::string vResponse( str::trim( aria.receiveLine() ) );