refactor: make piece checksums private (#2130)

* refactor: remove tr_info.pieces
This commit is contained in:
Charles Kerr 2021-11-12 10:42:51 -06:00 committed by GitHub
parent 82b3da0a54
commit d1f8c28fcd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 107 additions and 311 deletions

View File

@ -290,5 +290,5 @@ static std::optional<tr_sha1_digest_t> 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);
}

View File

@ -8,13 +8,16 @@
#include <algorithm>
#include <array>
#include <cstring> /* strlen() */
#include <cstring>
#include <iterator>
#include <string_view>
#include <vector>
#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<tr_sha1_digest_t>* 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<uint8_t*>(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_metainfo_parsed> 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<tr_metainfo_parsed>{ 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);

View File

@ -12,11 +12,15 @@
#error only libtransmission should #include this header.
#endif
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#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<tr_sha1_digest_t> 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_metainfo_parsed> tr_metainfoParse(tr_session const* session, tr_variant const* variant, tr_error** error);
void tr_metainfoRemoveSaved(tr_session const* session, tr_info const* info);

View File

@ -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);

View File

@ -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);
}

View File

@ -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<double> verify_progress;
std::vector<tr_sha1_digest_t> 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<tr_sha1_digest_t> piece_checksums_;
};
/* what piece index is this block in? */

View File

@ -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;

View File

@ -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)
{

View File

@ -16,6 +16,7 @@
#include <libtransmission/variant.h>
#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<QWidget*>(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<int64_t>(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();
}
}

View File

@ -10,7 +10,6 @@
#include <vector>
#include <QCryptographicHash>
#include <QDir>
#include <QFile>
#include <QMap>
@ -44,8 +43,6 @@ private slots:
void onAccepted();
void onPriorityChanged(QSet<int> const& file_indices, int);
void onWantedChanged(QSet<int> 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<bool> verify_flags_;
std::vector<bool> wanted_;
std::vector<int> 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_ = {};
};