mirror of
https://github.com/transmission/transmission
synced 2024-12-23 08:13:27 +00:00
971cc6d2d9
We don't currently (if ever) provide context help, so the button is useless. Moreover, on Windows 10 it's even larger than before and sometimes title text doesn't fit because of it.
453 lines
13 KiB
C++
453 lines
13 KiB
C++
/*
|
|
* This file Copyright (C) 2009-2015 Mnemosyne LLC
|
|
*
|
|
* It may be used under the GNU GPL versions 2 or 3
|
|
* or any future license endorsed by Mnemosyne LLC.
|
|
*
|
|
* $Id$
|
|
*/
|
|
|
|
#include <QFileInfo>
|
|
#include <QPushButton>
|
|
|
|
#include <libtransmission/transmission.h>
|
|
#include <libtransmission/utils.h> /* mime64 */
|
|
#include <libtransmission/variant.h>
|
|
|
|
#include "AddData.h"
|
|
#include "FreeSpaceLabel.h"
|
|
#include "OptionsDialog.h"
|
|
#include "Prefs.h"
|
|
#include "Session.h"
|
|
#include "Torrent.h"
|
|
#include "Utils.h"
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
OptionsDialog::OptionsDialog (Session& session, const Prefs& prefs, const AddData& addme, QWidget * parent):
|
|
BaseDialog (parent),
|
|
mySession (session),
|
|
myAdd (addme),
|
|
myHaveInfo (false),
|
|
myVerifyButton (nullptr),
|
|
myVerifyFile (nullptr),
|
|
myVerifyHash (QCryptographicHash::Sha1),
|
|
myEditTimer (this)
|
|
{
|
|
ui.setupUi (this);
|
|
|
|
QString title;
|
|
if (myAdd.type == AddData::FILENAME)
|
|
title = tr ("Open Torrent from File");
|
|
else
|
|
title = tr ("Open Torrent from URL or Magnet Link");
|
|
setWindowTitle (title);
|
|
|
|
myEditTimer.setInterval (2000);
|
|
myEditTimer.setSingleShot (true);
|
|
connect (&myEditTimer, SIGNAL (timeout ()), this, SLOT (onDestinationChanged ()));
|
|
|
|
if (myAdd.type == AddData::FILENAME)
|
|
{
|
|
ui.sourceStack->setCurrentWidget (ui.sourceButton);
|
|
ui.sourceButton->setMode (PathButton::FileMode);
|
|
ui.sourceButton->setTitle (tr ("Open Torrent"));
|
|
ui.sourceButton->setNameFilter (tr ("Torrent Files (*.torrent);;All Files (*.*)"));
|
|
ui.sourceButton->setPath (myAdd.filename);
|
|
connect (ui.sourceButton, SIGNAL (pathChanged (QString)), this, SLOT (onSourceChanged ()));
|
|
}
|
|
else
|
|
{
|
|
ui.sourceStack->setCurrentWidget (ui.sourceEdit);
|
|
ui.sourceEdit->setText (myAdd.readableName ());
|
|
ui.sourceEdit->selectAll ();
|
|
connect (ui.sourceEdit, SIGNAL (editingFinished ()), this, SLOT (onSourceChanged ()));
|
|
}
|
|
|
|
ui.sourceStack->setFixedHeight (ui.sourceStack->currentWidget ()->sizeHint ().height ());
|
|
ui.sourceLabel->setBuddy (ui.sourceStack->currentWidget ());
|
|
|
|
const QFontMetrics fontMetrics (font ());
|
|
const int width = fontMetrics.size (0, QString::fromUtf8 ("This is a pretty long torrent filename indeed.torrent")).width ();
|
|
ui.sourceStack->setMinimumWidth (width);
|
|
|
|
const QString downloadDir (Utils::removeTrailingDirSeparator (prefs.getString (Prefs::DOWNLOAD_DIR)));
|
|
ui.freeSpaceLabel->setSession (mySession);
|
|
ui.freeSpaceLabel->setPath (downloadDir);
|
|
|
|
if (session.isLocal ())
|
|
{
|
|
ui.destinationStack->setCurrentWidget (ui.destinationButton);
|
|
ui.destinationButton->setMode (PathButton::DirectoryMode);
|
|
ui.destinationButton->setTitle (tr ("Select Destination"));
|
|
ui.destinationButton->setPath (downloadDir);
|
|
myLocalDestination = downloadDir;
|
|
connect (ui.destinationButton, SIGNAL (pathChanged (QString)), this, SLOT (onDestinationChanged ()));
|
|
}
|
|
else
|
|
{
|
|
ui.destinationStack->setCurrentWidget (ui.destinationEdit);
|
|
ui.destinationEdit->setText (downloadDir);
|
|
ui.freeSpaceLabel->setPath (downloadDir);
|
|
connect (ui.destinationEdit, SIGNAL (textEdited (QString)), &myEditTimer, SLOT (start ()));
|
|
connect (ui.destinationEdit, SIGNAL (editingFinished ()), this, SLOT (onDestinationChanged ()));
|
|
}
|
|
|
|
ui.destinationStack->setFixedHeight (ui.destinationStack->currentWidget ()->sizeHint ().height ());
|
|
ui.destinationLabel->setBuddy (ui.destinationStack->currentWidget ());
|
|
|
|
ui.filesView->setEditable (false);
|
|
if (!session.isLocal ())
|
|
ui.filesView->hideColumn (2); // hide the % done, since we've no way of knowing
|
|
|
|
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
|
|
|
|
if (session.isLocal ())
|
|
{
|
|
myVerifyButton = new QPushButton (tr ("&Verify Local Data"), this);
|
|
ui.dialogButtons->addButton (myVerifyButton, QDialogButtonBox::ActionRole);
|
|
connect (myVerifyButton, SIGNAL (clicked (bool)), this, SLOT (onVerify ()));
|
|
}
|
|
|
|
ui.startCheck->setChecked (prefs.getBool (Prefs::START));
|
|
ui.trashCheck->setChecked (prefs.getBool (Prefs::TRASH_ORIGINAL));
|
|
|
|
connect (ui.dialogButtons, SIGNAL (rejected ()), this, SLOT (deleteLater ()));
|
|
connect (ui.dialogButtons, SIGNAL (accepted ()), this, SLOT (onAccepted ()));
|
|
|
|
connect (ui.filesView, SIGNAL (priorityChanged (QSet<int>, int)), this, SLOT (onPriorityChanged (QSet<int>, int)));
|
|
connect (ui.filesView, SIGNAL (wantedChanged (QSet<int>, bool)), this, SLOT (onWantedChanged (QSet<int>, bool)));
|
|
|
|
connect (&myVerifyTimer, SIGNAL (timeout ()), this, SLOT (onTimeout ()));
|
|
|
|
reload ();
|
|
}
|
|
|
|
OptionsDialog::~OptionsDialog ()
|
|
{
|
|
clearInfo ();
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
OptionsDialog::clearInfo ()
|
|
{
|
|
if (myHaveInfo)
|
|
tr_metainfoFree (&myInfo);
|
|
|
|
myHaveInfo = false;
|
|
myFiles.clear ();
|
|
}
|
|
|
|
void
|
|
OptionsDialog::reload ()
|
|
{
|
|
clearInfo ();
|
|
clearVerify ();
|
|
|
|
tr_ctor * ctor = tr_ctorNew (0);
|
|
|
|
switch (myAdd.type)
|
|
{
|
|
case AddData::MAGNET:
|
|
tr_ctorSetMetainfoFromMagnetLink (ctor, myAdd.magnet.toUtf8 ().constData ());
|
|
break;
|
|
|
|
case AddData::FILENAME:
|
|
tr_ctorSetMetainfoFromFile (ctor, myAdd.filename.toUtf8 ().constData ());
|
|
break;
|
|
|
|
case AddData::METAINFO:
|
|
tr_ctorSetMetainfo (ctor, reinterpret_cast<const quint8*> (myAdd.metainfo.constData ()), myAdd.metainfo.size ());
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
const int err = tr_torrentParse (ctor, &myInfo);
|
|
myHaveInfo = !err;
|
|
tr_ctorFree (ctor);
|
|
|
|
ui.filesView->clear ();
|
|
myFiles.clear ();
|
|
myPriorities.clear ();
|
|
myWanted.clear ();
|
|
|
|
const bool haveFilesToShow = myHaveInfo && myInfo.fileCount > 0;
|
|
|
|
ui.filesView->setVisible (haveFilesToShow);
|
|
if (myVerifyButton != nullptr)
|
|
myVerifyButton->setVisible (haveFilesToShow);
|
|
layout ()->setSizeConstraint (haveFilesToShow ? QLayout::SetDefaultConstraint : QLayout::SetFixedSize);
|
|
|
|
if (myHaveInfo)
|
|
{
|
|
myPriorities.insert (0, myInfo.fileCount, TR_PRI_NORMAL);
|
|
myWanted.insert (0, myInfo.fileCount, true);
|
|
|
|
for (tr_file_index_t i = 0; i < myInfo.fileCount; ++i)
|
|
{
|
|
TorrentFile file;
|
|
file.index = i;
|
|
file.priority = myPriorities[i];
|
|
file.wanted = myWanted[i];
|
|
file.size = myInfo.files[i].length;
|
|
file.have = 0;
|
|
file.filename = QString::fromUtf8 (myInfo.files[i].name);
|
|
myFiles.append (file);
|
|
}
|
|
}
|
|
|
|
ui.filesView->update (myFiles);
|
|
}
|
|
|
|
void
|
|
OptionsDialog::onPriorityChanged (const QSet<int>& fileIndices, int priority)
|
|
{
|
|
for (const int i: fileIndices)
|
|
myPriorities[i] = priority;
|
|
}
|
|
|
|
void
|
|
OptionsDialog::onWantedChanged (const QSet<int>& fileIndices, bool isWanted)
|
|
{
|
|
for (const int i: fileIndices)
|
|
myWanted[i] = isWanted;
|
|
}
|
|
|
|
void
|
|
OptionsDialog::onAccepted ()
|
|
{
|
|
// rpc spec section 3.4 "adding a torrent"
|
|
|
|
tr_variant args;
|
|
tr_variantInitDict (&args, 10);
|
|
QString downloadDir;
|
|
|
|
// "download-dir"
|
|
if (ui.destinationStack->currentWidget () == ui.destinationButton)
|
|
downloadDir = myLocalDestination.absolutePath ();
|
|
else
|
|
downloadDir = ui.destinationEdit->text ();
|
|
|
|
tr_variantDictAddStr (&args, TR_KEY_download_dir, downloadDir.toUtf8 ().constData ());
|
|
|
|
// paused
|
|
tr_variantDictAddBool (&args, TR_KEY_paused, !ui.startCheck->isChecked ());
|
|
|
|
// priority
|
|
const int index = ui.priorityCombo->currentIndex ();
|
|
const int priority = ui.priorityCombo->itemData (index).toInt ();
|
|
tr_variantDictAddInt (&args, TR_KEY_bandwidthPriority, priority);
|
|
|
|
// files-unwanted
|
|
int count = myWanted.count (false);
|
|
if (count > 0)
|
|
{
|
|
tr_variant * l = tr_variantDictAddList (&args, TR_KEY_files_unwanted, count);
|
|
for (int i = 0, n = myWanted.size (); i < n; ++i)
|
|
{
|
|
if (myWanted.at (i) == false)
|
|
tr_variantListAddInt (l, i);
|
|
}
|
|
}
|
|
|
|
// priority-low
|
|
count = myPriorities.count (TR_PRI_LOW);
|
|
if (count > 0)
|
|
{
|
|
tr_variant * l = tr_variantDictAddList (&args, TR_KEY_priority_low, count);
|
|
for (int i = 0, n = myPriorities.size (); i < n; ++i)
|
|
{
|
|
if (myPriorities.at (i) == TR_PRI_LOW)
|
|
tr_variantListAddInt (l, i);
|
|
}
|
|
}
|
|
|
|
// priority-high
|
|
count = myPriorities.count (TR_PRI_HIGH);
|
|
if (count > 0)
|
|
{
|
|
tr_variant * l = tr_variantDictAddList (&args, TR_KEY_priority_high, count);
|
|
for (int i = 0, n = myPriorities.size (); i < n; ++i)
|
|
{
|
|
if (myPriorities.at (i) == TR_PRI_HIGH)
|
|
tr_variantListAddInt (l, i);
|
|
}
|
|
}
|
|
|
|
mySession.addTorrent (myAdd, &args, ui.trashCheck->isChecked ());
|
|
|
|
deleteLater ();
|
|
}
|
|
|
|
void
|
|
OptionsDialog::onSourceChanged ()
|
|
{
|
|
if (ui.sourceStack->currentWidget () == ui.sourceButton)
|
|
myAdd.set (ui.sourceButton->path ());
|
|
else
|
|
myAdd.set (ui.sourceEdit->text ());
|
|
|
|
reload ();
|
|
}
|
|
|
|
void
|
|
OptionsDialog::onDestinationChanged ()
|
|
{
|
|
if (ui.destinationStack->currentWidget () == ui.destinationButton)
|
|
{
|
|
myLocalDestination = ui.destinationButton->path ();
|
|
ui.freeSpaceLabel->setPath (myLocalDestination.absolutePath ());
|
|
}
|
|
else
|
|
{
|
|
ui.freeSpaceLabel->setPath (ui.destinationEdit->text ());
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
**** VERIFY
|
|
****
|
|
***/
|
|
|
|
void
|
|
OptionsDialog::clearVerify ()
|
|
{
|
|
myVerifyHash.reset ();
|
|
myVerifyFile.close ();
|
|
myVerifyFilePos = 0;
|
|
myVerifyFlags.clear ();
|
|
myVerifyFileIndex = 0;
|
|
myVerifyPieceIndex = 0;
|
|
myVerifyPiecePos = 0;
|
|
myVerifyTimer.stop ();
|
|
|
|
for (TorrentFile& f: myFiles)
|
|
f.have = 0;
|
|
|
|
ui.filesView->update (myFiles);
|
|
}
|
|
|
|
void
|
|
OptionsDialog::onVerify ()
|
|
{
|
|
clearVerify ();
|
|
myVerifyFlags.insert (0, myInfo.pieceCount, false);
|
|
myVerifyTimer.setSingleShot (false);
|
|
myVerifyTimer.start (0);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
uint64_t getPieceSize (const tr_info * info, tr_piece_index_t pieceIndex)
|
|
{
|
|
if (pieceIndex != info->pieceCount - 1)
|
|
return info->pieceSize;
|
|
return info->totalSize % info->pieceSize;
|
|
}
|
|
}
|
|
|
|
void
|
|
OptionsDialog::onTimeout ()
|
|
{
|
|
if (myFiles.isEmpty ())
|
|
{
|
|
myVerifyTimer.stop ();
|
|
return;
|
|
}
|
|
|
|
const tr_file * file = &myInfo.files[myVerifyFileIndex];
|
|
|
|
if (!myVerifyFilePos && !myVerifyFile.isOpen ())
|
|
{
|
|
const QFileInfo fileInfo (myLocalDestination, QString::fromUtf8 (file->name));
|
|
myVerifyFile.setFileName (fileInfo.absoluteFilePath ());
|
|
myVerifyFile.open (QIODevice::ReadOnly);
|
|
}
|
|
|
|
int64_t leftInPiece = getPieceSize (&myInfo, myVerifyPieceIndex) - myVerifyPiecePos;
|
|
int64_t leftInFile = file->length - myVerifyFilePos;
|
|
int64_t bytesThisPass = qMin (leftInFile, leftInPiece);
|
|
bytesThisPass = qMin (bytesThisPass, static_cast<int64_t> (sizeof (myVerifyBuf)));
|
|
|
|
if (myVerifyFile.isOpen () && myVerifyFile.seek (myVerifyFilePos))
|
|
{
|
|
int64_t numRead = myVerifyFile.read (myVerifyBuf, bytesThisPass);
|
|
if (numRead == bytesThisPass)
|
|
myVerifyHash.addData (myVerifyBuf, numRead);
|
|
}
|
|
|
|
leftInPiece -= bytesThisPass;
|
|
leftInFile -= bytesThisPass;
|
|
myVerifyPiecePos += bytesThisPass;
|
|
myVerifyFilePos += bytesThisPass;
|
|
|
|
myVerifyBins[myVerifyFileIndex] += bytesThisPass;
|
|
|
|
if (leftInPiece == 0)
|
|
{
|
|
const QByteArray result (myVerifyHash.result ());
|
|
const bool matches = !memcmp (result.constData (),
|
|
myInfo.pieces[myVerifyPieceIndex].hash,
|
|
SHA_DIGEST_LENGTH);
|
|
myVerifyFlags[myVerifyPieceIndex] = matches;
|
|
myVerifyPiecePos = 0;
|
|
++myVerifyPieceIndex;
|
|
myVerifyHash.reset ();
|
|
|
|
FileList changedFiles;
|
|
if (matches)
|
|
{
|
|
for (auto i = myVerifyBins.begin (), end = myVerifyBins.end (); i != end; ++i)
|
|
{
|
|
TorrentFile& f (myFiles[i.key ()]);
|
|
f.have += i.value ();
|
|
changedFiles.append (f);
|
|
}
|
|
}
|
|
ui.filesView->update (changedFiles);
|
|
myVerifyBins.clear ();
|
|
}
|
|
|
|
if (leftInFile == 0)
|
|
{
|
|
myVerifyFile.close ();
|
|
++myVerifyFileIndex;
|
|
myVerifyFilePos = 0;
|
|
}
|
|
|
|
bool done = myVerifyPieceIndex >= myInfo.pieceCount;
|
|
if (done)
|
|
{
|
|
uint64_t have = 0;
|
|
for (const TorrentFile& f: myFiles)
|
|
have += f.have;
|
|
|
|
if (!have) // everything failed
|
|
{
|
|
// did the user accidentally specify the child directory instead of the parent?
|
|
const QStringList tokens = QString::fromUtf8 (file->name).split (QLatin1Char ('/'));
|
|
if (!tokens.empty () && myLocalDestination.dirName () == tokens.at (0))
|
|
{
|
|
// move up one directory and try again
|
|
myLocalDestination.cdUp ();
|
|
onVerify ();
|
|
done = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (done)
|
|
myVerifyTimer.stop ();
|
|
}
|