//=============================================================================
//
// File : DccBroker.cpp
// Creation date : Tue Sep 19 09 2000 10:21:54 by Szymon Stefanek
//
// This file is part of the KVIrc IRC client distribution
// Copyright (C) 2000-2010 Szymon Stefanek (pragma at kvirc dot net)
//
// This program is FREE software. You can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the HOPE that it will be USEFUL,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//=============================================================================
#include "kvi_settings.h"
#include "DccBroker.h"
#include "DccDialog.h"
#include "DccChatWindow.h"
#include "DccFileTransfer.h"
#include "DccVoiceWindow.h"
#ifndef COMPILE_DISABLE_DCC_VIDEO
#include "DccVideoWindow.h"
#endif
#ifdef COMPILE_DCC_CANVAS
#include "DccCanvasWindow.h"
#endif
#include "KviApplication.h"
#include "KviMainWindow.h"
#include "KviLocale.h"
#include "KviFileDialog.h"
#include "KviOptions.h"
#include "KviConsoleWindow.h"
#include "KviFileUtils.h"
#include "kvi_out.h"
#include "KviMediaManager.h"
#include "KviIrcConnection.h"
#include "KviSharedFilesManager.h"
#include "KviIrcMessage.h"
#include <QFileInfo>
#include <QString>
extern KVIRC_API KviMediaManager * g_pMediaManager;
extern KVIRC_API KviSharedFilesManager * g_pSharedFilesManager;
//#warning "The broker might lookup the remote host name"
DccBroker::DccBroker()
: QObject(nullptr)
{
setObjectName("dcc_broker");
DccFileTransfer::init();
m_pBoxList = new KviPointerList<DccDialog>;
m_pBoxList->setAutoDelete(false);
m_pDccWindowList = new KviPointerList<KviWindow>;
m_pDccWindowList->setAutoDelete(false);
m_pZeroPortTags = new KviPointerHashTable<QString, KviDccZeroPortTag>(17);
m_pZeroPortTags->setAutoDelete(true);
}
DccBroker::~DccBroker()
{
delete m_pZeroPortTags;
while(m_pBoxList->first())
delete m_pBoxList->first();
delete m_pBoxList;
m_pBoxList = nullptr;
while(m_pDccWindowList->first())
delete m_pDccWindowList->first();
delete m_pDccWindowList;
DccFileTransfer::done();
}
KviDccZeroPortTag * DccBroker::addZeroPortTag()
{
static unsigned int g_uNextZeroPortTag = 0;
g_uNextZeroPortTag++;
KviDccZeroPortTag * t = new KviDccZeroPortTag;
t->m_tTimestamp = QDateTime::currentDateTime();
t->m_szTag.setNum(g_uNextZeroPortTag);
//t->m_szTag.prepend("mIrc-zero-port-");
t->m_uResumePosition = 0;
// FIXME: we should clear this dict if it grows too high....
m_pZeroPortTags->insert(t->m_szTag, t);
return t;
}
KviDccZeroPortTag * DccBroker::findZeroPortTag(const QString & szTag)
{
KviDccZeroPortTag * t = m_pZeroPortTags->find(szTag);
if(!t)
return nullptr;
if(t->m_tTimestamp.secsTo(QDateTime::currentDateTime()) > 180)
{
// too late man...
m_pZeroPortTags->remove(szTag);
return nullptr;
}
return t;
}
void DccBroker::removeZeroPortTag(const QString & szTag)
{
m_pZeroPortTags->remove(szTag);
}
unsigned int DccBroker::dccBoxCount()
{
return m_pBoxList->count();
}
void DccBroker::unregisterDccWindow(KviWindow * wnd)
{
m_pDccWindowList->removeRef(wnd);
}
void DccBroker::unregisterDccBox(DccDialog * box)
{
//qDebug("Forgetting box %d",box);
m_pBoxList->removeRef(box);
}
void DccBroker::cancelDcc(DccDescriptor * dcc)
{
delete dcc;
dcc = nullptr;
}
void DccBroker::cancelDcc(DccDialog * box, DccDescriptor * dcc)
{
if(box)
box->forgetDescriptor();
delete dcc;
dcc = nullptr;
}
//
// RSEND
//
void DccBroker::rsendManage(DccDescriptor * dcc)
{
// We need the filename...
QFileInfo fi(dcc->szLocalFileName);
if(fi.exists())
rsendExecute(nullptr, dcc);
else
rsendAskForFileName(dcc);
}
void DccBroker::rsendAskForFileName(DccDescriptor * dcc)
{
QStringList filenames;
if(
KviFileDialog::askForOpenFileNames(
filenames,
__tr2qs_ctx("Choose Files to Send - KVIrc", "dcc"),
QString(),
QString(),
false,
true,
g_pMainWindow))
{
if(filenames.count() > 0)
{
DccDescriptor * d;
QStringList::Iterator it = filenames.begin();
while(it != filenames.end())
{
d = new DccDescriptor(*dcc);
d->szLocalFileName = *(it);
d->szLocalFileName = d->szLocalFileName.trimmed();
++it;
if(d->szLocalFileName.isEmpty())
cancelDcc(d);
else
rsendExecute(d);
}
delete dcc;
}
}
else
{
cancelDcc(dcc);
}
}
void DccBroker::rsendExecute(DccDescriptor * dcc)
{
if(!g_pApp->windowExists(dcc->console()))
{
// No way...we NEED the right IRC context...
g_pApp->activeConsole()->output(KVI_OUT_DCCERROR,
__tr2qs_ctx("Can't send DCC %Q request to %Q: IRC connection has been terminated", "dcc"),
&(dcc->szType), &(dcc->szNick));
delete dcc;
return;
}
// Ok...we need the file to exist
QFileInfo fi(dcc->szLocalFileName);
if(!(fi.exists() && fi.isReadable() && (fi.isFile()) && (fi.size() > 0)))
{
dcc->console()->output(KVI_OUT_DCCERROR, __tr2qs_ctx("Can't open file %Q for reading", "dcc"),
&(dcc->szLocalFileName));
delete dcc;
return;
}
dcc->szFileName = dcc->szLocalFileName;
dcc->szFileName = QFileInfo(dcc->szFileName).fileName();
QString fName = dcc->szFileName;
fName.replace(' ', "\\040"); // be cool :)
QString szTag;
if(dcc->isZeroPortRequest())
{
// actually we tagged it as "nonempty" in /dcc.rsend --zero-port
// retag it with something more reasonable
KviDccZeroPortTag * t = addZeroPortTag();
t->m_uFileSize = fi.size();
dcc->setZeroPortRequestTag(t->m_szTag.toLatin1()); // latin1() should be ok here
szTag = t->m_szTag;
// DCC [ST]SEND <filename> <fakeipaddress> <zero-port> <filesize> <sessionid>
dcc->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC %s %s 2130706433 0 %s %s%c",
dcc->console()->connection()->encodeText(dcc->szNick).data(),
0x01,
dcc->console()->connection()->encodeText(dcc->szType).data(),
dcc->console()->connection()->encodeText(fName).data(),
dcc->console()->connection()->encodeText(QString::number(fi.size())).data(),
dcc->console()->connection()->encodeText(szTag).data(),
0x01);
}
else
{
dcc->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC %s %s %s%c",
dcc->console()->connection()->encodeText(dcc->szNick).data(),
0x01,
dcc->console()->connection()->encodeText(dcc->szType).data(),
dcc->console()->connection()->encodeText(fName).data(),
dcc->console()->connection()->encodeText(QString::number(fi.size())).data(),
0x01);
szTag = dcc->szFileName;
}
// now add a file offer, so he we will accept it automatically
// 120 secs is a reasonable timeout
QString szMask = dcc->szNick;
szMask += "!*@*";
g_pSharedFilesManager->addSharedFile(szTag, dcc->szLocalFileName, szMask, 120);
delete dcc;
}
void DccBroker::rsendExecute(DccDialog * box, DccDescriptor * dcc)
{
if(box)
box->forgetDescriptor();
rsendExecute(dcc);
}
//
// DCC CHAT
//
void DccBroker::handleChatRequest(DccDescriptor * dcc)
{
if(!dcc->bAutoAccept)
{
// FIXME: better message ? Secure Direct Client Connection...eventually
// need confirmation
QString tmp = __tr2qs_ctx(
"<b>%1 [%2@%3]</b> requests a "
"<b>Direct Client Connection</b> in <b>%4</b> mode.<br>",
"dcc")
.arg(dcc->szNick, dcc->szUser, dcc->szHost, dcc->szType);
#ifdef COMPILE_SSL_SUPPORT
if(dcc->bIsSSL)
tmp += __tr2qs_ctx("The connection will be secured using SSL.<br>", "dcc");
#endif
if(dcc->isZeroPortRequest())
{
tmp += __tr2qs_ctx(
"You will be the passive side of the connection.<br>", "dcc");
}
else
{
tmp += __tr2qs_ctx(
"The connection target will be host <b>%1</b> on port <b>%2</b><br>", "dcc")
.arg(dcc->szIp, dcc->szPort);
}
QString caption = __tr2qs_ctx("DCC %1 Request - KVIrc", "dcc").arg(dcc->szType);
DccAcceptDialog * box = new DccAcceptDialog(this, dcc, tmp, caption);
m_pBoxList->append(box);
connect(box, SIGNAL(accepted(DccDialog *, DccDescriptor *)),
this, SLOT(executeChat(DccDialog *, DccDescriptor *)));
connect(box, SIGNAL(rejected(DccDialog *, DccDescriptor *)),
this, SLOT(cancelDcc(DccDialog *, DccDescriptor *)));
box->show();
}
else
{
// auto accept
executeChat(nullptr, dcc);
}
}
void DccBroker::executeChat(DccDialog * box, DccDescriptor * dcc)
{
if(box)
box->forgetDescriptor();
if(!g_pApp->windowExists(dcc->console()))
{
// rebind to the first available console....
dcc->setConsole(g_pApp->activeConsole());
}
KviCString szSubProto = dcc->szType;
szSubProto.toLower();
QString tmp = QString("DCC: %1 %2@%3:%4").arg(szSubProto.ptr(), dcc->szNick, dcc->szIp, dcc->szPort);
DccChatWindow * chat = new DccChatWindow(dcc, tmp.toUtf8().data());
bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : (KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccChat) || (dcc->bAutoAccept && KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccChatWhenAutoAccepted)));
g_pMainWindow->addWindow(chat, !bMinimized);
m_pDccWindowList->append(chat);
}
//
// ACTIVE VOICE
//
void DccBroker::activeVoiceManage(DccDescriptor * dcc)
{
if(!dcc->bAutoAccept)
{
// need confirmation
QString tmp = __tr2qs_ctx(
"<b>%1 [%2@%3]</b> requests a<br>"
"<b>Direct Client Connection</b> in <b>VOICE</b> mode.<br>"
"The connection target will be host <b>%4</b> on port <b>%5</b><br>",
"dcc")
.arg(dcc->szNick, dcc->szUser, dcc->szHost, dcc->szIp, dcc->szPort);
DccAcceptDialog * box = new DccAcceptDialog(this, dcc, tmp, __tr2qs_ctx("DCC VOICE request", "dcc"));
m_pBoxList->append(box);
connect(box, SIGNAL(accepted(DccDialog *, DccDescriptor *)),
this, SLOT(activeVoiceExecute(DccDialog *, DccDescriptor *)));
connect(box, SIGNAL(rejected(DccDialog *, DccDescriptor *)),
this, SLOT(cancelDcc(DccDialog *, DccDescriptor *)));
box->show();
}
else
{
// auto accept
activeVoiceExecute(nullptr, dcc);
}
}
void DccBroker::activeVoiceExecute(DccDialog * box, DccDescriptor * dcc)
{
if(box)
box->forgetDescriptor();
if(!g_pApp->windowExists(dcc->console()))
{
// rebind to the first available console....
dcc->setConsole(g_pApp->activeConsole());
}
KviCString tmp(KviCString::Format, "DCC: voice %s@%s:%s", dcc->szNick.toUtf8().data(), dcc->szIp.toUtf8().data(), dcc->szPort.toUtf8().data());
DccVoiceWindow * v = new DccVoiceWindow(dcc, tmp.ptr());
bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : (KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccVoice) || (dcc->bAutoAccept && KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccVoiceWhenAutoAccepted)));
g_pMainWindow->addWindow(v, !bMinimized);
m_pDccWindowList->append(v);
}
//
// PASSIVE VOICE
//
void DccBroker::passiveVoiceExecute(DccDescriptor * dcc)
{
KviCString tmp(KviCString::Format, "DCC: voice %s@%s:%s", dcc->szNick.toUtf8().data(), dcc->szIp.toUtf8().data(), dcc->szPort.toUtf8().data());
DccVoiceWindow * v = new DccVoiceWindow(dcc, tmp.ptr());
//#warning "Create minimized dcc voice ?... or maybe it's too much ? :)"
bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccChat);
g_pMainWindow->addWindow(v, !bMinimized);
m_pDccWindowList->append(v);
}
//
// ACTIVE VIDEO
//
#ifndef COMPILE_DISABLE_DCC_VIDEO
void DccBroker::activeVideoManage(DccDescriptor * dcc)
{
if(!dcc->bAutoAccept)
{
// need confirmation
QString tmp = __tr2qs_ctx(
"<b>%1 [%2@%3]</b> requests a<br>"
"<b>Direct Client Connection</b> in <b>VIDEO</b> mode.<br>"
"The connection target will be host <b>%4</b> on port <b>%5</b><br>",
"dcc")
.arg(dcc->szNick, dcc->szUser, dcc->szHost, dcc->szIp, dcc->szPort);
DccAcceptDialog * box = new DccAcceptDialog(this, dcc, tmp, __tr2qs_ctx("DCC VIDEO request", "dcc"));
m_pBoxList->append(box);
connect(box, SIGNAL(accepted(DccDialog *, DccDescriptor *)),
this, SLOT(activeVideoExecute(DccDialog *, DccDescriptor *)));
connect(box, SIGNAL(rejected(DccDialog *, DccDescriptor *)),
this, SLOT(cancelDcc(DccDialog *, DccDescriptor *)));
box->show();
}
else
{
// auto accept
activeVideoExecute(0, dcc);
}
}
void DccBroker::activeVideoExecute(DccDialog * box, DccDescriptor * dcc)
{
if(box)
box->forgetDescriptor();
if(!g_pApp->windowExists(dcc->console()))
{
// rebind to the first available console....
dcc->setConsole(g_pApp->activeConsole());
}
KviCString tmp(KviCString::Format, "DCC: video %s@%s:%s", dcc->szNick.toUtf8().data(), dcc->szIp.toUtf8().data(), dcc->szPort.toUtf8().data());
DccVideoWindow * v = new DccVideoWindow(dcc, tmp.ptr());
bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : (KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccVideo) || (dcc->bAutoAccept && KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccVideoWhenAutoAccepted)));
g_pMainWindow->addWindow(v, !bMinimized);
m_pDccWindowList->append(v);
#else
void DccBroker::activeVideoExecute(DccDialog *, DccDescriptor *)
{
#endif
}
//
// PASSIVE VIDEO
//
#ifndef COMPILE_DISABLE_DCC_VIDEO
void DccBroker::passiveVideoExecute(DccDescriptor * dcc)
{
KviCString tmp(KviCString::Format, "DCC: video %s@%s:%s", dcc->szNick.toUtf8().data(), dcc->szIp.toUtf8().data(), dcc->szPort.toUtf8().data());
DccVideoWindow * v = new DccVideoWindow(dcc, tmp.ptr());
//#warning "Create minimized dcc video ?... or maybe it's too much ? :)"
bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccChat);
g_pMainWindow->addWindow(v, !bMinimized);
m_pDccWindowList->append(v);
#else
void DccBroker::passiveVideoExecute(DccDescriptor *)
{
#endif
}
//
// ACTIVE CANVAS
//
#ifdef COMPILE_DCC_CANVAS
void DccBroker::activeCanvasManage(DccDescriptor * dcc)
{
if(!dcc->bAutoAccept)
{
// need confirmation
QString tmp = __tr2qs_ctx(
"<b>%1 [%2@%3]</b> requests a<br>"
"<b>Direct Client Connection</b> in <b>CANVAS</b> mode.<br>"
"The connection target will be host <b>%4</b> on port <b>%5</b><br>",
"dcc")
.arg(dcc->szNick, dcc->szUser, dcc->szHost, dcc->szIp, dcc->szPort);
DccAcceptDialog * box = new DccAcceptDialog(this, dcc, tmp, __tr2qs_ctx("DCC CANVAS request", "dcc"));
m_pBoxList->append(box);
connect(box, SIGNAL(accepted(DccDialog *, DccDescriptor *)),
this, SLOT(activeCanvasExecute(DccDialog *, DccDescriptor *)));
connect(box, SIGNAL(rejected(DccDialog *, DccDescriptor *)),
this, SLOT(cancelDcc(DccDialog *, DccDescriptor *)));
box->show();
}
else
{
// auto accept
activeCanvasExecute(0, dcc);
}
}
#endif
#ifdef COMPILE_DCC_CANVAS
void DccBroker::activeCanvasExecute(DccDialog * box, DccDescriptor * dcc)
{
if(box)
box->forgetDescriptor();
if(!g_pApp->windowExists(dcc->console()))
{
// rebind to the first available console....
dcc->setConsole(g_pApp->activeConsole());
}
KviCString tmp(KviCString::Format, "DCC: canvas %s@%s:%s", dcc->szNick.toUtf8().data(), dcc->szIp.toUtf8().data(), dcc->szPort.toUtf8().data());
DccCanvasWindow * cnv = new DccCanvasWindow(dcc, tmp.ptr());
//#warning "This option should be dedicated to Dcc Canvas!....for now we are using the DccChat options"
bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : (KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccChat) || (dcc->bAutoAccept && KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccChatWhenAutoAccepted)));
g_pMainWindow->addWindow(cnv, !bMinimized);
m_pDccWindowList->append(cnv);
#else
void DccBroker::activeCanvasExecute(DccDialog *, DccDescriptor *)
{
#endif
}
//
// PASSIVE CANVAS
//
#ifdef COMPILE_DCC_CANVAS
void DccBroker::passiveCanvasExecute(DccDescriptor * dcc)
{
KviCString tmp(KviCString::Format, "DCC: canvas %s@%s:%s", dcc->szNick.toUtf8().data(), dcc->szIp.toUtf8().data(), dcc->szPort.toUtf8().data());
DccCanvasWindow * cnv = new DccCanvasWindow(dcc, tmp.ptr());
//#warning "This option should be dedicated to Dcc Canvas!....for now we are using the DccChat options"
bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccChat);
g_pMainWindow->addWindow(cnv, !bMinimized);
m_pDccWindowList->append(cnv);
}
#endif
//
// SEND
//
void DccBroker::recvFileManage(DccDescriptor * dcc)
{
if(dcc->bIsIncomingAvatar)
{
bool bOk;
quint64 size = dcc->szFileSize.toULongLong(&bOk);
if(bOk)
{
if(size >= KVI_OPTION_UINT(KviOption_uintMaximumRequestedAvatarSize))
{
cancelDcc(nullptr, dcc);
return;
}
}
}
if(!dcc->bAutoAccept)
{
// need confirmation
QString tmp;
if(dcc->bActive)
{
// Normal active send: we will be connecting
tmp = __tr2qs_ctx(
"<b>%1 [%2@%3]</b> "
"wants to send you the file "
"'<b>%4</b>', "
"<b>%5</b> large.<br>"
"The connection target will be host <b>%6</b> on port <b>%7</b><br>",
"dcc")
.arg(dcc->szNick, dcc->szUser, dcc->szHost, dcc->szFileName)
.arg(KviQString::makeSizeReadable(dcc->szFileSize.toULongLong()))
.arg(dcc->szIp, dcc->szPort);
}
else
{
// passive: we will be listening!
tmp = __tr2qs_ctx(
"<b>%1 [%2@%3]</b> "
"wants to send you the file "
"'<b>%4</b>', "
"<b>%5</b> large.<br>"
"You will be the passive side of the connection.<br>",
"dcc")
.arg(dcc->szNick, dcc->szUser, dcc->szHost, dcc->szFileName)
.arg(KviQString::makeSizeReadable(dcc->szFileSize.toULongLong()));
}
if(dcc->bIsIncomingAvatar)
{
tmp += __tr2qs_ctx(
"<b>Note:</b>"
"The file appears to be an avatar that you have requested. "
"You should not change its filename. "
"Save it in a location where KVIrc can find it, such as "
"the 'avatars', 'incoming', or 'pics' directories, "
"your home directory, or the save directory for the incoming file type. "
"The default save path will probably work. "
"You can instruct KVIrc to accept incoming avatars automatically "
"by setting the option <tt>boolAutoAcceptIncomingAvatars</tt> to true.<br>",
"dcc");
}
//#warning "Maybe remove the pending avatar if rejected ?"
QString title = __tr2qs_ctx("DCC %1 Request - KVIrc", "dcc").arg(dcc->szType);
DccAcceptDialog * box = new DccAcceptDialog(this, dcc, tmp, title);
m_pBoxList->append(box);
connect(box, SIGNAL(accepted(DccDialog *, DccDescriptor *)),
this, SLOT(chooseSaveFileName(DccDialog *, DccDescriptor *)));
connect(box, SIGNAL(rejected(DccDialog *, DccDescriptor *)),
this, SLOT(cancelDcc(DccDialog *, DccDescriptor *)));
box->show();
}
else
{
// auto accept
if(_OUTPUT_VERBOSE)
{
dcc->console()->output(KVI_OUT_DCCMSG, __tr2qs_ctx("Auto-accepting DCC %Q request from %Q!%Q@%Q for file %Q", "dcc"),
&(dcc->szType), &(dcc->szNick), &(dcc->szUser),
&(dcc->szHost), &(dcc->szFileName));
}
chooseSaveFileName(nullptr, dcc);
}
}
void DccBroker::chooseSaveFileName(DccDialog * box, DccDescriptor * dcc)
{
if(box)
box->forgetDescriptor();
// Lookup the suggested save directory
dcc->szLocalFileName = "";
if(dcc->bIsIncomingAvatar)
g_pApp->getLocalKvircDirectory(dcc->szLocalFileName, KviApplication::Avatars);
else
{
if(KVI_OPTION_BOOL(KviOption_boolUseIncomingDccMediaTypeSavePath))
{
g_pMediaManager->lock();
if(KviMediaType * mt = g_pMediaManager->findMediaType(dcc->szFileName.toUtf8().data(), false))
{
if(mt->szSavePath.hasData())
{
if(KviFileUtils::directoryExists(mt->szSavePath.ptr()))
dcc->szLocalFileName = mt->szSavePath;
else
{
if(KviFileUtils::makeDir(mt->szSavePath.ptr()))
dcc->szLocalFileName = mt->szSavePath;
}
if(KVI_OPTION_BOOL(KviOption_boolSortReceivedByDccFilesByNicks))
{
KviQString::ensureLastCharIs(dcc->szLocalFileName, KVI_PATH_SEPARATOR_CHAR);
QString cleanNick = dcc->szNick;
KviFileUtils::cleanFileName(cleanNick);
dcc->szLocalFileName.append(cleanNick);
KviFileUtils::adjustFilePath(dcc->szLocalFileName);
}
KviFileUtils::makeDir(dcc->szLocalFileName);
}
}
g_pMediaManager->unlock();
}
if(dcc->szLocalFileName.isEmpty())
{
g_pApp->getLocalKvircDirectory(dcc->szLocalFileName, KviApplication::Incoming);
if(KVI_OPTION_BOOL(KviOption_boolSortReceivedByDccFilesByNicks))
{
KviQString::ensureLastCharIs(dcc->szLocalFileName, KVI_PATH_SEPARATOR_CHAR);
QString cleanNick = dcc->szNick;
KviFileUtils::cleanFileName(cleanNick);
dcc->szLocalFileName.append(cleanNick);
KviFileUtils::adjustFilePath(dcc->szLocalFileName);
KviFileUtils::makeDir(dcc->szLocalFileName);
}
}
}
KviFileUtils::adjustFilePath(dcc->szLocalFileName);
KviQString::ensureLastCharIs(dcc->szLocalFileName, KVI_PATH_SEPARATOR_CHAR);
if(!(dcc->bAutoAccept))
{
QString cleanFilename = dcc->szFileName;
KviFileUtils::cleanFileName(cleanFilename);
dcc->szLocalFileName.append(cleanFilename);
if(KviFileDialog::askForSaveFileName(
dcc->szLocalFileName,
__tr2qs_ctx("Choose Files to Save - KVIrc", "dcc"),
dcc->szLocalFileName,
QString(),
false,
false,
true,
g_pMainWindow))
{
renameOverwriteResume(nullptr, dcc);
}
else
{
cancelDcc(dcc);
}
}
else
{
// auto accept
// WE choose the filename
QString cleanFilename = dcc->szFileName;
KviFileUtils::cleanFileName(cleanFilename);
dcc->szLocalFileName.append(cleanFilename);
if(_OUTPUT_VERBOSE)
{
dcc->console()->output(KVI_OUT_DCCMSG, __tr2qs_ctx("Auto-saving DCC %Q file %Q as %Q", "dcc"),
&(dcc->szType), &(dcc->szFileName), &(dcc->szLocalFileName));
}
renameOverwriteResume(nullptr, dcc);
}
}
void DccBroker::renameOverwriteResume(DccDialog * box, DccDescriptor * dcc)
{
if(box)
box->forgetDescriptor();
// Check if file exists
QFileInfo fi(dcc->szLocalFileName);
if(fi.exists() && (fi.size() > 0)) // 0 byte files are senseless for us
{
dcc->szLocalFileSize.setNum(fi.size());
bool bOk;
quint64 iRemoteSize = dcc->szFileSize.toULongLong(&bOk);
if(!bOk)
iRemoteSize = 0;
if(!dcc->bAutoAccept)
{
QString tmp;
bool bDisableResume = false;
if(
(!bOk) || // remote size is unknown
(iRemoteSize > (quint64)fi.size()) // or it is larger than the actual size on disk
)
{
tmp = __tr2qs_ctx(
"The file '<b>%1</b>' already exists "
"and is <b>%2</b> large.<br>"
"Do you wish to<br>"
"<b>auto-rename</b> the new file,<br>"
"<b>overwrite</b> the existing file, or<br> "
"<b>resume</b> an incomplete download?",
"dcc")
.arg(dcc->szLocalFileName)
.arg(KviQString::makeSizeReadable(fi.size()));
}
else
{
bDisableResume = true;
// the file on disk is larger or equal to the remote one
tmp = __tr2qs_ctx(
"The file '<b>%1</b>' already exists "
"and is larger than the offered one.<br>"
"Do you wish to<br>"
"<b>auto-rename</b> the new file, or<br>"
"<b>overwrite</b> the existing file?",
"dcc")
.arg(dcc->szLocalFileName);
}
DccRenameDialog * box = new DccRenameDialog(this, dcc, tmp, bDisableResume);
m_pBoxList->append(box);
connect(box, SIGNAL(renameSelected(DccDialog *, DccDescriptor *)),
this, SLOT(renameDccSendFile(DccDialog *, DccDescriptor *)));
connect(box, SIGNAL(overwriteSelected(DccDialog *, DccDescriptor *)),
this, SLOT(recvFileExecute(DccDialog *, DccDescriptor *)));
connect(box, SIGNAL(cancelSelected(DccDialog *, DccDescriptor *)),
this, SLOT(cancelDcc(DccDialog *, DccDescriptor *)));
box->show();
return;
}
// auto resume ?
if(
KVI_OPTION_BOOL(KviOption_boolAutoResumeDccSendWhenAutoAccepted) && (bOk) && // only if the remote size is really known
(iRemoteSize > (quint64)fi.size()) && // only if the remote size is larger than the local size
(!DccFileTransfer::nonFailedTransferWithLocalFileName(dcc->szLocalFileName)) // only if there is no transfer with this local file name yet
)
{
// yep, auto resume...
dcc->bResume = true;
recvFileExecute(nullptr, dcc);
}
else if(iRemoteSize == (quint64)fi.size())
{
dcc->console()->output(KVI_OUT_DCCMSG, "Transfer aborted: file %Q already completed", &(dcc->szLocalFileName));
cancelDcc(nullptr, dcc);
}
else
{
// otherwise auto rename
renameDccSendFile(nullptr, dcc);
}
return;
}
dcc->szLocalFileSize = "0";
// everything OK
recvFileExecute(nullptr, dcc);
}
void DccBroker::renameDccSendFile(DccDialog * box, DccDescriptor * dcc)
{
if(box)
box->forgetDescriptor();
if(QFileInfo(dcc->szLocalFileName).exists())
{
KviCString szOrig = dcc->szLocalFileName;
int i = 1;
do
{
KviCString szNum;
szNum.setNum(i);
int idx = szOrig.findLastIdx('.');
if(idx != -1)
{
dcc->szLocalFileName = szOrig.left(idx);
dcc->szLocalFileName += ".";
dcc->szLocalFileName += szNum;
dcc->szLocalFileName += szOrig.right(szOrig.len() - idx);
}
else
{
dcc->szLocalFileName = szOrig;
dcc->szLocalFileName += ".";
dcc->szLocalFileName += szNum;
}
i++;
} while(QFileInfo(dcc->szLocalFileName).exists());
if(_OUTPUT_VERBOSE)
{
dcc->console()->output(KVI_OUT_DCCMSG, __tr2qs_ctx("File %s exists, auto-renaming to %Q", "dcc"),
szOrig.ptr(), &(dcc->szLocalFileName));
}
}
dcc->szLocalFileSize = "0"; // 0 for sure
recvFileExecute(nullptr, dcc);
}
void DccBroker::recvFileExecute(DccDialog * box, DccDescriptor * dcc)
{
if(box)
box->forgetDescriptor();
if(!g_pApp->windowExists(dcc->console()))
{
// rebind to the first available console....
dcc->setConsole(g_pApp->activeConsole());
}
DccFileTransfer * send = new DccFileTransfer(dcc);
bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : (KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccSend) || (dcc->bAutoAccept && KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccSendWhenAutoAccepted)));
send->invokeTransferWindow(bMinimized, bMinimized);
}
void DccBroker::sendFileManage(DccDescriptor * dcc)
{
QStringList filenames;
if(KviFileDialog::askForOpenFileNames(
filenames,
__tr2qs_ctx("Choose Files to Send - KVIrc", "dcc"),
QString(),
QString(),
false,
true,
g_pMainWindow))
{
if(filenames.count() > 0)
{
DccDescriptor * d;
QStringList::Iterator it = filenames.begin();
while(it != filenames.end())
{
d = new DccDescriptor(*dcc);
d->szLocalFileName = *(it);
d->szLocalFileName = d->szLocalFileName.trimmed();
++it;
if(d->szLocalFileName.isEmpty())
cancelDcc(d);
else
sendFileExecute(nullptr, d);
}
delete dcc;
}
}
else
{
cancelDcc(dcc);
}
}
void DccBroker::sendFileExecute(DccDialog * box, DccDescriptor * dcc)
{
if(box)
box->forgetDescriptor();
if(!g_pApp->windowExists(dcc->console()))
{
// rebind to the first available console....
dcc->setConsole(g_pApp->activeConsole());
}
QFileInfo fi(dcc->szLocalFileName);
if(!(fi.exists() && fi.isReadable() && (fi.isFile()) && (fi.size() > 0)))
{
dcc->console()->output(KVI_OUT_DCCERROR, __tr2qs_ctx("Can't open file %Q for reading", "dcc"),
&(dcc->szLocalFileName));
delete dcc;
return;
}
dcc->szFileName = dcc->szLocalFileName;
dcc->szFileName = QFileInfo(dcc->szFileName).fileName();
dcc->szLocalFileSize.setNum(fi.size());
DccFileTransfer * send = new DccFileTransfer(dcc);
bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccSend);
send->invokeTransferWindow(bMinimized, bMinimized);
}
bool DccBroker::canUnload()
{
if(m_pBoxList)
{
if((m_pBoxList->count() != 0) || (m_pDccWindowList->count() != 0) || (DccFileTransfer::transferCount() != 0))
return false;
} // else in the destructor anyway (going to die)
return true;
}
bool DccBroker::handleResumeAccepted(const char * filename, const char * port, const char * szZeroPortTag)
{
return DccFileTransfer::handleResumeAccepted(filename, port, szZeroPortTag);
}
bool DccBroker::handleResumeRequest(KviDccRequest * dcc, const char * filename, const char * port, unsigned long filePos, const char * szZeroPortTag)
{
//qDebug("HANDLE %s %s %u %s",filename,port,filePos,szZeroPortTag);
// the zeroPOrtTag is nonempty here only if port == 0
if(kvi_strEqualCI("0", port) && szZeroPortTag)
{
// zero port resume request (we have sent out a DCC SEND <filename> <fakeip> 0 <tag>
KviDccZeroPortTag * t = findZeroPortTag(QString(szZeroPortTag));
if(t)
{
// valid zero port resume request
if(filePos >= t->m_uFileSize)
return false; // invalid resume size
// ok!
t->m_uResumePosition = filePos;
KviCString szBuffy;
KviIrcServerParser::encodeCtcpParameter(filename, szBuffy);
dcc->ctcpMsg->msg->console()->connection()->sendFmtData(
"PRIVMSG %s :%cDCC ACCEPT %s %s %s %s%c",
dcc->ctcpMsg->msg->console()->connection()->encodeText(dcc->ctcpMsg->pSource->nick()).data(),
0x01,
szBuffy.ptr(),
port,
dcc->ctcpMsg->msg->console()->connection()->encodeText(QString::number(filePos)).data(),
szZeroPortTag,
0x01);
return true;
}
}
//qDebug("NOT A ZeRO PORT");
return DccFileTransfer::handleResumeRequest(filename, port, filePos);
}