root/hydranode/hncore/bt/files.cpp

Revision 2916, 28.9 kB (checked in by madcat, 3 years ago)

Emits PD_DL_FINISHED for torrent's files as well.

Line 
1 /*
2  *  Copyright (C) 2005-2006 Alo Sarv <madcat_@users.sourceforge.net>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program 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
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19 /**
20  * \file files.cpp     Implementation of TorrentFile and PartialTorrent classes
21  */
22
23 #include <hncore/bt/files.h>
24 #include <hnbase/timed_callback.h>
25 #include <hncore/metadata.h>
26 #include <boost/filesystem/operations.hpp>
27 #include <boost/algorithm/string/predicate.hpp>
28 #include <fcntl.h>
29 #include <bitset>
30 #include <errno.h>
31
32 namespace Bt {
33
34 // TorrentFile class
35 // -----------------
36 TorrentFile::InternalFile::InternalFile(
37         uint64_t begin, uint64_t size, SharedFile *f
38 ) : Range64(begin, begin + size - 1), m_file(f) {}
39
40 TorrentFile::TorrentFile(
41         const std::map<uint64_t, SharedFile*> &files, TorrentInfo ti,
42         PartialTorrent *pt
43 ) {
44         CHECK_THROW(files.size());
45         typedef std::map<uint64_t, SharedFile*>::const_iterator FIter;
46
47         if (pt) {
48                 setPartData(pt);
49         }
50
51         for (FIter it = files.begin(); it != files.end(); ++it) {
52                 if (!(*it).second->getSize()) {
53                         (*it).second->destroy();
54                         continue;
55                 }
56
57                 InternalFile f(
58                         (*it).first, (*it).second->getSize(), (*it).second
59                 );
60                 m_children.push(f);
61                 if (pt && (*it).second->isComplete()) {
62                         logDebug(
63                                 boost::format(
64                                         "Marking range %d..%d as complete."
65                                 ) % f.begin() % f.end()
66                         );
67                         pt->setComplete(f);
68                 }
69                 m_childrenReverse[(*it).second] = f.begin();
70                 SharedFile::getEventTable().addHandler(
71                         (*it).second, this, &TorrentFile::onSharedFileEvent
72                 );
73                 getUpSpeed.connect((*it).second->getUpSpeed);
74                 (*it).second->setParent(this);
75         }
76         m_size = ti.getSize();
77         getEventTable().postEvent(this, SF_ADDED);
78 }
79
80 // since we use custom MetaData, which is not recorded in MetaDb, we delete it
81 // ourselves
82 TorrentFile::~TorrentFile() {
83         delete getMetaData();
84
85         // reparent all children to null, otherwise Object deletes them
86         // note that the Object::CIter's become invalid after setParent()
87         // call, thus we have to re-initialize it every time
88         for (Object::CIter i = begin(); i != end(); i = begin()) {
89                 (*i).second->setParent(0);
90         }
91 }
92
93 std::string TorrentFile::read(uint64_t begin, uint64_t end) {
94         RangeList<InternalFile>::CIter i = m_children.getContains(begin, end);
95         CHECK_THROW(i != m_children.end());
96
97         std::string data;
98         uint64_t tmp = begin - (*i).begin();      // first relative offset
99         if ((*i).containsFull(begin, end)) {
100                 data = i->m_file->read(tmp, end - (*i).begin());
101         } else while (i != m_children.end() && (*i).contains(begin, end)) {
102                 if (end <= (*i).end()) {          // data ends in this file
103                         data += i->m_file->read(tmp, end - (*i).begin());
104                 } else {                          // data crosses this file
105                         data += i->m_file->read(tmp, i->m_file->getSize() - 1);
106                 }
107                 tmp = 0;
108                 ++i;
109         }
110
111         CHECK_THROW(data.size() == end - begin + 1);
112
113         return data;
114 }
115
116 void TorrentFile::onSharedFileEvent(SharedFile *file, int evt) {
117         if (evt == SF_DESTROY && m_children.size()) {
118                 RIter i = m_childrenReverse.find(file);
119                 CHECK_RET(i != m_childrenReverse.end());
120                 RangeList<InternalFile>::CIter j = m_children.getContains(
121                         (*i).second, (*i).second + file->getSize() - 1
122                 );
123                 CHECK_RET(j != m_children.end());
124                 m_childrenReverse.erase(i);
125                 m_children.erase(*j);
126                 if (!m_children.size()) {
127                         destroy();
128                 }
129         }
130 }
131
132 SharedFile* TorrentFile::getContains(Range64 range) const {
133         RangeList<InternalFile>::CIter it = m_children.getContains(range);
134         CHECK_THROW(it != m_children.end());
135         return (*it).m_file;
136 }
137
138 void TorrentFile::finishDownload() {
139         PartData::getEventTable().postEvent(getPartData(), PD_DL_FINISHED);
140 }
141
142 // PartialTorrent class
143 // --------------------
144 PartialTorrent::CacheImpl::CacheImpl(
145         uint64_t begin, uint64_t end, const boost::filesystem::path &p
146 ) : Range64(begin, end), m_loc(p) {
147         if (!boost::filesystem::exists(p)) {
148                 std::ofstream o(p.native_file_string().c_str());
149                 o.put('\0');
150         }
151 }
152
153 // this expects relative offset, inside this cachefile
154 //
155 // the buffer bookeeping is similar as is done inside PartData - buffered data
156 // is in a map, keyed by begin offset, so disk writes occour in a begin->end
157 // fashion, avoiding backwards seeking.
158 void PartialTorrent::CacheImpl::write(uint64_t begin, const std::string &data) {
159         m_buffer[begin] = data;
160 }
161
162 // again, very similar to what is done inside PartData::save method, we flush
163 // the data to disk. The usage of fcntl API instead of C++ iostreams is since
164 // GCC versions 3.2 through 3.4 lack 64bit IO support.
165 void PartialTorrent::CacheImpl::save() {
166         if (!m_buffer.size()) {
167                 return;
168         }
169
170         int fd = open(m_loc.native_file_string().c_str(), O_RDWR|O_BINARY);
171         if (!fd) {
172                 logError(
173                         boost::format("Failed to open cache file '%s': %s")
174                         % m_loc.native_file_string() % strerror(errno)
175                 );
176                 return;
177         }
178
179         for (BIter i = m_buffer.begin(); i != m_buffer.end(); ++i) try {
180                 uint64_t ret = lseek64(fd, (*i).first, SEEK_SET);
181                 CHECK_THROW(ret == static_cast<uint64_t>((*i).first));
182                 int c = ::write(fd, (*i).second.data(), (*i).second.size());
183                 CHECK_THROW(c == static_cast<int>((*i).second.size()));
184         } catch (...) {
185                 close(fd);
186                 throw std::runtime_error(
187                         "no space left on drive (torrent cache failure)"
188                 );
189         }
190         fsync(fd);
191         close(fd);
192         m_buffer.clear();
193 }
194
195 PartialTorrent::InternalFile::InternalFile(
196         uint64_t begin, uint64_t size, PartData *file
197 ) : Range64(begin, begin + size - 1), m_file(file) {}
198
199 // constructs a PartialTorrent; input must contain ALL files within this torrent
200 //
201 // the InternalFile and m_children / m_childrenReverse machinery should be
202 // rather self-explanatory. However, where it gets interesting is the cache
203 // files mechanism.
204 //
205 // Each chunk that crosses file boundaries must be cached. Each file within a
206 // cached chunk must be a separate physical file, in order to allow our custom
207 // hasher to work on cache or real files w/o any changes in code. Hence, the
208 // base logic is for each file beginning and ending, we have a cache-file which
209 // contains the two halves of the crossing chunk. It gets more complex though -
210 // we don't cache if the chunks end exactly at file boundaries; we don't want
211 // cache for the very first (and very last) chunks, since those can never be
212 // crossing files. On the other hand, they can cross chunks, if the first (or
213 // last) files are smaller than chunksize. Furthermore, a chunk can cross
214 // multiple files, that are smaller than the chunk size.
215 PartialTorrent::PartialTorrent(
216         const std::map<uint64_t, PartData*> &files,
217         const boost::filesystem::path &loc,
218         uint64_t size
219 ) : m_writing() {
220         using namespace boost::filesystem;
221         typedef std::map<uint64_t, PartData*>::const_iterator FIter;
222         CHECK_THROW(files.size());
223         m_loc = loc;
224
225         uint64_t i = 0;
226         for (FIter it = files.begin(); it != files.end(); ++it, ++i) {
227                 if (!(*it).second->getSize()) { // file with size zero
228                         continue;
229                 }
230                 uint64_t offset = (*it).first;
231                 if (it == files.begin()) {
232                         if (offset > 0) {
233                                 dontDownload(Range64(0, offset - 1));
234                         }
235                 } else if (m_children.back().end() + 1 != offset) {
236                         Range64 r(m_children.back().end() + 1, offset - 1);
237                         dontDownload(r);
238                 }
239                 InternalFile f(offset, (*it).second->getSize(), (*it).second);
240                 m_children.push(f);
241                 m_childrenReverse[(*it).second] = offset;
242                 RangeList64 cr = (*it).second->getCompletedRanges();
243                 for (RangeList64::CIter j = cr.begin(); j != cr.end(); ++j) {
244                         Range64 tmp(offset + (*j).begin(), offset + (*j).end());
245                         setComplete(tmp);
246                 }
247                 RangeList64 vr = (*it).second->getVerifiedRanges();
248                 for (RangeList64::CIter j = vr.begin(); j != vr.end(); ++j) {
249                         Range64 tmp(offset + (*j).begin(), offset + (*j).end());
250                         setVerified(tmp);
251                 }
252
253                 // set up inter-object communication
254                 (*it).second->dataAdded.connect(
255                         boost::bind(
256                                 &PartialTorrent::childDataAdded,
257                                 this, _1, _2, _3
258                         )
259                 );
260                 (*it).second->onCorruption.connect(
261                         boost::bind(
262                                 &PartialTorrent::childCorruption, this, _1, _2
263                         )
264                 );
265                 (*it).second->canComplete.connect(
266                         boost::bind(&PartialTorrent::childCanComplete, this, _1)
267                 );
268                 (*it).second->onAllocDone.connect(
269                         boost::bind(&PartialTorrent::childAllocDone, this, _1)
270                 );
271                 (*it).second->onPaused.connect(
272                         boost::bind(&PartialTorrent::childPaused, this, _1)
273                 );
274                 (*it).second->onStopped.connect(
275                         boost::bind(&PartialTorrent::childPaused, this, _1)
276                 );
277                 (*it).second->onCanceled.connect(
278                         boost::bind(&PartialTorrent::childPaused, this, _1)
279                 );
280                 (*it).second->onResumed.connect(
281                         boost::bind(&PartialTorrent::childResumed, this, _1)
282                 );
283                 (*it).second->onDestroyed.connect(
284                         boost::bind(&PartialTorrent::childDestroyed, this, _1)
285                 );
286                 (*it).second->onVerified.connect(
287                         boost::bind(
288                                 &PartialTorrent::childChunkVerified,
289                                 this, _1, _2, _3
290                         )
291                 );
292                 getDownSpeed.connect((*it).second->getDownSpeed);
293                 (*it).second->setParent(this);
294         }
295         if (m_children.back().end() < m_size - 1) {
296                 dontDownload(Range64(m_children.back().end() + 1, m_size - 1));
297         }
298
299         m_size = size;
300         onVerified.connect(
301                 boost::bind(
302                         &PartialTorrent::parentChunkVerified, this, _1, _2, _3
303                 )
304         );
305         getEventTable().postEvent(this, PD_ADDED);
306 }
307
308 PartialTorrent::~PartialTorrent() {
309         // reparent all children to null, otherwise Object deletes them
310         // note that the Object::CIter's become invalid after setParent()
311         // call, thus we have to re-initialize it every time
312         for (Object::CIter i = begin(); i != end(); i = begin()) {
313                 (*i).second->setParent(0);
314         }
315 }
316
317 // this machinery creates cache for all chunks that cross file
318 // boundaries
319 // note: chunk variable must also be 64bit, otherwise compiler will do some
320 //       calculations at 32bit precision only.
321 void PartialTorrent::initCache(const TorrentInfo &info) {
322         using namespace boost::filesystem;
323
324         std::vector<TorrentInfo::TorrentFile> files = info.getFiles();
325         uint64_t offset = 0;
326         uint64_t cacheSize = 0;
327         uint64_t chunkSize = info.getChunkSize();
328         boost::format cname("%s.cache.%d.%d");
329         std::map<uint64_t, uint32_t> chunkNumbers;
330
331         for (uint32_t i = 0; i < files.size(); ++i) {
332                 if (!files[i].getSize()) {
333                         continue;
334                 }
335
336                 // file beginning
337                 uint64_t chunk = offset / chunkSize;
338                 Range64 f(offset, offset + files[i].getSize() - 1);
339                 Range64 cr(chunk * chunkSize, (chunk + 1) * chunkSize - 1);
340                 offset += files[i].getSize();
341
342                 if (m_children.contains(cr) && !f.containsFull(cr)) {
343                         cname % getLocation().native_file_string() % chunk;
344                         cname % chunkNumbers[chunk]++;
345                         path cacheFile(cname.str(), native);
346
347                         if (f.contains(cr.begin())) {      // chunk begins here
348                                 CacheFile cf(cr.begin(), f.end(), cacheFile);
349                                 m_cache.push(cf);
350                                 doDownload(cf);
351                                 cacheSize += cf.length();
352                         } else if (f.contains(cr.end())) {  // chunk ends here
353                                 CacheFile cf(f.begin(), cr.end(), cacheFile);
354                                 m_cache.push(cf);
355                                 doDownload(cf);
356                                 cacheSize += cf.length();
357                         } else {                            // chunk passes this
358                                 CacheFile cf(f.begin(), f.end(), cacheFile);
359                                 m_cache.push(cf);
360                                 doDownload(cf);
361                                 cacheSize += cf.length();
362                         }
363                 }
364                 // file ending
365                 chunk = (offset - 1) / chunkSize;
366                 cr = Range64(chunk * chunkSize, (chunk + 1) * chunkSize - 1);
367                 if (m_children.contains(cr) && !f.containsFull(cr)) {
368                         cname % getLocation().native_file_string() % chunk;
369                         cname % chunkNumbers[chunk]++;
370                         path cacheFile(cname.str(), native);
371
372                         if (f.contains(cr.begin())) {      // chunk begins here
373                                 CacheFile cf(cr.begin(), f.end(), cacheFile);
374                                 m_cache.push(cf);
375                                 doDownload(cf);
376                                 cacheSize += cf.length();
377                         } else if (f.contains(cr.end())) {  // chunk ends here
378                                 CacheFile cf(f.begin(), cr.end(), cacheFile);
379                                 m_cache.push(cf);
380                                 doDownload(cf);
381                                 cacheSize += cf.length();
382                         } else {                            // chunk passes this
383                                 CacheFile cf(f.begin(), f.end(), cacheFile);
384                                 m_cache.push(cf);
385                                 doDownload(cf);
386                                 cacheSize += cf.length();
387                         }
388                 }
389         }
390         if (!m_name.size()) {
391                 m_name = info.getName();
392         }
393
394         logTrace("bt.files",
395                 boost::format("Torrent size: %s Cache size: %s (%.2f%%)")
396                 % Utils::bytesToString(m_size) % Utils::bytesToString(cacheSize)
397                 % (cacheSize * 100 / m_size)
398         );
399 }
400
401 PartData* PartialTorrent::getContains(Range64 range) const {
402         RangeList<InternalFile>::CIter it = m_children.getContains(range);
403         CHECK_THROW(it != m_children.end());
404         return (*it).m_file;
405 }
406
407 // Forwards the data one or more concrete sub-objects
408 //
409 // The most tricky part here is figuring out the relative offset (within the
410 // file where the data has to be written to), from the global offset that is
411 // given as input BEGIN parameter. We do that by substracting the containing
412 // file's global begin offset from the input begin offset, and thus get the
413 // relative offset.
414 //
415 // We must consider that we don't have physical files within the written data
416 // region, either beginning, middle or end; this can happen when downloads were
417 // canceled and such. Thus, we must calculatate the position and offset for
418 // EACH file separately, since we might need to skip over few files in the
419 // middle.
420 //
421 // Note that we must update cache BEFORE starting normal write operations, since
422 // normal write operations can trigger chunk verification, leading to
423 // verifyRange being called, but there we already need cache to be written.
424 void PartialTorrent::doWrite(uint64_t begin, const std::string &data) {
425         updateCache(begin, data);
426
427         Range64 toWrite(begin, begin + data.size() - 1);
428         RangeList<InternalFile>::CIter i = m_children.getContains(toWrite);
429
430         m_writing = true;
431         while (i != m_children.end() && (*i).contains(toWrite)) {
432                 uint64_t pos = 0;
433                 uint64_t offset = 0;
434                 if ((*i).begin() < begin) {
435                         offset = begin - (*i).begin();
436                 } else if ((*i).begin() > begin) {
437                         pos += (*i).begin() - begin;
438                 }
439
440                 uint64_t tmp = (*i).m_file->getSize() - offset;
441                 if (data.size() - pos < (*i).m_file->getSize() - offset) {
442                         tmp = data.size() - pos;
443                 }
444                 (*i++).m_file->write(offset, data.substr(pos, tmp));
445         }
446         m_writing = false;
447
448         setComplete(Range64(begin, begin + data.size() - 1));
449 }
450
451 // Updates the cache
452 //
453 // the tricky part here is that CacheImpl::write expects RELATIVE offset inside
454 // the cache file, while the input BEGIN variable for this function is the
455 // global offset inside the entire torrent. Furthermore, we must consider the
456 // situation where only part of the data needs to be written into the cache.
457 //
458 // The first relative offset can be zero (if begin == cachefile beginning), but
459 // usually is non-zero, the difference between input begin offset and the
460 // cachefile's begin offset (just as in doWrite() method).All following offsets
461 // are zeroes, since the data is continuous.
462 //
463 // We must also keep track of the position within the data to be written. We
464 // default to zero, however if the cachefile global begin offset is larger than
465 // the input BEGIN offset, we adjust the position in the data accordingly, and
466 // start writing from a later position in the input data.
467 //
468 // Inside the while loop, we also must keep track of how long each of the cache
469 // files is, since the input data may cross multiple file boundaries. So, in
470 // order to write to each cache file only as much was supposed to be written
471 // there, we check if the to-be-written length of the data (variable named TMP)
472 // would flow over the end of the the cachefile. If that's the case, the TMP
473 // variable is adjusted to only write until the end of that specific file, and
474 // the loop continues to next file.
475 //
476 // We can also do early return in this function if none of the data needs to
477 // be cached.
478 void PartialTorrent::updateCache(uint64_t begin, const std::string &data) {
479         Range64 toWrite(begin, begin + data.size() - 1);
480         if (!m_cache.contains(toWrite)) {
481                 return; // region is not cached
482         }
483         RangeList<CacheFile>::CIter i = m_cache.getContains(toWrite);
484         CHECK_RET(i != m_cache.end());
485
486         uint64_t pos = 0;
487         uint64_t offset = 0;
488         if ((*i).begin() < begin) {
489                 offset = begin - (*i).begin();
490         } else if ((*i).begin() > begin) {
491                 pos += (*i).begin() - begin + 1;
492         }
493         while (i != m_cache.end() && (*i).contains(toWrite)) {
494                 uint64_t tmp = (*i).length() - offset;
495                 if (data.size() - pos < (*i).length() - offset) {
496                         tmp = data.size() - pos;
497                 }
498                 (*i++).m_impl->write(offset, data.substr(pos, tmp));
499                 pos += tmp;
500                 offset = 0;
501         }
502 }
503
504 void PartialTorrent::childDataAdded(
505         PartData *file, uint64_t offset, uint32_t amount
506 ) {
507         if (!m_writing) {
508                 RIter i = m_childrenReverse.find(file);
509                 CHECK_RET(i != m_childrenReverse.end());
510                 CHECK_RET((*i).first == file);
511
512                 setComplete(
513                         Range64(
514                                 offset + (*i).second,
515                                 offset + (*i).second + amount - 1
516                         )
517                 );
518         }
519 }
520
521 HashWorkPtr PartialTorrent::verifyRange(
522         Range64 range, const HashBase *ref, bool doSave
523 ) {
524         boost::intrusive_ptr<TorrentHasher> c, cc;
525         std::set<PartData*> waitAlloc;
526         std::vector<boost::filesystem::path> files;
527         std::pair<uint64_t, uint64_t> relativeOffsets;
528         relativeOffsets = std::make_pair(range.begin(), range.end());
529
530         RangeList<InternalFile>::CIter i = m_children.getContains(range);
531
532         // can happen on some circumstances; technically, we could still try
533         // to verify cache here, but what's the point really?
534         if (i == m_children.end()) {
535                 return HashWorkPtr();
536         }
537
538         if ((*i).begin() > range.begin()) {
539                 relativeOffsets.first = 0;
540         } else {
541                 relativeOffsets.first = range.begin() - (*i).begin();
542         }
543
544         // use copy of passed range, since we modify it later, but need to pass
545         // the original to TorrentHasher, otherwise we break in PartData later
546         Range64 tmpRange(range);
547         uint64_t lastEnd = (*i).end();
548
549         while (i != m_children.end() && (*i).contains(tmpRange)) {
550                 if ((*i).begin() > tmpRange.begin()) {
551                         // chunk beginning/middle phys file missing -
552                         // use cache instead
553                         logDebug("using cache for hashing");
554                         RangeList<CacheFile>::CIter j;
555                         j = m_cache.getContains(tmpRange);
556                         CHECK_FAIL(j != m_cache.end());
557                         (*j).m_impl->save();
558                         files.push_back((*j).m_impl->getLocation());
559                         relativeOffsets.second = (*j).length() - 1;
560                         if ((*j).end() < tmpRange.end()) {
561                                 tmpRange.begin((*j).end() + 1);
562                         } else {
563                                 break;
564                         }
565                 } else {
566                         if (doSave) {
567                                 (*i).m_file->save();
568                         }
569                         if ((*i).m_file->allocInProgress()) {
570                                 waitAlloc.insert((*i).m_file);
571                         }
572                         files.push_back((*i).m_file->getLocation());
573                         CHECK_FAIL(tmpRange.end() >= (*i).begin());
574                         relativeOffsets.second = tmpRange.end() - (*i).begin();
575                         lastEnd = (*i).end();
576                         if ((*i).end() < tmpRange.end()) {
577                                 tmpRange.begin((*i).end() + 1);
578                         } else {
579                                 break;
580                         }
581                 }
582                 i = m_children.getContains(tmpRange);
583         }
584         // chunk end physical file missing - use cache instead
585         if (lastEnd < range.end()) {
586                 RangeList<CacheFile>::CIter j = m_cache.getContains(tmpRange);
587                 while (j != m_cache.end()) {
588                         if (doSave) {
589                                 (*j).m_impl->save();
590                         }
591                         files.push_back((*j).m_impl->getLocation());
592                         relativeOffsets.second = (*j).length() - 1;
593                         if ((*j).end() < tmpRange.end()) {
594                                 tmpRange.begin((*j).end() + 1);
595                         } else {
596                                 break;
597                         }
598                         j = m_cache.getContains(tmpRange);
599                 }
600         }
601
602         c = new TorrentHasher(range, files, relativeOffsets, ref);
603         c->waitAlloc(waitAlloc);
604         if (c->canRun()) {
605                 IOThread::instance().postWork(c);
606         } else {
607                 m_pendingChecks.push_back(c);
608         }
609
610         // also verify cache
611         files.clear();
612         RangeList<CacheFile>::CIter j = m_cache.getContains(range);
613         if (j == m_cache.end()) {
614                 return c;
615         }
616         relativeOffsets.first = 0;
617         while (j != m_cache.end() && (*j).contains(range)) {
618                 if (doSave) {
619                         (*j).m_impl->save();
620                 }
621                 files.push_back((*j).m_impl->getLocation());
622                 relativeOffsets.second = (*j++).length() - 1;
623         }
624         cc = new TorrentHasher(range, files, relativeOffsets, ref);
625         cc->getEventTable().addHandler(c, this, &PartialTorrent::onCacheVerify);
626         IOThread::instance().postWork(cc);
627         return c;
628 }
629
630 // note: don't save children here (they are saved from fileslist, or explicitly
631 // as needed from verifyRange)
632 void PartialTorrent::save() {
633         RangeList<CacheFile>::CIter i = m_cache.begin();
634         while (i != m_cache.end()) try {
635                 (*i++).m_impl->save();
636         } catch (std::exception &e) {
637                 logError(boost::format("Saving torrent cache: %s") % e.what());
638                 logMsg(
639                         boost::format(
640                                 "Info: Auto-pausing '%s' for the above reason."
641                         ) % getName()
642                 );
643                 autoPause();
644                 return;
645         }
646         if (isAutoPaused()) {
647                 logMsg(
648                         boost::format("Info: Auto-resuming file '%s'.")
649                         % getName()
650                 );
651                 resume();
652         }
653 }
654
655 void PartialTorrent::childAllocDone(PartData *file) {
656         std::list<boost::intrusive_ptr<TorrentHasher> >::iterator i, j;
657         i = m_pendingChecks.begin();
658         while (i != m_pendingChecks.end()) {
659                 (*i)->allocDone(file);
660                 if ((*i)->canRun()) {
661                         IOThread::instance().postWork(*i);
662                         j = i++;
663                         m_pendingChecks.erase(j);
664                 } else {
665                         ++i;
666                 }
667         }
668 }
669
670 void PartialTorrent::onCacheVerify(HashWorkPtr wrk, HashEvent evt) {
671         if (evt == HASH_VERIFIED) {
672                 logDebug(
673                         COL_BGREEN " === Cache verification suceeded ==="
674                         COL_NONE
675                 );
676         } else if (evt == HASH_FAILED) {
677                 logDebug(
678                         COL_BMAGENTA " !!! Cache verification failed !!!"
679                         COL_NONE
680                 );
681         }
682 }
683
684 // base class gets updated from childCorruption() signal handler, there's no
685 // need to call base class's corrupt() method here.
686 void PartialTorrent::corruption(Range64 r) {
687         RangeList<InternalFile>::CIter i = m_children.getContains(r);
688
689         if (i == m_children.end() || (*i).begin() > r.end()) {
690                 return;
691         }
692
693         while (i != m_children.end() && (*i).contains(r)) {
694                 if ((*i).containsFull(r)) {
695                         Range64 k(r.begin()-(*i).begin(), r.end()-(*i).begin());
696                         CHECK_FAIL(k.length() == r.length());
697                         (*i).m_file->corruption(k);
698                 } else if ((*i).contains(r.begin())) {
699                         Range64 k(r.begin() - (*i).begin(), (*i).length() - 1);
700                         CHECK_FAIL(k.length() < r.length());
701                         (*i).m_file->corruption(k);
702                 } else if ((*i).contains(r.end())) {
703                         Range64 k(0, r.end() - (*i).begin());
704                         CHECK_FAIL(k.length() < r.length());
705                         (*i).m_file->corruption(k);
706                 } else {
707                         CHECK_FAIL((*i).length() < r.length());
708                         CHECK_FAIL((*i).length());
709                         (*i).m_file->corruption(Range64(0, (*i).length() - 1));
710                 }
711                 ++i;
712         }
713 }
714
715 void PartialTorrent::childCorruption(PartData *file, Range64 r) {
716         RIter i = m_childrenReverse.find(file);
717         CHECK_RET(i != m_childrenReverse.end());
718         CHECK_RET((*i).first == file);
719
720         Range64 tmpR(
721                 r.begin() + (*i).second,
722                 r.begin() + (*i).second + r.length() - 1
723         );
724         CHECK(tmpR.length() == r.length());
725         setCorrupt(tmpR);
726 }
727
728 bool PartialTorrent::childCanComplete(PartData *file) {
729         RIter i = m_childrenReverse.find(file);
730         CHECK_RETVAL(i != m_childrenReverse.end(), true);
731         CHECK_RETVAL((*i).first == file,           true);
732
733         if (file->getMetaData() && file->getMetaData()->getHashSetCount()) {
734                 return true;
735         } else {
736                 return isVerified(
737                         Range64((*i).second, (*i).second + file->getSize() - 1)
738                 );
739         }
740 }
741
742 void PartialTorrent::childChunkVerified(
743         PartData *file, uint64_t chunkSize, uint64_t chunk
744 ) {
745         RIter i = m_childrenReverse.find(file);
746         CHECK_RET(i != m_childrenReverse.end());
747         CHECK_RET((*i).first == file);
748         Range64 tmp(
749                 (*i).second + (chunk * chunkSize),
750                 (*i).second + (chunk + 1) * chunkSize
751         );
752         setVerified(tmp);
753 }
754
755 void PartialTorrent::childPaused(PartData *file) {
756         RIter i = m_childrenReverse.find(file);
757         CHECK_RET(i != m_childrenReverse.end());
758         CHECK_RET((*i).first == file);
759
760         Range64 toRemove((*i).second, (*i).second + file->getSize() - 1);
761         RangeList<CacheFile>::CIter one = m_cache.getContains(toRemove.begin());
762         if (one != m_cache.end() && toRemove.contains((*one).end() + 1)) {
763                 toRemove.begin((*one).end() + 1);
764         }
765         RangeList<CacheFile>::CIter two = m_cache.getContains(toRemove.end());
766         if (two != m_cache.end() && toRemove.contains((*two).begin() - 1)) {
767                 toRemove.end((*two).begin() - 1);
768         }
769         if (!m_cache.contains(toRemove)) {
770                 dontDownload(toRemove);
771         }
772
773         // when removing multiple files next to each other, remove the bordering
774         // caches as well, since we don't need them anymore.
775         if (one != m_cache.end() && !canDownloadSome(*one)) {
776                 m_cache.erase(*one);
777                 dontDownload(*one);
778         }
779         if (one != two && two != m_cache.end() && !canDownloadSome(*two)) {
780                 m_cache.erase(*two);
781                 dontDownload(*two);
782         }
783 }
784
785 // todo: re-add missing cache entries if needed
786 void PartialTorrent::childResumed(PartData *file) {
787         RIter i = m_childrenReverse.find(file);
788         CHECK_RET(i != m_childrenReverse.end());
789         CHECK_RET((*i).first == file);
790
791         Range64 toAdd((*i).second, (*i).second + file->getSize() - 1);
792         doDownload(toAdd);
793 }
794
795 void PartialTorrent::childDestroyed(PartData *file) {
796         RIter i = m_childrenReverse.find(file);
797         CHECK_RET(i != m_childrenReverse.end());
798         CHECK_RET((*i).first == file);
799
800         Range64 r((*i).second, (*i).second + file->getSize() - 1);
801         CHECK_RET(m_children.getContains(r) != m_children.end());
802         CHECK_RET(*m_children.getContains(r) == r);
803         m_children.erase(r);
804         m_childrenReverse.erase(file);
805         file->setParent(reinterpret_cast<Object*>(0));
806
807         if (!m_children.size()) {
808                 if (!file->isComplete()) {
809                         PartData::cancel();
810                         cleanCache(true);
811                 } else {
812                         getEventTable().postEvent(this, PD_COMPLETE);
813                         cleanCache(false);
814                 }
815                 destroy();
816         }
817 }
818
819 void PartialTorrent::parentChunkVerified(
820         PartData *file, uint64_t chunkSize, uint64_t chunk
821 ) {
822         CHECK_RET(file == this);
823
824         Range64 c(chunk * chunkSize, ((chunk + 1) * chunkSize) - 1);
825         RangeList<InternalFile>::CIter i = m_children.getContains(c);
826
827         CHECK_RET(i != m_children.end());
828
829         while (i != m_children.end() && (*i).contains(c)) {
830                 uint64_t offset = (*i).begin();
831                 if (c.containsFull(*i)) {           // all of file was verified
832                         (*i).m_file->setVerified(Range64(0, (*i).length() - 1));
833                 } else if ((*i).containsFull(c)) {  // entire chunk is in this
834                         Range64 tmp(c.begin() - offset, c.end() - offset);
835                         (*i).m_file->setVerified(tmp);
836                 } else if ((*i).contains(c.begin())) { // begins here
837                         Range64 tmp(c.begin() - offset, (*i).length() - 1);
838                         (*i).m_file->setVerified(tmp);
839                 } else if ((*i).contains(c.end())) { // ends here
840                         Range64 tmp(0, c.end() - offset);
841                         (*i).m_file->setVerified(tmp);
842                 }
843                 (*i++).m_file->tryComplete();
844         }
845 }
846
847 void PartialTorrent::pause() {
848         RIter i = m_childrenReverse.begin();
849         while (i != m_childrenReverse.end()) {
850                 (*i++).first->pause();
851         }
852         PartData::pause();
853 }
854
855 void PartialTorrent::stop() {
856         RIter i = m_childrenReverse.begin();
857         while (i != m_childrenReverse.end()) {
858                 (*i++).first->stop();
859         }
860         PartData::stop();
861 }
862
863 void PartialTorrent::resume() {
864         RIter i = m_childrenReverse.begin();
865         while (i != m_childrenReverse.end()) {
866                 (*i++).first->resume();
867         }
868         PartData::resume();
869 }
870
871 // don't call PartData::cancel() here, since that's done indirectly from
872 // onChildDestroyed
873 void PartialTorrent::cancel() {
874         RIter i = m_childrenReverse.begin();
875         while (i != m_childrenReverse.end()) {
876                 (*i++).first->cancel();
877         }
878 }
879
880 void PartialTorrent::allocDiskSpace() {
881         RIter i = m_childrenReverse.begin();
882         while (i != m_childrenReverse.end()) {
883                 (*i++).first->allocDiskSpace();
884         }
885 }
886
887 void PartialTorrent::cleanCache(bool delTorrent) {
888         std::string tmp(getLocation().leaf());
889         boost::filesystem::directory_iterator it(getLocation().branch_path());
890         boost::filesystem::directory_iterator end;
891         while (it != end) {
892                 if (!delTorrent && (*it).leaf() == tmp) {
893                         ++it;
894                         continue;
895                 }
896                 if (boost::algorithm::starts_with((*it).leaf(), tmp)) try {
897                         logDebug("Removing " + (*it).native_file_string());
898                         boost::filesystem::remove(*it);
899                 } catch (std::exception &e) {}
900                 ++it;
901         }
902 }
903
904 // TorrentHasher class
905 // -------------------
906 TorrentHasher::TorrentHasher(
907         Range64 globalOffsets,
908         const std::vector<boost::filesystem::path> &files,
909         std::pair<uint64_t, uint64_t> relativeOffsets, const HashBase *ref
910 ) : HashWork(files[0], relativeOffsets.first, relativeOffsets.second, ref),
911 m_files(files), m_curFile(m_files.begin()), m_globalOffsets(globalOffsets) {
912         CHECK(files.size());
913         CHECK(ref);
914 }
915
916 TorrentHasher::~TorrentHasher() {
917 }
918
919 // while the actual reading is done by base class, here we "swap out" the
920 // underlying file when crossing file boundaries. Basically, if we reach the end
921 // of current file, and we still need data, we switch (via openFile() call) to
922 // the next file. openFile() opens the file pointed to by m_fileName, which we
923 // adjust before calling it.
924 uint64_t TorrentHasher::readNext(uint64_t pos) {
925         if (m_files.size() == 1) {
926                 return HashWork::readNext(pos);
927         } else {
928                 Iter i = m_curFile;
929                 if (++i == m_files.end()) {
930                         return HashWork::readNext(pos);
931                 } else {
932                         uint64_t tmpEnd = m_end;
933                         m_end = std::numeric_limits<uint64_t>::max();
934                         uint64_t ret = HashWork::readNext(pos);
935                         m_end = tmpEnd;
936                         if (ret < getBufSize() && ++m_curFile != m_files.end()){
937                                 m_fileName = *m_curFile;
938                                 openFile();
939                         }
940                         return ret;
941                 }
942         }
943 }
944
945 // before sending the hash results back to the calling code, re-adjust the
946 // offsets to global, since internally we used relative offsets within the
947 // first and last files of this hash job (in case the job crossed multiple
948 // file boundaries)
949 void TorrentHasher::finish() {
950         if (!isFull()) {
951                 m_begin = m_globalOffsets.begin();
952                 m_end   = m_globalOffsets.end();
953         }
954         HashWork::finish();
955 }
956
957 } // end Bt namespace
Note: See TracBrowser for help on using the browser.