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

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

trunk: Merged in qt 4.6.1 sources.

File size: 13.6 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 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:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "filemanager.h"
43#include "metainfo.h"
44
45#include <QByteArray>
46#include <QDir>
47#include <QFile>
48#include <QTimer>
49#include <QTimerEvent>
50#include <QCryptographicHash>
51
52FileManager::FileManager(QObject *parent)
53 : QThread(parent)
54{
55 quit = false;
56 totalLength = 0;
57 readId = 0;
58 startVerification = false;
59 wokeUp = false;
60 newFile = false;
61 numPieces = 0;
62 verifiedPieces.fill(false);
63}
64
65FileManager::~FileManager()
66{
67 quit = true;
68 cond.wakeOne();
69 wait();
70
71 foreach (QFile *file, files) {
72 file->close();
73 delete file;
74 }
75}
76
77int FileManager::read(int pieceIndex, int offset, int length)
78{
79 ReadRequest request;
80 request.pieceIndex = pieceIndex;
81 request.offset = offset;
82 request.length = length;
83
84 QMutexLocker locker(&mutex);
85 request.id = readId++;
86 readRequests << request;
87
88 if (!wokeUp) {
89 wokeUp = true;
90 QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
91 }
92
93 return request.id;
94}
95
96void FileManager::write(int pieceIndex, int offset, const QByteArray &data)
97{
98 WriteRequest request;
99 request.pieceIndex = pieceIndex;
100 request.offset = offset;
101 request.data = data;
102
103 QMutexLocker locker(&mutex);
104 writeRequests << request;
105
106 if (!wokeUp) {
107 wokeUp = true;
108 QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
109 }
110}
111
112void FileManager::verifyPiece(int pieceIndex)
113{
114 QMutexLocker locker(&mutex);
115 pendingVerificationRequests << pieceIndex;
116 startVerification = true;
117
118 if (!wokeUp) {
119 wokeUp = true;
120 QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
121 }
122}
123
124int FileManager::pieceLengthAt(int pieceIndex) const
125{
126 QMutexLocker locker(&mutex);
127 return (sha1s.size() == pieceIndex + 1)
128 ? (totalLength % pieceLength) : pieceLength;
129}
130
131QBitArray FileManager::completedPieces() const
132{
133 QMutexLocker locker(&mutex);
134 return verifiedPieces;
135}
136
137void FileManager::setCompletedPieces(const QBitArray &pieces)
138{
139 QMutexLocker locker(&mutex);
140 verifiedPieces = pieces;
141}
142
143QString FileManager::errorString() const
144{
145 return errString;
146}
147
148void FileManager::run()
149{
150 if (!generateFiles())
151 return;
152
153 do {
154 {
155 // Go to sleep if there's nothing to do.
156 QMutexLocker locker(&mutex);
157 if (!quit && readRequests.isEmpty() && writeRequests.isEmpty() && !startVerification)
158 cond.wait(&mutex);
159 }
160
161 // Read pending read requests
162 mutex.lock();
163 QList<ReadRequest> newReadRequests = readRequests;
164 readRequests.clear();
165 mutex.unlock();
166 while (!newReadRequests.isEmpty()) {
167 ReadRequest request = newReadRequests.takeFirst();
168 QByteArray block = readBlock(request.pieceIndex, request.offset, request.length);
169 emit dataRead(request.id, request.pieceIndex, request.offset, block);
170 }
171
172 // Write pending write requests
173 mutex.lock();
174 QList<WriteRequest> newWriteRequests = writeRequests;
175 writeRequests.clear();
176 while (!quit && !newWriteRequests.isEmpty()) {
177 WriteRequest request = newWriteRequests.takeFirst();
178 writeBlock(request.pieceIndex, request.offset, request.data);
179 }
180
181 // Process pending verification requests
182 if (startVerification) {
183 newPendingVerificationRequests = pendingVerificationRequests;
184 pendingVerificationRequests.clear();
185 verifyFileContents();
186 startVerification = false;
187 }
188 mutex.unlock();
189 newPendingVerificationRequests.clear();
190
191 } while (!quit);
192
193 // Write pending write requests
194 mutex.lock();
195 QList<WriteRequest> newWriteRequests = writeRequests;
196 writeRequests.clear();
197 mutex.unlock();
198 while (!newWriteRequests.isEmpty()) {
199 WriteRequest request = newWriteRequests.takeFirst();
200 writeBlock(request.pieceIndex, request.offset, request.data);
201 }
202}
203
204void FileManager::startDataVerification()
205{
206 QMutexLocker locker(&mutex);
207 startVerification = true;
208 cond.wakeOne();
209}
210
211bool FileManager::generateFiles()
212{
213 numPieces = -1;
214
215 // Set up the thread local data
216 if (metaInfo.fileForm() == MetaInfo::SingleFileForm) {
217 QMutexLocker locker(&mutex);
218 MetaInfoSingleFile singleFile = metaInfo.singleFile();
219
220 QString prefix;
221 if (!destinationPath.isEmpty()) {
222 prefix = destinationPath;
223 if (!prefix.endsWith("/"))
224 prefix += "/";
225 QDir dir;
226 if (!dir.mkpath(prefix)) {
227 errString = tr("Failed to create directory %1").arg(prefix);
228 emit error();
229 return false;
230 }
231 }
232 QFile *file = new QFile(prefix + singleFile.name);
233 if (!file->open(QFile::ReadWrite)) {
234 errString = tr("Failed to open/create file %1: %2")
235 .arg(file->fileName()).arg(file->errorString());
236 emit error();
237 return false;
238 }
239
240 if (file->size() != singleFile.length) {
241 newFile = true;
242 if (!file->resize(singleFile.length)) {
243 errString = tr("Failed to resize file %1: %2")
244 .arg(file->fileName()).arg(file->errorString());
245 emit error();
246 return false;
247 }
248 }
249 fileSizes << file->size();
250 files << file;
251 file->close();
252
253 pieceLength = singleFile.pieceLength;
254 totalLength = singleFile.length;
255 sha1s = singleFile.sha1Sums;
256 } else {
257 QMutexLocker locker(&mutex);
258 QDir dir;
259 QString prefix;
260
261 if (!destinationPath.isEmpty()) {
262 prefix = destinationPath;
263 if (!prefix.endsWith("/"))
264 prefix += "/";
265 }
266 if (!metaInfo.name().isEmpty()) {
267 prefix += metaInfo.name();
268 if (!prefix.endsWith("/"))
269 prefix += "/";
270 }
271 if (!dir.mkpath(prefix)) {
272 errString = tr("Failed to create directory %1").arg(prefix);
273 emit error();
274 return false;
275 }
276
277 foreach (const MetaInfoMultiFile &entry, metaInfo.multiFiles()) {
278 QString filePath = QFileInfo(prefix + entry.path).path();
279 if (!QFile::exists(filePath)) {
280 if (!dir.mkpath(filePath)) {
281 errString = tr("Failed to create directory %1").arg(filePath);
282 emit error();
283 return false;
284 }
285 }
286
287 QFile *file = new QFile(prefix + entry.path);
288 if (!file->open(QFile::ReadWrite)) {
289 errString = tr("Failed to open/create file %1: %2")
290 .arg(file->fileName()).arg(file->errorString());
291 emit error();
292 return false;
293 }
294
295 if (file->size() != entry.length) {
296 newFile = true;
297 if (!file->resize(entry.length)) {
298 errString = tr("Failed to resize file %1: %2")
299 .arg(file->fileName()).arg(file->errorString());
300 emit error();
301 return false;
302 }
303 }
304 fileSizes << file->size();
305 files << file;
306 file->close();
307
308 totalLength += entry.length;
309 }
310
311 sha1s = metaInfo.sha1Sums();
312 pieceLength = metaInfo.pieceLength();
313 }
314 numPieces = sha1s.size();
315 return true;
316}
317
318QByteArray FileManager::readBlock(int pieceIndex, int offset, int length)
319{
320 QByteArray block;
321 qint64 startReadIndex = (quint64(pieceIndex) * pieceLength) + offset;
322 qint64 currentIndex = 0;
323
324 for (int i = 0; !quit && i < files.size() && length > 0; ++i) {
325 QFile *file = files[i];
326 qint64 currentFileSize = fileSizes.at(i);
327 if ((currentIndex + currentFileSize) > startReadIndex) {
328 if (!file->isOpen()) {
329 if (!file->open(QFile::ReadWrite)) {
330 errString = tr("Failed to read from file %1: %2")
331 .arg(file->fileName()).arg(file->errorString());
332 emit error();
333 break;
334 }
335 }
336
337 file->seek(startReadIndex - currentIndex);
338 QByteArray chunk = file->read(qMin<qint64>(length, currentFileSize - file->pos()));
339 file->close();
340
341 block += chunk;
342 length -= chunk.size();
343 startReadIndex += chunk.size();
344 if (length < 0) {
345 errString = tr("Failed to read from file %1 (read %3 bytes): %2")
346 .arg(file->fileName()).arg(file->errorString()).arg(length);
347 emit error();
348 break;
349 }
350 }
351 currentIndex += currentFileSize;
352 }
353 return block;
354}
355
356bool FileManager::writeBlock(int pieceIndex, int offset, const QByteArray &data)
357{
358 qint64 startWriteIndex = (qint64(pieceIndex) * pieceLength) + offset;
359 qint64 currentIndex = 0;
360 int bytesToWrite = data.size();
361 int written = 0;
362
363 for (int i = 0; !quit && i < files.size(); ++i) {
364 QFile *file = files[i];
365 qint64 currentFileSize = fileSizes.at(i);
366
367 if ((currentIndex + currentFileSize) > startWriteIndex) {
368 if (!file->isOpen()) {
369 if (!file->open(QFile::ReadWrite)) {
370 errString = tr("Failed to write to file %1: %2")
371 .arg(file->fileName()).arg(file->errorString());
372 emit error();
373 break;
374 }
375 }
376
377 file->seek(startWriteIndex - currentIndex);
378 qint64 bytesWritten = file->write(data.constData() + written,
379 qMin<qint64>(bytesToWrite, currentFileSize - file->pos()));
380 file->close();
381
382 if (bytesWritten <= 0) {
383 errString = tr("Failed to write to file %1: %2")
384 .arg(file->fileName()).arg(file->errorString());
385 emit error();
386 return false;
387 }
388
389 written += bytesWritten;
390 startWriteIndex += bytesWritten;
391 bytesToWrite -= bytesWritten;
392 if (bytesToWrite == 0)
393 break;
394 }
395 currentIndex += currentFileSize;
396 }
397 return true;
398}
399
400void FileManager::verifyFileContents()
401{
402 // Verify all pieces the first time
403 if (newPendingVerificationRequests.isEmpty()) {
404 if (verifiedPieces.count(true) == 0) {
405 verifiedPieces.resize(sha1s.size());
406
407 int oldPercent = 0;
408 if (!newFile) {
409 int numPieces = sha1s.size();
410
411 for (int index = 0; index < numPieces; ++index) {
412 verifySinglePiece(index);
413
414 int percent = ((index + 1) * 100) / numPieces;
415 if (oldPercent != percent) {
416 emit verificationProgress(percent);
417 oldPercent = percent;
418 }
419 }
420 }
421 }
422 emit verificationDone();
423 return;
424 }
425
426 // Verify all pending pieces
427 foreach (int index, newPendingVerificationRequests)
428 emit pieceVerified(index, verifySinglePiece(index));
429}
430
431bool FileManager::verifySinglePiece(int pieceIndex)
432{
433 QByteArray block = readBlock(pieceIndex, 0, pieceLength);
434 QByteArray sha1Sum = QCryptographicHash::hash(block, QCryptographicHash::Sha1);
435
436 if (sha1Sum != sha1s.at(pieceIndex))
437 return false;
438 verifiedPieces.setBit(pieceIndex);
439 return true;
440}
441
442void FileManager::wakeUp()
443{