gecko-dev/netwerk/cache/module/disk/nsDiskModule.cpp

840 lines
19 KiB
C++
Raw Normal View History

1999-09-10 23:16:40 +00:00
/* -*- 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 "nsICacheObject.h"
#include "nsCacheObject.h"
#include "nsCacheManager.h"
#include "nsFileStream.h"
#include "nsCachePref.h"
#include "nsRepository.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;
nsICacheObject* 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 nsICacheObject* 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 nsICacheObject* LRUObject(DB* pDB);
static NS_DEFINE_IID (kICacheObjectIID, NS_ICACHEOBJECT_IID);
static NS_DEFINE_IID (kCacheObjectCID, NS_CACHEOBJECT_CID);
static NS_DEFINE_IID (kICacheManagerIID, NS_ICACHEMANAGER_IID);
static NS_DEFINE_IID (kCacheManagerCID, NS_CACHEMANAGER_CID);
static nsICachePref*
GetPrefsInstance ()
{
nsICacheManager* pICacheMgr = nsnull;
nsServiceManager::GetService (kCacheManagerCID, kICacheManagerIID,
(nsISupports **) &pICacheMgr);
if (!pICacheMgr)
return nsnull;
nsICachePref* pICachePref = nsnull;
pICacheMgr->GetPrefs (&pICachePref);
return pICachePref;
}
//
// Constructors: nsDiskModule
//
nsDiskModule::nsDiskModule():
m_SizeInUse(0),
m_pEnumeration(0),
m_pNext(0),
m_pDB(0)
{
nsICachePref* pICachePref = GetPrefsInstance();
PR_ASSERT (pICachePref);
pICachePref->GetDiskCacheSize (&m_Size);
PR_INIT_CLIST(&g_RecentlyUsedList);
}
nsDiskModule::nsDiskModule(const PRUint32 size):
m_Size (size),
m_SizeInUse(0),
m_pEnumeration(0),
m_pNext(0),
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;
}
}
NS_IMETHODIMP
nsDiskModule::AddObject(nsICacheObject* io_pObject)
{
ENSURE_INIT;
if (!m_pDB || !io_pObject)
{
// Set some error state TODO
return NS_ERROR_FAILURE;
}
char *url = nsnull;
nsresult r = io_pObject->GetAddress (&url);
if (r == NS_OK && url)
{
MonitorLocker ml(this);
//Remove the earliar copy- silently handles if not found
RemoveByURL(url);
// TODO optimize these further- make static - Gagan
DBT* key = PR_NEW(DBT);
DBT* data = PR_NEW(DBT);
//Set the module
io_pObject->SetModuleIndex (nsCacheManager::DISK);
//Close the corresponding file
nsStream* pStream = (nsStream *) nsnull;
r = io_pObject->GetStream (&pStream);
if (pStream)
PR_Close(((nsFileStream*)pStream)->FileDesc());
key->data = (void*)url;
/* Later on change this to include post data- io_pObject->KeyData() */
key->size = PL_strlen(url);
io_pObject->GetInfo ((void **) &data->data);
io_pObject->GetInfoSize (&data->size);
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++;
PRUint32 size;
io_pObject->GetSize(&size);
m_SizeInUse += size;
}
PR_Free(key);
PR_Free(data);
return ((status == 0) ? NS_OK : NS_ERROR_FAILURE);
}
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsDiskModule::ContainsCacheObj(nsICacheObject* io_pObject, PRBool* o_bContain)
{
ENSURE_INIT;
*o_bContain = PR_FALSE;
if (!m_pDB || !io_pObject)
return NS_OK;
char *url;
if (io_pObject->GetAddress (&url) != NS_OK)
return NS_OK;
nsICacheObject* pTemp = 0;
if (GetObjectByURL(url, &pTemp) != NS_OK)
return NS_OK;
if (pTemp)
{
//PR_ASSERT(io_pObject == pTemp);
// until I do a copyFrom function
*o_bContain = PR_TRUE;
}
return NS_OK;
}
NS_IMETHODIMP
nsDiskModule::ContainsURL(const char* i_url, PRBool* o_bContain)
{
ENSURE_INIT;
*o_bContain = PR_FALSE;
if (!m_pDB || !i_url || !*i_url)
return NS_OK;
DBT key, data;
key.data = (void*) i_url;
key.size = PL_strlen(i_url);
int status = (*m_pDB->get)(m_pDB, &key, &data, 0);
*o_bContain = (status == 0)? PR_TRUE : PR_FALSE;
return NS_OK;
}
NS_IMETHODIMP
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--;
}
return NS_OK;
}
NS_IMETHODIMP
nsDiskModule::GetObjectByIndex(const PRUint32 i_index,
nsICacheObject** ppICacheObject)
{
ENSURE_INIT;
if (!ppICacheObject)
return NS_ERROR_NULL_POINTER;
*ppICacheObject = (nsICacheObject *) nsnull;
if (!m_pDB)
return NS_OK;
//todo
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsDiskModule::GetObjectByURL(const char* i_url, nsICacheObject** ppICacheObject)
{
ENSURE_INIT;
MonitorLocker ml((nsDiskModule*)this);
if (!ppICacheObject)
return NS_ERROR_NULL_POINTER;
*ppICacheObject = (nsICacheObject *) nsnull;
if (!m_pDB || !i_url || !*i_url)
return NS_OK;
/* Check amongst recently used objects */
recentlyUsedObject* obj;
PRCList* list = &g_RecentlyUsedList;
if (!PR_CLIST_IS_EMPTY(&g_RecentlyUsedList))
{
list = g_RecentlyUsedList.next;
nsICacheObject* pObj;
while (list != &g_RecentlyUsedList)
{
obj = OBJECT_PTR(list);
pObj = obj->cacheObject;
char *addr;
nsresult r = pObj->GetAddress (&addr);
if (r != NS_OK)
return r;
if (0 == PL_strcasecmp(i_url, addr)) //todo also validate
{
*ppICacheObject = pObj;
return NS_OK;
}
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))
{
nsICacheObject* pTemp;
nsresult rv = nsComponentManager::CreateInstance(kCacheObjectCID,
nsnull,
kICacheObjectIID,
(void**) &pTemp);
PR_ASSERT(rv == NS_OK);
pTemp->SetInfo(data.data);
recentlyUsedObject* pNode = PR_NEWZAP(recentlyUsedObject);
PR_APPEND_LINK(&pNode->link, &g_RecentlyUsedList);
pNode->cacheObject = pTemp;
*ppICacheObject = pTemp;
}
return NS_OK;
}
NS_IMETHODIMP
nsDiskModule::GetStreamFor(const nsICacheObject* i_pObject, nsStream** o_stream)
{
ENSURE_INIT;
MonitorLocker ml(this);
*o_stream = 0;
if (i_pObject)
{
nsresult r = i_pObject->GetStream(o_stream);
if (r != NS_OK)
return r;
char *filename = nsnull;
i_pObject->GetFilename (&filename);
PR_ASSERT(filename);
char* fullname = FullFilename(filename);
if (fullname)
{
*o_stream = new nsFileStream(fullname);
}
}
return NS_OK;
}
// Private Function
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 */
char *filename;
nsICachePref* pICachePref = GetPrefsInstance();
PR_ASSERT (pICachePref);
pICachePref->GetDiskCacheDBFilename (&filename);
m_pDB = dbopen(
FullFilename(filename),
O_RDWR | O_CREAT,
0600, // this is octal, yixiong
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)
{
nsresult rv = nsComponentManager::CreateInstance(kCacheObjectCID,
nsnull,
kICacheObjectIID,
(void**) &g_pTempObj);
PR_ASSERT (rv == NS_OK);
}
if(!(status = (*m_pDB->seq)(m_pDB, &key, &data, R_FIRST)))
{
do
{
/* Also validate the corresponding file here *///TODO
g_pTempObj->SetInfo(data.data);
PRUint32 size;
g_pTempObj->GetSize(&size);
m_SizeInUse += size;
m_Entries++;
}
while(!(status = (*m_pDB->seq) (m_pDB, &key, &data, R_NEXT)));
}
if (status < 0)
return PR_FALSE;
return PR_TRUE;
}
NS_IMETHODIMP
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 NS_ERROR_FAILURE;
PRUint32 nObjectsToFree = needToFree/avg;
if (nObjectsToFree < 1)
nObjectsToFree = 1;
while (nObjectsToFree > 0)
{
RemoveByObject (LRUObject(m_pDB));
--nObjectsToFree;
}
return NS_OK;
}
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsDiskModule::RemoveByURL(const char* i_url)
{
ENSURE_INIT;
nsICacheObject* pICacheObject;
nsresult r = GetObjectByURL(i_url, &pICacheObject);
if (r != NS_OK)
return r;
return RemoveByObject(pICacheObject);
}
NS_IMETHODIMP
nsDiskModule::RemoveByObject(nsICacheObject* pObject)
{
MonitorLocker ml(this);
nsresult bStatus = NS_ERROR_FAILURE;
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->SetState(nsCacheObject::EXPIRED);
//Remove it from the index
DBT key;
pObject->GetAddress((char **) &key.data);
key.size = PL_strlen((char *)key.data);
if (0 == (*m_pDB->del)(m_pDB, &key, 0))
{
--m_Entries;
PRUint32 size;
pObject->GetSize (&size);
m_SizeInUse -= 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;
nsICacheObject* 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
char *filename;
pObject->GetFilename(&filename);
if (PR_SUCCESS == PR_Delete(FullFilename(filename)))
{
bStatus = NS_OK;
}
else
{
//Failed to delete the file off the disk!
bStatus = NS_ERROR_FAILURE;
}
//Finally delete it
delete pObject;
pObject = 0;
return bStatus;
}
NS_IMETHODIMP
nsDiskModule::RemoveByIndex(const PRUint32 i_index)
{
//This will probably go away.
ENSURE_INIT;
//TODO
// Also remove the file corresponding to this item.
//return PR_FALSE;
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsDiskModule::RemoveAll(void)
{
MonitorLocker ml (this);
while (m_Entries > 0)
{
RemoveByIndex (--m_Entries);
}
return NS_OK;
}
NS_IMETHODIMP
nsDiskModule::Revalidate(void)
{
ENSURE_INIT;
//TODO - This will add a dependency on HTTP lib
//return PR_FALSE;
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
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();
}
return NS_OK;
}
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;
}
char *folder;
nsICachePref* pICachePref = GetPrefsInstance();
PR_ASSERT (pICachePref);
pICachePref->GetDiskCacheFolder (&folder);
PR_ASSERT (folder);
PL_strcpy(g_FullFilename, folder);
if (0==cacheFolderLength)
cacheFolderLength = PL_strlen(folder);
#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;
}
nsICacheObject* LRUObject(DB* pDB)
{
int status;
DBT key, data;
nsICacheObject* pTempObj;
nsresult rv = nsComponentManager::CreateInstance(kCacheObjectCID,
nsnull,
kICacheObjectIID,
(void**) &pTempObj);
if (rv != NS_OK)
return nsnull;
nsICacheObject* pOldest=0;
if(!(status = (*pDB->seq)(pDB, &key, &data, R_FIRST)))
{
do
{
pTempObj->SetInfo(data.data);
PRIntervalTime old, current;
if (pOldest)
{
pOldest->GetLastAccessed (&old);
}
pTempObj->GetLastAccessed (&current);
if (!pOldest || (old > current))
pOldest = pTempObj;
}
while(!(status = (*pDB->seq) (pDB, &key, &data, R_NEXT)));
}
return ((nsICacheObject *) pOldest);
}
NS_IMETHODIMP
nsDiskModule::QueryInterface(const nsIID& aIID, void** aInstancePtr)
{
NS_ASSERTION(aInstancePtr, "no instance pointer");
*aInstancePtr = 0;
if (aIID.Equals(nsCOMTypeInfo<nsISupports>::GetIID()) ||
aIID.Equals(nsCOMTypeInfo<nsICacheModule>::GetIID()))
{
*aInstancePtr = NS_STATIC_CAST (nsICacheModule*, this);
NS_ADDREF_THIS();
return NS_OK;
}
return NS_NOINTERFACE;
}
NS_IMPL_ADDREF(nsDiskModule);
NS_IMPL_RELEASE(nsDiskModule);
NS_METHOD
nsDiskModule::Create(nsISupports* aOuter, const nsIID& aIID, void** aResult)
{
nsDiskModule* cm = new nsDiskModule();
if (cm == nsnull)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF (cm);
nsresult rv = cm->QueryInterface (aIID, aResult);
NS_RELEASE (cm);
return rv;
}
NS_IMETHODIMP
nsDiskModule::Enable(PRBool i_bEnable)
{
m_Enabled = i_bEnable;
return NS_OK;
}
NS_IMETHODIMP
nsDiskModule::GetNumOfEntries(PRUint32* o_nEntries)
{
*o_nEntries = m_Entries;
return NS_OK;
}
NS_IMETHODIMP
nsDiskModule::GetEnumerator(nsEnumeration** o_enum)
{
MonitorLocker ml((nsMonitorable*)this);
if (!m_pEnumeration)
{
PR_ASSERT(m_pIterator);
((nsDiskModule*)this)->m_pEnumeration =
new nsEnumeration((nsIterator*)m_pIterator);
}
else
((nsDiskModule*)this)->m_pEnumeration->Reset();
*o_enum = m_pEnumeration;
return NS_OK;
}
NS_IMETHODIMP
nsDiskModule::IsEnabled(PRBool* o_bEnabled)
{
*o_bEnabled = m_Enabled;
return NS_OK;
}
NS_IMETHODIMP
nsDiskModule::IsReadOnly (PRBool* o_bReadOnly)
{
*o_bReadOnly = PR_FALSE; // for Now
return NS_OK;
}
NS_IMETHODIMP
nsDiskModule::GetNextModule(nsICacheModule** o_pICacheModule)
{
*o_pICacheModule = m_pNext;
return NS_OK;
}
NS_IMETHODIMP
nsDiskModule::SetNextModule(nsICacheModule* pNext)
{
/* No overwriting */
PR_ASSERT(m_pNext == 0);
if (m_pNext)
{
/* ERROR */
delete m_pNext; //Worst case.
m_pNext = 0;
}
m_pNext = pNext;
return NS_OK;
}
NS_IMETHODIMP
nsDiskModule::GetSize(PRUint32* o_size)
{
*o_size = m_Size;
return NS_OK;
}
NS_IMETHODIMP
nsDiskModule::GetSizeInUse(PRUint32* o_size)
{
*o_size = m_SizeInUse;
return NS_OK;
}
#undef ENSURE_INIT