96d8ea85a2774803b1c4521b78a380a6c41bfb03
[profile/ivi/qtbase.git] / examples / network / torrent / filemanager.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the examples of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:BSD$
9 ** You may use this file under the terms of the BSD license as follows:
10 **
11 ** "Redistribution and use in source and binary forms, with or without
12 ** modification, are permitted provided that the following conditions are
13 ** met:
14 **   * Redistributions of source code must retain the above copyright
15 **     notice, this list of conditions and the following disclaimer.
16 **   * Redistributions in binary form must reproduce the above copyright
17 **     notice, this list of conditions and the following disclaimer in
18 **     the documentation and/or other materials provided with the
19 **     distribution.
20 **   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
21 **     of its contributors may be used to endorse or promote products derived
22 **     from this software without specific prior written permission.
23 **
24 **
25 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40
41 #include "filemanager.h"
42 #include "metainfo.h"
43
44 #include <QByteArray>
45 #include <QDir>
46 #include <QFile>
47 #include <QTimer>
48 #include <QTimerEvent>
49 #include <QCryptographicHash>
50
51 FileManager::FileManager(QObject *parent)
52     : QThread(parent)
53 {
54     quit = false;
55     totalLength = 0;
56     readId = 0;
57     startVerification = false;
58     wokeUp = false;
59     newFile = false;
60     numPieces = 0;
61     verifiedPieces.fill(false);
62 }
63
64 FileManager::~FileManager()
65 {
66     quit = true;
67     cond.wakeOne();
68     wait();
69
70     foreach (QFile *file, files) {
71         file->close();
72         delete file;
73     }
74 }
75
76 int FileManager::read(int pieceIndex, int offset, int length)
77 {
78     ReadRequest request;
79     request.pieceIndex = pieceIndex;
80     request.offset = offset;
81     request.length = length;
82
83     QMutexLocker locker(&mutex);
84     request.id = readId++;
85     readRequests << request;
86
87     if (!wokeUp) {
88         wokeUp = true;
89         QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
90     }
91
92     return request.id;
93 }
94
95 void FileManager::write(int pieceIndex, int offset, const QByteArray &data)
96 {
97     WriteRequest request;
98     request.pieceIndex = pieceIndex;
99     request.offset = offset;
100     request.data = data;
101
102     QMutexLocker locker(&mutex);
103     writeRequests << request;
104
105     if (!wokeUp) {
106         wokeUp = true;
107         QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
108     }
109 }
110
111 void FileManager::verifyPiece(int pieceIndex)
112 {
113     QMutexLocker locker(&mutex);
114     pendingVerificationRequests << pieceIndex;
115     startVerification = true;
116
117     if (!wokeUp) {
118         wokeUp = true;
119         QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
120     }
121 }
122
123 int FileManager::pieceLengthAt(int pieceIndex) const
124 {
125     QMutexLocker locker(&mutex);
126     return (sha1s.size() == pieceIndex + 1)
127         ? (totalLength % pieceLength) : pieceLength;
128 }
129
130 QBitArray FileManager::completedPieces() const
131 {
132     QMutexLocker locker(&mutex);
133     return verifiedPieces;
134 }
135
136 void FileManager::setCompletedPieces(const QBitArray &pieces)
137 {
138     QMutexLocker locker(&mutex);
139     verifiedPieces = pieces;
140 }
141
142 QString FileManager::errorString() const
143 {
144     return errString;
145 }
146
147 void FileManager::run()
148 {
149     if (!generateFiles())
150         return;
151
152     do {
153         {
154             // Go to sleep if there's nothing to do.
155             QMutexLocker locker(&mutex);
156             if (!quit && readRequests.isEmpty() && writeRequests.isEmpty() && !startVerification)
157                 cond.wait(&mutex);
158         }
159
160         // Read pending read requests
161         mutex.lock();
162         QList<ReadRequest> newReadRequests = readRequests;
163         readRequests.clear();
164         mutex.unlock();
165         while (!newReadRequests.isEmpty()) {
166             ReadRequest request = newReadRequests.takeFirst();
167             QByteArray block = readBlock(request.pieceIndex, request.offset, request.length);
168             emit dataRead(request.id, request.pieceIndex, request.offset, block);
169         }
170
171         // Write pending write requests
172         mutex.lock();
173         QList<WriteRequest> newWriteRequests = writeRequests;
174         writeRequests.clear();
175         while (!quit && !newWriteRequests.isEmpty()) {
176             WriteRequest request = newWriteRequests.takeFirst();
177             writeBlock(request.pieceIndex, request.offset, request.data);
178         }
179
180         // Process pending verification requests
181         if (startVerification) {
182             newPendingVerificationRequests = pendingVerificationRequests;
183             pendingVerificationRequests.clear();
184             verifyFileContents();
185             startVerification = false;
186         }
187         mutex.unlock();
188         newPendingVerificationRequests.clear();
189
190     } while (!quit);
191
192     // Write pending write requests
193     mutex.lock();
194     QList<WriteRequest> newWriteRequests = writeRequests;
195     writeRequests.clear();
196     mutex.unlock();
197     while (!newWriteRequests.isEmpty()) {
198         WriteRequest request = newWriteRequests.takeFirst();
199         writeBlock(request.pieceIndex, request.offset, request.data);
200     }
201 }
202
203 void FileManager::startDataVerification()
204 {
205     QMutexLocker locker(&mutex);
206     startVerification = true;
207     cond.wakeOne();
208 }
209
210 bool FileManager::generateFiles()
211 {
212     numPieces = -1;
213
214     // Set up the thread local data
215     if (metaInfo.fileForm() == MetaInfo::SingleFileForm) {
216         QMutexLocker locker(&mutex);
217         MetaInfoSingleFile singleFile = metaInfo.singleFile();
218
219         QString prefix;
220         if (!destinationPath.isEmpty()) {
221             prefix = destinationPath;
222             if (!prefix.endsWith("/"))
223                 prefix += "/";
224             QDir dir;
225             if (!dir.mkpath(prefix)) {
226                 errString = tr("Failed to create directory %1").arg(prefix);
227                 emit error();
228                 return false;
229             }
230         }
231         QFile *file = new QFile(prefix + singleFile.name);
232         if (!file->open(QFile::ReadWrite)) {
233             errString = tr("Failed to open/create file %1: %2")
234                         .arg(file->fileName()).arg(file->errorString());
235             emit error();
236             return false;
237         }
238
239         if (file->size() != singleFile.length) {
240             newFile = true;
241             if (!file->resize(singleFile.length)) {
242                 errString = tr("Failed to resize file %1: %2")
243                             .arg(file->fileName()).arg(file->errorString());
244                 emit error();
245                 return false;
246             }
247         }
248         fileSizes << file->size();
249         files << file;
250         file->close();
251
252         pieceLength = singleFile.pieceLength;
253         totalLength = singleFile.length;
254         sha1s = singleFile.sha1Sums;
255     } else {
256         QMutexLocker locker(&mutex);
257         QDir dir;
258         QString prefix;
259
260         if (!destinationPath.isEmpty()) {
261             prefix = destinationPath;
262             if (!prefix.endsWith("/"))
263                 prefix += "/";
264         }
265         if (!metaInfo.name().isEmpty()) {
266             prefix += metaInfo.name();
267             if (!prefix.endsWith("/"))
268                 prefix += "/";
269         }
270         if (!dir.mkpath(prefix)) {
271             errString = tr("Failed to create directory %1").arg(prefix);
272             emit error();
273             return false;
274         }
275
276         foreach (const MetaInfoMultiFile &entry, metaInfo.multiFiles()) {
277             QString filePath = QFileInfo(prefix + entry.path).path();
278             if (!QFile::exists(filePath)) {
279                 if (!dir.mkpath(filePath)) {
280                     errString = tr("Failed to create directory %1").arg(filePath);
281                     emit error();
282                     return false;
283                 }
284             }
285
286             QFile *file = new QFile(prefix + entry.path);
287             if (!file->open(QFile::ReadWrite)) {
288                 errString = tr("Failed to open/create file %1: %2")
289                             .arg(file->fileName()).arg(file->errorString());
290                 emit error();
291                 return false;
292             }
293
294             if (file->size() != entry.length) {
295                 newFile = true;
296                 if (!file->resize(entry.length)) {
297                     errString = tr("Failed to resize file %1: %2")
298                                 .arg(file->fileName()).arg(file->errorString());
299                     emit error();
300                     return false;
301                 }
302             }
303             fileSizes << file->size();
304             files << file;
305             file->close();
306
307             totalLength += entry.length;
308         }
309
310         sha1s = metaInfo.sha1Sums();
311         pieceLength = metaInfo.pieceLength();
312     }
313     numPieces = sha1s.size();
314     return true;
315 }
316
317 QByteArray FileManager::readBlock(int pieceIndex, int offset, int length)
318 {
319     QByteArray block;
320     qint64 startReadIndex = (quint64(pieceIndex) * pieceLength) + offset;
321     qint64 currentIndex = 0;
322
323     for (int i = 0; !quit && i < files.size() && length > 0; ++i) {
324         QFile *file = files[i];
325         qint64 currentFileSize = fileSizes.at(i);
326         if ((currentIndex + currentFileSize) > startReadIndex) {
327             if (!file->isOpen()) {
328                 if (!file->open(QFile::ReadWrite)) {
329                     errString = tr("Failed to read from file %1: %2")
330                         .arg(file->fileName()).arg(file->errorString());
331                     emit error();
332                     break;
333                 }
334             }
335
336             file->seek(startReadIndex - currentIndex);
337             QByteArray chunk = file->read(qMin<qint64>(length, currentFileSize - file->pos()));
338             file->close();
339
340             block += chunk;
341             length -= chunk.size();
342             startReadIndex += chunk.size();
343             if (length < 0) {
344                 errString = tr("Failed to read from file %1 (read %3 bytes): %2")
345                             .arg(file->fileName()).arg(file->errorString()).arg(length);
346                 emit error();
347                 break;
348             }
349         }
350         currentIndex += currentFileSize;
351     }
352     return block;
353 }
354
355 bool FileManager::writeBlock(int pieceIndex, int offset, const QByteArray &data)
356 {
357     qint64 startWriteIndex = (qint64(pieceIndex) * pieceLength) + offset;
358     qint64 currentIndex = 0;
359     int bytesToWrite = data.size();
360     int written = 0;
361
362     for (int i = 0; !quit && i < files.size(); ++i) {
363         QFile *file = files[i];
364         qint64 currentFileSize = fileSizes.at(i);
365
366         if ((currentIndex + currentFileSize) > startWriteIndex) {
367             if (!file->isOpen()) {
368                 if (!file->open(QFile::ReadWrite)) {
369                     errString = tr("Failed to write to file %1: %2")
370                         .arg(file->fileName()).arg(file->errorString());
371                     emit error();
372                     break;
373                 }
374             }
375
376             file->seek(startWriteIndex - currentIndex);
377             qint64 bytesWritten = file->write(data.constData() + written,
378                                               qMin<qint64>(bytesToWrite, currentFileSize - file->pos()));
379             file->close();
380
381             if (bytesWritten <= 0) {
382                 errString = tr("Failed to write to file %1: %2")
383                             .arg(file->fileName()).arg(file->errorString());
384                 emit error();
385                 return false;
386             }
387
388             written += bytesWritten;
389             startWriteIndex += bytesWritten;
390             bytesToWrite -= bytesWritten;
391             if (bytesToWrite == 0)
392                 break;
393         }
394         currentIndex += currentFileSize;
395     }
396     return true;
397 }
398
399 void FileManager::verifyFileContents()
400 {
401     // Verify all pieces the first time
402     if (newPendingVerificationRequests.isEmpty()) {
403         if (verifiedPieces.count(true) == 0) {
404             verifiedPieces.resize(sha1s.size());
405
406             int oldPercent = 0;
407             if (!newFile) {
408                 int numPieces = sha1s.size();
409
410                 for (int index = 0; index < numPieces; ++index) {
411                     verifySinglePiece(index);
412
413                     int percent = ((index + 1) * 100) / numPieces;
414                     if (oldPercent != percent) {
415                         emit verificationProgress(percent);
416                         oldPercent = percent;
417                     }
418                 }
419             }
420         }
421         emit verificationDone();
422         return;
423     }
424
425     // Verify all pending pieces
426     foreach (int index, newPendingVerificationRequests)
427         emit pieceVerified(index, verifySinglePiece(index));
428 }
429
430 bool FileManager::verifySinglePiece(int pieceIndex)
431 {
432     QByteArray block = readBlock(pieceIndex, 0, pieceLength);
433     QByteArray sha1Sum = QCryptographicHash::hash(block, QCryptographicHash::Sha1);
434
435     if (sha1Sum != sha1s.at(pieceIndex))
436         return false;
437     verifiedPieces.setBit(pieceIndex);
438     return true;
439 }
440
441 void FileManager::wakeUp()
442 {
443     QMutexLocker locker(&mutex);
444     wokeUp = false;
445     cond.wakeOne();
446 }