src/network/access/qnetworkaccesscache.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the QtNetwork module of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "qnetworkaccesscache_p.h"
       
    43 #include "QtCore/qpointer.h"
       
    44 #include "QtCore/qdatetime.h"
       
    45 #include "QtCore/qqueue.h"
       
    46 #include "qnetworkaccessmanager_p.h"
       
    47 #include "qnetworkreply_p.h"
       
    48 #include "qnetworkrequest.h"
       
    49 
       
    50 QT_BEGIN_NAMESPACE
       
    51 
       
    52 enum ExpiryTimeEnum {
       
    53     ExpiryTime = 120
       
    54 };
       
    55 
       
    56 namespace {
       
    57     struct Receiver
       
    58     {
       
    59         QPointer<QObject> object;
       
    60         const char *member;
       
    61     };
       
    62 }
       
    63 
       
    64 // idea copied from qcache.h
       
    65 struct QNetworkAccessCache::Node
       
    66 {
       
    67     QDateTime timestamp;
       
    68     QQueue<Receiver> receiverQueue;
       
    69     QByteArray key;
       
    70 
       
    71     Node *older, *newer;
       
    72     CacheableObject *object;
       
    73 
       
    74     int useCount;
       
    75 
       
    76     Node()
       
    77         : older(0), newer(0), object(0), useCount(0)
       
    78     { }
       
    79 };
       
    80 
       
    81 QNetworkAccessCache::CacheableObject::CacheableObject()
       
    82 {
       
    83     // leave the members uninitialized
       
    84     // they must be initialized by the derived class's constructor
       
    85 }
       
    86 
       
    87 QNetworkAccessCache::CacheableObject::~CacheableObject()
       
    88 {
       
    89 #if 0 //def QT_DEBUG
       
    90     if (!key.isEmpty() && Ptr()->hasEntry(key))
       
    91         qWarning() << "QNetworkAccessCache: object" << (void*)this << "key" << key
       
    92                    << "destroyed without being removed from cache first!";
       
    93 #endif
       
    94 }
       
    95 
       
    96 void QNetworkAccessCache::CacheableObject::setExpires(bool enable)
       
    97 {
       
    98     expires = enable;
       
    99 }
       
   100 
       
   101 void QNetworkAccessCache::CacheableObject::setShareable(bool enable)
       
   102 {
       
   103     shareable = enable;
       
   104 }
       
   105 
       
   106 QNetworkAccessCache::QNetworkAccessCache()
       
   107     : oldest(0), newest(0)
       
   108 {
       
   109 }
       
   110 
       
   111 QNetworkAccessCache::~QNetworkAccessCache()
       
   112 {
       
   113     clear();
       
   114 }
       
   115 
       
   116 void QNetworkAccessCache::clear()
       
   117 {
       
   118     NodeHash hashCopy = hash;
       
   119     hash.clear();
       
   120 
       
   121     // remove all entries
       
   122     NodeHash::Iterator it = hashCopy.begin();
       
   123     NodeHash::Iterator end = hashCopy.end();
       
   124     for ( ; it != end; ++it) {
       
   125         it->object->key.clear();
       
   126         it->object->dispose();
       
   127     }
       
   128 
       
   129     // now delete:
       
   130     hashCopy.clear();
       
   131 
       
   132     timer.stop();
       
   133 
       
   134     oldest = newest = 0;
       
   135 }
       
   136 
       
   137 /*!
       
   138     Appens the entry given by @p key to the end of the linked list.
       
   139     (i.e., makes it the newest entry)
       
   140  */
       
   141 void QNetworkAccessCache::linkEntry(const QByteArray &key)
       
   142 {
       
   143     NodeHash::Iterator it = hash.find(key);
       
   144     if (it == hash.end())
       
   145         return;
       
   146 
       
   147     Node *const node = &it.value();
       
   148     Q_ASSERT(node != oldest && node != newest);
       
   149     Q_ASSERT(node->older == 0 && node->newer == 0);
       
   150     Q_ASSERT(node->useCount == 0);
       
   151 
       
   152     if (newest) {
       
   153         Q_ASSERT(newest->newer == 0);
       
   154         newest->newer = node;
       
   155         node->older = newest;
       
   156     }
       
   157     if (!oldest) {
       
   158         // there are no entries, so this is the oldest one too
       
   159         oldest = node;
       
   160     }
       
   161 
       
   162     node->timestamp = QDateTime::currentDateTime().addSecs(ExpiryTime);
       
   163     newest = node;
       
   164 }
       
   165 
       
   166 /*!
       
   167     Removes the entry pointed by @p key from the linked list.
       
   168     Returns true if the entry removed was the oldest one.
       
   169  */
       
   170 bool QNetworkAccessCache::unlinkEntry(const QByteArray &key)
       
   171 {
       
   172     NodeHash::Iterator it = hash.find(key);
       
   173     if (it == hash.end())
       
   174         return false;
       
   175 
       
   176     Node *const node = &it.value();
       
   177 
       
   178     bool wasOldest = false;
       
   179     if (node == oldest) {
       
   180         oldest = node->newer;
       
   181         wasOldest = true;
       
   182     }
       
   183     if (node == newest)
       
   184         newest = node->older;
       
   185     if (node->older)
       
   186         node->older->newer = node->newer;
       
   187     if (node->newer)
       
   188         node->newer->older = node->older;
       
   189 
       
   190     node->newer = node->older = 0;
       
   191     return wasOldest;
       
   192 }
       
   193 
       
   194 void QNetworkAccessCache::updateTimer()
       
   195 {
       
   196     timer.stop();
       
   197 
       
   198     if (!oldest)
       
   199         return;
       
   200 
       
   201     int interval = QDateTime::currentDateTime().secsTo(oldest->timestamp);
       
   202     if (interval <= 0) {
       
   203         interval = 0;
       
   204     } else {
       
   205         // round up the interval
       
   206         interval = (interval + 15) & ~16;
       
   207     }
       
   208 
       
   209     timer.start(interval * 1000, this);
       
   210 }
       
   211 
       
   212 bool QNetworkAccessCache::emitEntryReady(Node *node, QObject *target, const char *member)
       
   213 {
       
   214     if (!connect(this, SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*)),
       
   215                  target, member, Qt::QueuedConnection))
       
   216         return false;
       
   217 
       
   218     emit entryReady(node->object);
       
   219     disconnect(SIGNAL(entryReady(QNetworkAccessCache::CacheableObject*)));
       
   220 
       
   221     return true;
       
   222 }
       
   223 
       
   224 void QNetworkAccessCache::timerEvent(QTimerEvent *)
       
   225 {
       
   226     // expire old items
       
   227     QDateTime now = QDateTime::currentDateTime();
       
   228 
       
   229     while (oldest && oldest->timestamp < now) {
       
   230         Node *next = oldest->newer;
       
   231         oldest->object->dispose();
       
   232 
       
   233         hash.remove(oldest->key); // oldest gets deleted
       
   234         oldest = next;
       
   235     }
       
   236 
       
   237     // fixup the list
       
   238     if (oldest)
       
   239         oldest->older = 0;
       
   240     else
       
   241         newest = 0;
       
   242 
       
   243     updateTimer();
       
   244 }
       
   245 
       
   246 void QNetworkAccessCache::addEntry(const QByteArray &key, CacheableObject *entry)
       
   247 {
       
   248     Q_ASSERT(!key.isEmpty());
       
   249 
       
   250     if (unlinkEntry(key))
       
   251         updateTimer();
       
   252 
       
   253     Node &node = hash[key];     // create the entry in the hash if it didn't exist
       
   254     if (node.useCount)
       
   255         qWarning("QNetworkAccessCache::addEntry: overriding active cache entry '%s'",
       
   256                  key.constData());
       
   257     if (node.object)
       
   258         node.object->dispose();
       
   259     node.object = entry;
       
   260     node.object->key = key;
       
   261     node.key = key;
       
   262     node.useCount = 1;
       
   263 }
       
   264 
       
   265 bool QNetworkAccessCache::hasEntry(const QByteArray &key) const
       
   266 {
       
   267     return hash.contains(key);
       
   268 }
       
   269 
       
   270 bool QNetworkAccessCache::requestEntry(const QByteArray &key, QObject *target, const char *member)
       
   271 {
       
   272     NodeHash::Iterator it = hash.find(key);
       
   273     if (it == hash.end())
       
   274         return false;           // no such entry
       
   275 
       
   276     Node *node = &it.value();
       
   277 
       
   278     if (node->useCount > 0 && !node->object->shareable) {
       
   279         // object is not shareable and is in use
       
   280         // queue for later use
       
   281         Q_ASSERT(node->older == 0 && node->newer == 0);
       
   282         Receiver receiver;
       
   283         receiver.object = target;
       
   284         receiver.member = member;
       
   285         node->receiverQueue.enqueue(receiver);
       
   286 
       
   287         // request queued
       
   288         return true;
       
   289     } else {
       
   290         // node not in use or is shareable
       
   291         if (unlinkEntry(key))
       
   292             updateTimer();
       
   293 
       
   294         ++node->useCount;
       
   295         return emitEntryReady(node, target, member);
       
   296     }
       
   297 }
       
   298 
       
   299 QNetworkAccessCache::CacheableObject *QNetworkAccessCache::requestEntryNow(const QByteArray &key)
       
   300 {
       
   301     NodeHash::Iterator it = hash.find(key);
       
   302     if (it == hash.end())
       
   303         return 0;
       
   304     if (it->useCount > 0) {
       
   305         if (it->object->shareable) {
       
   306             ++it->useCount;
       
   307             return it->object;
       
   308         }
       
   309 
       
   310         // object in use and not shareable
       
   311         return 0;
       
   312     }
       
   313 
       
   314     // entry not in use, let the caller have it
       
   315     bool wasOldest = unlinkEntry(key);
       
   316     ++it->useCount;
       
   317 
       
   318     if (wasOldest)
       
   319         updateTimer();
       
   320     return it->object;
       
   321 }
       
   322 
       
   323 void QNetworkAccessCache::releaseEntry(const QByteArray &key)
       
   324 {
       
   325     NodeHash::Iterator it = hash.find(key);
       
   326     if (it == hash.end()) {
       
   327         qWarning("QNetworkAccessCache::releaseEntry: trying to release key '%s' that is not in cache",
       
   328                  key.constData());
       
   329         return;
       
   330     }
       
   331 
       
   332     Node *node = &it.value();
       
   333     Q_ASSERT(node->useCount > 0);
       
   334 
       
   335     // are there other objects waiting?
       
   336     if (!node->receiverQueue.isEmpty()) {
       
   337         // queue another activation
       
   338         Receiver receiver;
       
   339         do {
       
   340             receiver = node->receiverQueue.dequeue();
       
   341         } while (receiver.object.isNull() && !node->receiverQueue.isEmpty());
       
   342 
       
   343         if (!receiver.object.isNull()) {
       
   344             emitEntryReady(node, receiver.object, receiver.member);
       
   345             return;
       
   346         }
       
   347     }
       
   348 
       
   349     if (!--node->useCount) {
       
   350         // no objects waiting; add it back to the expiry list
       
   351         if (node->object->expires)
       
   352             linkEntry(key);
       
   353 
       
   354         if (oldest == node)
       
   355             updateTimer();
       
   356     }
       
   357 }
       
   358 
       
   359 void QNetworkAccessCache::removeEntry(const QByteArray &key)
       
   360 {
       
   361     NodeHash::Iterator it = hash.find(key);
       
   362     if (it == hash.end()) {
       
   363         qWarning("QNetworkAccessCache::removeEntry: trying to remove key '%s' that is not in cache",
       
   364                  key.constData());
       
   365         return;
       
   366     }
       
   367 
       
   368     Node *node = &it.value();
       
   369     if (unlinkEntry(key))
       
   370         updateTimer();
       
   371     if (node->useCount > 1)
       
   372         qWarning("QNetworkAccessCache::removeEntry: removing active cache entry '%s'",
       
   373                  key.constData());
       
   374 
       
   375     node->object->key.clear();
       
   376     hash.remove(node->key);
       
   377 }
       
   378 
       
   379 QT_END_NAMESPACE