- reformat tranfser/send/receive.h/cpp (kill millions of wrong spaces)
[konversation:konversation.git] / src / dcc / transferrecv.cpp
1 /*
2   receive a file on DCC protocol
3   begin:     Mit Aug 7 2002
4   copyright: (C) 2002 by Dario Abatianni
5   email:     eisfuchs@tigress.com
6 */
7 /*
8   Copyright (C) 2004-2007 Shintaro Matsuoka <shin@shoegazed.org>
9   Copyright (C) 2004,2005 John Tapsell <john@geola.co.uk>
10   Copyright (C) 2009 Michael Kreitzer <mrgrim@gr1m.org>
11   Copyright (C) 2009 Bernd Buschinski <b.buschinski@web.de>
12 */
13 /*
14   This program is free software; you can redistribute it and/or modify
15   it under the terms of the GNU General Public License as published by
16   the Free Software Foundation; either version 2 of the License, or
17   (at your option) any later version.
18 */
19
20 #include "transferrecv.h"
21 #include "dcccommon.h"
22 #include "transfermanager.h"
23 #include "application.h"
24 #include "connectionmanager.h"
25 #include "server.h"
26 #include "upnprouter.h"
27
28 #include <QDateTime>
29 #include <QTcpSocket>
30 #include <QTcpServer>
31 #include <QFileInfo>
32
33 #include <KUser>
34 #include <KAuthorized>
35 #include <KIO/Job>
36 #include <KIO/NetAccess>
37
38 /*
39  *flow chart*
40
41  TransferRecv()
42
43  start()              : called from TransferPanel when user pushes the accept button
44   | \
45   | requestResume()   : called when user chooses to resume in ResumeDialog. it emits the signal ResumeRequest()
46   |
47   | startResume()     : called by "Server"
48   | |
49 connectToSender()
50
51 connectionSuccess()  : called by recvSocket
52
53 */
54
55 namespace Konversation
56 {
57     namespace DCC
58     {
59         TransferRecv::TransferRecv(QObject *parent)
60             : Transfer(Transfer::Receive, parent)
61         {
62             kDebug();
63
64             m_serverSocket = 0;
65             m_recvSocket = 0;
66             m_writeCacheHandler = 0;
67
68             m_connectionTimer = new QTimer(this);
69             m_connectionTimer->setSingleShot(true);
70             connect(m_connectionTimer, SIGNAL(timeout()), this, SLOT(connectionTimeout()));
71             //timer hasn't started yet.  qtimer will be deleted automatically when 'this' object is deleted
72         }
73
74         TransferRecv::~TransferRecv()
75         {
76             kDebug();
77             cleanUp();
78         }
79
80         void TransferRecv::cleanUp()
81         {
82             kDebug();
83
84             stopConnectionTimer();
85             disconnect(m_connectionTimer, 0, 0, 0);
86
87             finishTransferLogger();
88             if (m_serverSocket)
89             {
90                 m_serverSocket->close();
91                 m_serverSocket = 0;
92
93                 if (m_reverse && Preferences::self()->dccUPnP())
94                 {
95                     UPnP::UPnPRouter *router = Application::instance()->getDccTransferManager()->getUPnPRouter();
96                     if (router)
97                     {
98                         router->undoForward(m_ownPort, QAbstractSocket::TcpSocket);
99                     }
100                 }
101             }
102             if (m_recvSocket)
103             {
104                 disconnect(m_recvSocket, 0, 0, 0);
105                 m_recvSocket->close();
106                 m_recvSocket = 0;                         // the instance will be deleted automatically by its parent
107             }
108             if (m_writeCacheHandler)
109             {
110                 m_writeCacheHandler->closeNow();
111                 m_writeCacheHandler->deleteLater();
112                 m_writeCacheHandler = 0;
113             }
114             Transfer::cleanUp();
115         }
116
117         void TransferRecv::setPartnerIp(const QString &ip)
118         {
119             if (getStatus() == Configuring)
120             {
121                 m_partnerIp = ip;
122             }
123         }
124
125         void TransferRecv::setPartnerPort(quint16 port)
126         {
127             if (getStatus() == Configuring)
128             {
129                 m_partnerPort = port;
130             }
131         }
132
133         void TransferRecv::setFileSize(quint64 fileSize)
134         {
135             if (getStatus() == Configuring)
136             {
137                 m_fileSize = fileSize;
138             }
139         }
140
141         void TransferRecv::setFileName(const QString &fileName)
142         {
143             if (getStatus() == Configuring)
144             {
145                 m_fileName = fileName;
146                 m_saveFileName = m_fileName;
147             }
148         }
149
150         void TransferRecv::setFileURL(const KUrl &url)
151         {
152             if (getStatus() == Preparing || getStatus() == Configuring || getStatus() == Queued)
153             {
154                 m_fileURL = url;
155                 m_saveFileName = url.fileName();
156             }
157         }
158
159         void TransferRecv::setReverse(bool reverse, const QString &reverseToken)
160         {
161             if (getStatus() == Configuring)
162             {
163                 m_reverse = reverse;
164                 if (reverse)
165                 {
166                     m_partnerPort = 0;
167                     m_reverseToken = reverseToken;
168                 }
169             }
170         }
171
172         bool TransferRecv::queue()
173         {
174             kDebug();
175
176             if (getStatus() != Configuring)
177             {
178                 return false;
179             }
180
181             if (m_partnerIp.isEmpty())
182             {
183                 return false;
184             }
185
186             if (m_ownIp.isEmpty())
187             {
188                 m_ownIp = DccCommon::getOwnIp(Application::instance()->getConnectionManager()->getServerByConnectionId(m_connectionId));
189             }
190
191             if (!KAuthorized::authorizeKAction("allow_downloading"))
192             {
193                 //note we have this after the initialisations so that item looks okay
194                 //Do not have the rights to send the file.  Shouldn't have gotten this far anyway
195                 failed(i18n("The admin has restricted the right to receive files"));
196                 return false;
197             }
198
199             // check if the sender IP is valid
200             if (m_partnerIp == "0.0.0.0")
201             {
202                 failed(i18n("Invalid sender address (%1)", m_partnerIp));
203                 return false;
204             }
205
206             // TODO: should we support it?
207             if (m_fileSize == 0)
208             {
209                 failed(i18n("Unsupported negotiation (filesize=0)"));
210                 return false;
211             }
212
213             if (m_fileName.isEmpty())
214             {
215                 m_fileName = "unnamed_file_" + QDateTime::currentDateTime().toString(Qt::ISODate).remove(':');
216                 m_saveFileName = m_fileName;
217             }
218
219             if (m_fileURL.isEmpty())
220             {
221                 // determine default incoming file URL
222
223                 // set default folder
224                 if (!Preferences::self()->dccPath().isEmpty())
225                 {
226                     m_fileURL = KUrl(Preferences::self()->dccPath());
227                 }
228                 else
229                 {
230                     m_fileURL.setPath(KUser(KUser::UseRealUserID).homeDir());  // default folder is *not* specified
231                 }
232
233                 // add a slash if there is none
234                 m_fileURL.adjustPath(KUrl::AddTrailingSlash);
235
236                 // Append folder with partner's name if wanted
237                 if (Preferences::self()->dccCreateFolder())
238                 {
239                     m_fileURL.addPath(m_partnerNick + '/');
240                 }
241
242                 // Just incase anyone tries to do anything nasty
243                 QString fileNameSanitized = sanitizeFileName(m_saveFileName);
244
245                 // Append partner's name to file name if wanted
246                 if (Preferences::self()->dccAddPartner())
247                 {
248                     m_fileURL.addPath(m_partnerNick + '.' + fileNameSanitized);
249                 }
250                 else
251                 {
252                     m_fileURL.addPath(fileNameSanitized);
253                 }
254             }
255
256             return Transfer::queue();
257         }
258
259         void TransferRecv::abort()                     // public slot
260         {
261             kDebug();
262
263             if (getStatus() == Transfer::Queued)
264             {
265                 Server *server = Application::instance()->getConnectionManager()->getServerByConnectionId(m_connectionId);
266                 if (server)
267                 {
268                     server->dccRejectSend(m_partnerNick, transferFileName(m_fileName));
269                 }
270             }
271
272             if(m_writeCacheHandler)
273             {
274                 m_writeCacheHandler->write(true);       // flush
275             }
276
277             cleanUp();
278             setStatus(Aborted);
279             emit done(this);
280         }
281
282         void TransferRecv::start()                     // public slot
283         {
284             kDebug() << "[BEGIN]";
285
286             if (getStatus() != Queued)
287             {
288                 return;
289             }
290
291             setStatus(Preparing);
292
293             prepareLocalKio(false, false);
294
295             kDebug() << "[END]";
296         }
297
298         void TransferRecv::prepareLocalKio(bool overwrite, bool resume, KIO::fileoffset_t startPosition)
299         {
300             kDebug()
301                 << "URL: " << m_fileURL << endl
302                 << "Overwrite: " << overwrite << endl
303                 << "Resume: " << resume << " (Position: " << startPosition << ")";
304
305             m_resumed = resume;
306             m_transferringPosition = startPosition;
307
308             if (!createDirs(m_fileURL.upUrl()))
309             {
310                 askAndPrepareLocalKio(i18n("<b>Cannot create the folder.</b><br/>"
311                     "Folder: %1<br/>",
312                     m_fileURL.upUrl().prettyUrl()),
313                     ResumeDialog::RA_Rename | ResumeDialog::RA_Cancel,
314                     ResumeDialog::RA_Rename);
315                 return;
316             }
317
318             if (Application::instance()->getDccTransferManager()->isLocalFileInWritingProcess(m_fileURL))
319             {
320                 askAndPrepareLocalKio(i18n("<b>The file is used by another transfer.</b><br/>"
321                     "%1<br/>",
322                     m_fileURL.prettyUrl()),
323                     ResumeDialog::RA_Rename | ResumeDialog::RA_Cancel,
324                     ResumeDialog::RA_Rename);
325                 return;
326             }
327
328             KIO::JobFlags flags;
329             if(overwrite)
330             {
331                 flags |= KIO::Overwrite;
332             }
333             if(m_resumed)
334             {
335                 flags |= KIO::Resume;
336             }
337             //for now, maybe later
338             flags |= KIO::HideProgressInfo;
339             KIO::TransferJob *transferJob = KIO::put(m_fileURL, -1, flags);
340
341             if (!transferJob)
342             {
343                 kDebug() << "KIO::put() returned NULL. what happened?";
344                 failed(i18n("Could not create a KIO instance"));
345                 return;
346             }
347
348             transferJob->setAutoDelete(true);
349             connect(transferJob, SIGNAL(canResume(KIO::Job*, KIO::filesize_t)), this, SLOT(slotLocalCanResume(KIO::Job*, KIO::filesize_t)));
350             connect(transferJob, SIGNAL(result(KJob*)), this, SLOT(slotLocalGotResult(KJob*)));
351             connect(transferJob, SIGNAL(dataReq(KIO::Job*, QByteArray&)), this, SLOT(slotLocalReady(KIO::Job*)));
352         }
353
354         void TransferRecv::askAndPrepareLocalKio(const QString &message, int enabledActions, ResumeDialog::ReceiveAction defaultAction, KIO::fileoffset_t startPosition)
355         {
356             switch (ResumeDialog::ask(this, message, enabledActions, defaultAction))
357             {
358                 case ResumeDialog::RA_Resume:
359                     prepareLocalKio(false, true, startPosition);
360                     break;
361                 case ResumeDialog::RA_Overwrite:
362                     prepareLocalKio(true, false);
363                     break;
364                 case ResumeDialog::RA_Rename:
365                     prepareLocalKio(false, false);
366                     break;
367                 case ResumeDialog::RA_Cancel:
368                 default:
369                     setStatus(Queued);
370             }
371         }
372
373         bool TransferRecv::createDirs(const KUrl &dirURL) const
374         {
375             KUrl kurl(dirURL);
376             QString surl = kurl.url();
377
378             //First we split directories until we reach to the top,
379             //since we need to create directories one by one
380
381             QStringList dirList;
382             while (surl != kurl.upUrl().url())
383             {
384                 dirList.prepend(surl);
385                 kurl = kurl.upUrl();
386                 surl = kurl.url();
387             }
388
389             //Now we create the directories
390
391             QStringList::ConstIterator it;
392             for (it=dirList.constBegin(); it != dirList.constEnd(); ++it)
393             {
394                 if (!KIO::NetAccess::exists(*it, KIO::NetAccess::SourceSide, NULL))
395                 {
396                     if (!KIO::NetAccess::mkdir(*it, NULL, -1))
397                     {
398                         return false;
399                     }
400                 }
401             }
402
403             return true;
404         }
405
406         void TransferRecv::slotLocalCanResume(KIO::Job *job, KIO::filesize_t size)
407         {
408             kDebug() << "[BEGIN]" << endl
409                 << "size: " << size;
410
411             KIO::TransferJob* transferJob = dynamic_cast<KIO::TransferJob*>(job);
412             if (!transferJob)
413             {
414                 kDebug() << "not a TransferJob? returning";
415                 return;
416             }
417
418             if (size != 0)
419             {
420                 disconnect(transferJob, 0, 0, 0);
421                 if (Preferences::self()->dccAutoResume())
422                 {
423                     prepareLocalKio(false, true, size);
424                 }
425                 else
426                 {
427                     askAndPrepareLocalKio(i18np(
428                         "<b>A partial file exists:</b><br/>"
429                         "%2<br/>"
430                         "Size of the partial file: 1 byte.<br/>",
431                         "<b>A partial file exists:</b><br/>"
432                         "%2<br/>"
433                         "Size of the partial file: %1 bytes.<br/>",
434                         size,
435                         m_fileURL.prettyUrl()),
436                         ResumeDialog::RA_Resume | ResumeDialog::RA_Overwrite | ResumeDialog::RA_Rename | ResumeDialog::RA_Cancel,
437                         ResumeDialog::RA_Resume,
438                         size);
439                 }
440                 transferJob->putOnHold();
441             }
442
443             kDebug() << "[END]";
444         }
445
446         void TransferRecv::slotLocalGotResult(KJob *job)
447         {
448             kDebug() << "[BEGIN]";
449
450             KIO::TransferJob* transferJob = static_cast<KIO::TransferJob*>(job);
451             disconnect(transferJob, 0, 0, 0);
452
453             switch (transferJob->error())
454             {
455                 case 0:                                   // no error
456                     kDebug() << "TransferRecv::slotLocalGotResult(): job->error() returned 0." << endl
457                         << "TransferRecv::slotLocalGotResult(): Why was I called in spite of no error?";
458                     break;
459                 case KIO::ERR_FILE_ALREADY_EXIST:
460                     askAndPrepareLocalKio(i18nc("%1=fileName, %2=local filesize, %3=sender filesize",
461                                                 "<b>The file already exists.</b><br/>"
462                                                 "%1 (%2)<br/>"
463                                                 "Sender reports file size of %3<br/>",
464                                                 m_fileURL.prettyUrl(), KIO::convertSize(QFileInfo(m_fileURL.path()).size()),
465                                                 KIO::convertSize(m_fileSize)),
466                                           ResumeDialog::RA_Overwrite | ResumeDialog::RA_Rename | ResumeDialog::RA_Cancel,
467                                           ResumeDialog::RA_Overwrite);
468                     break;
469                 default:
470                     askAndPrepareLocalKio(i18n("<b>Could not open the file.<br/>"
471                         "Error: %1</b><br/>"
472                         "%2<br/>",
473                         transferJob->error(),
474                         m_fileURL.prettyUrl()),
475                         ResumeDialog::RA_Rename | ResumeDialog::RA_Cancel,
476                         ResumeDialog::RA_Rename);
477             }
478
479             kDebug() << "[END]";
480         }
481
482         void TransferRecv::slotLocalReady(KIO::Job *job)
483         {
484             kDebug();
485
486             KIO::TransferJob* transferJob = static_cast<KIO::TransferJob*>(job);
487
488             disconnect(transferJob, 0, 0, 0);           // WriteCacheHandler will control the job after this
489
490             m_writeCacheHandler = new TransferRecvWriteCacheHandler(transferJob);
491
492             connect(m_writeCacheHandler, SIGNAL(done()), this, SLOT(slotLocalWriteDone()));
493             connect(m_writeCacheHandler, SIGNAL(gotError(const QString&)), this, SLOT(slotLocalGotWriteError(const QString&)));
494
495             if (!m_resumed)
496             {
497                 connectWithSender();
498             }
499             else
500             {
501                 requestResume();
502             }
503         }
504
505         void TransferRecv::connectWithSender()
506         {
507             if (m_reverse)
508             {
509                 if (!startListeningForSender())
510                 {
511                     return;
512                 }
513
514                 Server *server = Application::instance()->getConnectionManager()->getServerByConnectionId(m_connectionId);
515                 if (!server)
516                 {
517                     failed(i18n("Could not send Reverse DCC SEND acknowledgement to the partner via the IRC server."));
518                     return;
519                 }
520
521                 m_ownIp = DccCommon::getOwnIp(server);
522                 m_ownPort = m_serverSocket->serverPort();
523
524                 if (Preferences::self()->dccUPnP())
525                 {
526                     UPnP::UPnPRouter *router = Application::instance()->getDccTransferManager()->getUPnPRouter();
527
528                     if (router && router->forward(QHostAddress(server->getOwnIpByNetworkInterface()), m_ownPort, QAbstractSocket::TcpSocket))
529                     {
530                         connect(router, SIGNAL(forwardComplete(bool, quint16)), this, SLOT(sendReverseAck(bool, quint16)));
531                     }
532                     else
533                     {
534                         sendReverseAck(true, 0); // Try anyways on error
535                     }
536                 }
537                 else
538                 {
539                     sendReverseAck(false, 0);
540                 }
541             }
542             else
543             {
544                 connectToSendServer();
545             }
546         }
547
548         void TransferRecv::sendReverseAck(bool error, quint16 port)
549         {
550             Server *server = Application::instance()->getConnectionManager()->getServerByConnectionId(m_connectionId);
551             if (!server)
552             {
553                 failed(i18n("Could not send Reverse DCC SEND acknowledgement to the partner via the IRC server."));
554                 return;
555             }
556
557             kDebug() << "sendReverseAck()" << endl;
558
559             if (Preferences::self()->dccUPnP() && this->sender())
560             {
561                 if (port != m_ownPort) return; // Somebody elses forward succeeded
562
563                 disconnect (this->sender(), SIGNAL(forwardComplete(bool, quint16)), this, SLOT(sendRequest(bool, quint16)));
564
565                 if (error)
566                 {
567                     server->appendMessageToFrontmost(i18nc("Universal Plug and Play", "UPnP"), i18n("Failed to forward port <numid>%1</numid>. Sending DCC request to remote user regardless.", m_ownPort), false);
568                 }
569             }
570
571             setStatus(WaitingRemote, i18n("Waiting for connection"));
572
573             server->dccReverseSendAck(m_partnerNick, transferFileName(m_fileName), DccCommon::textIpToNumericalIp(m_ownIp), m_ownPort, m_fileSize, m_reverseToken);
574         }
575
576         void TransferRecv::requestResume()
577         {
578             kDebug();
579
580             setStatus(WaitingRemote, i18n("Waiting for remote host's acceptance"));
581
582             startConnectionTimer(30);
583
584             kDebug() << "Requesting resume for " << m_partnerNick << " file " << m_fileName << " partner " << m_partnerPort;
585
586             Server *server = Application::instance()->getConnectionManager()->getServerByConnectionId(m_connectionId);
587             if (!server)
588             {
589                 kDebug() << "Could not retrieve the instance of Server. Connection id: " << m_connectionId;
590                 failed(i18n("Could not send DCC RECV resume request to the partner via the IRC server."));
591                 return;
592             }
593
594             if (m_reverse)
595             {
596                 server->dccPassiveResumeGetRequest(m_partnerNick, transferFileName(m_fileName), m_partnerPort, m_transferringPosition, m_reverseToken);
597             }
598             else
599             {
600                 server->dccResumeGetRequest(m_partnerNick, transferFileName(m_fileName), m_partnerPort, m_transferringPosition);
601             }
602         }
603
604                                                           // public slot
605         void TransferRecv::startResume(quint64 position)
606         {
607             kDebug() << "Position:" << position;
608
609             stopConnectionTimer();
610
611             if ((quint64)m_transferringPosition != position)
612             {
613                 kDebug() << "TransferRecv::startResume(): remote responsed an unexpected position"<< endl
614                     << "TransferRecv::startResume(): expected: " << m_transferringPosition << endl
615                     << "TransferRecv::startResume(): remote response: " << position;
616                 failed(i18n("Unexpected response from remote host"));
617                 return;
618             }
619
620             connectWithSender();
621         }
622
623         void TransferRecv::connectToSendServer()
624         {
625             kDebug();
626
627             // connect to sender
628
629             setStatus(Connecting);
630
631             startConnectionTimer(30);
632
633             m_recvSocket = new QTcpSocket(this);
634
635             connect(m_recvSocket, SIGNAL(connected()), this, SLOT(startReceiving()));
636             connect(m_recvSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectionFailed(QAbstractSocket::SocketError)));
637
638             kDebug() << "Attempting to connect to " << m_partnerIp << ":" << m_partnerPort;
639
640             m_recvSocket->connectToHost(m_partnerIp, m_partnerPort);
641         }
642
643         bool TransferRecv::startListeningForSender()
644         {
645             // Set up server socket
646             QString failedReason;
647             if (Preferences::self()->dccSpecificSendPorts())
648             {
649                 m_serverSocket = DccCommon::createServerSocketAndListen(this, &failedReason, Preferences::self()->dccSendPortsFirst(), Preferences::self()->dccSendPortsLast());
650             }
651             else
652             {
653                 m_serverSocket = DccCommon::createServerSocketAndListen(this, &failedReason);
654             }
655
656             if (!m_serverSocket)
657             {
658                 failed(failedReason);
659                 return false;
660             }
661
662             connect(m_serverSocket, SIGNAL(newConnection()), this, SLOT(slotServerSocketReadyAccept()));
663
664             startConnectionTimer(30);
665
666             return true;
667         }
668
669         void TransferRecv::slotServerSocketReadyAccept()
670         {
671             //reverse dcc
672
673             m_recvSocket = m_serverSocket->nextPendingConnection();
674             if (!m_recvSocket)
675             {
676                 failed(i18n("Could not accept the connection (socket error)."));
677                 return;
678             }
679
680             connect(m_recvSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectionFailed(QAbstractSocket::SocketError)));
681
682             // we don't need ServerSocket anymore
683             m_serverSocket->close();
684             m_serverSocket = 0; // Will be deleted by parent
685
686             if (Preferences::self()->dccUPnP())
687             {
688                 UPnP::UPnPRouter *router = Application::instance()->getDccTransferManager()->getUPnPRouter();
689                 if (router)
690                 {
691                     router->undoForward(m_ownPort, QAbstractSocket::TcpSocket);
692                 }
693             }
694
695             startReceiving();
696         }
697
698         void TransferRecv::startReceiving()
699         {
700             kDebug();
701             stopConnectionTimer();
702
703             connect(m_recvSocket, SIGNAL(readyRead()), this, SLOT(readData()));
704
705             m_transferStartPosition = m_transferringPosition;
706
707             //we don't need the original filename anymore, overwrite it to display the correct one in transfermanager/panel
708             m_fileName = m_saveFileName;
709
710             m_ownPort = m_recvSocket->localPort();
711
712             startTransferLogger();                          // initialize CPS counter, ETA counter, etc...
713
714             setStatus(Transferring);
715         }
716
717                                                           // slot
718         void TransferRecv::connectionFailed(QAbstractSocket::SocketError errorCode)
719         {
720             kDebug() << "Code = " << errorCode << ", string = " << m_recvSocket->errorString();
721             failed(m_recvSocket->errorString());
722         }
723
724         void TransferRecv::readData()                  // slot
725         {
726             //kDebug();
727             qint64 actual = m_recvSocket->read(m_buffer, m_bufferSize);
728             if (actual > 0)
729             {
730                 //actual is the size we read in, and is guaranteed to be less than m_bufferSize
731                 m_transferringPosition += actual;
732                 m_writeCacheHandler->append(m_buffer, actual);
733                 m_writeCacheHandler->write(false);
734                 //in case we could not read all the data, leftover data could get lost
735                 if (m_recvSocket->bytesAvailable() > 0)
736                 {
737                     readData();
738                 }
739                 else
740                 {
741                     sendAck();
742                 }
743             }
744         }
745
746         void TransferRecv::sendAck()                   // slot
747         {
748             //kDebug() << m_transferringPosition << "/" << (KIO::fileoffset_t)m_fileSize;
749
750             //It is bound to be 32bit according to dcc specs, -> 4GB limit.
751             //But luckily no client ever reads this value,
752             //except for old mIRC versions, but they couldn't send or receive files over 4GB anyway.
753             //Note: The resume and filesize are set via dcc send command and can be over 4GB
754
755             quint32 pos = intel((quint32)m_transferringPosition);
756
757             m_recvSocket->write((char*)&pos, 4);
758             if (m_transferringPosition == (KIO::fileoffset_t)m_fileSize)
759             {
760                 kDebug() << "Sent final ACK.";
761                 disconnect(m_recvSocket, 0, 0, 0);
762                 m_writeCacheHandler->close();             // WriteCacheHandler will send the signal done()
763             }
764             else if (m_transferringPosition > (KIO::fileoffset_t)m_fileSize)
765             {
766                 kDebug() << "The remote host sent larger data than expected: " << m_transferringPosition;
767                 failed(i18n("Transfer error"));
768             }
769         }
770
771         void TransferRecv::slotLocalWriteDone()        // <-WriteCacheHandler::done()
772         {
773             kDebug();
774             cleanUp();
775             setStatus(Done);
776             emit done(this);
777         }
778
779                                                           // <- WriteCacheHandler::gotError()
780         void TransferRecv::slotLocalGotWriteError(const QString &errorString)
781         {
782             kDebug();
783             failed(i18n("KIO error: %1", errorString));
784         }
785
786         void TransferRecv::startConnectionTimer(int secs)
787         {
788             kDebug();
789             m_connectionTimer->start(secs * 1000);
790         }
791
792         void TransferRecv::stopConnectionTimer()
793         {
794             if (m_connectionTimer->isActive())
795             {
796                 m_connectionTimer->stop();
797                 kDebug();
798             }
799         }
800
801         void TransferRecv::connectionTimeout()         // slot
802         {
803             kDebug();
804             failed(i18n("Timed out"));
805         }
806
807         // WriteCacheHandler
808
809         TransferRecvWriteCacheHandler::TransferRecvWriteCacheHandler(KIO::TransferJob *transferJob)
810             : m_transferJob(transferJob)
811         {
812             m_writeReady = true;
813             m_cacheStream = 0;
814
815             connect(m_transferJob, SIGNAL(dataReq(KIO::Job*, QByteArray&)), this, SLOT(slotKIODataReq(KIO::Job*, QByteArray&)));
816             connect(m_transferJob, SIGNAL(result(KJob*)), this, SLOT(slotKIOResult(KJob*)));
817
818             m_transferJob->setAsyncDataEnabled(m_writeAsyncMode = true);
819         }
820
821         TransferRecvWriteCacheHandler::~TransferRecvWriteCacheHandler()
822         {
823             closeNow();
824         }
825
826                                                           // public
827         void TransferRecvWriteCacheHandler::append(char *data, int size)
828         {
829             // sendAsyncData() and dataReq() cost a lot of time, so we should pack some caches.
830
831             static const int maxWritePacketSize = 1 * 1024 * 1024; // 1meg
832
833             if (m_cacheList.isEmpty() || m_cacheList.back().size() + size > maxWritePacketSize)
834             {
835                 m_cacheList.append(QByteArray());
836                 delete m_cacheStream;
837                 m_cacheStream = new QDataStream(&m_cacheList.back(), QIODevice::WriteOnly);
838             }
839
840             m_cacheStream->writeRawData(data, size);
841         }
842
843                                                           // public
844         bool TransferRecvWriteCacheHandler::write(bool force)
845         {
846             // force == false: return without doing anything when the whole cache size is smaller than maxWritePacketSize
847
848             if (m_cacheList.isEmpty() || !m_transferJob || !m_writeReady || !m_writeAsyncMode)
849             {
850                 return false;
851             }
852
853             if (!force && m_cacheList.count() < 2)
854             {
855                 return false;
856             }
857
858             // do write
859             m_writeReady = false;
860
861             m_transferJob->sendAsyncData(m_cacheList.front());
862             //kDebug() << "wrote " << m_cacheList.front().size() << " bytes.";
863             m_cacheList.pop_front();
864
865             return true;
866         }
867
868         void TransferRecvWriteCacheHandler::close()    // public
869         {
870             kDebug();
871             write(true);                                // write once if kio is ready to write
872             m_transferJob->setAsyncDataEnabled(m_writeAsyncMode = false);
873             kDebug() << "switched to synchronized mode.";
874             kDebug() << "flushing... (remaining caches: " << m_cacheList.count() << ")";
875         }
876
877         void TransferRecvWriteCacheHandler::closeNow() // public
878         {
879             write(true);                                // flush
880             if (m_transferJob)
881             {
882                 m_transferJob->kill();
883                 m_transferJob = 0;
884             }
885             m_cacheList.clear();
886             delete m_cacheStream;
887             m_cacheStream = 0;
888         }
889
890         void TransferRecvWriteCacheHandler::slotKIODataReq(KIO::Job *job, QByteArray &data)
891         {
892             Q_UNUSED(job);
893
894             // We are in writeAsyncMode if there is more data to be read in from dcc
895             if (m_writeAsyncMode)
896             {
897                 m_writeReady = true;
898             }
899             else
900             {
901                 // No more data left to read from incoming dcctransfer
902                 if (!m_cacheList.isEmpty())
903                 {
904                     // once we write everything in cache, the file is complete.
905                     // This function will be called once more after this last data is written.
906                     data = m_cacheList.front();
907                     kDebug() << "will write " << m_cacheList.front().size() << " bytes.";
908                     m_cacheList.pop_front();
909                 }
910                 else
911                 {
912                     // finally, no data left to write or read.
913                     kDebug() << "flushing done.";
914                     m_transferJob = 0;
915                     emit done();                          // -> TransferRecv::slotLocalWriteDone()
916                 }
917             }
918         }
919
920         void TransferRecvWriteCacheHandler::slotKIOResult(KJob *job)
921         {
922             Q_ASSERT(m_transferJob);
923
924             disconnect(m_transferJob, 0, 0, 0);
925             m_transferJob = 0;
926
927             if (job->error())
928             {
929                 QString errorString = job->errorString();
930                 closeNow();
931                 emit gotError(errorString);             // -> TransferRecv::slotLocalGotWriteError()
932             }
933         }
934
935     }
936 }
937
938 #include "transferrecv.moc"