1 /* <!-- copyright */
2 /*
3 * aria2 - The high speed download utility
4 *
5 * Copyright (C) 2006 Tatsuhiro Tsujikawa
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 *
21 * In addition, as a special exception, the copyright holders give
22 * permission to link the code of portions of this program with the
23 * OpenSSL library under certain conditions as described in each
24 * individual source file, and distribute linked combinations
25 * including the two.
26 * You must obey the GNU General Public License in all respects
27 * for all of the code used other than OpenSSL. If you modify
28 * file(s) with this exception, you may extend this exception to your
29 * version of the file(s), but you are not obligated to do so. If you
30 * do not wish to do so, delete this exception statement from your
31 * version. If you delete this exception statement from all source
32 * files in the program, then also delete it here.
33 */
34 /* copyright --> */
35 #include "download_helper.h"
36
37 #include <algorithm>
38 #include <sstream>
39
40 #include "RequestGroup.h"
41 #include "Option.h"
42 #include "prefs.h"
43 #include "Metalink2RequestGroup.h"
44 #include "ProtocolDetector.h"
45 #include "paramed_string.h"
46 #include "UriListParser.h"
47 #include "DownloadContext.h"
48 #include "RecoverableException.h"
49 #include "DlAbortEx.h"
50 #include "message.h"
51 #include "fmt.h"
52 #include "FileEntry.h"
53 #include "LogFactory.h"
54 #include "File.h"
55 #include "util.h"
56 #include "array_fun.h"
57 #include "OptionHandler.h"
58 #include "ByteArrayDiskWriter.h"
59 #include "a2functional.h"
60 #include "ByteArrayDiskWriterFactory.h"
61 #include "MetadataInfo.h"
62 #include "OptionParser.h"
63 #include "SegList.h"
64 #include "download_handlers.h"
65 #include "SimpleRandomizer.h"
66 #ifdef ENABLE_BITTORRENT
67 # include "bittorrent_helper.h"
68 # include "BtConstants.h"
69 # include "ValueBaseBencodeParser.h"
70 #endif // ENABLE_BITTORRENT
71
72 namespace aria2 {
73
74 namespace {
unfoldURI(std::vector<std::string> & result,const std::vector<std::string> & args)75 void unfoldURI(std::vector<std::string>& result,
76 const std::vector<std::string>& args)
77 {
78 for (const auto& i : args) {
79 paramed_string::expand(std::begin(i), std::end(i),
80 std::back_inserter(result));
81 }
82 }
83 } // namespace
84
85 namespace {
86 template <typename InputIterator>
splitURI(std::vector<std::string> & result,InputIterator begin,InputIterator end,size_t numSplit,size_t maxIter)87 void splitURI(std::vector<std::string>& result, InputIterator begin,
88 InputIterator end, size_t numSplit, size_t maxIter)
89 {
90 size_t numURIs = std::distance(begin, end);
91 if (numURIs >= numSplit) {
92 result.insert(std::end(result), begin, end);
93 }
94 else if (numURIs > 0) {
95 size_t num = std::min(numSplit / numURIs, maxIter);
96 for (size_t i = 0; i < num; ++i) {
97 result.insert(std::end(result), begin, end);
98 }
99 if (num < maxIter) {
100 result.insert(std::end(result), begin, begin + (numSplit % numURIs));
101 }
102 }
103 }
104 } // namespace
105
106 namespace {
getGID(const std::shared_ptr<Option> & option)107 std::shared_ptr<GroupId> getGID(const std::shared_ptr<Option>& option)
108 {
109 std::shared_ptr<GroupId> gid;
110 if (option->defined(PREF_GID)) {
111 a2_gid_t n;
112 if (GroupId::toNumericId(n, option->get(PREF_GID).c_str()) != 0) {
113 throw DL_ABORT_EX(
114 fmt("%s is invalid for GID.", option->get(PREF_GID).c_str()));
115 }
116 gid = GroupId::import(n);
117 if (!gid) {
118 throw DL_ABORT_EX(
119 fmt("GID %s is not unique.", option->get(PREF_GID).c_str()));
120 }
121 }
122 else {
123 gid = GroupId::create();
124 }
125 return gid;
126 }
127 } // namespace
128
129 namespace {
130 std::shared_ptr<RequestGroup>
createRequestGroup(const std::shared_ptr<Option> & optionTemplate,const std::vector<std::string> & uris,bool useOutOption=false)131 createRequestGroup(const std::shared_ptr<Option>& optionTemplate,
132 const std::vector<std::string>& uris,
133 bool useOutOption = false)
134 {
135 auto option = util::copy(optionTemplate);
136 auto rg = std::make_shared<RequestGroup>(getGID(option), option);
137 auto dctx = std::make_shared<DownloadContext>(
138 option->getAsInt(PREF_PIECE_LENGTH), 0,
139 useOutOption && !option->blank(PREF_OUT)
140 ? util::applyDir(option->get(PREF_DIR), option->get(PREF_OUT))
141 : A2STR::NIL);
142 dctx->getFirstFileEntry()->setUris(uris);
143 dctx->getFirstFileEntry()->setMaxConnectionPerServer(
144 option->getAsInt(PREF_MAX_CONNECTION_PER_SERVER));
145 const std::string& checksum = option->get(PREF_CHECKSUM);
146 if (!checksum.empty()) {
147 auto p = util::divide(std::begin(checksum), std::end(checksum), '=');
148 std::string hashType(p.first.first, p.first.second);
149 std::string hexDigest(p.second.first, p.second.second);
150 util::lowercase(hashType);
151 dctx->setDigest(hashType,
152 util::fromHex(std::begin(hexDigest), std::end(hexDigest)));
153 }
154 rg->setDownloadContext(dctx);
155
156 if (option->getAsBool(PREF_ENABLE_RPC)) {
157 rg->setPauseRequested(option->getAsBool(PREF_PAUSE));
158 }
159
160 removeOneshotOption(option);
161 return rg;
162 }
163 } // namespace
164
165 #if defined(ENABLE_BITTORRENT) || defined(ENABLE_METALINK)
166 namespace {
167 std::shared_ptr<MetadataInfo>
createMetadataInfo(const std::shared_ptr<GroupId> & gid,const std::string & uri)168 createMetadataInfo(const std::shared_ptr<GroupId>& gid, const std::string& uri)
169 {
170 return std::make_shared<MetadataInfo>(gid, uri);
171 }
172 } // namespace
173
174 namespace {
createMetadataInfoDataOnly()175 std::shared_ptr<MetadataInfo> createMetadataInfoDataOnly()
176 {
177 return std::make_shared<MetadataInfo>();
178 }
179 } // namespace
180 #endif // ENABLE_BITTORRENT || ENABLE_METALINK
181
182 #ifdef ENABLE_BITTORRENT
183
184 namespace {
185 std::shared_ptr<RequestGroup>
createBtRequestGroup(const std::string & metaInfoUri,const std::shared_ptr<Option> & optionTemplate,const std::vector<std::string> & auxUris,const ValueBase * torrent,bool adjustAnnounceUri=true)186 createBtRequestGroup(const std::string& metaInfoUri,
187 const std::shared_ptr<Option>& optionTemplate,
188 const std::vector<std::string>& auxUris,
189 const ValueBase* torrent, bool adjustAnnounceUri = true)
190 {
191 auto option = util::copy(optionTemplate);
192 auto gid = getGID(option);
193 auto rg = std::make_shared<RequestGroup>(gid, option);
194 auto dctx = std::make_shared<DownloadContext>();
195 // may throw exception
196 bittorrent::loadFromMemory(torrent, dctx, option, auxUris,
197 metaInfoUri.empty() ? "default" : metaInfoUri);
198 for (auto& fe : dctx->getFileEntries()) {
199 auto& uris = fe->getRemainingUris();
200 std::shuffle(std::begin(uris), std::end(uris),
201 *SimpleRandomizer::getInstance());
202 }
203 if (metaInfoUri.empty()) {
204 rg->setMetadataInfo(createMetadataInfoDataOnly());
205 }
206 else {
207 rg->setMetadataInfo(createMetadataInfo(gid, metaInfoUri));
208 }
209 if (adjustAnnounceUri) {
210 bittorrent::adjustAnnounceUri(bittorrent::getTorrentAttrs(dctx), option);
211 }
212 auto sgl = util::parseIntSegments(option->get(PREF_SELECT_FILE));
213 sgl.normalize();
214 dctx->setFileFilter(std::move(sgl));
215 std::istringstream indexOutIn(option->get(PREF_INDEX_OUT));
216 auto indexPaths = util::createIndexPaths(indexOutIn);
217 for (const auto& i : indexPaths) {
218 dctx->setFilePathWithIndex(i.first,
219 util::applyDir(option->get(PREF_DIR), i.second));
220 }
221 rg->setDownloadContext(dctx);
222
223 if (option->getAsBool(PREF_ENABLE_RPC)) {
224 rg->setPauseRequested(option->getAsBool(PREF_PAUSE));
225 }
226
227 // Remove "metalink" from Accept Type list to avoid server from
228 // responding Metalink file for web-seeding URIs.
229 dctx->setAcceptMetalink(false);
230 removeOneshotOption(option);
231 return rg;
232 }
233 } // namespace
234
235 namespace {
236 std::shared_ptr<RequestGroup>
createBtMagnetRequestGroup(const std::string & magnetLink,const std::shared_ptr<Option> & optionTemplate)237 createBtMagnetRequestGroup(const std::string& magnetLink,
238 const std::shared_ptr<Option>& optionTemplate)
239 {
240 auto dctx = std::make_shared<DownloadContext>(METADATA_PIECE_SIZE, 0);
241
242 // We only know info hash. Total Length is unknown at this moment.
243 dctx->markTotalLengthIsUnknown();
244
245 bittorrent::loadMagnet(magnetLink, dctx);
246 auto torrentAttrs = bittorrent::getTorrentAttrs(dctx);
247
248 if (optionTemplate->getAsBool(PREF_BT_LOAD_SAVED_METADATA)) {
249 // Try to read .torrent file saved by aria2 (see
250 // UTMetadataPostDownloadHandler and --bt-save-metadata option).
251 auto torrentFilename =
252 util::applyDir(optionTemplate->get(PREF_DIR),
253 util::toHex(torrentAttrs->infoHash) + ".torrent");
254
255 bittorrent::ValueBaseBencodeParser parser;
256 auto torrent = parseFile(parser, torrentFilename);
257 if (torrent) {
258 auto rg = createBtRequestGroup(torrentFilename, optionTemplate, {},
259 torrent.get());
260 const auto& actualInfoHash =
261 bittorrent::getTorrentAttrs(rg->getDownloadContext())->infoHash;
262
263 if (torrentAttrs->infoHash == actualInfoHash) {
264 A2_LOG_NOTICE(fmt("BitTorrent metadata was loaded from %s",
265 torrentFilename.c_str()));
266 rg->setMetadataInfo(createMetadataInfo(rg->getGroupId(), magnetLink));
267 return rg;
268 }
269
270 A2_LOG_WARN(
271 fmt("BitTorrent metadata loaded from %s has unexpected infohash %s\n",
272 torrentFilename.c_str(), util::toHex(actualInfoHash).c_str()));
273 }
274 }
275
276 auto option = util::copy(optionTemplate);
277 bittorrent::adjustAnnounceUri(torrentAttrs, option);
278 // torrentAttrs->name may contain "/", but we use basename of
279 // FileEntry::getPath() to print out in-memory download entry.
280 // Since "/" is treated as separator, we replace it with "-".
281 dctx->getFirstFileEntry()->setPath(
282 util::replace(torrentAttrs->name, "/", "-"));
283
284 auto gid = getGID(option);
285 auto rg = std::make_shared<RequestGroup>(gid, option);
286 rg->setFileAllocationEnabled(false);
287 rg->setPreLocalFileCheckEnabled(false);
288 rg->setDownloadContext(dctx);
289 rg->clearPostDownloadHandler();
290 rg->addPostDownloadHandler(
291 download_handlers::getUTMetadataPostDownloadHandler());
292 rg->setDiskWriterFactory(std::make_shared<ByteArrayDiskWriterFactory>());
293 rg->setMetadataInfo(createMetadataInfo(gid, magnetLink));
294 rg->markInMemoryDownload();
295
296 if (option->getAsBool(PREF_ENABLE_RPC)) {
297 rg->setPauseRequested(option->getAsBool(PREF_PAUSE));
298 }
299
300 removeOneshotOption(option);
301 return rg;
302 }
303 } // namespace
304
createRequestGroupForBitTorrent(std::vector<std::shared_ptr<RequestGroup>> & result,const std::shared_ptr<Option> & option,const std::vector<std::string> & uris,const std::string & metaInfoUri,const std::string & torrentData,bool adjustAnnounceUri)305 void createRequestGroupForBitTorrent(
306 std::vector<std::shared_ptr<RequestGroup>>& result,
307 const std::shared_ptr<Option>& option, const std::vector<std::string>& uris,
308 const std::string& metaInfoUri, const std::string& torrentData,
309 bool adjustAnnounceUri)
310 {
311 std::unique_ptr<ValueBase> torrent;
312 bittorrent::ValueBaseBencodeParser parser;
313 if (torrentData.empty()) {
314 torrent = parseFile(parser, metaInfoUri);
315 }
316 else {
317 ssize_t error;
318 torrent = parser.parseFinal(torrentData.c_str(), torrentData.size(), error);
319 }
320 if (!torrent) {
321 throw DL_ABORT_EX2("Bencode decoding failed",
322 error_code::BENCODE_PARSE_ERROR);
323 }
324 createRequestGroupForBitTorrent(result, option, uris, metaInfoUri,
325 torrent.get(), adjustAnnounceUri);
326 }
327
createRequestGroupForBitTorrent(std::vector<std::shared_ptr<RequestGroup>> & result,const std::shared_ptr<Option> & option,const std::vector<std::string> & uris,const std::string & metaInfoUri,const ValueBase * torrent,bool adjustAnnounceUri)328 void createRequestGroupForBitTorrent(
329 std::vector<std::shared_ptr<RequestGroup>>& result,
330 const std::shared_ptr<Option>& option, const std::vector<std::string>& uris,
331 const std::string& metaInfoUri, const ValueBase* torrent,
332 bool adjustAnnounceUri)
333 {
334 std::vector<std::string> nargs;
335 if (option->get(PREF_PARAMETERIZED_URI) == A2_V_TRUE) {
336 unfoldURI(nargs, uris);
337 }
338 else {
339 nargs = uris;
340 }
341 // we ignore -Z option here
342 size_t numSplit = option->getAsInt(PREF_SPLIT);
343 auto rg = createBtRequestGroup(metaInfoUri, option, nargs, torrent,
344 adjustAnnounceUri);
345 rg->setNumConcurrentCommand(numSplit);
346 result.push_back(rg);
347 }
348
349 #endif // ENABLE_BITTORRENT
350
351 #ifdef ENABLE_METALINK
createRequestGroupForMetalink(std::vector<std::shared_ptr<RequestGroup>> & result,const std::shared_ptr<Option> & option,const std::string & metalinkData)352 void createRequestGroupForMetalink(
353 std::vector<std::shared_ptr<RequestGroup>>& result,
354 const std::shared_ptr<Option>& option, const std::string& metalinkData)
355 {
356 if (metalinkData.empty()) {
357 Metalink2RequestGroup().generate(result, option->get(PREF_METALINK_FILE),
358 option,
359 option->get(PREF_METALINK_BASE_URI));
360 }
361 else {
362 auto dw = std::make_shared<ByteArrayDiskWriter>();
363 dw->setString(metalinkData);
364 Metalink2RequestGroup().generate(result, dw, option,
365 option->get(PREF_METALINK_BASE_URI));
366 }
367 }
368 #endif // ENABLE_METALINK
369
370 namespace {
371 class AccRequestGroup {
372 private:
373 std::vector<std::shared_ptr<RequestGroup>>& requestGroups_;
374 ProtocolDetector detector_;
375 std::shared_ptr<Option> option_;
376 bool ignoreLocalPath_;
377 bool throwOnError_;
378
379 public:
AccRequestGroup(std::vector<std::shared_ptr<RequestGroup>> & requestGroups,std::shared_ptr<Option> option,bool ignoreLocalPath=false,bool throwOnError=false)380 AccRequestGroup(std::vector<std::shared_ptr<RequestGroup>>& requestGroups,
381 std::shared_ptr<Option> option, bool ignoreLocalPath = false,
382 bool throwOnError = false)
383 : requestGroups_(requestGroups),
384 option_(std::move(option)),
385 ignoreLocalPath_(ignoreLocalPath),
386 throwOnError_(throwOnError)
387 {
388 }
389
operator ()(const std::string & uri)390 void operator()(const std::string& uri)
391 {
392 if (detector_.isStreamProtocol(uri)) {
393 std::vector<std::string> streamURIs;
394 size_t numIter = option_->getAsInt(PREF_MAX_CONNECTION_PER_SERVER);
395 size_t numSplit = option_->getAsInt(PREF_SPLIT);
396 size_t num = std::min(numIter, numSplit);
397 for (size_t i = 0; i < num; ++i) {
398 streamURIs.push_back(uri);
399 }
400 auto rg = createRequestGroup(option_, streamURIs);
401 rg->setNumConcurrentCommand(numSplit);
402 requestGroups_.push_back(rg);
403 }
404 #ifdef ENABLE_BITTORRENT
405 else if (detector_.guessTorrentMagnet(uri)) {
406 requestGroups_.push_back(createBtMagnetRequestGroup(uri, option_));
407 }
408 else if (!ignoreLocalPath_ && detector_.guessTorrentFile(uri)) {
409 try {
410 bittorrent::ValueBaseBencodeParser parser;
411 auto torrent = parseFile(parser, uri);
412 if (!torrent) {
413 throw DL_ABORT_EX2("Bencode decoding failed",
414 error_code::BENCODE_PARSE_ERROR);
415 }
416 requestGroups_.push_back(
417 createBtRequestGroup(uri, option_, {}, torrent.get()));
418 }
419 catch (RecoverableException& e) {
420 if (throwOnError_) {
421 throw;
422 }
423 else {
424 // error occurred while parsing torrent file.
425 // We simply ignore it.
426 A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, e);
427 }
428 }
429 }
430 #endif // ENABLE_BITTORRENT
431 #ifdef ENABLE_METALINK
432 else if (!ignoreLocalPath_ && detector_.guessMetalinkFile(uri)) {
433 try {
434 Metalink2RequestGroup().generate(requestGroups_, uri, option_,
435 option_->get(PREF_METALINK_BASE_URI));
436 }
437 catch (RecoverableException& e) {
438 if (throwOnError_) {
439 throw;
440 }
441 else {
442 // error occurred while parsing metalink file.
443 // We simply ignore it.
444 A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, e);
445 }
446 }
447 }
448 #endif // ENABLE_METALINK
449 else {
450 if (throwOnError_) {
451 throw DL_ABORT_EX(fmt(MSG_UNRECOGNIZED_URI, uri.c_str()));
452 }
453 else {
454 A2_LOG_ERROR(fmt(MSG_UNRECOGNIZED_URI, uri.c_str()));
455 }
456 }
457 }
458 };
459 } // namespace
460
461 namespace {
462 class StreamProtocolFilter {
463 private:
464 ProtocolDetector detector_;
465
466 public:
operator ()(const std::string & uri)467 bool operator()(const std::string& uri)
468 {
469 return detector_.isStreamProtocol(uri);
470 }
471 };
472 } // namespace
473
createRequestGroupForUri(std::vector<std::shared_ptr<RequestGroup>> & result,const std::shared_ptr<Option> & option,const std::vector<std::string> & uris,bool ignoreForceSequential,bool ignoreLocalPath,bool throwOnError)474 void createRequestGroupForUri(
475 std::vector<std::shared_ptr<RequestGroup>>& result,
476 const std::shared_ptr<Option>& option, const std::vector<std::string>& uris,
477 bool ignoreForceSequential, bool ignoreLocalPath, bool throwOnError)
478 {
479 std::vector<std::string> nargs;
480 if (option->get(PREF_PARAMETERIZED_URI) == A2_V_TRUE) {
481 unfoldURI(nargs, uris);
482 }
483 else {
484 nargs = uris;
485 }
486 if (!ignoreForceSequential &&
487 option->get(PREF_FORCE_SEQUENTIAL) == A2_V_TRUE) {
488 std::for_each(
489 std::begin(nargs), std::end(nargs),
490 AccRequestGroup(result, option, ignoreLocalPath, throwOnError));
491 }
492 else {
493 auto strmProtoEnd = std::stable_partition(
494 std::begin(nargs), std::end(nargs), StreamProtocolFilter());
495 // let's process http/ftp protocols first.
496 if (std::begin(nargs) != strmProtoEnd) {
497 size_t numIter = option->getAsInt(PREF_MAX_CONNECTION_PER_SERVER);
498 size_t numSplit = option->getAsInt(PREF_SPLIT);
499 std::vector<std::string> streamURIs;
500 splitURI(streamURIs, std::begin(nargs), strmProtoEnd, numSplit, numIter);
501 try {
502 auto rg = createRequestGroup(option, streamURIs, true);
503 rg->setNumConcurrentCommand(numSplit);
504 result.push_back(rg);
505 }
506 catch (RecoverableException& e) {
507 if (throwOnError) {
508 throw;
509 }
510 else {
511 A2_LOG_ERROR_EX(EX_EXCEPTION_CAUGHT, e);
512 }
513 }
514 }
515 // process remaining URIs(local metalink, BitTorrent files)
516 std::for_each(
517 strmProtoEnd, std::end(nargs),
518 AccRequestGroup(result, option, ignoreLocalPath, throwOnError));
519 }
520 }
521
createRequestGroupFromUriListParser(std::vector<std::shared_ptr<RequestGroup>> & result,const Option * option,UriListParser * uriListParser)522 bool createRequestGroupFromUriListParser(
523 std::vector<std::shared_ptr<RequestGroup>>& result, const Option* option,
524 UriListParser* uriListParser)
525 {
526 // Since result already contains some entries, we cache the size of
527 // it. Later, we use this value to determine RequestGroup is
528 // actually created.
529 size_t num = result.size();
530 while (uriListParser->hasNext()) {
531 std::vector<std::string> uris;
532 Option tempOption;
533 uriListParser->parseNext(uris, tempOption);
534 if (uris.empty()) {
535 continue;
536 }
537 auto requestOption = std::make_shared<Option>(*option);
538 requestOption->remove(PREF_OUT);
539 const auto& oparser = OptionParser::getInstance();
540 for (size_t i = 1, len = option::countOption(); i < len; ++i) {
541 auto pref = option::i2p(i);
542 auto h = oparser->find(pref);
543 if (h && h->getInitialOption() && tempOption.defined(pref)) {
544 requestOption->put(pref, tempOption.get(pref));
545 }
546 }
547 // This does not throw exception because throwOnError = false.
548 createRequestGroupForUri(result, requestOption, uris);
549 if (num < result.size()) {
550 return true;
551 }
552 }
553 return false;
554 }
555
openUriListParser(const std::string & filename)556 std::shared_ptr<UriListParser> openUriListParser(const std::string& filename)
557 {
558 std::string listPath;
559
560 auto f = File(filename);
561 if (!f.exists() || f.isDir()) {
562 throw DL_ABORT_EX(fmt(EX_FILE_OPEN, filename.c_str(),
563 "File not found or it is a directory"));
564 }
565 listPath = filename;
566
567 return std::make_shared<UriListParser>(listPath);
568 }
569
createRequestGroupForUriList(std::vector<std::shared_ptr<RequestGroup>> & result,const std::shared_ptr<Option> & option)570 void createRequestGroupForUriList(
571 std::vector<std::shared_ptr<RequestGroup>>& result,
572 const std::shared_ptr<Option>& option)
573 {
574 auto uriListParser = openUriListParser(option->get(PREF_INPUT_FILE));
575 while (createRequestGroupFromUriListParser(result, option.get(),
576 uriListParser.get()))
577 ;
578 }
579
createMetadataInfoFromFirstFileEntry(const std::shared_ptr<GroupId> & gid,const std::shared_ptr<DownloadContext> & dctx)580 std::shared_ptr<MetadataInfo> createMetadataInfoFromFirstFileEntry(
581 const std::shared_ptr<GroupId>& gid,
582 const std::shared_ptr<DownloadContext>& dctx)
583 {
584 if (dctx->getFileEntries().empty()) {
585 return nullptr;
586 }
587 else {
588 auto uris = dctx->getFileEntries()[0]->getUris();
589 if (uris.empty()) {
590 return nullptr;
591 }
592 return std::make_shared<MetadataInfo>(gid, uris[0]);
593 }
594 }
595
removeOneshotOption(const std::shared_ptr<Option> & option)596 void removeOneshotOption(const std::shared_ptr<Option>& option)
597 {
598 option->remove(PREF_PAUSE);
599 option->remove(PREF_GID);
600 }
601
602 } // namespace aria2
603