NodeCache.cpp

Go to the documentation of this file.
00001 /*
00002 
00003 Copyright (C) 2005-2007 by Peter Dimov.
00004 
00005 This file is part of Calitko (http://www.calitko.org).
00006 
00007 Calitko is free software; you can redistribute it and/or modify
00008 it under the terms of the GNU General Public License as published by
00009 the Free Software Foundation; either version 2 of the License, or
00010 (at your option) any later version.
00011 
00012 Calitko is distributed in the hope that it will be useful,
00013 but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 GNU General Public License for more details.
00016 
00017 You should have received a copy of the GNU General Public License
00018 along with Calitko; if not, write to the Free Software
00019 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00020 
00021 */
00022 
00023 #include "Qt.h"
00024 #include "NodeCache.h"
00025 #include "Imports.cpp"
00026 
00027 using namespace Gnutella::Bootstrapping;
00028 
00029 namespace Gnutella {
00030 namespace Bootstrapping {
00031 
00032 QDataStream & operator << (QDataStream &stream,
00033                            const NodeCache::NodeAvailability &availability)
00034 {
00035     return stream << static_cast <uchar> (availability);
00036 }
00037 
00038 QDataStream & operator >> (QDataStream &stream,
00039                            NodeCache::NodeAvailability &availability)
00040 {
00041     uchar ch;
00042     stream >> ch;
00043     availability = static_cast <NodeCache::NodeAvailability> (ch);
00044     return stream;
00045 }
00046 
00047 enum Constants
00048 {
00049     AvailabilityExpiration      = 600,
00050     MaximalCacheSize            = 2000,
00051     ShrinkCacheSize             = 1500
00052 };
00053 
00054 struct Entry;
00055 typedef QHash <NodeAddress, Entry*>     NodeTable;
00056 typedef QLinkedList <Entry*>            EntryList;
00057 typedef EntryList::iterator             EntryIterator;
00058 
00059 // \todo The NodeAddress is already a mamber of NodeInfo. Remove it from entry?
00060 struct Entry
00061 {
00062     NodeCache::NodeAvailability     availability;
00063     QDateTime                       timeLastAdded;
00064     NodeInfo                        nodeInfo;
00065     EntryIterator                   availabilityListIterator;
00066     EntryIterator                   completeListIterator;
00067 
00068     Entry() : availability (NodeCache::UnknownAvailability),
00069               timeLastAdded (QDateTime::currentDateTime()),
00070               nodeInfo(), availabilityListIterator(), completeListIterator()
00071     {}
00072 };
00073 
00074 class NodeCachePrivate
00075 {
00076 public:
00077     NodeTable                   nodeTable;
00078     EntryList                   availabilityList[NodeCache::AvailabilityCount];
00079     EntryList                   completeList;
00080 
00081     NodeCachePrivate() : nodeTable(), completeList()    {}
00082     ~NodeCachePrivate()                                 {}
00083 };
00084 
00085 } // namespace Bootstrapping;
00086 } // namespace Gnutella;
00087 
00088 NodeCache::NodeCache()
00089  :  d (new NodeCachePrivate())
00090 {
00091 }
00092 
00093 NodeCache::~NodeCache()
00094 {
00095     delete d;
00096 }
00097 
00098 NodeSet NodeCache::getNodes (int count, NodeAvailability availability, bool freshNodes)
00099 {
00100     class AnyNode : public Predicate
00101     {
00102     public:
00103         bool operator() (const NodeInfo &) const { return true; }
00104     };
00105 
00106     return getNodes (count, availability, freshNodes,
00107                      static_cast <const Predicate &> (AnyNode()));
00108 }
00109 
00110 /*
00111     The cache must have some size! Question now is whether to have some size per
00112     HostAvailability, or one size for all hosts in the cache. The problem with the
00113     latter is that we don't know which hosts to take away (from which availability
00114     list). Probably the best solution would be to remove the oldiest entries. This
00115     means we need an additional list that stores the HostInfo * in the correct order
00116     similar to what we do now with HostInfoList.
00117 */
00118 void NodeCache::addNode (const NodeInfo &nodeInfo,
00119                          NodeAvailability availability)
00120 {
00121     Q_ASSERT (nodeInfo.address != NodeAddress::Null);
00122 qDebug() << "addNode() " << nodeInfo.address.toString() << nodeInfo.vendorCode.toString();
00123 
00124     NodeTable::iterator i = d->nodeTable.find (nodeInfo.address);
00125     if (i == d->nodeTable.end()) {  // This is a new node.
00126         if (d->completeList.size() >= MaximalCacheSize) {
00127             Entry *entry;
00128             for (int i = d->completeList.size() - ShrinkCacheSize; i > 0; i--) {
00129                 entry = d->completeList.takeLast();
00130                 d->availabilityList[entry->availability].erase (entry->availabilityListIterator);
00131                 int count = d->nodeTable.remove (entry->nodeInfo.address);
00132                 Q_ASSERT (count == 1); // Could be if two different addresses are equal => NodeAddress::operator == bug!
00133                 delete entry;
00134             }
00135         }
00136         Entry *entry                        = new Entry;
00137         entry->nodeInfo                     = nodeInfo;
00138         entry->availability                 = availability;
00139         d->nodeTable.insert (nodeInfo.address, entry);
00140 
00141         EntryList &availabilityList         = d->availabilityList[availability];
00142         entry->availabilityListIterator     = availabilityList.insert (availabilityList.begin(), entry);
00143         entry->completeListIterator         = d->completeList.insert (d->completeList.begin(), entry);
00144 
00145         emit nodeAdded(); // \todo Emit only in this case?
00146     } else { // The host is already in the cache.
00147         Entry *entry                = *i;
00148         entry->nodeInfo             = nodeInfo;
00149         updateNode (nodeInfo.address, availability);
00150     }
00151 }
00152 
00153 void NodeCache::updateNode (const NodeAddress &nodeAddress,
00154                             NodeAvailability availability)
00155 {
00156     Q_ASSERT (nodeAddress != NodeAddress::Null);
00157 
00158     NodeTable::iterator i = d->nodeTable.find (nodeAddress);
00159     if (i != d->nodeTable.end()) {  // This is NOT a new node.
00160         Entry *entry                = *i;
00161         QDateTime currentDateTime   = QDateTime::currentDateTime();
00162         // We overwrite the hostInfo entry if either the entry expired, or we set higher availability.
00163         bool canOverwrite = availability >= entry->availability
00164                             || currentDateTime > entry->timeLastAdded.addSecs (AvailabilityExpiration);
00165 
00166         if (canOverwrite) {
00167             d->availabilityList[entry->availability].erase (entry->availabilityListIterator);
00168             d->completeList.erase (entry->completeListIterator);
00169 
00170             entry->availability                 = availability;
00171             entry->timeLastAdded                = currentDateTime;
00172 
00173             EntryList &availabilityList         = d->availabilityList[availability];
00174             entry->availabilityListIterator     = availabilityList.insert (availabilityList.begin(), entry);
00175             entry->completeListIterator         = d->completeList.insert (d->completeList.begin(), entry);
00176         }
00177     }
00178 }
00179 
00185 NodeSet NodeCache::getNodes (int count, NodeAvailability availability, bool freshNodes, const Predicate &pred)
00186 {
00187     NodeSet nodes;
00188     EntryList &availabilityList = d->availabilityList[availability];
00189     EntryIterator i             = availabilityList.begin();
00190     QDateTime expirationTime    = QDateTime::currentDateTime().addSecs (-AvailabilityExpiration);
00191     for (; i != availabilityList.end()
00192             && nodes.size() < count
00193             && (!freshNodes
00194                 || (*i)->timeLastAdded > expirationTime)
00195          ; i++)
00196         if (pred((*i)->nodeInfo))
00197             nodes.insert ((*i)->nodeInfo.address);
00198     return nodes;
00199 }
00200 
00201 bool NodeCache::loadNodes (const QString &filename)
00202 {
00203     QFile file (filename);
00204     if (!file.exists() || !file.open (QIODevice::ReadOnly))
00205         return false;
00206 
00207     QDataStream stream (&file);
00208     // \todo Introduce format versioning.
00209     int count = 0;
00210     stream >> count;
00211     // \todo Verify the host is not duplicated due to corrupted file
00212     // \todo get the end() iterators in advance.
00213     while (count-- > 0) {
00214         Entry *entry = new Entry;
00215 
00216         stream >> entry->nodeInfo;
00217         stream >> entry->timeLastAdded;
00218         stream >> entry->availability;
00219 
00220         // We now add all as UnknownAvailability
00221         entry->availability = UnknownAvailability;
00222 
00223         entry->completeListIterator     = d->completeList.insert (d->completeList.end(), entry);
00224         entry->availabilityListIterator = d->availabilityList[entry->availability].insert (d->availabilityList[entry->availability].end(), entry);
00225         d->nodeTable.insert (entry->nodeInfo.address, entry);
00226     }
00227     return true;
00228 }
00229 
00230 bool NodeCache::storeNodes (const QString &filename)
00231 {
00232     // \todo Do not store the hosts that were unavailable?
00233     // The list was sordered by access time, which means that
00234     QFile file (filename);
00235 
00236     if (!file.open (QIODevice::WriteOnly))
00237         return false;
00238 
00239     QDataStream stream (&file);
00240 
00241     // We now do not store the host that were unavailable.
00242     stream << d->completeList.count() - d->availabilityList[NodeUnavailable].count();
00243     EntryIterator i = d->completeList.begin();
00244     EntryIterator e = d->completeList.end();
00245     for (; i != e; i++) {
00246         if ((*i)->availability != NodeUnavailable) {
00247             stream << (*i)->nodeInfo;
00248             stream << (*i)->timeLastAdded;
00249             stream << (*i)->availability;
00250         }
00251     }
00252     return true;
00253 }