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/media/MediaAria2c.h"
34 #include "zypp/media/proxyinfo/ProxyInfos.h"
35 #include "zypp/media/ProxyInfo.h"
36 #include "zypp/media/MediaUserAuth.h"
37 #include "zypp/media/MediaCurl.h"
38 #include "zypp/thread/Once.h"
40 #include <sys/types.h>
42 #include <sys/mount.h>
46 #include <boost/format.hpp>
48 #define DETECT_DIR_INDEX 0
49 #define CONNECT_TIMEOUT 60
50 #define TRANSFER_TIMEOUT 60 * 3
51 #define TRANSFER_TIMEOUT_MAX 60 * 60
55 using namespace zypp::base;
62 Pathname MediaAria2c::_cookieFile = "/var/lib/YaST2/cookies";
63 Pathname MediaAria2c::_aria2cPath = "/usr/local/bin/aria2c";
64 std::string MediaAria2c::_aria2cVersion = "WE DON'T KNOW ARIA2C VERSION";
66 //check if aria2c is present in the system
68 MediaAria2c::existsAria2cmd()
77 ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
79 for(std::string ariaResponse( aria.receiveLine());
80 ariaResponse.length();
81 ariaResponse = aria.receiveLine())
86 return ( aria.close() == 0 );
90 * comannd line for aria.
91 * The argument list gets passed as reference
94 void fillAriaCmdLine( const Pathname &ariapath,
95 const string &ariaver,
96 const TransferSettings &s,
98 const Pathname &destination,
99 ExternalProgram::Arguments &args )
101 args.push_back(ariapath.c_str());
102 args.push_back(str::form("--user-agent=%s", s.userAgentString().c_str()));
103 args.push_back("--summary-interval=1");
104 args.push_back("--follow-metalink=mem");
105 args.push_back("--check-integrity=true");
106 args.push_back("--file-allocation=none");
108 // save the stats of the mirrors and use them as input later
109 Pathname statsFile = ZConfig::instance().repoCachePath() / "aria2.stats";
110 args.push_back(str::form("--server-stat-of=%s", statsFile.c_str()));
111 args.push_back(str::form("--server-stat-if=%s", statsFile.c_str()));
112 args.push_back("--uri-selector=adaptive");
114 // only present in recent aria lets find out the aria version
115 vector<string> fields;
116 // "aria2c version x.x"
117 str::split( ariaver, std::back_inserter(fields));
118 if ( fields.size() == 3 )
120 if ( Edition(fields[2]) >= Edition("1.1.2") )
121 args.push_back( "--use-head=false");
124 if ( s.maxDownloadSpeed() > 0 )
125 args.push_back(str::form("--max-download-limit=%ld", s.maxDownloadSpeed()));
126 if ( s.minDownloadSpeed() > 0 )
127 args.push_back(str::form("--lowest-speed-limit=%ld", s.minDownloadSpeed()));
129 args.push_back(str::form("--max-tries=%ld", s.maxSilentTries()));
131 if ( Edition(fields[2]) < Edition("1.2.0") )
132 WAR << "aria2c is older than 1.2.0, some features may be disabled" << endl;
134 // TODO make this one configurable
135 args.push_back(str::form("--max-concurrent-downloads=%ld", s.maxConcurrentConnections()));
137 // add the anonymous id.
138 for ( TransferSettings::Headers::const_iterator it = s.headersBegin();
139 it != s.headersEnd();
141 args.push_back(str::form("--header=%s", it->c_str() ));
143 args.push_back( str::form("--connect-timeout=%ld", s.timeout()));
145 if ( s.username().empty() )
147 if ( url.getScheme() == "ftp" )
150 args.push_back(str::form("--ftp-user=%s", "suseuser" ));
151 args.push_back(str::form("--ftp-passwd=%s", VERSION ));
155 DBG << "Anonymous FTP identification: '" << id << "'" << endl;
160 if ( url.getScheme() == "ftp" )
161 args.push_back(str::form("--ftp-user=%s", s.username().c_str() ));
162 else if ( url.getScheme() == "http" ||
163 url.getScheme() == "https" )
164 args.push_back(str::form("--http-user=%s", s.username().c_str() ));
166 if ( s.password().size() )
168 if ( url.getScheme() == "ftp" )
169 args.push_back(str::form("--ftp-passwd=%s", s.password().c_str() ));
170 else if ( url.getScheme() == "http" ||
171 url.getScheme() == "https" )
172 args.push_back(str::form("--http-passwd=%s", s.password().c_str() ));
176 if ( s.proxyEnabled() )
178 args.push_back(str::form("--http-proxy=%s", s.proxy().c_str() ));
179 if ( ! s.proxyUsername().empty() )
181 args.push_back(str::form("--http-proxy-user=%s", s.proxyUsername().c_str() ));
182 if ( ! s.proxyPassword().empty() )
183 args.push_back(str::form("--http-proxy-passwd=%s", s.proxyPassword().c_str() ));
187 if ( ! destination.empty() )
188 args.push_back(str::form("--dir=%s", destination.c_str()));
190 args.push_back(url.asString().c_str());
193 const char *const MediaAria2c::agentString()
195 // we need to add the release and identifier to the
197 // The target could be not initialized, and then this information
199 Target_Ptr target = zypp::getZYpp()->getTarget();
201 static const std::string _value(
205 , MediaAria2c::_aria2cVersion.c_str()
206 , target ? target->targetDistribution().c_str() : ""
209 return _value.c_str();
214 MediaAria2c::MediaAria2c( const Url & url_r,
215 const Pathname & attach_point_hint_r )
216 : MediaCurl( url_r, attach_point_hint_r )
218 MIL << "MediaAria2c::MediaAria2c(" << url_r << ", " << attach_point_hint_r << ")" << endl;
220 //At this point, we initialize aria2c path
221 _aria2cPath = Pathname( whereisAria2c().asString() );
224 _aria2cVersion = getAria2cVersion();
227 void MediaAria2c::attachTo (bool next)
229 MediaCurl::attachTo(next);
230 _settings.setUserAgentString(agentString());
234 MediaAria2c::checkAttachPoint(const Pathname &apoint) const
236 return MediaCurl::checkAttachPoint( apoint );
239 void MediaAria2c::disconnectFrom()
241 MediaCurl::disconnectFrom();
244 void MediaAria2c::releaseFrom( const std::string & ejectDev )
246 MediaCurl::releaseFrom(ejectDev);
249 static Url getFileUrl(const Url & url, const Pathname & filename)
252 string path = url.getPathName();
253 if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
254 filename.absolute() )
256 // If url has a path with trailing slash, remove the leading slash from
257 // the absolute file name
258 path += filename.asString().substr( 1, filename.asString().size() - 1 );
260 else if ( filename.relative() )
262 // Add trailing slash to path, if not already there
263 if (path.empty()) path = "/";
264 else if (*path.rbegin() != '/' ) path += "/";
265 // Remove "./" from begin of relative file name
266 path += filename.asString().substr( 2, filename.asString().size() - 2 );
270 path += filename.asString();
273 newurl.setPathName(path);
277 void MediaAria2c::getFile( const Pathname & filename ) const
279 // Use absolute file name to prevent access of files outside of the
280 // hierarchy below the attach point.
281 getFileCopy(filename, localPath(filename).absolutename());
284 void MediaAria2c::getFileCopy( const Pathname & filename , const Pathname & target) const
286 callback::SendReport<DownloadProgressReport> report;
288 Url fileurl(getFileUrl(_url, filename));
292 ExternalProgram::Arguments args;
294 fillAriaCmdLine(_aria2cPath, _aria2cVersion, _settings, fileurl, target.dirname(), args);
300 report->start(_url, target.asString() );
302 ExternalProgram aria(args, ExternalProgram::Stderr_To_Stdout);
304 // progress line like: [#1 SIZE:8.3MiB/10.1MiB(82%) CN:5 SPD:6899.88KiB/s]
305 // we save it until we find a string with FILE: later
307 // file line, which tell which file is the previous progress
308 // ie: FILE: ./packages.FL.gz
309 double average_speed = 0;
310 long average_speed_count = 0;
312 // here we capture aria output exceptions
313 vector<string> ariaExceptions;
316 for(std::string ariaResponse( aria.receiveLine());
317 ariaResponse.length();
318 ariaResponse = aria.receiveLine())
320 //cout << ariaResponse;
321 string line = str::trim(ariaResponse);
323 // look for the progress line and save it for later
324 if ( str::hasPrefix(line, "[#") )
328 // save error messages for later
329 else if ( str::hasPrefix(line, "Exception: ") )
331 // for auth exception, we throw
332 if (!line.substr(0,31).compare("Exception: Authorization failed") )
334 ZYPP_THROW(MediaUnauthorizedException(
335 _url, "Login failed.", "Login failed", "auth hint"
338 // otherwise, remember the error
339 string excpMsg = line.substr(10, line.size());
340 DBG << "aria2c reported: '" << excpMsg << "'" << endl;
341 ariaExceptions.push_back(excpMsg);
343 else if ( str::hasPrefix(line, "FILE: ") )
346 Pathname theFile(line.substr(6, line.size()));
347 // is the report about the filename we are downloading?
348 // aria may report progress about metalinks, torrent and
349 // other stuff which is not the main transfer
350 if ( theFile == target )
352 // once we find the FILE: line, progress has to be
354 if ( ! progressLine.empty() )
356 // get the percentage (progress) data
358 size_t left_bound = progressLine.find('(',0) + 1;
359 size_t count = progressLine.find('%',left_bound) - left_bound;
360 string progressStr = progressLine.substr(left_bound, count);
362 if ( count != string::npos )
363 progress = std::atoi(progressStr.c_str());
365 ERR << "Can't parse progress from '" << progressStr << "'" << endl;
367 double current_speed = 0;
368 left_bound = progressLine.find("SPD:",0) + 4;
369 count = progressLine.find("KiB/s",left_bound) - left_bound;
370 if ( count != string::npos )
371 { // convert the string to a double
372 string speedStr = progressLine.substr(left_bound, count);
374 current_speed = boost::lexical_cast<double>(speedStr);
376 catch (const std::exception&) {
377 ERR << "Can't parse speed from '" << speedStr << "'" << endl;
382 // we have a new average speed
383 average_speed_count++;
385 // this is basically A: average
386 // ((n-1)A(n-1) + Xn)/n = A(n)
387 average_speed = (((average_speed_count - 1 )*average_speed) + current_speed)/average_speed_count;
389 report->progress ( progress, _url, average_speed, current_speed );
390 // clear the progress line to detect mismatches between
391 // [# and FILE: lines
392 progressLine.clear();
396 WAR << "aria2c reported a file, but no progress data available" << endl;
402 DBG << "Progress is not about '" << target << "' but '" << theFile << "'" << endl;
407 // other line type, just ignore it.
411 int code = aria.close();
420 MediaTimeoutException e(_url);
421 for_(it, ariaExceptions.begin(), ariaExceptions.end())
426 case 4: // max notfound reached
428 MediaFileNotFoundException e(_url, filename);
429 for_(it, ariaExceptions.begin(), ariaExceptions.end())
434 case 6: // network problem
435 case 7: // unfinished downloads (ctr-c)
439 MediaException e(str::form("Failed to download %s from %s", filename.c_str(), _url.asString().c_str()));
440 for_(it, ariaExceptions.begin(), ariaExceptions.end())
447 report->finish( _url , zypp::media::DownloadProgressReport::NO_ERROR, "");
450 // retry with proper authentication data
451 catch (MediaUnauthorizedException & ex_r)
453 if(authenticate(ex_r.hint(), !retry))
457 report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
462 // unexpected exception
463 catch (MediaException & excpt_r)
465 // FIXME: error number fix
466 report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
467 ZYPP_RETHROW(excpt_r);
472 report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
475 bool MediaAria2c::getDoesFileExist( const Pathname & filename ) const
477 return MediaCurl::getDoesFileExist(filename);
480 bool MediaAria2c::doGetDoesFileExist( const Pathname & filename ) const
482 return MediaCurl::doGetDoesFileExist(filename);
485 void MediaAria2c::getDir( const Pathname & dirname, bool recurse_r ) const
487 MediaCurl::getDir(dirname, recurse_r);
490 bool MediaAria2c::authenticate(const std::string & availAuthTypes, bool firstTry) const
496 void MediaAria2c::getDirInfo( std::list<std::string> & retlist,
497 const Pathname & dirname, bool dots ) const
499 getDirectoryYast( retlist, dirname, dots );
502 void MediaAria2c::getDirInfo( filesystem::DirContent & retlist,
503 const Pathname & dirname, bool dots ) const
505 getDirectoryYast( retlist, dirname, dots );
508 std::string MediaAria2c::getAria2cVersion()
517 ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
519 std::string vResponse = aria.receiveLine();
521 return str::trim(vResponse);
524 #define ARIA_DEFAULT_BINARY "/usr/bin/aria2c"
526 Pathname MediaAria2c::whereisAria2c()
528 Pathname aria2cPathr(ARIA_DEFAULT_BINARY);
537 ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
539 std::string ariaResponse( aria.receiveLine());
540 int code = aria.close();
544 aria2cPathr = str::trim(ariaResponse);
545 MIL << "We will use aria2c located here: " << aria2cPathr << endl;
549 MIL << "We don't know were is ari2ac binary. We will use aria2c located here: " << aria2cPathr << endl;