/** * Copyright (C) 2004-2005 Alo Sarv <madcat_@users.sourceforge.net> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <hn/rangelist.h> #include <hn/hasher.h> #include <boost/filesystem/path.hpp> #include <boost/weak_ptr.hpp> #include <boost/shared_ptr.hpp> #include <boost/enable_shared_from_this.hpp> #include <boost/multi_index_container.hpp> #include <boost/multi_index/key_extractors.hpp> #include <boost/multi_index/ordered_index.hpp> #include <map> #include <list> class SharedFile; class HashBase; class PartData; /** * Events emitted from PartData object */ enum PDEvent { PD_DATA_ADDED = 1, //!< Posted whenever a new PartData is constructed PD_DATA_FLUSHED, //!< Posted whenever a partdata flushes buffers PD_DESTROY, //!< Posted whenever a partdata is about to be destroyed PD_VERIFYING, //!< When file is complete, but in verification phase PD_MOVING, //!< When file is verified, and moving is in progress PD_COMPLETE //!< File has been completed and placed in Incoming dir }; class PartData { public: DECLARE_EVENT_TABLE(PartData*, int); class UsedRange; class LockedRange; private: class Chunk : public Range64 { public: /** * Construct new chunk. * * @param parent Parent object * @param begin Begin offset (inclusive) * @param end End offset (inclusive) * @param hash Optional chunkhash for this data */ Chunk( PartData *parent, uint64_t begin, uint64_t end, const HashBase *hash = 0 ); /** * Construct Chunk from pre-existing Range object * * @param parent Parent object * @param range The range * @param hash Optional chunkhash for this data */ Chunk(PartData *parent, Range64 range, const HashBase *hash =0); //! \name Getters //! @{ bool isVerified() const { return m_verified; } bool isPartial() const { return m_partial; } uint32_t getAvail() const { return m_avail; } uint32_t getUseCnt() const { return m_useCnt; } bool isComplete() const; //! @} //! \name Setters //! @{ void setVerified(bool v) { m_verified = v; } void setPartial(bool p) { m_partial = p; } void setAvail(uint32_t a) { m_avail = a; } void setUseCnt(uint32_t u) { m_useCnt = u; } //! @} //! Need to declare this here too for Boost.MultiIndex to work uint64_t length() const { return Range64::length(); } public: // Needed public for Boost::multi_index PartData *m_parent; //!< File this chunk belongs to const HashBase *m_hash; //!< Optional bool m_verified;//!< If it is verified. bool m_partial; //!< If it is partially downloaded uint32_t m_avail; //!< Availability count uint32_t m_useCnt; //!< Use count private: Chunk(); //!< Forbidden friend class LockedRange; //! Allowed by LockedRange class void write(uint64_t begin, const std::string &data); void onHashEvent(HashWorkPtr w, HashEvent evt); }; struct ID_Pos; struct ID_Verified; struct ID_Partial; struct ID_Avail; struct ID_UseCnt; struct ID_Length; typedef boost::multi_index_container< Chunk, boost::multi_index::indexed_by< boost::multi_index::ordered_non_unique< boost::multi_index::tag<ID_Pos>, boost::multi_index::identity<Chunk> >, boost::multi_index::ordered_non_unique< boost::multi_index::tag<ID_Verified>, boost::multi_index::member< Chunk, bool, &Chunk::m_verified > >, boost::multi_index::ordered_non_unique< boost::multi_index::tag<ID_Partial>, boost::multi_index::member< Chunk, bool, &Chunk::m_partial > >, boost::multi_index::ordered_non_unique< boost::multi_index::tag<ID_Avail>, boost::multi_index::member< Chunk, uint32_t, &Chunk::m_avail > >, boost::multi_index::ordered_non_unique< boost::multi_index::tag<ID_UseCnt>, boost::multi_index::member< Chunk, uint32_t, &Chunk::m_useCnt > >, boost::multi_index::ordered_non_unique< boost::multi_index::tag<ID_Length>, boost::multi_index::const_mem_fun< Chunk, uint64_t, &Chunk::length > > > > ChunkMap; typedef ChunkMap::index<ID_Pos >::type CMPosIndex; typedef ChunkMap::index<ID_Verified>::type CMVerifiedIndex; typedef ChunkMap::index<ID_Partial >::type CMPartialIndex; typedef ChunkMap::index<ID_Avail >::type CMAvailIndex; typedef ChunkMap::index<ID_UseCnt >::type CMUseIndex; typedef ChunkMap::index<ID_Length >::type CMLenIndex; public: /** * \brief Construct a NEW temporary file * * Using this constructor, a new temporary file is constructed, at * specified location with specified size. * * @param size Size of the resulting file. * @param loc Location on disk where to store the temp file * @param dest Destination where to write the complete file * * \note The disk space indicated by @param size is not allocated on * actual disk right away. Instead, the size is allocated * dynamically as the file grows. This can be changed from global * application preferences though. */ PartData( uint64_t size, const boost::filesystem::path &loc, const boost::filesystem::path &dest ); /** * \brief Load a previously constructed temporary file. * * This method can be used to resume a previously started download, by * reading the necessery data from @param loc. * * @param loc Path to PartData reference file, which contains the * data required to resume the download. */ PartData(const boost::filesystem::path &loc); /** * \brief Add an availability chunk mask * * Allows modules to register chunk availability maps, so PartData * can decide the lowest-available chunk to be returned from get* * methods * * @param chunkSize Size of one chunk * @param chunks Boolean vector, where each true indicates * the source having the part, and false the source * not having the part. */ void addSourceMask(uint32_t chunkSize, const std::vector<bool> &chunks); /** * \brief Optimized version of addSourceMask(), adds a full source. * * Similar to addSourceMask(), this adds a source which has the entire * file. * * @param chunkSize Size of one chunk */ void addFullSource(uint32_t chunkSize); /** * \brief Locates a range that PartData considers most important. * * The current implementation considers partially completed ranges top * priority, followed by rarest chunks, and then lowest used chunks. * * @param size Optional, size of the range to be aquired. * @return Pointer to a range marked as 'used'. * * \note The size of the given range my be smaller than was requested. * \note Current implementation ignores the size parameter. * \throws PartData::RangeError if no usable range could be found. */ boost::shared_ptr<UsedRange> getRange(uint32_t size = 0); /** * \brief Locates a range which is also contained in passed chunkmap. * * This method restricts getRange() call to only chunks which are * indicated by a true value in the passed chunkmap (e.g. partial * sources). * * @param size Size of a chunk in the chunkmap * @param chunks The chunks the source has * @return Pointer to a range marked as 'used'. * * \throws PartData::RangeError if no usable range could be found. */ boost::shared_ptr<UsedRange> getRange( uint32_t size, const std::vector<bool> &chunks ); /** * \brief Simply writes data starting at specified offset. * * Checks will be performed to ensure the validity of the location * and that it's not already complete or locked. */ void write(uint64_t beginOffset, const std::string &data); /** * \name Check for completeness. * Methods to check whether the entire file, or a part of it, is * complete. */ //!@{ bool isComplete() const; bool isComplete(const Range64 &subRange) const; bool isComplete(uint64_t begin, uint64_t end) const; //!@} /** * \name Generic accessors */ //!@{ uint32_t getChunkCount(uint32_t chunkSize) const; MetaData* getMetaData() const { return m_md; } void setMetaData(MetaData *md); //!@} /** * \brief Range marked as "in use". * * UsedRange concept is similar to many thread libraries lock object * concepts - you retrieve one via get() methods in PartData, and when * it is destroyed, it takes care that all used/locked ranges do get * freed properly. This object may only be used when wrapped in * boost::shared_ptr. * * \note There may be multiple UsedRange's refering to same Chunk. * This is indicated by m_useCnt member of Chunk class. */ class UsedRange : public Range64, public boost::enable_shared_from_this<UsedRange> { public: /** * \brief Aquire a lock within this UsedRange * * @param size Size of the requested lock * @return Locked range object * * \note The returned locked range may be smaller than requested * \throws PartData::LockError if locking fails for any reason */ boost::shared_ptr<LockedRange> getLock(uint32_t size); /** * Check this UsedRange for completeness */ bool isComplete() const; //! Destructor public for boost::checked_deleter ~UsedRange(); private: friend class PartData; /** * \brief Constructor * * Allowed only by PartData. UsedRange keeps a pointer back to * its parent object, and also sets up event handers as * neccesery to ensure the pointer remains valid. * * \note The template may be left undefined here since it's * only called from inside partdata.cpp */ template<typename IterType> UsedRange(PartData *parent, IterType it); //! copying is not allowed UsedRange(const UsedRange&); UsedRange& operator=(const UsedRange&); //! Parent PartData PartData *m_parent; //! Chunk this UsedRange refers to CMAvailIndex::iterator m_chunk; }; /** * \brief LockedRange object is an exclusivly locked Range in PartData. * * The lock is aquired upon call to UsedRange::getLock() method, which * constructs the lock object and returns to client code. The indicated * range in PartData is then exclusivly locked, with this object being * the only one allowed to access the locked region of the file. Upon * the destruction of this object, the lock is automatically freed. */ class LockedRange : public Range64 { public: //! Destructor public for boost::checked_deleter ~LockedRange(); /** * \brief Write data to within this locked region. * * @param beginOffset Begin offset where to write data * @param data Data to be written * * \throws LockError If attempting to write outside this lock */ void write(uint64_t beginOffset, const std::string &data); private: friend class PartData; /** * \brief Construct new Lock * * @param parent PartData object this lock belongs to * @param r Range to be locked */ LockedRange(PartData *parent, Range64 r); /** * \brief Construct new lock and associate with chunk * * @param parent PartData object this lock belongs to * @param r Range to be locked * @param it Iterator to chunk the lock belongs to */ template<typename IterType> LockedRange(PartData *parent, Range64 r, IterType it); //! Copying locks is forbidden LockedRange(const LockedRange&); //! Copying locks is forbidden LockedRange& operator=(const LockedRange&); //!< Parent file PartData *m_parent; //! The chunk containing this LockedRange. May be invalid. CMAvailIndex::iterator m_chunk; }; //! Exception class struct LockError : public std::runtime_error { LockError(const std::string&); }; //! Exception class struct RangeError : public std::runtime_error { RangeError(const std::string&); }; private: friend class SharedFile; friend int test_main(int, char[]); //! Copying part files is not allowed PartData(const PartData&); PartData& operator=(const PartData&); //! Only allowed by SharedFile ~PartData(); /** * \short Aquire lock on a subrange of UsedRange. * * @param r UsedRange to aquire lock within * @param size Upper limit on the size of the LockedRange requested. * @return Locked range * * \throws PartData::LockError if no lock could be aquired. */ boost::shared_ptr<LockedRange> getLock( boost::shared_ptr<UsedRange> r, uint32_t size ); /** * \name Implementation functions */ //!@{ void checkAddChunkMap(uint32_t chunkSize); void addHashSet(const HashSetBase *hs); template<typename Predicate> boost::shared_ptr<UsedRange> doGetRange(uint64_t size, Predicate &pred); void doWrite(uint64_t begin, const std::string &data); void flushBuffer(); void onHashEvent(HashWorkPtr p, HashEvent evt); boost::logic::tribool verifyHashSet(const HashSetBase *hs); void doComplete(); //!@} /** * \name Main rangelists. * * These lists are exclusive, no Range may exist simultaneously in * multiple of these lists. Also, none of these lists may contain * overlapping ranges. */ //! @{ RangeList64 m_complete; //!< Complete ranges RangeList64 m_locked; //!< Locked ranges RangeList64 m_corrupt; //!< Corrupt ranges //! @} /** * \name Implementation data members */ //!@{ ChunkMap m_chunks; uint64_t m_size; boost::filesystem::path m_location; boost::filesystem::path m_destination; std::map<uint64_t, std::string> m_buffer; typedef std::map<uint64_t, std::string>::iterator BIter; uint32_t m_toFlush; MetaData *m_md; uint16_t m_pendingHashes; //! Pointer to full rehash job (if any) in progress, used for canceling //! full rehash in case a chunkhash fails while this is in progress. HashWorkPtr m_fullJob; //!} public: //! For testing purposes only void printCompleted(); }; typedef boost::shared_ptr<PartData::UsedRange > UsedRangePtr; typedef boost::shared_ptr<PartData::LockedRange> LockedRangePtr;