Browse Source

- It is not possible to pause torrents in finished tab (closes #129498)

adaptive-webui-19844
Christophe Dumez 18 years ago
parent
commit
80fa44f8bb
  1. 4
      TODO
  2. 42
      src/FinishedTorrents.cpp
  3. 150
      src/GUI.cpp

4
TODO

@ -44,6 +44,10 @@
- Add checking icon to documentation - Add checking icon to documentation
* beta3 * beta3
- Translations update (IN PROGRESS) - Translations update (IN PROGRESS)
- Really enable UPnP/LSD/NAT-PMP (recently disabled as a default in libtorrent)
- make use of finishedChecking alert if hydri implements it
- Clean up delayed progress column sorting code
- Clean up pause after checking code
- Wait for some bug fixes in libtorrent : - Wait for some bug fixes in libtorrent :
- upload/download limit per torrent (Ticket #83) - upload/download limit per torrent (Ticket #83)
- double free or corruption on exit (Ticket #84) - double free or corruption on exit (Ticket #84)

42
src/FinishedTorrents.cpp

@ -56,7 +56,7 @@ FinishedTorrents::FinishedTorrents(QObject *parent, bittorrent *BTSession){
finishedListDelegate = new FinishedListDelegate(finishedList); finishedListDelegate = new FinishedListDelegate(finishedList);
finishedList->setItemDelegate(finishedListDelegate); finishedList->setItemDelegate(finishedListDelegate);
connect(finishedList, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayFinishedListMenu(const QPoint&))); connect(finishedList, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayFinishedListMenu(const QPoint&)));
connect(finishedList, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(propertiesSelection())); connect(finishedList, SIGNAL(doubleClicked(const QModelIndex&)), parent, SLOT(togglePausedState(const QModelIndex&)));
actionDelete->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/delete.png"))); actionDelete->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/delete.png")));
actionPreview_file->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/preview.png"))); actionPreview_file->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/preview.png")));
actionDelete_Permanently->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/delete_perm.png"))); actionDelete_Permanently->setIcon(QIcon(QString::fromUtf8(":/Icons/skin/delete_perm.png")));
@ -75,9 +75,6 @@ FinishedTorrents::~FinishedTorrents(){
} }
void FinishedTorrents::addFinishedSHA(QString hash){ void FinishedTorrents::addFinishedSHA(QString hash){
if(BTSession->getUncheckedTorrentsList().indexOf(hash) != -1){
BTSession->setTorrentFinishedChecking(hash);
}
if(finishedSHAs.indexOf(hash) == -1) { if(finishedSHAs.indexOf(hash) == -1) {
finishedSHAs << hash; finishedSHAs << hash;
int row = finishedListModel->rowCount(); int row = finishedListModel->rowCount();
@ -91,15 +88,13 @@ void FinishedTorrents::addFinishedSHA(QString hash){
finishedListModel->setData(finishedListModel->index(row, F_RATIO), QVariant(QString(misc::toString(BTSession->getRealRatio(hash)).c_str()))); finishedListModel->setData(finishedListModel->index(row, F_RATIO), QVariant(QString(misc::toString(BTSession->getRealRatio(hash)).c_str())));
finishedListModel->setData(finishedListModel->index(row, F_HASH), QVariant(hash)); finishedListModel->setData(finishedListModel->index(row, F_HASH), QVariant(hash));
finishedListModel->setData(finishedListModel->index(row, F_PROGRESS), QVariant((double)1.)); finishedListModel->setData(finishedListModel->index(row, F_PROGRESS), QVariant((double)1.));
// Start the torrent if it was paused
if(h.is_paused()) { if(h.is_paused()) {
h.resume(); finishedListModel->setData(finishedListModel->index(row, F_NAME), QIcon(":/Icons/skin/paused.png"), Qt::DecorationRole);
if(QFile::exists(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+hash+".paused")) { setRowColor(row, "red");
QFile::remove(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+hash+".paused"); }else{
}
}
finishedListModel->setData(finishedListModel->index(row, F_NAME), QVariant(QIcon(":/Icons/skin/seeding.png")), Qt::DecorationRole); finishedListModel->setData(finishedListModel->index(row, F_NAME), QVariant(QIcon(":/Icons/skin/seeding.png")), Qt::DecorationRole);
setRowColor(row, "orange"); setRowColor(row, "orange");
}
// Create .finished file // Create .finished file
QFile finished_file(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+hash+".finished"); QFile finished_file(misc::qBittorrentPath()+"BT_backup"+QDir::separator()+hash+".finished");
finished_file.open(QIODevice::WriteOnly | QIODevice::Text); finished_file.open(QIODevice::WriteOnly | QIODevice::Text);
@ -172,10 +167,25 @@ void FinishedTorrents::updateFinishedList(){
qDebug("Problem: This torrent is not valid in finished list"); qDebug("Problem: This torrent is not valid in finished list");
continue; continue;
} }
if(h.is_paused()){
h.resume(); // No paused torrents in finished list
}
torrent_status torrentStatus = h.status(); torrent_status torrentStatus = h.status();
int row = getRowFromHash(hash);
if(row == -1){
std::cerr << "ERROR: Can't find torrent in finished list\n";
continue;
}
if(BTSession->getTorrentsToPauseAfterChecking().indexOf(hash) != -1){
// Pause torrent if it finished checking and it is was supposed to be paused.
// This is a trick to see the progress of the pause torrents on startup
if(torrentStatus.state != torrent_status::checking_files && torrentStatus.state != torrent_status::queued_for_checking){
qDebug("Paused torrent finished checking with state: %d", torrentStatus.state);
finishedListModel->setData(finishedListModel->index(row, F_PROGRESS), QVariant((double)torrentStatus.progress));
finishedListModel->setData(finishedListModel->index(row, F_NAME), QVariant(QIcon(":/Icons/skin/paused.png")), Qt::DecorationRole);
setRowColor(row, "red");
BTSession->pauseTorrent(hash);
continue;
}
}
if(h.is_paused()) continue;
if(torrentStatus.state == torrent_status::downloading || (torrentStatus.state != torrent_status::checking_files && torrentStatus.state != torrent_status::queued_for_checking && torrentStatus.progress != 1.)) { if(torrentStatus.state == torrent_status::downloading || (torrentStatus.state != torrent_status::checking_files && torrentStatus.state != torrent_status::queued_for_checking && torrentStatus.progress != 1.)) {
// What are you doing here? go back to download tab! // What are you doing here? go back to download tab!
qDebug("Info: a torrent was moved from finished to download tab"); qDebug("Info: a torrent was moved from finished to download tab");
@ -183,11 +193,6 @@ void FinishedTorrents::updateFinishedList(){
emit torrentMovedFromFinishedList(h); emit torrentMovedFromFinishedList(h);
continue; continue;
} }
int row = getRowFromHash(hash);
if(row == -1){
std::cerr << "ERROR: Can't find torrent in finished list\n";
continue;
}
finishedListModel->setData(finishedListModel->index(row, F_UPSPEED), QVariant((double)torrentStatus.upload_payload_rate)); finishedListModel->setData(finishedListModel->index(row, F_UPSPEED), QVariant((double)torrentStatus.upload_payload_rate));
finishedListModel->setData(finishedListModel->index(row, F_SEEDSLEECH), QVariant(QString(misc::toString(torrentStatus.num_seeds, true).c_str())+"/"+QString(misc::toString(torrentStatus.num_peers - torrentStatus.num_seeds, true).c_str()))); finishedListModel->setData(finishedListModel->index(row, F_SEEDSLEECH), QVariant(QString(misc::toString(torrentStatus.num_seeds, true).c_str())+"/"+QString(misc::toString(torrentStatus.num_peers - torrentStatus.num_seeds, true).c_str())));
finishedListModel->setData(finishedListModel->index(row, F_RATIO), QVariant(QString(misc::toString(BTSession->getRealRatio(hash)).c_str()))); finishedListModel->setData(finishedListModel->index(row, F_RATIO), QVariant(QString(misc::toString(BTSession->getRealRatio(hash)).c_str())));
@ -268,7 +273,6 @@ void FinishedTorrents::displayFinishedListMenu(const QPoint& pos){
if(index.column() == F_NAME){ if(index.column() == F_NAME){
// Get the file name // Get the file name
QString fileHash = finishedListModel->data(finishedListModel->index(index.row(), F_HASH)).toString(); QString fileHash = finishedListModel->data(finishedListModel->index(index.row(), F_HASH)).toString();
// Get handle and pause the torrent
torrent_handle h = BTSession->getTorrentHandle(fileHash); torrent_handle h = BTSession->getTorrentHandle(fileHash);
myFinishedListMenu.addAction(actionDelete); myFinishedListMenu.addAction(actionDelete);
myFinishedListMenu.addAction(actionDelete_Permanently); myFinishedListMenu.addAction(actionDelete_Permanently);

150
src/GUI.cpp

@ -357,27 +357,6 @@ void GUI::readParamsOnSocket(){
} }
} }
// Toggle paused state of selected torrent
void GUI::togglePausedState(const QModelIndex& index){
int row = index.row();
QString fileHash = DLListModel->data(DLListModel->index(row, HASH)).toString();
if(BTSession->isPaused(fileHash)){
BTSession->resumeTorrent(fileHash);
setInfoBar(tr("'%1' resumed.", "e.g: xxx.avi resumed.").arg(QString(BTSession->getTorrentHandle(fileHash).name().c_str())));
DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/connecting.png")), Qt::DecorationRole);
setRowColor(row, "grey");
}else{
BTSession->pauseTorrent(fileHash);
DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)0.0));
DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)0.0));
DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1));
setInfoBar(tr("'%1' paused.", "xxx.avi paused.").arg(QString(BTSession->getTorrentHandle(fileHash).name().c_str())));
DLListModel->setData(DLListModel->index(row, NAME), QIcon(":/Icons/skin/paused.png"), Qt::DecorationRole);
DLListModel->setData(DLListModel->index(row, SEEDSLEECH), QVariant("0/0"));
setRowColor(row, "red");
}
}
void GUI::on_actionSet_download_limit_triggered(){ void GUI::on_actionSet_download_limit_triggered(){
QModelIndexList selectedIndexes = downloadList->selectionModel()->selectedIndexes(); QModelIndexList selectedIndexes = downloadList->selectionModel()->selectedIndexes();
QModelIndex index; QModelIndex index;
@ -1382,15 +1361,67 @@ void GUI::configureSession(bool deleteOptions){
qDebug("Session configured"); qDebug("Session configured");
} }
// Toggle paused state of selected torrent
void GUI::togglePausedState(const QModelIndex& index){
int row = index.row();
bool inDownloadList = true;
if(tabs->currentIndex() > 1) return;
if(tabs->currentIndex() == 1)
inDownloadList = false;
QString fileHash;
if(inDownloadList)
fileHash = DLListModel->data(DLListModel->index(row, HASH)).toString();
else
fileHash = finishedTorrentTab->getFinishedListModel()->data(finishedTorrentTab->getFinishedListModel()->index(row, F_HASH)).toString();
if(BTSession->isPaused(fileHash)){
BTSession->resumeTorrent(fileHash);
setInfoBar(tr("'%1' resumed.", "e.g: xxx.avi resumed.").arg(QString(BTSession->getTorrentHandle(fileHash).name().c_str())));
if(inDownloadList){
DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/connecting.png")), Qt::DecorationRole);
setRowColor(row, "grey");
}else{
finishedTorrentTab->getFinishedListModel()->setData(finishedTorrentTab->getFinishedListModel()->index(row, F_NAME), QVariant(QIcon(":/Icons/skin/seeding.png")), Qt::DecorationRole);
finishedTorrentTab->setRowColor(row, "orange");
}
}else{
BTSession->pauseTorrent(fileHash);
if(inDownloadList){
DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)0.0));
DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)0.0));
DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1));
DLListModel->setData(DLListModel->index(row, NAME), QIcon(":/Icons/skin/paused.png"), Qt::DecorationRole);
DLListModel->setData(DLListModel->index(row, SEEDSLEECH), QVariant("0/0"));
setRowColor(row, "red");
}else{
finishedTorrentTab->getFinishedListModel()->setData(finishedTorrentTab->getFinishedListModel()->index(row, F_UPSPEED), QVariant((double)0.0));
finishedTorrentTab->getFinishedListModel()->setData(finishedTorrentTab->getFinishedListModel()->index(row, F_NAME), QIcon(":/Icons/skin/paused.png"), Qt::DecorationRole);
finishedTorrentTab->getFinishedListModel()->setData(finishedTorrentTab->getFinishedListModel()->index(row, F_SEEDSLEECH), QVariant("0/0"));
setRowColor(row, "red");
}
setInfoBar(tr("'%1' paused.", "xxx.avi paused.").arg(QString(BTSession->getTorrentHandle(fileHash).name().c_str())));
}
}
// Pause All Downloads in DL list // Pause All Downloads in DL list
void GUI::on_actionPause_All_triggered(){ void GUI::on_actionPause_All_triggered(){
QString fileHash; QString fileHash;
bool change = false; bool change = false;
// update download list bool inDownloadList = true;
unsigned int nbRows = DLListModel->rowCount(); if(tabs->currentIndex() > 1) return;
if(tabs->currentIndex() == 1)
inDownloadList = false;
unsigned int nbRows;
if(inDownloadList)
nbRows = DLListModel->rowCount();
else
nbRows = finishedTorrentTab->getFinishedListModel()->rowCount();
for(unsigned int i=0; i<nbRows; ++i){ for(unsigned int i=0; i<nbRows; ++i){
if(inDownloadList)
fileHash = DLListModel->data(DLListModel->index(i, HASH)).toString(); fileHash = DLListModel->data(DLListModel->index(i, HASH)).toString();
else
fileHash = finishedTorrentTab->getFinishedListModel()->data(finishedTorrentTab->getFinishedListModel()->index(i, F_HASH)).toString();
if(BTSession->pauseTorrent(fileHash)){ if(BTSession->pauseTorrent(fileHash)){
if(inDownloadList){
// Update DL list items // Update DL list items
DLListModel->setData(DLListModel->index(i, DLSPEED), QVariant((double)0.)); DLListModel->setData(DLListModel->index(i, DLSPEED), QVariant((double)0.));
DLListModel->setData(DLListModel->index(i, UPSPEED), QVariant((double)0.)); DLListModel->setData(DLListModel->index(i, UPSPEED), QVariant((double)0.));
@ -1398,6 +1429,13 @@ void GUI::on_actionPause_All_triggered(){
DLListModel->setData(DLListModel->index(i, NAME), QVariant(QIcon(":/Icons/skin/paused.png")), Qt::DecorationRole); DLListModel->setData(DLListModel->index(i, NAME), QVariant(QIcon(":/Icons/skin/paused.png")), Qt::DecorationRole);
DLListModel->setData(DLListModel->index(i, SEEDSLEECH), QVariant("0/0")); DLListModel->setData(DLListModel->index(i, SEEDSLEECH), QVariant("0/0"));
setRowColor(i, "red"); setRowColor(i, "red");
}else{
// Update finished list items
finishedTorrentTab->getFinishedListModel()->setData(finishedTorrentTab->getFinishedListModel()->index(i, F_UPSPEED), QVariant((double)0.));
finishedTorrentTab->getFinishedListModel()->setData(finishedTorrentTab->getFinishedListModel()->index(i, F_NAME), QVariant(QIcon(":/Icons/skin/paused.png")), Qt::DecorationRole);
finishedTorrentTab->getFinishedListModel()->setData(finishedTorrentTab->getFinishedListModel()->index(i, F_SEEDSLEECH), QVariant("0/0"));
finishedTorrentTab->setRowColor(i, "red");
}
change = true; change = true;
} }
} }
@ -1407,22 +1445,41 @@ void GUI::on_actionPause_All_triggered(){
// pause selected items in the list // pause selected items in the list
void GUI::on_actionPause_triggered(){ void GUI::on_actionPause_triggered(){
QModelIndexList selectedIndexes = downloadList->selectionModel()->selectedIndexes(); QModelIndexList selectedIndexes;
bool inDownloadList = true;
if(tabs->currentIndex() > 1) return;
if(tabs->currentIndex() == 1)
inDownloadList = false;
if (inDownloadList)
selectedIndexes = downloadList->selectionModel()->selectedIndexes();
else
selectedIndexes = finishedTorrentTab->getFinishedList()->selectionModel()->selectedIndexes();
QModelIndex index; QModelIndex index;
foreach(index, selectedIndexes){ foreach(index, selectedIndexes){
if(index.column() == NAME){ if(index.column() == NAME){
// Get the file name // Get the file name
QString fileHash = DLListModel->data(DLListModel->index(index.row(), HASH)).toString(); QString fileHash;
if(inDownloadList)
fileHash = DLListModel->data(DLListModel->index(index.row(), HASH)).toString();
else
fileHash = finishedTorrentTab->getFinishedListModel()->data(finishedTorrentTab->getFinishedListModel()->index(index.row(), F_HASH)).toString();
if(BTSession->pauseTorrent(fileHash)){ if(BTSession->pauseTorrent(fileHash)){
// Update DL status // Update DL status
int row = index.row(); int row = index.row();
if(inDownloadList){
DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)0.0)); DLListModel->setData(DLListModel->index(row, DLSPEED), QVariant((double)0.0));
DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)0.0)); DLListModel->setData(DLListModel->index(row, UPSPEED), QVariant((double)0.0));
DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1)); DLListModel->setData(DLListModel->index(row, ETA), QVariant((qlonglong)-1));
setInfoBar(tr("'%1' paused.", "xxx.avi paused.").arg(QString(BTSession->getTorrentHandle(fileHash).name().c_str())));
DLListModel->setData(DLListModel->index(row, NAME), QIcon(":/Icons/skin/paused.png"), Qt::DecorationRole); DLListModel->setData(DLListModel->index(row, NAME), QIcon(":/Icons/skin/paused.png"), Qt::DecorationRole);
DLListModel->setData(DLListModel->index(row, SEEDSLEECH), QVariant("0/0")); DLListModel->setData(DLListModel->index(row, SEEDSLEECH), QVariant("0/0"));
setRowColor(row, "red"); setRowColor(row, "red");
}else{
finishedTorrentTab->getFinishedListModel()->setData(finishedTorrentTab->getFinishedListModel()->index(row, F_UPSPEED), QVariant((double)0.0));
finishedTorrentTab->getFinishedListModel()->setData(finishedTorrentTab->getFinishedListModel()->index(row, F_NAME), QIcon(":/Icons/skin/paused.png"), Qt::DecorationRole);
finishedTorrentTab->getFinishedListModel()->setData(finishedTorrentTab->getFinishedListModel()->index(row, F_SEEDSLEECH), QVariant("0/0"));
finishedTorrentTab->setRowColor(row, "red");
}
setInfoBar(tr("'%1' paused.", "xxx.avi paused.").arg(QString(BTSession->getTorrentHandle(fileHash).name().c_str())));
} }
} }
} }
@ -1431,14 +1488,30 @@ void GUI::on_actionPause_triggered(){
// Resume All Downloads in DL list // Resume All Downloads in DL list
void GUI::on_actionStart_All_triggered(){ void GUI::on_actionStart_All_triggered(){
QString fileHash; QString fileHash;
unsigned int nbRows = DLListModel->rowCount();
bool change = false; bool change = false;
bool inDownloadList = true;
if(tabs->currentIndex() > 1) return;
if(tabs->currentIndex() == 1)
inDownloadList = false;
unsigned int nbRows;
if(inDownloadList)
nbRows = DLListModel->rowCount();
else
nbRows = finishedTorrentTab->getFinishedListModel()->rowCount();
for(unsigned int i=0; i<nbRows; ++i){ for(unsigned int i=0; i<nbRows; ++i){
if(inDownloadList)
fileHash = DLListModel->data(DLListModel->index(i, HASH)).toString(); fileHash = DLListModel->data(DLListModel->index(i, HASH)).toString();
else
fileHash = finishedTorrentTab->getFinishedListModel()->data(finishedTorrentTab->getFinishedListModel()->index(i, F_HASH)).toString();
// Remove .paused file // Remove .paused file
if(BTSession->resumeTorrent(fileHash)){ if(BTSession->resumeTorrent(fileHash)){
if(inDownloadList){
DLListModel->setData(DLListModel->index(i, NAME), QVariant(QIcon(":/Icons/skin/connecting.png")), Qt::DecorationRole); DLListModel->setData(DLListModel->index(i, NAME), QVariant(QIcon(":/Icons/skin/connecting.png")), Qt::DecorationRole);
setRowColor(i, "grey"); setRowColor(i, "grey");
}else{
finishedTorrentTab->getFinishedListModel()->setData(finishedTorrentTab->getFinishedListModel()->index(i, F_NAME), QVariant(QIcon(":/Icons/skin/seeding.png")), Qt::DecorationRole);
finishedTorrentTab->setRowColor(i, "orange");
}
change = true; change = true;
} }
} }
@ -1448,18 +1521,35 @@ void GUI::on_actionStart_All_triggered(){
// start selected items in the list // start selected items in the list
void GUI::on_actionStart_triggered(){ void GUI::on_actionStart_triggered(){
QModelIndexList selectedIndexes = downloadList->selectionModel()->selectedIndexes(); QModelIndexList selectedIndexes;
bool inDownloadList = true;
if(tabs->currentIndex() > 1) return;
if(tabs->currentIndex() == 1)
inDownloadList = false;
if (inDownloadList)
selectedIndexes = downloadList->selectionModel()->selectedIndexes();
else
selectedIndexes = finishedTorrentTab->getFinishedList()->selectionModel()->selectedIndexes();
QModelIndex index; QModelIndex index;
foreach(index, selectedIndexes){ foreach(index, selectedIndexes){
if(index.column() == NAME){ if(index.column() == NAME){
// Get the file name // Get the file name
QString fileHash = DLListModel->data(DLListModel->index(index.row(), HASH)).toString(); QString fileHash;
if(inDownloadList)
fileHash = DLListModel->data(DLListModel->index(index.row(), HASH)).toString();
else
fileHash = finishedTorrentTab->getFinishedListModel()->data(finishedTorrentTab->getFinishedListModel()->index(index.row(), F_HASH)).toString();
if(BTSession->resumeTorrent(fileHash)){ if(BTSession->resumeTorrent(fileHash)){
// Update DL status // Update DL status
int row = index.row(); int row = index.row();
setInfoBar(tr("'%1' resumed.", "e.g: xxx.avi resumed.").arg(QString(BTSession->getTorrentHandle(fileHash).name().c_str()))); if(inDownloadList){
DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/connecting.png")), Qt::DecorationRole); DLListModel->setData(DLListModel->index(row, NAME), QVariant(QIcon(":/Icons/skin/connecting.png")), Qt::DecorationRole);
setRowColor(row, "grey"); setRowColor(row, "grey");
}else{
finishedTorrentTab->getFinishedListModel()->setData(finishedTorrentTab->getFinishedListModel()->index(row, F_NAME), QVariant(QIcon(":/Icons/skin/seeding.png")), Qt::DecorationRole);
finishedTorrentTab->setRowColor(row, "orange");
}
setInfoBar(tr("'%1' resumed.", "e.g: xxx.avi resumed.").arg(QString(BTSession->getTorrentHandle(fileHash).name().c_str())));
} }
} }
} }

Loading…
Cancel
Save