Mozilla/mozilla/network/cache/nu/src/nsDiskModule.cpp
gagan%netscape.com 2bd31be77d Cache changes, bug fixes, etc.
git-svn-id: svn://10.0.0.236/trunk@16020 18797224-902f-48f8-a5cc-f745e15eee43
1998-12-09 02:43:16 +00:00

544 lines
13 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
/*
* nsDiskModule
*
* Gagan Saksena 02/02/98
*
*/
#include "prtypes.h"
#include "prmem.h"
#include "plstr.h"
#include "prlog.h"
#include "prclist.h"
#include "prio.h"
#include "prsystem.h" // Directory Separator
#include "nsDiskModule.h"
#include "nsCacheObject.h"
#include "nsCacheManager.h"
#include "nsFileStream.h"
#include "nsCachePref.h"
#include "mcom_db.h"
#define ENSURE_INIT \
if (!m_pDB) \
{ \
nsDiskModule* pThis = (nsDiskModule*) this; \
PRBool res = pThis->InitDB(); \
PR_ASSERT(res); \
}
struct recentlyUsedObject {
PRCList link;
nsCacheObject* cacheObject;
};
/* Find pointer to recentlyUsedObject struct
* from the list linkaged embedded in it
*/
#define OBJECT_PTR(_link) \
((recentlyUsedObject*) ((char*) (_link) - offsetof(recentlyUsedObject,link)))
//File static list of recently used cache objects
static PRCList g_RecentlyUsedList;
static nsCacheObject* g_pTempObj=0;
static char* g_FullFilename=0;
const static int MAX_FILENAME_LEN = 512;
const static int MAX_OBJECTS_IN_RECENTLY_USED_LIST = 20; // change later TODO.
// Every time we cleanup we cleanup to 75% of the max available size.
// This should ideally change to a more const number than a percentage.
// Since someone may have a bigger cache size, so we don't really want to
// be cleaning up 5 megs if some one has a 20 megs cache size.
// I will revisit this issue once the basic architecture is up and running.
#define CLEANUP_FACTOR 0.75
//Returns the full filename including the cache directory.
static char* FullFilename(const char* i_Filename);
//Returns the Least Recently Used object in the database
static nsCacheObject* LRUObject(DB* pDB);
//
// Constructor: nsDiskModule
//
nsDiskModule::nsDiskModule(const PRUint32 size):
nsCacheModule(size),
m_pDB(0)
{
PR_INIT_CLIST(&g_RecentlyUsedList);
}
nsDiskModule::~nsDiskModule()
{
//This will free up any operational "over the limit" cache objects.
GarbageCollect();
if (m_pDB)
{
(*m_pDB->sync)(m_pDB, 0);
(*m_pDB->close)(m_pDB);
m_pDB = 0;
}
/* Clean up the recently used list */
recentlyUsedObject* obj;
while (!PR_CLIST_IS_EMPTY(&g_RecentlyUsedList))
{
obj = (recentlyUsedObject*) PR_LIST_HEAD(&g_RecentlyUsedList);
if (obj->cacheObject)
delete obj->cacheObject;
PR_REMOVE_LINK(&obj->link);
}
PR_ASSERT(PR_CLIST_IS_EMPTY(&g_RecentlyUsedList));
if (g_FullFilename)
{
delete[] g_FullFilename;
g_FullFilename = 0;
}
if (g_pTempObj)
{
delete g_pTempObj;
g_pTempObj = 0;
}
}
PRBool nsDiskModule::AddObject(nsCacheObject* io_pObject)
{
ENSURE_INIT;
if (!m_pDB || !io_pObject)
{
// Set some error state TODO
return PR_FALSE;
}
if (io_pObject->Address())
{
MonitorLocker ml(this);
//Remove the earliar copy- silently handles if not found
Remove(io_pObject->Address());
// TODO optimize these further- make static - Gagan
DBT* key = PR_NEW(DBT);
DBT* data = PR_NEW(DBT);
//Set the module
io_pObject->Module(nsCacheManager::DISK);
//Close the corresponding file
PR_ASSERT(io_pObject->Stream());
if (io_pObject->Stream())
PR_Close(((nsFileStream*)io_pObject->Stream())->FileDesc());
key->data = (void*)io_pObject->Address();
/* Later on change this to include post data- io_pObject->KeyData() */
key->size = PL_strlen(io_pObject->Address());
data->data = io_pObject->Info();
data->size = io_pObject->InfoSize();
int status = (*m_pDB->put)(m_pDB, key, data, 0);
if (status == 0)
{
// if (m_Sync == EVERYTIME)
status = (*m_pDB->sync)(m_pDB, 0);
m_Entries++;
m_SizeInUse += io_pObject->Size();
}
PR_Free(key);
PR_Free(data);
return (status == 0);
}
return PR_FALSE;
}
PRBool nsDiskModule::Contains(nsCacheObject* io_pObject) const
{
ENSURE_INIT;
if (!m_pDB || !io_pObject)
return PR_FALSE;
nsCacheObject* pTemp = GetObject(io_pObject->Address());
if (pTemp)
{
//PR_ASSERT(io_pObject == pTemp);
// until I do a copyFrom function
return PR_TRUE;
}
return PR_FALSE;
}
PRBool nsDiskModule::Contains(const char* i_url) const
{
ENSURE_INIT;
if (!m_pDB || !i_url || !*i_url)
return PR_FALSE;
DBT key, data;
key.data = (void*) i_url;
key.size = PL_strlen(i_url);
int status = (*m_pDB->get)(m_pDB, &key, &data, 0);
return (status == 0);
}
void nsDiskModule::GarbageCollect(void)
{
MonitorLocker ml(this);
ReduceSizeTo((PRUint32)(CLEANUP_FACTOR*m_Size));
// if the recentlyusedlist has grown too big, trim some objects from there as well
// Count how many are there
int count=0;
if (!PR_CLIST_IS_EMPTY(&g_RecentlyUsedList))
{
PRCList* list = g_RecentlyUsedList.next;
while (list != &g_RecentlyUsedList)
{
count++;
list = list->next;
}
}
// Then trim the extra ones.
int extra = count-MAX_OBJECTS_IN_RECENTLY_USED_LIST;
while (extra>0)
{
recentlyUsedObject* obj = (recentlyUsedObject*) PR_LIST_HEAD(&g_RecentlyUsedList);
if (obj->cacheObject)
delete obj->cacheObject;
PR_REMOVE_LINK(&obj->link);
extra--;
}
}
nsCacheObject* nsDiskModule::GetObject(const PRUint32 i_index) const
{
ENSURE_INIT;
if (!m_pDB)
return 0;
//todo
return 0;
}
nsCacheObject* nsDiskModule::GetObject(const char* i_url) const
{
ENSURE_INIT;
MonitorLocker ml((nsDiskModule*)this);
if (!m_pDB || !i_url || !*i_url)
return 0;
/* Check amongst recently used objects */
recentlyUsedObject* obj;
PRCList* list = &g_RecentlyUsedList;
if (!PR_CLIST_IS_EMPTY(&g_RecentlyUsedList))
{
list = g_RecentlyUsedList.next;
nsCacheObject* pObj;
while (list != &g_RecentlyUsedList)
{
obj = OBJECT_PTR(list);
pObj = obj->cacheObject;
if (0 == PL_strcasecmp(i_url, pObj->Address())) //todo also validate
return pObj;
list = list->next;
}
}
DBT key, data;
key.data = (void*) i_url;
key.size = PL_strlen(i_url);
if (0 == (*m_pDB->get)(m_pDB, &key, &data, 0))
{
nsCacheObject* pTemp = new nsCacheObject();
pTemp->Info(data.data);
recentlyUsedObject* pNode = PR_NEWZAP(recentlyUsedObject);
PR_APPEND_LINK(&pNode->link, &g_RecentlyUsedList);
pNode->cacheObject = pTemp;
return pTemp;
}
return 0;
}
nsStream* nsDiskModule::GetStreamFor(const nsCacheObject* i_pObject)
{
ENSURE_INIT;
MonitorLocker ml(this);
if (i_pObject)
{
nsStream* pStream = i_pObject->Stream();
if (pStream)
return pStream;
PR_ASSERT(*i_pObject->Filename());
char* fullname = FullFilename(i_pObject->Filename());
if (fullname)
{
return new nsFileStream(fullname);
}
}
return 0;
}
PRBool nsDiskModule::InitDB(void)
{
MonitorLocker ml(this);
if (m_pDB)
return PR_TRUE;
HASHINFO hash_info = {
16*1024, /* bucket size */
0, /* fill factor */
0, /* number of elements */
0, /* bytes to cache */
0, /* hash function */
0}; /* byte order */
m_pDB = dbopen(
FullFilename(nsCachePref::GetInstance()->DiskCacheDBFilename()),
O_RDWR | O_CREAT,
0600,
DB_HASH,
&hash_info);
if (!m_pDB)
return PR_FALSE;
/* Open and read in the number of existing entries */
m_Entries = 0;
m_SizeInUse = 0;
int status;
DBT key, data;
if (0 == g_pTempObj)
g_pTempObj = new nsCacheObject();
if(!(status = (*m_pDB->seq)(m_pDB, &key, &data, R_FIRST)))
{
do
{
/* Also validate the corresponding file here *///TODO
g_pTempObj->Info(data.data);
m_SizeInUse += g_pTempObj->Size();
m_Entries++;
}
while(!(status = (*m_pDB->seq) (m_pDB, &key, &data, R_NEXT)));
}
if (status < 0)
return PR_FALSE;
return PR_TRUE;
}
PRBool nsDiskModule::ReduceSizeTo(const PRUint32 i_NewSize)
{
MonitorLocker ml(this);
PRInt32 needToFree = m_SizeInUse - i_NewSize;
if ((m_Entries>0) && (needToFree > 0))
{
PRUint32 avg = m_SizeInUse/m_Entries;
if (avg==0)
return PR_FALSE;
PRUint32 nObjectsToFree = needToFree/avg;
if (nObjectsToFree < 1)
nObjectsToFree = 1;
while (nObjectsToFree > 0)
{
Remove(LRUObject(m_pDB));
--nObjectsToFree;
}
return PR_TRUE;
}
return PR_FALSE;
}
PRBool nsDiskModule::Remove(const char* i_url)
{
ENSURE_INIT;
return Remove(GetObject(i_url));
}
PRBool nsDiskModule::Remove(nsCacheObject* pObject)
{
MonitorLocker ml(this);
PRBool bStatus = PR_FALSE;
if (!pObject)
return bStatus;
//PR_ASSERT(Contains(pObject);
// TODO Mark the objects state for deletion so that we dont
// read it in the meanwhile.
pObject->State(nsCacheObject::EXPIRED);
//Remove it from the index
DBT key;
key.data = (void*) pObject->Address();
key.size = PL_strlen(pObject->Address());
if (0 == (*m_pDB->del)(m_pDB, &key, 0))
{
--m_Entries;
m_SizeInUse -= pObject->Size();
if (-1 == (*m_pDB->sync)(m_pDB, 0))
{
//Failed to sync database
PR_ASSERT(0);
}
}
//Remove it from the recently used list
recentlyUsedObject* obj;
PRCList* list = &g_RecentlyUsedList;
if (!PR_CLIST_IS_EMPTY(&g_RecentlyUsedList))
{
list = g_RecentlyUsedList.next;
nsCacheObject* pObj;
PRBool bDone = PR_FALSE;
while ((list != &g_RecentlyUsedList) && !bDone)
{
obj = OBJECT_PTR(list);
pObj = obj->cacheObject;
if (pObj == pObject)
{
bDone = PR_TRUE;
PR_REMOVE_LINK(&obj->link);
}
list = list->next;
}
}
//Remove it from the disk
if (PR_SUCCESS == PR_Delete(FullFilename(pObject->Filename())))
{
bStatus = PR_TRUE;
}
else
{
//Failed to delete the file off the disk!
bStatus = PR_FALSE;
}
//Finally delete it
delete pObject;
pObject = 0;
return PR_FALSE;
}
PRBool nsDiskModule::Remove(const PRUint32 i_index)
{
//This will probably go away.
ENSURE_INIT;
//TODO
// Also remove the file corresponding to this item.
return PR_FALSE;
}
PRBool nsDiskModule::Revalidate(void)
{
ENSURE_INIT;
//TODO - This will add a dependency on HTTP lib
return PR_FALSE;
}
void nsDiskModule::SetSize(const PRUint32 i_Size)
{
MonitorLocker ml(this);
m_Size = i_Size;
if (m_Size >0)
{
ReduceSizeTo((PRUint32)(CLEANUP_FACTOR*m_Size));
}
else
{
RemoveAll();
}
}
char* FullFilename(const char* i_Filename)
{
static int cacheFolderLength=0;
if (0 == g_FullFilename)
{
g_FullFilename = new char[MAX_FILENAME_LEN];
*g_FullFilename = '\0';
if (0 == g_FullFilename)
return 0;
}
PL_strcpy(g_FullFilename, nsCachePref::GetInstance()->DiskCacheFolder());
if (0==cacheFolderLength)
cacheFolderLength = PL_strlen(nsCachePref::GetInstance()->DiskCacheFolder());
#ifndef XP_MAC
if (g_FullFilename[cacheFolderLength-1]!=PR_GetDirectorySeparator())
{
g_FullFilename[cacheFolderLength] = PR_GetDirectorySeparator();
g_FullFilename[cacheFolderLength+1] = '\0';
}
#endif
PL_strcat(g_FullFilename, i_Filename);
return g_FullFilename;
}
nsCacheObject* LRUObject(DB* pDB)
{
int status;
DBT key, data;
nsCacheObject* pTempObj = new nsCacheObject();
nsCacheObject* pOldest=0;
if(!(status = (*pDB->seq)(pDB, &key, &data, R_FIRST)))
{
do
{
pTempObj->Info(data.data);
if (!pOldest ||
(pOldest->LastAccessed() > pTempObj->LastAccessed()))
pOldest = pTempObj;
}
while(!(status = (*pDB->seq) (pDB, &key, &data, R_NEXT)));
}
return pOldest;
}
#undef ENSURE_INIT