sharedfile.cpp

Go to the documentation of this file.
00001 /**
00002  *  Copyright (C) 2004-2005 Alo Sarv <madcat_@users.sourceforge.net>
00003  *
00004  *  This program is free software; you can redistribute it and/or modify
00005  *  it under the terms of the GNU General Public License as published by
00006  *  the Free Software Foundation; either version 2 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License
00015  *  along with this program; if not, write to the Free Software
00016  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00017  */
00018 
00019 /** \file sharedfile.cpp Implementation of SharedFile class */
00020 
00021 #include <hn/hnprec.h>
00022 #include <hn/sharedfile.h>
00023 #include <hn/partdata.h>
00024 #include <hn/metadata.h>
00025 #include <hn/metadb.h>
00026 #include <hn/fileslist.h>                    // Needed for Object() constructor
00027 #include <boost/filesystem/operations.hpp>
00028 #include <boost/filesystem/exception.hpp>
00029 #include <fcntl.h>
00030 
00031 /**
00032  * MoveWork class is a job object submitted to WorkThread for processing;
00033  * MoveWork performs file moving from source directory to destination directory,
00034  * commonly used when completing a file.
00035  *
00036  * MoveWork emits EVT_MOVE_OK when moving succeeds, or EVT_MOVE_FAILED if it
00037  * fails.
00038  *
00039  * \note Event constants are defined inside MoveWork class scope
00040  * \note MoveWork object should always be constructed into boost::shared_ptr
00041  */
00042 class MoveWork :
00043         public ThreadWork,
00044         public boost::enable_shared_from_this<MoveWork>
00045 {
00046 public:
00047         DECLARE_EVENT_TABLE(boost::shared_ptr<MoveWork>, int);
00048         enum MoveEvent { EVT_MOVE_OK, EVT_MOVE_FAILED };
00049 
00050         /**
00051          * Constructor
00052          *
00053          * @param src    Source location
00054          * @param dest   Destination location
00055          */
00056         MoveWork(
00057                 const boost::filesystem::path &src,
00058                 const boost::filesystem::path &dest
00059         ) : m_src(src), m_dest(dest) {}
00060 
00061         /**
00062          * Processes the moving. This may take some time.
00063          *
00064          * When both source and destination are located on same partition, the
00065          * file is simply renamed, which takes nearly no time. However, when the
00066          * source and destination are located on different partitions, entire
00067          * data needs to be copied. Furthermore, some implementations do not
00068          * support renaming accross partitions, so we fall back to explicit
00069          * copy + remove in those cases.
00070          *
00071          * Upon successful completition, EVT_MOVE_OK is emitted. On failure,
00072          * EVT_MOVE_FAILEd is emitted.
00073          */
00074         bool process() try {
00075                 logMsg(
00076                         boost::format("Moving file %s -> %s")
00077                         % m_src.native_file_string()
00078                         % m_dest.native_file_string()
00079                 );
00080                 try {
00081                         boost::filesystem::rename(m_src, m_dest);
00082                 } catch (const boost::filesystem::filesystem_error&) {
00083                         boost::filesystem::copy_file(m_src, m_dest);
00084                         boost::filesystem::remove(m_src);
00085                 }
00086                 getEventTable().postEvent(shared_from_this(), EVT_MOVE_OK);
00087                 setComplete();
00088                 return true;
00089         } catch (std::exception &e) {
00090                 logError(boost::format("Moving completed file: %s") % e.what());
00091                 getEventTable().postEvent(shared_from_this(), EVT_MOVE_FAILED);
00092                 setComplete();
00093                 return true;
00094         }
00095         //! \returns source location
00096         boost::filesystem::path getSrc()  const { return m_src; }
00097         //! \returns destination location
00098         boost::filesystem::path getDest() const { return m_dest; }
00099 private:
00100         const boost::filesystem::path m_src;  //!< Source path
00101         const boost::filesystem::path m_dest; //!< Destination path
00102 };
00103 IMPLEMENT_EVENT_TABLE(MoveWork, boost::shared_ptr<MoveWork>, int);
00104 
00105 // SharedFile Class
00106 // ----------------
00107 IMPLEMENT_EVENT_TABLE(SharedFile, SharedFile*, int);
00108 
00109 // Constructor for existing Shared File object (NOT partial file)
00110 SharedFile::SharedFile(boost::filesystem::path location, MetaData *md /* =0 */)
00111 : Object(&FilesList::instance(), location.leaf()),
00112 m_location(location), m_metaData(md), m_partData(), m_uploaded(),
00113 m_size(Utils::getFileSize(m_location)) {
00114         using namespace boost::filesystem;
00115 
00116         // explicit sanity checking
00117         if (!exists(location)) {
00118                 std::string msg = (boost::format(
00119                         "Specified file '%s' does not exist."
00120                 ) % location.native_file_string()).str();
00121                 throw std::runtime_error(msg);
00122         }
00123         if (is_directory(location)) {
00124                 std::string msg = (boost::format(
00125                         "Specified file '%s' is a directory."
00126                 ) % location.native_file_string()).str();
00127                 throw std::runtime_error(msg);
00128         }
00129         if (is_empty(location)) {
00130                 std::string msg = (boost::format(
00131                         "Attempt to create empty shared file from '%s'."
00132                 ) % location.native_file_string()).str();
00133                 throw std::runtime_error(msg);
00134         }
00135         setName(getName());
00136 
00137         verify(); // That function performs all the verification we need
00138 
00139         if (m_metaData) {
00140                 getEventTable().postEvent(this, SF_METADATA_ADDED);
00141         }
00142 
00143         logTrace(TRACE_SHAREDFILE, boost::format(
00144                 "New sharedfile: %s %|60t| Size: %.2fMB"
00145                 ) % location.native_file_string() % (m_size/1024.0/1024.0)
00146         );
00147         getEventTable().postEvent(this, SF_ADDED);
00148 }
00149 
00150 // Construct using PartData and (optional) MetaData pointers. This constructor
00151 // creates a 'partial' shared file. Note that we do not require filename/path
00152 // to be passed here - we can retrieve it ourselves from PartData (which does
00153 // require m_destination string).
00154 // Preconditions:  partData->getDestination() returns valid destination path
00155 //                 partData->getFileSize() returns a correct file size
00156 SharedFile::SharedFile(PartData *partData, MetaData *metaData /* = 0 */)
00157 : Object(&FilesList::instance(), "sharedFile"), m_metaData(metaData),
00158 m_partData(partData), m_uploaded(), m_size() {
00159         CHECK_THROW(partData);
00160 
00161         setPartData(partData);
00162 
00163         // We need file name ...
00164         using namespace boost::filesystem;
00165         m_size = partData->getSize();
00166         setName(getName());                   // Also for Object hierarchy
00167 
00168         verify();
00169 
00170         // don't even continue if we'r a duplicate ... and if we'r a temp file,
00171         // delete our temp files too to avoid further errors during next loads
00172         if (m_metaData && isDuplicate()) {
00173                 boost::format fmt("Duplicate %s: %s");
00174                 if (m_partData) {
00175                         fmt % "incomplete download and shared file";
00176                         m_partData->deleteFiles();
00177                 } else {
00178                         fmt % "shared file";
00179                 }
00180                 fmt % getName();
00181                 throw std::runtime_error(fmt.str());
00182         }
00183 
00184         if (m_metaData && m_metaData != m_partData->getMetaData()) {
00185                 // both have metadata - trust the one from metadb
00186                 delete m_partData->getMetaData();
00187                 m_partData->setMetaData(m_metaData);
00188         } else if (!m_metaData && m_partData->getMetaData()) {
00189                 // we'r missing metadata, partdata has it
00190                 m_metaData = m_partData->getMetaData();
00191         }
00192 
00193         getEventTable().postEvent(this, SF_ADDED);
00194 }
00195 
00196 // Verifies our filesize
00197 void SharedFile::checkFileSize(MetaData *md) {
00198         CHECK_THROW(md);
00199 
00200         if (getSize() != md->getFileSize()) {
00201                 // Doh - something is fishy ... Not much we can do tho
00202                 logWarning(boost::format(
00203                         "SharedFile: MetaData filesize (%d) and real "
00204                         "filesize (%d) do not match!")
00205                         % md->getFileSize() % getSize()
00206                 );
00207                 md->setFileSize(getSize()); // Fix MetaData
00208         }
00209 }
00210 
00211 // Destructor
00212 SharedFile::~SharedFile() {
00213         try {
00214                 // Abort hash job (if any)
00215                 boost::shared_ptr<HashWork> hw(m_pendingJob);
00216                 hw->invalidate();
00217         } catch (boost::bad_weak_ptr &) {
00218                 // Ignored - no job was in progress
00219         }
00220         if (m_partData) {
00221                 PartData::getEventTable().safeDelete(m_partData);
00222         }
00223 }
00224 
00225 // Locate MetaData
00226 MetaData* SharedFile::findMetaData(
00227         boost::filesystem::path loc, const std::string &name, PartData *pd
00228 ) {
00229         // Retrieve file size and mod date
00230         uint64_t fileSize = 0;
00231         if (pd) {
00232                 fileSize = pd->getSize();
00233         } else {
00234                 fileSize = Utils::getFileSize(loc);
00235         }
00236         uint32_t fileDate = Utils::getModDate(loc);
00237 
00238         logTrace(
00239                 TRACE_SHAREDFILE,
00240                 boost::format("Searching for MetaData for file %s (%s)")
00241                 % loc.native_file_string() % name
00242         );
00243         logTrace(
00244                 TRACE_SHAREDFILE,
00245                 boost::format("FileName is %s, size is %d") % name
00246                 % fileSize
00247         );
00248 
00249         std::vector<MetaData*> md = MetaDb::instance().find(name);
00250         logTrace(
00251                 TRACE_SHAREDFILE,
00252                 boost::format("%d possible candidates found.") % md.size()
00253         );
00254         for (
00255                 std::vector<MetaData*>::iterator i = md.begin();
00256                 i != md.end(); ++i
00257         ) {
00258                 // Verify size
00259                 if ((*i)->getFileSize() != fileSize) {
00260                         logTrace(TRACE_SHAREDFILE, boost::format(
00261                                 "Filesize doesn't match (%d != %d)")
00262                                 % (*i)->getFileSize() % fileSize
00263                         );
00264                         continue;
00265                 }
00266                 // Verify modification date
00267                 if ((*i)->getModDate() != fileDate) {
00268                         logTrace(TRACE_SHAREDFILE, boost::format(
00269                                 "Modification date doesn't match (%d != %d)"
00270                                 ) % (*i)->getModDate() % fileDate
00271                         );
00272                         continue;
00273                 }
00274                 return *i;
00275         }
00276         logTrace(TRACE_SHAREDFILE, "Not found.");
00277         return 0;
00278 }
00279 
00280 //! Handle PartData events
00281 void SharedFile::onPartEvent(PartData *pd, int evt) {
00282         CHECK_THROW(pd == m_partData);
00283 
00284         if (evt == PD_DATA_FLUSHED) {
00285                 updateModDate();
00286         } else if (evt == PD_DESTROY) {
00287                 m_pdSigHandler.disconnect();
00288                 if (!m_partData->isComplete()) {
00289                         // download was canceled - destroy SharedFile also
00290                         destroy();
00291                 }
00292                 m_partData = 0; // deleted by EventTable safeDelete()
00293         } else if (evt == PD_COMPLETE) {
00294                 //! Optimize this: Don't call second thread when src and dest
00295                 //! are on same partition.
00296                 MoveWorkPtr p(
00297                         new MoveWork(pd->getLocation(), pd->getDestination())
00298                 );
00299                 MoveWork::getEventTable().addHandler(
00300                         p, this, &SharedFile::onMoveEvent
00301                 );
00302                 logDebug(
00303                         boost::format("Requesting MoveWork(%s -> %s)")
00304                         % p->getSrc().native_file_string()
00305                         % p->getDest().native_file_string()
00306                 );
00307                 WorkThread::instance().postWork(p);
00308         }
00309 }
00310 
00311 void SharedFile::onMoveEvent(MoveWorkPtr wrk, int evt) {
00312         if (evt == MoveWork::EVT_MOVE_OK) {
00313                 m_partData->deleteFiles();
00314                 m_partData->destroy();
00315                 setLocation(wrk->getDest());
00316                 logMsg(
00317                         boost::format(
00318                                 COL_BBLUE "Download complete: "
00319                                 COL_BYELLOW "%s" COL_NONE
00320                         ) % getName()
00321                 );
00322         }
00323 }
00324 
00325 void SharedFile::setPartData(PartData *pd) {
00326         CHECK_THROW(pd);
00327 
00328         m_pdSigHandler = PartData::getEventTable().addHandler(
00329                 pd, this, &SharedFile::onPartEvent
00330         );
00331         m_partData = pd;
00332         m_location = m_partData->getLocation();
00333 }
00334 
00335 // Destroy a shared file. Notice that we remove ourselves as event handler from
00336 // our partdata before also signalling partdata destruction (in case we have
00337 // partdata), because otherwise we'd end up getting called back ourselves from
00338 // event table, but since we are also heading for destruction, that would be
00339 // really bad karma ...
00340 void SharedFile::destroy() {
00341         getEventTable().postEvent(this, SF_DESTROY);
00342         if (m_partData) {
00343                 m_pdSigHandler.disconnect();
00344                 m_partData->destroy();
00345         }
00346 }
00347 
00348 std::ostream& operator<<(std::ostream &o, const SharedFile &) {
00349         // TODO: SharedFile::operator<< (core/gui comm)
00350         logError("SharedFile::operator<< not implemented!");
00351         return o;
00352 }
00353 
00354 void SharedFile::verify() {
00355         if (!m_metaData) {
00356                 logTrace(
00357                         TRACE_SHAREDFILE,
00358                         boost::format("verify(): location=`%s' filename=`%s'")
00359                         % getLocation() % getName()
00360                 );
00361                 m_metaData = findMetaData(m_location, getName(), m_partData);
00362         }
00363         if (!m_metaData && !m_partData) {
00364                 // Too bad -> rehash the thing
00365                 boost::shared_ptr<HashWork> hw(new HashWork(m_location));
00366                 HashWork::getEventTable().addHandler(
00367                         hw, this, &SharedFile::onHashEvent
00368                 );
00369 
00370                 // Keep a weak reference to the job in case we want to abort it
00371                 m_pendingJob = boost::weak_ptr<HashWork>(hw);
00372                 WorkThread::instance().postWork(hw);
00373         } else if (m_metaData) {
00374                 checkFileSize(m_metaData);
00375                 // Associate MetaData with this SharedFile
00376                 MetaDb::instance().push(m_metaData, this);
00377         }
00378 }
00379 
00380 void SharedFile::onHashEvent(boost::shared_ptr<HashWork> hw, HashEvent evt) {
00381         // Clear all event handlers for this job
00382         HashWork::getEventTable().delHandler(hw);
00383 
00384         if (evt == HASH_FATAL_ERROR) {
00385                 logError(boost::format(
00386                         "Hasher reported fatal error. Removing shared file %s"
00387                 ) % hw->getFileName().native_file_string());
00388                 destroy();
00389         } else if (m_metaData) {
00390                 logDebug("TODO! Implement MetaData combining.");
00391         } else {
00392                 CHECK_THROW(hw->getMetaData());
00393                 CHECK_THROW(hw->getMetaData()->getHashSetCount());
00394                 m_metaData = hw->getMetaData();
00395 
00396                 if (!isDuplicate()) {
00397                         MetaDb::instance().push(hw->getMetaData(), this);
00398                         getEventTable().postEvent(this, SF_METADATA_ADDED);
00399                 }
00400         }
00401 }
00402 
00403 // check if there are any other SharedFiles with identical hash as this one.
00404 // if that's the case, and the found file is partial, cancel the download,
00405 // otherwise, simply record this location also in the existing file and
00406 // destroy(). Return true if duplicate was found, false otherwise.
00407 bool SharedFile::isDuplicate() {
00408         CHECK_THROW(m_metaData);
00409         SharedFile *sf = 0;
00410         for (uint32_t i = 0; i < m_metaData->getHashSetCount(); ++i) {
00411                 sf = MetaDb::instance().findSharedFile(
00412                         m_metaData->getHashSet(i)->getFileHash()
00413                 );
00414                 if (sf == this) {
00415                         sf = 0;
00416                 }
00417                 if (sf) {
00418                         break;
00419                 }
00420         }
00421         if (sf) {
00422                 if (sf->isPartial()) {
00423                         logMsg(
00424                                 boost::format(
00425                                         "Currently downloaded file `%s' found "
00426                                         "shared at `%s'.\nAborting download - "
00427                                         "you already have this file."
00428                                 ) % sf->getName()
00429                                 % m_location.native_file_string()
00430                         );
00431                         sf->getPartData()->destroy();
00432                         sf->setLocation(m_location);
00433                         sf->setMetaData(m_metaData);
00434                 } else {
00435                         sf->addLocation(m_location);
00436                         delete m_metaData;
00437                         m_metaData = 0;
00438                 }
00439                 destroy();
00440                 return true;
00441         }
00442         return false;
00443 }
00444 
00445 // read data from disk and return it
00446 // note that we ALWAYS check modification date for file before reading data from
00447 // it. While this comes with a performance penalty, this is a needed security
00448 // feature here, to avoid EVER sending out unverified data.
00449 //
00450 // when the main location fails, either due to modification, or due to non-
00451 // existance, we attempt to switch to alternative locations, if present, and
00452 // recurse into this function again, until we either run out of alternative
00453 // locations, or find a location where the file has correct modification date
00454 // and is readable.
00455 std::string SharedFile::read(uint64_t begin, uint64_t end) {
00456         if (m_partData) {
00457                 CHECK_THROW(m_partData->isComplete(begin, end));
00458         }
00459 
00460         bool ok = false;
00461 
00462         if (m_metaData) {
00463                 ok = Utils::getModDate(m_location) == m_metaData->getModDate();
00464         }
00465 
00466         int fd = open(
00467                 getPath().native_file_string().c_str(),
00468                 O_RDONLY|O_LARGEFILE|O_BINARY
00469         );
00470 
00471         if (fd == -1 || !ok) { // open failed or modification date is wrong
00472                 if (m_locations.size()) {
00473                         setLocation(m_locations.back().second);
00474                         m_locations.pop_back();
00475                         return read(begin, end);
00476                 }
00477                 boost::format fmt("Unable to open shared file %s%s");
00478                 fmt % getName();
00479                 if (isPartial()) {
00480                         fmt % (" (loc=" + getPath().native_file_string() + ")");
00481                 } else {
00482                         fmt % "";
00483                 }
00484                 destroy(); // sorry, but if our file died, so shall we
00485                 throw std::runtime_error(fmt.str());
00486         }
00487 
00488         boost::scoped_array<char> buf(new char[end - begin + 1]);
00489 
00490         try {
00491                 uint64_t ret = lseek64(fd, begin, SEEK_SET);
00492                 CHECK_THROW(ret == begin);
00493                 int check = ::read(fd, buf.get(), end - begin + 1);
00494                 CHECK_THROW(check == static_cast<int>(end - begin + 1));
00495         } catch (...) {
00496                 close(fd);
00497                 throw;
00498         }
00499         close(fd);
00500 
00501         std::string ret(buf.get(), end - begin + 1);
00502 
00503         return ret;
00504 }
00505 
00506 std::string SharedFile::getName() const {
00507         if (m_partData) {
00508                 return m_partData->getDestination().leaf();
00509         } else {
00510                 return m_location.leaf();
00511         }
00512 }
00513 
00514 void SharedFile::updateModDate() {
00515         if (m_metaData) {
00516                 logTrace(TRACE_SHAREDFILE, "Updating modification date.");
00517                 m_metaData->setModDate(Utils::getModDate(m_location));
00518         }
00519 }
00520 
00521 bool SharedFile::isPartial() const {
00522         return m_partData;
00523 }
00524 bool SharedFile::isComplete() const {
00525         return !m_partData;
00526 }
00527 
00528 // change the metadata for this shared file.
00529 void SharedFile::setMetaData(MetaData *md) {
00530         CHECK_THROW(md);
00531         m_metaData = md;
00532 }
00533 
00534 void SharedFile::addLocation(const boost::filesystem::path &loc) {
00535         m_locations.push_back(std::make_pair(Utils::getModDate(loc), loc));
00536 }
00537 
00538 void SharedFile::setLocation(const boost::filesystem::path &loc) {
00539         m_location = loc;
00540         if (m_metaData) {
00541                 m_metaData->setModDate(Utils::getModDate(loc));
00542         }
00543 }