454 lines
13 KiB
C++
454 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):
|
|
QDialog (parent, Qt::Dialog),
|
|
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 ();
|
|
}
|