1 /*
2 * Copyright (C) 2010, 2011 Daniele E. Domenichelli <daniele.domenichelli@gmail.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18 
19 
20 #include "handle-outgoing-file-transfer-channel-job.h"
21 #include "telepathy-base-job_p.h"
22 #include "ktp-fth-debug.h"
23 
24 #include <QTimer>
25 #include <QDebug>
26 #include <QUrl>
27 
28 #include <KLocalizedString>
29 #include <kio/global.h>
30 #include <kjobtrackerinterface.h>
31 
32 #include <TelepathyQt/OutgoingFileTransferChannel>
33 #include <TelepathyQt/PendingReady>
34 #include <TelepathyQt/PendingOperation>
35 #include <TelepathyQt/Contact>
36 
37 class HandleOutgoingFileTransferChannelJobPrivate : public KTp::TelepathyBaseJobPrivate
38 {
39     Q_DECLARE_PUBLIC(HandleOutgoingFileTransferChannelJob)
40 
41 public:
42     HandleOutgoingFileTransferChannelJobPrivate();
43     virtual ~HandleOutgoingFileTransferChannelJobPrivate();
44 
45     Tp::OutgoingFileTransferChannelPtr channel;
46     QFile* file;
47     QUrl uri;
48     qulonglong offset;
49 
50     void init();
51     bool kill();
52     void provideFile();
53 
54     void __k__start();
55     void __k__onInitialOffsetDefined(qulonglong offset);
56     void __k__onFileTransferChannelStateChanged(Tp::FileTransferState state, Tp::FileTransferStateChangeReason reason);
57     void __k__onFileTransferChannelTransferredBytesChanged(qulonglong count);
58     void __k__onProvideFileFinished(Tp::PendingOperation* op);
59     void __k__onCancelOperationFinished(Tp::PendingOperation* op);
60     void __k__onInvalidated();
61 };
62 
HandleOutgoingFileTransferChannelJob(Tp::OutgoingFileTransferChannelPtr channel,QObject * parent)63 HandleOutgoingFileTransferChannelJob::HandleOutgoingFileTransferChannelJob(Tp::OutgoingFileTransferChannelPtr channel,
64                                                                            QObject* parent)
65     : TelepathyBaseJob(*new HandleOutgoingFileTransferChannelJobPrivate(), parent)
66 {
67     qCDebug(KTP_FTH_MODULE);
68     Q_D(HandleOutgoingFileTransferChannelJob);
69 
70     d->channel = channel;
71     d->init();
72 }
73 
~HandleOutgoingFileTransferChannelJob()74 HandleOutgoingFileTransferChannelJob::~HandleOutgoingFileTransferChannelJob()
75 {
76     KIO::getJobTracker()->unregisterJob(this);
77     qCDebug(KTP_FTH_MODULE);
78 }
79 
start()80 void HandleOutgoingFileTransferChannelJob::start()
81 {
82     qCDebug(KTP_FTH_MODULE);
83     KIO::getJobTracker()->registerJob(this);
84     // KWidgetJobTracker has an internal timer of 500 ms, if we don't wait here
85     // when the job description is emitted it won't be ready
86     QTimer::singleShot(500, this, SLOT(__k__start()));
87 }
88 
doKill()89 bool HandleOutgoingFileTransferChannelJob::doKill()
90 {
91     qCDebug(KTP_FTH_MODULE) << "Outgoing file transfer killed.";
92     Q_D(HandleOutgoingFileTransferChannelJob);
93     return d->kill();
94 }
95 
HandleOutgoingFileTransferChannelJobPrivate()96 HandleOutgoingFileTransferChannelJobPrivate::HandleOutgoingFileTransferChannelJobPrivate()
97     : file(0),
98       offset(0)
99 {
100     qCDebug(KTP_FTH_MODULE);
101 }
102 
~HandleOutgoingFileTransferChannelJobPrivate()103 HandleOutgoingFileTransferChannelJobPrivate::~HandleOutgoingFileTransferChannelJobPrivate()
104 {
105     qCDebug(KTP_FTH_MODULE);
106 }
107 
init()108 void HandleOutgoingFileTransferChannelJobPrivate::init()
109 {
110     qCDebug(KTP_FTH_MODULE);
111     Q_Q(HandleOutgoingFileTransferChannelJob);
112 
113     if (channel.isNull()) {
114         qCritical() << "Channel cannot be NULL";
115         q->setError(KTp::NullChannel);
116         q->setErrorText(i18n("Invalid channel"));
117         QTimer::singleShot(0, q, SLOT(__k__doEmitResult()));
118         return;
119     }
120 
121     Tp::Features features = Tp::Features() << Tp::FileTransferChannel::FeatureCore;
122     if (!channel->isReady(Tp::Features() << Tp::FileTransferChannel::FeatureCore)) {
123         qCritical() << "Channel must be ready with Tp::FileTransferChannel::FeatureCore";
124         q->setError(KTp::FeatureNotReady);
125         q->setErrorText(i18n("Channel is not ready"));
126         QTimer::singleShot(0, q, SLOT(__k__doEmitResult()));
127         return;
128     }
129 
130     uri = QUrl(channel->uri());
131     if (uri.isEmpty()) {
132         qCWarning(KTP_FTH_MODULE) << "URI property missing";
133         q->setError(KTp::UriPropertyMissing);
134         q->setErrorText(i18n("URI property is missing"));
135         QTimer::singleShot(0, q, SLOT(__k__doEmitResult()));
136         return;
137     }
138     if (!uri.isLocalFile()) {
139         // TODO handle this!
140         qCWarning(KTP_FTH_MODULE) << "Not a local file";
141         q->setError(KTp::NotALocalFile);
142         q->setErrorText(i18n("This is not a local file"));
143         QTimer::singleShot(0, q, SLOT(__k__doEmitResult()));
144         return;
145     }
146 
147     q->setCapabilities(KJob::Killable);
148     q->setTotalAmount(KJob::Bytes, channel->size());
149     q->setProcessedAmountAndCalculateSpeed(0);
150 
151     q->connect(channel.data(),
152                SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)),
153                SLOT(__k__onInvalidated()));
154     q->connect(channel.data(),
155                SIGNAL(initialOffsetDefined(qulonglong)),
156                SLOT(__k__onInitialOffsetDefined(qulonglong)));
157     q->connect(channel.data(),
158                SIGNAL(stateChanged(Tp::FileTransferState,Tp::FileTransferStateChangeReason)),
159                SLOT(__k__onFileTransferChannelStateChanged(Tp::FileTransferState,Tp::FileTransferStateChangeReason)));
160     q->connect(channel.data(),
161                SIGNAL(transferredBytesChanged(qulonglong)),
162                SLOT(__k__onFileTransferChannelTransferredBytesChanged(qulonglong)));
163 }
164 
__k__start()165 void HandleOutgoingFileTransferChannelJobPrivate::__k__start()
166 {
167     qCDebug(KTP_FTH_MODULE);
168     Q_Q(HandleOutgoingFileTransferChannelJob);
169 
170     Q_ASSERT(!q->error());
171     if (q->error()) {
172         qCWarning(KTP_FTH_MODULE) << "Job was started in error state. Something wrong happened." << q->errorString();
173         QTimer::singleShot(0, q, SLOT(__k__doEmitResult()));
174         return;
175     }
176 
177     Q_EMIT q->description(q, i18n("Outgoing file transfer"),
178                           qMakePair<QString, QString>(i18n("To"), channel->targetContact()->alias()),
179                           qMakePair<QString, QString>(i18n("Filename"), channel->uri()));
180 
181     if (channel->state() == Tp::FileTransferStateAccepted) {
182         provideFile();
183     }
184 }
185 
kill()186 bool HandleOutgoingFileTransferChannelJobPrivate::kill()
187 {
188     qCDebug(KTP_FTH_MODULE);
189     Q_Q(HandleOutgoingFileTransferChannelJob);
190 
191     if (channel->state() != Tp::FileTransferStateCancelled) {
192         Tp::PendingOperation *cancelOperation = channel->cancel();
193         q->connect(cancelOperation,
194                    SIGNAL(finished(Tp::PendingOperation*)),
195                    SLOT(__k__onCancelOperationFinished(Tp::PendingOperation*)));
196     } else {
197         QTimer::singleShot(0, q, SLOT(__k__doEmitResult()));
198     }
199 
200     return true;
201 }
202 
__k__onInitialOffsetDefined(qulonglong offset)203 void HandleOutgoingFileTransferChannelJobPrivate::__k__onInitialOffsetDefined(qulonglong offset)
204 {
205     qCDebug(KTP_FTH_MODULE);
206     Q_Q(HandleOutgoingFileTransferChannelJob);
207 
208     this->offset = offset;
209     q->setProcessedAmountAndCalculateSpeed(offset);
210 }
211 
__k__onFileTransferChannelStateChanged(Tp::FileTransferState state,Tp::FileTransferStateChangeReason stateReason)212 void HandleOutgoingFileTransferChannelJobPrivate::__k__onFileTransferChannelStateChanged(Tp::FileTransferState state,
213                                                                                          Tp::FileTransferStateChangeReason stateReason)
214 {
215     qCDebug(KTP_FTH_MODULE);
216     Q_Q(HandleOutgoingFileTransferChannelJob);
217 
218     qCDebug(KTP_FTH_MODULE) << "Outgoing file transfer channel state changed to" << state << "with reason" << stateReason;
219 
220     switch (state) {
221     case Tp::FileTransferStateNone:
222         // This is bad
223         qCWarning(KTP_FTH_MODULE) << "An unknown error occurred.";
224         q->setError(KTp::TelepathyErrorError);
225         q->setErrorText(i18n("An unknown error occurred"));
226         QTimer::singleShot(0, q, SLOT(__k__doEmitResult()));
227         break;
228     case Tp::FileTransferStateCompleted:
229         qCDebug(KTP_FTH_MODULE) << "Outgoing file transfer completed";
230         Q_EMIT q->infoMessage(q, i18n("Outgoing file transfer")); // [Finished] is added automatically to the notification
231         QTimer::singleShot(0, q, SLOT(__k__doEmitResult()));
232         break;
233     case Tp::FileTransferStateCancelled:
234         q->setError(KTp::FileTransferCancelled);
235         q->setErrorText(i18n("Outgoing file transfer was canceled."));
236         q->kill(KJob::Quietly);
237         break;
238     case Tp::FileTransferStateAccepted:
239         provideFile();
240         break;
241     case Tp::FileTransferStatePending:
242     case Tp::FileTransferStateOpen:
243     default:
244         break;
245     }
246 }
247 
provideFile()248 void HandleOutgoingFileTransferChannelJobPrivate::provideFile()
249 {
250     qCDebug(KTP_FTH_MODULE);
251     Q_Q(HandleOutgoingFileTransferChannelJob);
252 
253     file = new QFile(uri.toLocalFile(), q->parent());
254     qCDebug(KTP_FTH_MODULE) << "Providing file" << file->fileName();
255 
256     Tp::PendingOperation* provideFileOperation = channel->provideFile(file);
257     q->connect(provideFileOperation,
258                SIGNAL(finished(Tp::PendingOperation*)),
259                SLOT(__k__onProvideFileFinished(Tp::PendingOperation*)));
260 }
261 
__k__onFileTransferChannelTransferredBytesChanged(qulonglong count)262 void HandleOutgoingFileTransferChannelJobPrivate::__k__onFileTransferChannelTransferredBytesChanged(qulonglong count)
263 {
264     qCDebug(KTP_FTH_MODULE);
265     Q_Q(HandleOutgoingFileTransferChannelJob);
266 
267     qCDebug(KTP_FTH_MODULE).nospace() << "Sending " << channel->fileName() << " - "
268                        << "Transferred bytes = " << offset + count << " ("
269                        << ((int)(((double)(offset + count) / channel->size()) * 100)) << "% done)";
270     q->setProcessedAmountAndCalculateSpeed(offset + count);
271 }
272 
__k__onProvideFileFinished(Tp::PendingOperation * op)273 void HandleOutgoingFileTransferChannelJobPrivate::__k__onProvideFileFinished(Tp::PendingOperation* op)
274 {
275     // This method is called when the "provideFile" operation is finished,
276     // therefore the file was not sent yet.
277     qCDebug(KTP_FTH_MODULE);
278     Q_Q(HandleOutgoingFileTransferChannelJob);
279 
280     if (op->isError()) {
281         qCWarning(KTP_FTH_MODULE) << "Unable to provide file - " << op->errorName() << ":" << op->errorMessage();
282         q->setError(KTp::ProvideFileError);
283         q->setErrorText(i18n("Cannot provide file"));
284         QTimer::singleShot(0, q, SLOT(__k__doEmitResult()));
285     }
286 }
287 
__k__onCancelOperationFinished(Tp::PendingOperation * op)288 void HandleOutgoingFileTransferChannelJobPrivate::__k__onCancelOperationFinished(Tp::PendingOperation* op)
289 {
290     qCDebug(KTP_FTH_MODULE);
291     Q_Q(HandleOutgoingFileTransferChannelJob);
292 
293     if (op->isError()) {
294         qCWarning(KTP_FTH_MODULE) << "Unable to cancel file transfer - " << op->errorName() << ":" << op->errorMessage();
295         q->setError(KTp::CancelFileTransferError);
296         q->setErrorText(i18n("Cannot cancel outgoing file transfer"));
297     }
298 
299     qCDebug(KTP_FTH_MODULE) << "File transfer cancelled";
300     QTimer::singleShot(0, q, SLOT(__k__doEmitResult()));
301 }
302 
__k__onInvalidated()303 void HandleOutgoingFileTransferChannelJobPrivate::__k__onInvalidated()
304 {
305     qCDebug(KTP_FTH_MODULE);
306     Q_Q(HandleOutgoingFileTransferChannelJob);
307 
308     qCWarning(KTP_FTH_MODULE) << "File transfer invalidated!" << channel->invalidationMessage() << "reason" << channel->invalidationReason();
309     Q_EMIT q->infoMessage(q, i18n("File transfer invalidated. %1", channel->invalidationMessage()));
310 
311     QTimer::singleShot(0, q, SLOT(__k__doEmitResult()));
312 }
313 
314 #include "moc_handle-outgoing-file-transfer-channel-job.cpp"
315