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

source code of qtbase/examples/network/torrent/filemanager.cpp