source: trunk/examples/network/torrent/filemanager.cpp@ 846

Last change on this file since 846 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 13.7 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the examples of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:BSD$
10** You may use this file under the terms of the BSD license as follows:
11**
12** "Redistribution and use in source and binary forms, with or without
13** modification, are permitted provided that the following conditions are
14** met:
15** * Redistributions of source code must retain the above copyright
16** notice, this list of conditions and the following disclaimer.
17** * Redistributions in binary form must reproduce the above copyright
18** notice, this list of conditions and the following disclaimer in
19** the documentation and/or other materials provided with the
20** distribution.
21** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
22** the names of its contributors may be used to endorse or promote
23** products derived from this software without specific prior written
24** permission.
25**
26** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
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
51FileManager::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
64FileManager::~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
76int 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
95void 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
111void 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
123int FileManager::pieceLengthAt(int pieceIndex) const
124{
125 QMutexLocker locker(&mutex);
126 return (sha1s.size() == pieceIndex + 1)
127 ? (totalLength % pieceLength) : pieceLength;
128}
129
130QBitArray FileManager::completedPieces() const
131{
132 QMutexLocker locker(&mutex);
133 return verifiedPieces;
134}
135
136void FileManager::setCompletedPieces(const QBitArray &pieces)
137{
138 QMutexLocker locker(&mutex);
139 verifiedPieces = pieces;
140}
141
142QString FileManager::errorString() const
143{
144 return errString;
145}
146
147void 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
203void FileManager::startDataVerification()
204{
205 QMutexLocker locker(&mutex);
206 startVerification = true;
207 cond.wakeOne();
208}
209
210bool 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
317QByteArray 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
355bool 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
399void 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
430bool 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
441void FileManager::wakeUp()
442{
443 QMutexLocker locker(&mutex);
444 wokeUp = false;
445 cond.wakeOne();
446}
Note: See TracBrowser for help on using the repository browser.