fileslist.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 fileslist.cpp Implementation of FilesList class */
00020 
00021 #include <hn/hnprec.h>
00022 #include <hn/fileslist.h>
00023 #include <hn/event.h>
00024 #include <hn/hydranode.h>
00025 #include <hn/prefs.h>
00026 #include <hn/metadata.h>
00027 #include <hn/metadb.h>
00028 #include <hn/sharedfile.h>
00029 #include <hn/partdata.h>
00030 #include <fstream>
00031 #include <boost/filesystem/operations.hpp>
00032 #include <boost/lexical_cast.hpp>
00033 #include <boost/random/mersenne_twister.hpp>     // For random number generator
00034 #include <boost/algorithm/string/predicate.hpp>
00035 #include <boost/algorithm/string/replace.hpp>
00036 #include <boost/algorithm/string/trim.hpp>
00037 
00038 //! Random number generator functor
00039 static boost::mt19937 getRandom;
00040 
00041 // FilesList Class
00042 // ---------------
00043 // Constructor (private)
00044 FilesList::FilesList() : Object(&HydraNode::instance(), "files") {}
00045 
00046 // Destructor (private)
00047 FilesList::~FilesList() {
00048         for (SFIter i = m_list.begin(); i != m_list.end(); ++i) {
00049                 delete *i;
00050         }
00051 
00052         if (HashWork::getTime() && HashWork::getHashed()) {
00053                 boost::format fmt("Hasher: %s checksummed in %.2fs (%s/s)");
00054                 uint64_t amount = HashWork::getHashed();
00055                 float    time   = HashWork::getTime();
00056                 fmt % Utils::bytesToString(amount) % time;
00057                 fmt % Utils::bytesToString(static_cast<uint64_t>(amount/time));
00058                 logMsg(fmt);
00059         }
00060 
00061 }
00062 
00063 FilesList& FilesList::instance() {
00064         static FilesList fl;
00065         return fl;
00066 }
00067 
00068 void FilesList::push(SharedFile *file) {
00069         CHECK_THROW(file != 0);
00070         if (!m_list.insert(file).second) {
00071                 throw std::runtime_error(
00072                         "Attempt to insert a SharedFile which already exists."
00073                 );
00074         }
00075 }
00076 
00077 void FilesList::addSharedDir(const std::string &path, bool recurse) try {
00078         CHECK_THROW(!path.empty());
00079         verifyPath(path);
00080         scanSharedDir(path, recurse);
00081 } catch (std::exception &e) {
00082         logError(boost::format("Error scanning shared directory: %s")%e.what());
00083 }
00084 
00085 void FilesList::addTempDir(const std::string &path) try {
00086         CHECK_RET(!path.empty());
00087         verifyPath(path);
00088 
00089         m_tempDirs.insert(path);
00090         scanTempDir(path);
00091 } catch (std::exception &e) {
00092         logError(boost::format("Unable to scan temp dir: %s") % e.what());
00093 }
00094 
00095 // \todo remSharedDir recurse
00096 void FilesList::remSharedDir(const std::string &path, bool /* recurse */) {
00097         CHECK_RET(!path.empty());
00098         verifyPath(path);
00099 
00100         std::map<std::string, bool>::iterator ret = m_sharedDirs.find(path);
00101         if (ret == m_sharedDirs.end()) {
00102                 logDebug(boost::format(
00103                         "Attempt to un-share path '%s' which was never shared."
00104                 ) % path);
00105                 return;
00106         }
00107         m_sharedDirs.erase(ret);
00108 
00109         // Scan through main map and locate entries which were in this
00110         // directory, and destroy them.
00111         for (SFIter i = m_list.begin(); i != m_list.end(); ++i) {
00112                 if ((*i)->getLocation() == path) {
00113                         (*i)->destroy();
00114                 }
00115         }
00116 }
00117 
00118 void FilesList::remTempDir(const std::string &path) {
00119         CHECK_RET(!path.empty());
00120         verifyPath(path);
00121 
00122         std::set<std::string>::iterator ret = m_tempDirs.find(path);
00123         if (ret == m_tempDirs.end()) {
00124                 logDebug(boost::format(
00125                         "Attempt to remove a temporary directory '%s' which is"
00126                         " not a temporary directory."
00127                 ) % path);
00128                 return;
00129         }
00130         m_tempDirs.erase(ret);
00131 
00132         // Scan through main map and locate part files which were in this
00133         // directory, and destroy them.
00134         for (SFIter i = m_list.begin(); i != m_list.end(); ++i) {
00135                 if ((*i)->getLocation() == path && (*i)->isPartial()) {
00136                         (*i)->destroy();
00137                 }
00138         }
00139 }
00140 
00141 void FilesList::verifyPath(const std::string &path) {
00142         boost::filesystem::path p(path, boost::filesystem::no_check);
00143         if (!boost::filesystem::is_directory(p)) {
00144                 std::string msg = (boost::format(
00145                         "Specified path '%s' is not a directory."
00146                 ) % path).str();
00147                 throw std::runtime_error(msg);
00148         }
00149         if (!boost::filesystem::exists(p)) {
00150                 std::string msg = (boost::format(
00151                         "Specified path '%s' does not exist."
00152                 ) % path).str();
00153                 throw std::runtime_error(msg);
00154         }
00155 }
00156 
00157 //! This variable keeps track of recursion count in scanSharedDir (which calls
00158 //! itself during recursive directories scanning) in order to write preferences
00159 //! only during first entrance of the function during recursive scan, but not
00160 //! during any subsequent recursive entrances. The variable is initialized to
00161 //! zero, increased by one during every entrance of scanSharedDir() method, and
00162 //! reduced by one during every exit of scanSharedDir() method. Thus, if this
00163 //! variable is set to 1, we are dealing with first entrance of the function.
00164 //! Once the recursion ends, the variable should be back at zero.
00165 static int recursion = 0;
00166 
00167 //! Directories scanning, optionally recursing.
00168 void FilesList::scanSharedDir(const std::string &dir, bool recurse) try {
00169         verifyPath(dir);
00170         recursion++;
00171         using namespace boost::filesystem;
00172 
00173         if (recursion == 1) {
00174                 logMsg(
00175                         boost::format("Scanning shared directory %s %s")
00176                         % dir % (recurse ? "recursivly" : "")
00177                 );
00178                 SDIter i = m_sharedDirs.find(dir);
00179                 if (i == m_sharedDirs.end()) {
00180                         m_sharedDirs.insert(std::make_pair(dir, recurse));
00181                 } else {
00182                         // We already have this dir - update it
00183                         if ((*i).second != recurse) {
00184                                 (*i).second = recurse;
00185                         }
00186                 }
00187                 // Write to prefs
00188                 boost::format key("/SharedDirs/Dir_%d");
00189                 key % m_sharedDirs.size();
00190                 Prefs::instance().write(key.str(), dir);
00191                 Prefs::instance().write(
00192                         "/SharedDirs/Count", m_sharedDirs.size()
00193                 );
00194                 if (recurse) {
00195                         Prefs::instance().write(key.str() + "_recurse", true);
00196                 }
00197         }
00198 
00199         // Scan it
00200         directory_iterator end_itr; // Default constructor -> end iterator
00201         for (directory_iterator i(path(dir, no_check)); i != end_itr; ++i) {
00202                 if (is_directory(*i)) {
00203                         if (recurse) {
00204                                 scanSharedDir((*i).string(), recurse);
00205                         }
00206                         continue;
00207                 }
00208                 NIter j = m_list.get<1>().find((*i).string());
00209                 if (j == m_list.get<1>().end()) {
00210                         SharedFile *f = new SharedFile(*i);
00211                         m_list.insert(f);
00212                 } else {
00213                         // Let the file re-verify its integrity
00214                         (*j)->verify();
00215                 }
00216         }
00217         recursion--;
00218 } catch (std::exception &er) {
00219         logError(boost::format("Scanning shared directories: %s") % er.what());
00220         recursion--;
00221 }
00222 MSVC_ONLY(;)
00223 
00224 /**
00225  * Scan directory for part files.
00226  */
00227 void FilesList::scanTempDir(const std::string &dir) {
00228         using namespace boost::filesystem;
00229         using namespace boost::algorithm;
00230 
00231         logDebug(boost::format("Scanning temp directory: %s") % dir);
00232 
00233         verifyPath(dir);
00234         uint32_t found = 0;
00235         directory_iterator end_itr; // default constructor -> end iterator
00236         for (directory_iterator i(dir); i != end_itr; ++i) {
00237                 if (!iends_with((*i).string(), ".tmp.dat")) {
00238                         continue;
00239                 }
00240                 if (loadTempFile((*i).string())) {
00241                         ++found;
00242                 }
00243         }
00244         logMsg(boost::format("Scanning Temp dir: Found %d temp files.") %found);
00245 }
00246 
00247 // bah ... writing exception-safe loading is crap. We really ought to consider
00248 // moving sharedfile, partdata and metadata completely to smart-pointers, would
00249 // certainly make things safer and cleaner (not only here, but all over the
00250 // place). But for the time being - *ARGH*
00251 bool FilesList::loadTempFile(const boost::filesystem::path &file) {
00252         using namespace boost::filesystem;
00253         using namespace boost::algorithm;
00254 
00255         std::string partName = file.string();
00256         replace_last(partName, ".tmp.dat.bak", ".tmp"); // in case of backups
00257         replace_last(partName, ".tmp.dat", ".tmp");
00258         PartData *pd = 0;
00259         SharedFile *sf = 0;
00260         MetaData *md = 0;
00261         if (exists(partName)) try {
00262                 pd = new PartData(file);
00263                 md = pd->getMetaData();
00264 
00265                 // SharedFile constructor calls findMetaData()
00266                 sf = new SharedFile(pd);
00267 
00268                 if (sf->getMetaData()) {
00269                         // associate MetaData with SharedFile
00270                         MetaDb::instance().push(sf->getMetaData(), sf);
00271                 }
00272 
00273                 m_list.insert(sf);
00274                 return true;
00275         } catch (std::exception &e) {
00276                 logError(boost::format(
00277                         "Failed to load part file %s:\n  what(): %s"
00278                 ) % partName % e.what());
00279                 delete pd; delete sf; delete md;
00280                 if (exists(file.native_file_string() + "dat.bak")) {
00281                         logDebug("Attempting to load backup...");
00282                         loadTempFile(file.native_file_string() + ".bak");
00283                 }
00284         } else {
00285                 // clean out any leftovers from previous downloads
00286                 remove(file);
00287                 path tmp(file.string() + ".bak", native);
00288                 if (exists(tmp) && !is_directory(tmp)) {
00289                         remove(tmp);
00290                 }
00291         }
00292         return false;
00293 }
00294 
00295 void FilesList::onSharedFileEvent(SharedFile *sf, int evt) {
00296         if (evt != SF_DESTROY) {
00297                 return;
00298         }
00299         m_list.erase(sf);
00300         delete sf;
00301 }
00302 
00303 // Customization virtual functions
00304 uint8_t FilesList::getOperCount() const {
00305         return 1;
00306 }
00307 
00308 Object::Operation FilesList::getOper(uint8_t n) const {
00309         switch (n) {
00310                 case 0: {
00311                         Object::Operation o("adddir", true);
00312                         o.addArg(
00313                                 Object::Operation::Argument(
00314                                         "sharedpath", true, ODT_STRING
00315                                 )
00316                         );
00317                         o.addArg(
00318                                 Object::Operation::Argument(
00319                                         "recurse", false, ODT_BOOL
00320                                 )
00321                         );
00322                         return o;
00323                 }
00324                 default:
00325                         throw std::runtime_error("No such operation.");
00326         }
00327 }
00328 
00329 void FilesList::doOper(const Object::Operation &oper) {
00330         logDebug(
00331                 boost::format("FilesList: Received operation %s")
00332                 % oper.getName()
00333         );
00334         logDebug(boost::format("argcount=%d") % oper.getArgCount());
00335         for (uint8_t i = 0; i < oper.getArgCount(); ++i) {
00336                 logDebug(
00337                         boost::format("Argument %s=%s")
00338                         % oper.getArg(i).getName()
00339                         % oper.getArg(i).getValue()
00340                 );
00341         }
00342 
00343         if (oper.getName() == "adddir") {
00344                 uint8_t argc = oper.getArgCount();
00345                 if (!argc) {
00346                         logError("Invalid argument count to operation.");
00347                         return;
00348                 }
00349                 std::string path;
00350                 bool recurse = false;
00351                 try {
00352                         path = oper.getArg("sharedpath").getValue();
00353                 } catch (...) {
00354                         logError("Need argument `sharedpath`");
00355                         return;
00356                 }
00357                 try {
00358                         recurse = boost::lexical_cast<bool>(
00359                                 oper.getArg("recurse").getValue()
00360                         );
00361                 } catch (boost::bad_lexical_cast&) {
00362                         logError("Bad argument type for argument `recurse`");
00363                 } catch (std::runtime_error&) {
00364                         // Ignored
00365                 }
00366                 scanSharedDir(path, recurse);
00367         }
00368 }
00369 // Start a new download
00370 // --------------------
00371 // First, we must construct a PartData, which will serve as the empty new
00372 // download. PartData constructor needs full temp file path, so we must figure
00373 // out a temp file name at this point. I think a random integer number would
00374 // suffice here, altough checks must be made that there already isn't a file
00375 // with same name. Different clients use different systems for temp file names,
00376 // some use integers numbers starting from zero, some use real file name. Using
00377 // real file name opens up the problem of user thinking it IS a real full file,
00378 // oblivios of the fact that it is actually a half-complete file. So using a
00379 // random integer value here should avoid any confusion.
00380 //
00381 // Thus, the next loop generates random ID's, checking for existance of the
00382 // file with that ID, until we find a free ID. Once we'v established that,
00383 // we can head on to constructing the actual PartData object.
00384 //
00385 // After that, it's just tieing things together. PartData to SharedFile,
00386 // the newly created MetaData also to the SharedFile (and also submit to MetaDb)
00387 // and insert the entire compound into our internal list. This completes the
00388 // download initialization sequence.
00389 void FilesList::createDownload(std::string name, MetaData *md) {
00390         using boost::filesystem::path;
00391         using boost::filesystem::no_check;
00392 
00393         boost::algorithm::trim(name);
00394         uint32_t id = 0;
00395 
00396         Prefs::instance().setPath("/");
00397         std::string tmpdir = Prefs::instance().read<std::string>("Temp", "");
00398         path p(tmpdir, no_check);
00399 
00400         logDebug(boost::format("Got tempdir: %s") % tmpdir);
00401         logDebug(boost::format("TempPath: %s") % p.string());
00402 
00403         std::string fname;
00404         do {
00405                 id = getRandom();
00406                 fname = boost::lexical_cast<std::string>(id);
00407                 fname += ".tmp";
00408         } while (boost::filesystem::exists(p/fname));
00409 
00410         p /= fname;
00411         logDebug(boost::format("Temp file location: %s") % p.string());
00412 
00413         path dest(Prefs::instance().read<std::string>("Incoming", ""),no_check);
00414         dest /= path(name, no_check);
00415 
00416         PartData *pd = new PartData(md->getFileSize(), p, dest);
00417         SharedFile *sf = new SharedFile(pd, md);
00418 
00419         MetaDb::instance().push(md, sf);
00420         push(sf);
00421         pd->save();
00422 
00423         logMsg(boost::format("Download started: %s") % name);
00424 }
00425 
00426 void FilesList::savePartFiles() {
00427         for (SFIter i = m_list.begin(); i != m_list.end(); ++i) {
00428                 if ((*i)->isPartial()) {
00429                         (*i)->getPartData()->save();
00430                         (*i)->updateModDate();
00431                 }
00432         }
00433 }
00434 
00435