diff --git a/libtransmission/inout.cc b/libtransmission/inout.cc index 5affdad8f..dbff7a081 100644 --- a/libtransmission/inout.cc +++ b/libtransmission/inout.cc @@ -290,5 +290,5 @@ static std::optional recalculateHash(tr_torrent* tor, tr_piece bool tr_ioTestPiece(tr_torrent* tor, tr_piece_index_t piece) { auto const hash = recalculateHash(tor, piece); - return hash && *hash == tor->info.pieces[piece]; + return hash && *hash == tor->pieceHash(piece); } diff --git a/libtransmission/metainfo.cc b/libtransmission/metainfo.cc index ffe35dc81..220ca1a88 100644 --- a/libtransmission/metainfo.cc +++ b/libtransmission/metainfo.cc @@ -8,13 +8,16 @@ #include #include -#include /* strlen() */ +#include #include #include +#include #include "transmission.h" #include "crypto-utils.h" /* tr_sha1 */ +#include "error.h" +#include "error-types.h" #include "file.h" #include "log.h" #include "metainfo.h" @@ -467,8 +470,8 @@ static void geturllist(tr_info* inf, tr_variant* meta) static char const* tr_metainfoParseImpl( tr_session const* session, tr_info* inf, - bool* hasInfoDict, - size_t* infoDictLength, + std::vector* pieces, + uint64_t* infoDictLength, tr_variant const* meta_in) { int64_t i = 0; @@ -482,11 +485,6 @@ static char const* tr_metainfoParseImpl( tr_variant* infoDict = nullptr; bool b = tr_variantDictFindDict(meta, TR_KEY_info, &infoDict); - if (hasInfoDict != nullptr) - { - *hasInfoDict = b; - } - if (!b) { /* no info dictionary... is this a magnet link? */ @@ -633,9 +631,10 @@ static char const* tr_metainfoParseImpl( return "pieces"; } - inf->pieceCount = std::size(sv) / SHA_DIGEST_LENGTH; - inf->pieces = tr_new0(tr_sha1_digest_t, inf->pieceCount); - std::copy_n(std::data(sv), std::size(sv), (uint8_t*)(inf->pieces)); + auto const n_pieces = std::size(sv) / SHA_DIGEST_LENGTH; + inf->pieceCount = n_pieces; + pieces->resize(n_pieces); + std::copy_n(std::data(sv), std::size(sv), reinterpret_cast(std::data(*pieces))); auto const* const errstr = parseFiles( inf, @@ -674,23 +673,19 @@ static char const* tr_metainfoParseImpl( return nullptr; } -bool tr_metainfoParse( - tr_session const* session, - tr_variant const* meta_in, - tr_info* inf, - bool* hasInfoDict, - size_t* infoDictLength) +std::optional tr_metainfoParse(tr_session const* session, tr_variant const* meta_in, tr_error** error) { - char const* badTag = tr_metainfoParseImpl(session, inf, hasInfoDict, infoDictLength, meta_in); - bool const success = badTag == nullptr; + auto out = tr_metainfo_parsed{}; - if (badTag != nullptr) + char const* bad_tag = tr_metainfoParseImpl(session, &out.info, &out.pieces, &out.info_dict_length, meta_in); + if (bad_tag != nullptr) { - tr_logAddNamedError(inf->name, _("Invalid metadata entry \"%s\""), badTag); - tr_metainfoFree(inf); + tr_error_set(error, TR_ERROR_EINVAL, _("Error parsing metainfo: %s"), bad_tag); + tr_metainfoFree(&out.info); + return {}; } - return success; + return std::optional{ std::move(out) }; } void tr_metainfoFree(tr_info* inf) @@ -706,7 +701,6 @@ void tr_metainfoFree(tr_info* inf) } tr_free(inf->webseeds); - tr_free(inf->pieces); tr_free(inf->files); tr_free(inf->comment); tr_free(inf->creator); diff --git a/libtransmission/metainfo.h b/libtransmission/metainfo.h index 73f7726be..35a8329bb 100644 --- a/libtransmission/metainfo.h +++ b/libtransmission/metainfo.h @@ -12,11 +12,15 @@ #error only libtransmission should #include this header. #endif +#include +#include #include #include +#include #include "transmission.h" +struct tr_error; struct tr_variant; enum tr_metainfo_basename_format @@ -25,12 +29,32 @@ enum tr_metainfo_basename_format TR_METAINFO_BASENAME_HASH }; -bool tr_metainfoParse( - tr_session const* session, - tr_variant const* variant, - tr_info* setmeInfo, - bool* setmeHasInfoDict, - size_t* setmeInfoDictLength); +struct tr_metainfo_parsed +{ + tr_info info = {}; + uint64_t info_dict_length = 0; + std::vector pieces; + + tr_metainfo_parsed() = default; + + tr_metainfo_parsed(tr_metainfo_parsed&& that) + { + std::swap(this->info, that.info); + std::swap(this->pieces, that.pieces); + std::swap(this->info_dict_length, that.info_dict_length); + } + + tr_metainfo_parsed(tr_metainfo_parsed const&) = delete; + + tr_metainfo_parsed& operator=(tr_metainfo_parsed const&) = delete; + + ~tr_metainfo_parsed() + { + tr_metainfoFree(&info); + } +}; + +std::optional tr_metainfoParse(tr_session const* session, tr_variant const* variant, tr_error** error); void tr_metainfoRemoveSaved(tr_session const* session, tr_info const* info); diff --git a/libtransmission/torrent-magnet.cc b/libtransmission/torrent-magnet.cc index b30015649..349e771a3 100644 --- a/libtransmission/torrent-magnet.cc +++ b/libtransmission/torrent-magnet.cc @@ -296,23 +296,18 @@ void tr_torrentSetMetadataPiece(tr_torrent* tor, int piece, void const* data, in dbgmsg(tor, "Saving completed metadata to \"%s\"", path); tr_variantMergeDicts(tr_variantDictAddDict(&newMetainfo, TR_KEY_info, 0), &infoDict); - auto hasInfo = bool{}; - auto info = tr_info{}; - auto infoDictLength = size_t{}; - success = tr_metainfoParse(tor->session, &newMetainfo, &info, &hasInfo, &infoDictLength); - - if (success && tr_getBlockSize(info.pieceSize) == 0) + auto info = tr_metainfoParse(tor->session, &newMetainfo, nullptr); + if (info && tr_getBlockSize(info->info.pieceSize) == 0) { tr_torrentSetLocalError(tor, "%s", _("Magnet torrent's metadata is not usable")); - tr_metainfoFree(&info); success = false; } if (success) { /* keep the new info */ - tor->info = info; - tor->infoDictLength = infoDictLength; + std::swap(tor->info, info->info); + std::swap(tor->infoDictLength, info->info_dict_length); /* save the new .torrent file */ tr_variantToFile(&newMetainfo, TR_VARIANT_FMT_BENC, tor->info.torrent); diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index e9a70e9e9..3a28f8ad0 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -978,114 +978,66 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor) tr_sessionUnlock(session); } -static tr_parse_result torrentParseImpl( - tr_ctor const* ctor, - tr_info* setmeInfo, - bool* setmeHasInfo, - size_t* dictLength, - int* setme_duplicate_id) +tr_parse_result tr_torrentParse(tr_ctor const* ctor, tr_info* setmeInfo) { - tr_session* session = tr_ctorGetSession(ctor); - tr_parse_result result = TR_PARSE_OK; - - tr_info tmp; - if (setmeInfo == nullptr) - { - setmeInfo = &tmp; - } - - *setmeInfo = {}; - tr_variant const* metainfo = nullptr; if (!tr_ctorGetMetainfo(ctor, &metainfo)) { return TR_PARSE_ERR; } - auto hasInfo = bool{}; - bool const didParse = tr_metainfoParse(session, metainfo, setmeInfo, &hasInfo, dictLength); - bool const doFree = didParse && (setmeInfo == &tmp); - - if (!didParse) + auto parsed = tr_metainfoParse(tr_ctorGetSession(ctor), metainfo, nullptr); + if (!parsed) { - result = TR_PARSE_ERR; + return TR_PARSE_ERR; } - if (didParse && hasInfo && tr_getBlockSize(setmeInfo->pieceSize) == 0) + if (setmeInfo != nullptr) { - result = TR_PARSE_ERR; + std::swap(*setmeInfo, parsed->info); } - if (didParse && session != nullptr && result == TR_PARSE_OK) - { - tr_torrent const* const tor = tr_torrentFindFromHash(session, setmeInfo->hash); - - if (tor != nullptr) - { - result = TR_PARSE_DUPLICATE; - - if (setme_duplicate_id != nullptr) - { - *setme_duplicate_id = tr_torrentId(tor); - } - } - } - - if (doFree) - { - tr_metainfoFree(setmeInfo); - } - - if (setmeHasInfo != nullptr) - { - *setmeHasInfo = hasInfo; - } - - return result; -} - -tr_parse_result tr_torrentParse(tr_ctor const* ctor, tr_info* setmeInfo) -{ - return torrentParseImpl(ctor, setmeInfo, nullptr, nullptr, nullptr); + return TR_PARSE_OK; } tr_torrent* tr_torrentNew(tr_ctor const* ctor, int* setme_error, int* setme_duplicate_id) { - tr_torrent* tor = nullptr; - TR_ASSERT(ctor != nullptr); - TR_ASSERT(tr_isSession(tr_ctorGetSession(ctor))); + auto* const session = tr_ctorGetSession(ctor); + TR_ASSERT(tr_isSession(session)); - auto tmpInfo = tr_info{}; - auto hasInfo = bool{}; - auto len = size_t{}; - tr_parse_result const r = torrentParseImpl(ctor, &tmpInfo, &hasInfo, &len, setme_duplicate_id); - - if (r == TR_PARSE_OK) + tr_variant const* metainfo = nullptr; + tr_ctorGetMetainfo(ctor, &metainfo); + auto parsed = tr_metainfoParse(session, metainfo, nullptr); + if (!parsed) { - tor = new tr_torrent{}; - tor->info = tmpInfo; - - if (hasInfo) + if (setme_error != nullptr) { - tor->infoDictLength = len; + *setme_error = TR_PARSE_ERR; } - torrentInit(tor, ctor); + return nullptr; } - else + + tr_torrent const* const dupe = tr_torrentFindFromHash(session, parsed->info.hash); + if (dupe != nullptr) { - if (r == TR_PARSE_DUPLICATE) + if (setme_duplicate_id != nullptr) { - tr_metainfoFree(&tmpInfo); + *setme_duplicate_id = tr_torrentId(dupe); } if (setme_error != nullptr) { - *setme_error = r; + *setme_error = TR_PARSE_DUPLICATE; } + + return nullptr; } + auto* tor = new tr_torrent{}; + tor->takeMetainfo(std::move(*parsed)); + torrentInit(tor, ctor); return tor; } @@ -2644,21 +2596,13 @@ bool tr_torrentSetAnnounceList(tr_torrent* tor, tr_tracker_info const* trackers_ } /* try to parse it back again, to make sure it's good */ - auto tmpInfo = tr_info{}; - auto hasInfo = bool{}; - if (tr_metainfoParse(tor->session, &metainfo, &tmpInfo, &hasInfo, &tor->infoDictLength)) + auto parsed = tr_metainfoParse(tor->session, &metainfo, nullptr); + if (parsed) { /* it's good, so keep these new trackers and free the old ones */ - tr_info swap; - swap.trackers = tor->info.trackers; - swap.trackerCount = tor->info.trackerCount; - tor->info.trackers = tmpInfo.trackers; - tor->info.trackerCount = tmpInfo.trackerCount; - tmpInfo.trackers = swap.trackers; - tmpInfo.trackerCount = swap.trackerCount; + std::swap(tor->info.trackers, parsed->info.trackers); + std::swap(tor->info.trackerCount, parsed->info.trackerCount); tr_torrentMarkEdited(tor); - - tr_metainfoFree(&tmpInfo); tr_variantToFile(&metainfo, TR_VARIANT_FMT_BENC, tor->info.torrent); } @@ -3771,3 +3715,10 @@ void tr_torrentRenamePath( tr_runInEventThread(tor->session, torrentRenamePath, data); } + +void tr_torrent::takeMetainfo(tr_metainfo_parsed&& parsed) +{ + std::swap(this->info, parsed.info); + std::swap(this->piece_checksums_, parsed.pieces); + std::swap(this->infoDictLength, parsed.info_dict_length); +} diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index 3e09bddac..a3ec6f97e 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -30,6 +30,7 @@ class tr_swarm; struct tr_magnet_info; +struct tr_metainfo_parsed; struct tr_session; struct tr_torrent; struct tr_torrent_tiers; @@ -141,7 +142,14 @@ struct tr_torrent int magicNumber; std::optional verify_progress; - std::vector piece_checksums; + + tr_sha1_digest_t pieceHash(tr_piece_index_t i) const + { + TR_ASSERT(i < std::size(this->piece_checksums_)); + return this->piece_checksums_[i]; + } + + void takeMetainfo(tr_metainfo_parsed&& parsed); tr_stat_errtype error; char errorString[128]; @@ -280,7 +288,7 @@ struct tr_torrent char* incompleteDir; /* Length, in bytes, of the "info" dict in the .torrent file. */ - size_t infoDictLength; + uint64_t infoDictLength; /* Offset, in bytes, of the beginning of the "info" dict in the .torrent file. * @@ -395,6 +403,9 @@ struct tr_torrent bool finishedSeedingByIdle; tr_labels_t labels; + +private: + mutable std::vector piece_checksums_; }; /* what piece index is this block in? */ diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h index e869babff..0963be535 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -1612,7 +1612,6 @@ struct tr_info char* source; tr_file* files; - tr_sha1_digest_t* pieces; /* these trackers are sorted by tier */ tr_tracker_info* trackers; diff --git a/libtransmission/verify.cc b/libtransmission/verify.cc index 654ada46d..3ecba12f8 100644 --- a/libtransmission/verify.cc +++ b/libtransmission/verify.cc @@ -96,7 +96,7 @@ static bool verifyTorrent(tr_torrent* tor, bool* stopFlag) if (leftInPiece == 0) { auto hash = tr_sha1_final(sha); - auto const hasPiece = hash && *hash == tor->info.pieces[pieceIndex]; + auto const hasPiece = hash && *hash == tor->pieceHash(pieceIndex); if (hasPiece || hadPiece) { diff --git a/qt/OptionsDialog.cc b/qt/OptionsDialog.cc index 2054c8f4c..fad48d0f6 100644 --- a/qt/OptionsDialog.cc +++ b/qt/OptionsDialog.cc @@ -16,6 +16,7 @@ #include #include "AddData.h" +#include "FileTreeModel.h" #include "FreeSpaceLabel.h" #include "OptionsDialog.h" #include "Prefs.h" @@ -34,7 +35,6 @@ using ::trqt::variant_helpers::listAdd; OptionsDialog::OptionsDialog(Session& session, Prefs const& prefs, AddData addme, QWidget* parent) : BaseDialog(parent) , add_(std::move(addme)) - , verify_button_(new QPushButton(tr("&Verify Local Data"), this)) , session_(session) , is_local_(session_.isLocal()) { @@ -100,15 +100,11 @@ OptionsDialog::OptionsDialog(Session& session, Prefs const& prefs, AddData addme connect(ui_.destinationEdit, &QLineEdit::editingFinished, this, &OptionsDialog::onDestinationChanged); ui_.filesView->setEditable(false); - ui_.priorityCombo->addItem(tr("High"), TR_PRI_HIGH); ui_.priorityCombo->addItem(tr("Normal"), TR_PRI_NORMAL); ui_.priorityCombo->addItem(tr("Low"), TR_PRI_LOW); ui_.priorityCombo->setCurrentIndex(1); // Normal - ui_.dialogButtons->addButton(verify_button_, QDialogButtonBox::ActionRole); - connect(verify_button_, &QAbstractButton::clicked, this, &OptionsDialog::onVerify); - ui_.startCheck->setChecked(prefs.getBool(Prefs::START)); ui_.trashCheck->setChecked(prefs.getBool(Prefs::TRASH_ORIGINAL)); @@ -118,8 +114,6 @@ OptionsDialog::OptionsDialog(Session& session, Prefs const& prefs, AddData addme connect(ui_.filesView, &FileTreeView::priorityChanged, this, &OptionsDialog::onPriorityChanged); connect(ui_.filesView, &FileTreeView::wantedChanged, this, &OptionsDialog::onWantedChanged); - connect(&verify_timer_, &QTimer::timeout, this, &OptionsDialog::onTimeout); - connect(&session_, &Session::sessionUpdated, this, &OptionsDialog::onSessionUpdated); updateWidgetsLocality(); @@ -149,7 +143,6 @@ void OptionsDialog::clearInfo() void OptionsDialog::reload() { clearInfo(); - clearVerify(); tr_ctor* ctor = tr_ctorNew(nullptr); @@ -183,7 +176,6 @@ void OptionsDialog::reload() bool const have_files_to_show = have_info_ && info_.fileCount > 0; ui_.filesView->setVisible(have_files_to_show); - verify_button_->setEnabled(have_files_to_show); layout()->setSizeConstraint(have_files_to_show ? QLayout::SetDefaultConstraint : QLayout::SetFixedSize); if (have_info_) @@ -205,6 +197,7 @@ void OptionsDialog::reload() } ui_.filesView->update(files_); + ui_.filesView->hideColumn(FileTreeModel::COL_PROGRESS); } void OptionsDialog::updateWidgetsLocality() @@ -212,11 +205,6 @@ void OptionsDialog::updateWidgetsLocality() ui_.destinationStack->setCurrentWidget(is_local_ ? static_cast(ui_.destinationButton) : ui_.destinationEdit); ui_.destinationStack->setFixedHeight(ui_.destinationStack->currentWidget()->sizeHint().height()); ui_.destinationLabel->setBuddy(ui_.destinationStack->currentWidget()); - - // hide the % done when non-local, since we've no way of knowing - (ui_.filesView->*(is_local_ ? &QTreeView::showColumn : &QTreeView::hideColumn))(2); - - verify_button_->setVisible(is_local_); } void OptionsDialog::onSessionUpdated() @@ -353,154 +341,3 @@ void OptionsDialog::onDestinationChanged() ui_.freeSpaceLabel->setPath(ui_.destinationEdit->text()); } } - -/*** -**** -**** VERIFY -**** -***/ - -void OptionsDialog::clearVerify() -{ - verify_hash_.reset(); - verify_file_.close(); - verify_file_pos_ = 0; - verify_flags_.clear(); - verify_file_index_ = 0; - verify_piece_index_ = 0; - verify_piece_pos_ = 0; - verify_timer_.stop(); - - for (TorrentFile& f : files_) - { - f.have = 0; - } - - ui_.filesView->update(files_); -} - -void OptionsDialog::onVerify() -{ - clearVerify(); - verify_flags_.assign(info_.pieceCount, false); - verify_timer_.setSingleShot(false); - verify_timer_.start(0); -} - -namespace -{ - -uint64_t getPieceSize(tr_info const* info, tr_piece_index_t piece_index) -{ - if (piece_index != info->pieceCount - 1) - { - return info->pieceSize; - } - - return info->totalSize % info->pieceSize; -} - -} // namespace - -void OptionsDialog::onTimeout() -{ - if (files_.empty()) - { - verify_timer_.stop(); - return; - } - - tr_file const* file = &info_.files[verify_file_index_]; - - if (verify_file_pos_ == 0 && !verify_file_.isOpen()) - { - QFileInfo const file_info(local_destination_, QString::fromUtf8(file->name)); - verify_file_.setFileName(file_info.absoluteFilePath()); - verify_file_.open(QIODevice::ReadOnly); - } - - int64_t left_in_piece = getPieceSize(&info_, verify_piece_index_) - verify_piece_pos_; - int64_t left_in_file = file->length - verify_file_pos_; - int64_t bytes_this_pass = qMin(left_in_file, left_in_piece); - bytes_this_pass = qMin(bytes_this_pass, static_cast(sizeof(verify_buf_))); - - if (verify_file_.isOpen() && verify_file_.seek(verify_file_pos_)) - { - int64_t num_read = verify_file_.read(verify_buf_, bytes_this_pass); - - if (num_read == bytes_this_pass) - { - verify_hash_.addData(verify_buf_, num_read); - } - } - - left_in_piece -= bytes_this_pass; - left_in_file -= bytes_this_pass; - verify_piece_pos_ += bytes_this_pass; - verify_file_pos_ += bytes_this_pass; - - verify_bins_[verify_file_index_] += bytes_this_pass; - - if (left_in_piece == 0) - { - QByteArray const result(verify_hash_.result()); - bool const matches = memcmp(result.constData(), std::data(info_.pieces[verify_piece_index_]), SHA_DIGEST_LENGTH) == 0; - verify_flags_[verify_piece_index_] = matches; - verify_piece_pos_ = 0; - ++verify_piece_index_; - verify_hash_.reset(); - - FileList changed_files; - - if (matches) - { - for (auto i = verify_bins_.begin(), end = verify_bins_.end(); i != end; ++i) - { - TorrentFile& f(files_[i.key()]); - f.have += i.value(); - changed_files.push_back(f); - } - } - - ui_.filesView->update(changed_files); - verify_bins_.clear(); - } - - if (left_in_file == 0) - { - verify_file_.close(); - ++verify_file_index_; - verify_file_pos_ = 0; - } - - bool done = verify_piece_index_ >= info_.pieceCount; - - if (done) - { - uint64_t have = 0; - - for (TorrentFile const& f : files_) - { - have += f.have; - } - - if (have == 0) // everything failed - { - // did the user accidentally specify the child directory instead of the parent? - QStringList const tokens = QString::fromUtf8(file->name).split(QLatin1Char('/')); - - if (!tokens.empty() && local_destination_.dirName() == tokens.at(0)) - { - // move up one directory and try again - local_destination_.cdUp(); - onVerify(); - done = false; - } - } - } - - if (done) - { - verify_timer_.stop(); - } -} diff --git a/qt/OptionsDialog.h b/qt/OptionsDialog.h index 20fd1be01..83b36b15e 100644 --- a/qt/OptionsDialog.h +++ b/qt/OptionsDialog.h @@ -10,7 +10,6 @@ #include -#include #include #include #include @@ -44,8 +43,6 @@ private slots: void onAccepted(); void onPriorityChanged(QSet const& file_indices, int); void onWantedChanged(QSet const& file_indices, bool); - void onVerify(); - void onTimeout(); void onSourceChanged(); void onDestinationChanged(); @@ -58,29 +55,17 @@ private: void reload(); void updateWidgetsLocality(); void clearInfo(); - void clearVerify(); AddData add_; FileList files_; - QCryptographicHash verify_hash_ = QCryptographicHash(QCryptographicHash::Sha1); QDir local_destination_; - QFile verify_file_; - QPushButton* verify_button_ = {}; QTimer edit_timer_; - QTimer verify_timer_; - std::vector verify_flags_; std::vector wanted_; std::vector priorities_; Session& session_; Ui::OptionsDialog ui_ = {}; - mybins_t verify_bins_; tr_info info_ = {}; - uint64_t verify_file_pos_ = {}; - uint32_t verify_piece_index_ = {}; - uint32_t verify_piece_pos_ = {}; - int verify_file_index_ = {}; - char verify_buf_[2048 * 4] = {}; bool have_info_ = {}; bool is_local_ = {}; };