
//#define LOCAL_DEBUG
#include "debug.h"

#include "config.h"
#include "fileitem.h"
#include "header.h"
#include "acfg.h"
#include "acbuf.h"
#include "fileio.h"
#include "cleaner.h"

#include <errno.h>
#include <algorithm>

using namespace MYSTD;

#define MAXTEMPDELAY acfg::maxtempdelay // 27

header const & fileitem_base::GetHeaderUnlocked()
{
	return m_head;
}

string fileitem_base::GetHttpMsg()
{
	setLockGuard;
	if(m_head.frontLine.length()>9)
		return m_head.frontLine.substr(9);
	return m_head.frontLine;
}

time_t fileitem_base::m_nEarliestExpiration(0);

fileitem_base::fileitem_base(MYSTD::string sPath) :
	condition(),
	m_nIncommingCount(0),
	m_nSizeSeen(0),
	m_bCheckFreshness(true),
	m_bHeadOnly(false),
	m_nRangeLimit(-1),
	m_bAllowStoreData(true),
	m_nSizeChecked(0),
	m_filefd(-1),
	m_nDlRefsCount(0),
	m_status(FIST_FRESH),
	m_nTimeExpireAt(0)
{

	// FIXME: this sucks, belongs into the fileitem
	if(acfg::stupidfs)
	{
		// fix weird chars that we don't like in the filesystem
		replaceChars(sPath, ENEMIESOFDOSFS, '_');
		replaceChars(sPath, ENEMIESOFDOSFS, '_');
#ifdef WIN32
		replaceChars(sPath, "/", '\\');
#endif
	}

	// not using hexmap yet, convert just what is really needed for APT

	for (int i = 0; i < int(sPath.length()) - 3; i++)
	{
		if (sPath[i] == '%')
		{
			switch (sPath[i + 1])
			{
			case '7':
				switch (sPath[i + 2])
				{
				case 'E':
				case 'e':
					sPath.replace(i, 3, "~", 1);
					break;
				}
				break;
			case '5':
				switch (sPath[i + 2])
				{
				case 'B':
				case 'b':
					sPath.replace(i, 3, "[", 1);
					break;
				case 'D':
				case 'd':
					sPath.replace(i, 3, "]", 1);
					break;
				}
				break;
			default:
				continue;
			}
		}
	}

	m_sPathRel = sPath;
	m_sPathAbs = acfg::cacheDirSlash+sPath;

	//ldbg("item created, m_sPath: "<< m_sPath);
}


fileitem_base::fileitem_base() :
	condition(),
	m_nIncommingCount(0),
	m_nSizeSeen(0),
	m_bCheckFreshness(true),
	m_bHeadOnly(false),
	m_nRangeLimit(-1),
	m_bAllowStoreData(true),
	m_nSizeChecked(0),
	m_filefd(-1),
	m_nDlRefsCount(0),
	m_status(FIST_FRESH),
	m_nTimeExpireAt(0)
{
}

fileitem_base::~fileitem_base()
{
	setLockGuard;
//	m_head.clear();
	checkforceclose(m_filefd);
}


void fileitem_base::IncDlRefCount()
{
	setLockGuard;
	m_nDlRefsCount++;
}

void fileitem_base::DecDlRefCount(const string &sReason)
{
	setLockGuard;
	
	notifyAll();

	m_nDlRefsCount--;
	if(m_nDlRefsCount>0)
		return; // someone will care...
	
	// ... otherwise: the last downloader disappeared, need to tell observers

	if (m_status<FIST_COMPLETE)
	{
		m_status=FIST_LOCALERROR;
		m_head.clear();
		m_head.frontLine=string("HTTP/1.1 ")+sReason;
		m_head.type=header::ANSWER;

		if (acfg::debug&LOG_MORE)
			aclog::misc(string("Download of ")+m_sPathRel+" aborted");
	}
	checkforceclose(m_filefd);
}

uint64_t fileitem_base::GetTransferCount()
{
	setLockGuard;
	uint64_t ret=m_nIncommingCount;
	m_nIncommingCount=0;
	return ret;
}

int fileitem_base::GetFileFd() {
	LOGSTART("fileitem::GetFileFd");
	setLockGuard;

	ldbg("Opening " << m_sPathAbs);
	int fd=open(m_sPathAbs.c_str(), O_RDONLY);
	
#ifdef HAVE_FADVISE
	// optional, experimental
	if(fd>=0)
		posix_fadvise(fd, 0, m_nSizeChecked, POSIX_FADV_SEQUENTIAL);
#endif

	return fd;
}

off_t GetFileSize(cmstring & path, off_t defret)
{
	struct stat stbuf;
	return (0==::stat(path.c_str(), &stbuf)) ? stbuf.st_size : defret;
}


void fileitem_base::ResetCacheState()
{
	setLockGuard;
	m_nSizeSeen = 0;
	m_nSizeChecked = 0;
	m_status = FIST_FRESH;
	m_bAllowStoreData = true;
	m_head.clear();
}

FiStatus fileitem_base::Setup(bool bCheckFreshness)
{
	LOGSTART2("fileitem::Setup", bCheckFreshness);

	setLockGuard;

	if(m_status>FIST_FRESH)
		return m_status;
	m_status=FIST_INITED;
	m_bCheckFreshness = bCheckFreshness;
	
	if(m_head.LoadFromFile(m_sPathAbs+".head") >0 && m_head.type==header::ANSWER )
	{
		if(200 != m_head.getStatus())
			goto error_clean;
		
		LOG("good head");

		m_nSizeSeen=GetFileSize(m_sPathAbs, 0);

		// some plausibility checks
		if(m_bCheckFreshness)
		{
			const char *p=m_head.h[header::LAST_MODIFIED];
			if(!p)
				goto error_clean; // suspicious, cannot use it
			LOG("check freshness, last modified: " << p );

			// that will cause check by if-mo-only later, needs to be sure about the size here
			if(acfg::vrangeops == 0
					&& m_nSizeSeen != atoofft(m_head.h[header::CONTENT_LENGTH], -17))
			{
				m_nSizeSeen = 0;
			}
		}
		else
		{
			// non-volatile files, so could accept the length, do some checks first
			const char *pContLen=m_head.h[header::CONTENT_LENGTH];
			if(pContLen)
			{
				off_t nContLen=atoofft(pContLen); // if it's 0 then we assume it's 0
				
				// file larger than it could ever be?
				if(nContLen < m_nSizeSeen)
					goto error_clean;


				LOG("Content-Length has a sane range");
				
				m_nSizeChecked=m_nSizeSeen;

				// is it complete? and 0 value also looks weird, try to verify later
				if(m_nSizeSeen == nContLen && nContLen>0)
					m_status=FIST_COMPLETE;
			}
			else
			{
				// no content length known, assume it's ok
				m_nSizeChecked=m_nSizeSeen;
			}
		}
	}
	else // -> no .head file
	{
		// maybe there is some left-over without head file?
		// Don't thrust volatile data, but otherwise try to reuse?
		if(!bCheckFreshness)
			m_nSizeSeen=GetFileSize(m_sPathAbs, 0);
	}
	LOG("resulting status: " << m_status);
	return m_status;

	error_clean:
			unlink((m_sPathAbs+".head").c_str());
			m_head.clear();
			m_nSizeSeen=0;
			m_status=FIST_INITED;
			return m_status; // unuseable, to be redownloaded
}

bool fileitem_base::CheckUsableRange_unlocked(off_t nRangeLastByte)
{
	if(m_status == FIST_COMPLETE)
		return true;
	if(m_status < FIST_INITED || m_status > FIST_COMPLETE)
		return false;
	if(m_status >= FIST_DLGOTHEAD)
		return nRangeLastByte > m_nSizeChecked;

	// special exceptions for static files
	return (m_status == FIST_INITED && !m_bCheckFreshness
			&& m_nSizeSeen>0 && nRangeLastByte >=0 && nRangeLastByte <m_nSizeSeen
			&& atoofft(m_head.h[header::CONTENT_LENGTH], -255) > nRangeLastByte);
}

bool fileitem_base::SetupClean(bool bForce)
{
	setLockGuard;

	if(bForce)
	{
		if(m_status>FIST_FRESH)
			m_status = FIST_LOCALERROR;
	}
	else
	{
		if(m_status>FIST_FRESH)
			return false;
		m_status=FIST_INITED;
	}
	if(::truncate(m_sPathAbs.c_str(), 0) || ::truncate((m_sPathAbs+".head").c_str(), 0))
		return false;

	m_head.clear();
	m_nSizeSeen=m_nSizeChecked=0;

	return true;
}

void fileitem_base::SetupComplete()
{
	setLockGuard;
	notifyAll();
	m_nSizeChecked = m_nSizeSeen;
	m_status = FIST_COMPLETE;
}

inline void _LogWithErrno(const char *msg, const string & sFile)
{
	aclog::errnoFmter f;
	aclog::err( (sFile+" storage error ["+msg+"]: "+f).c_str());
}

#ifndef MINIBUILD

#define SETERROR(x) { m_bAllowStoreData=false; m_head.frontLine="HTTP/1.1 "; \
	m_head.frontLine+=x; m_status=FIST_LOCALERROR; _LogWithErrno(x, m_sPathRel); }
#define SETERRORKILLFILE(x) { SETERROR(x); goto kill_file; }
#define BOUNCE(x) { SETERROR(x); return false; }

bool fileitem_with_storage::DownloadStartedStoreHeader(const header & h, const char *pNextData,
		bool bForcedRestart)
{
	LOGSTART("fileitem::DownloadStartedStoreHeader");

	setLockGuard;

	USRDBG(5, "Download started, storeHeader for " << m_sPathRel << ", current status: " << m_status);
	
	if (m_status > FIST_DLPENDING)
	{
		if (!bForcedRestart) // conflict with another thread's initial attempt?
			return false;

		// some precautions, make sure to be in the right state
		int n = h.getStatus();
		if (FIST_DLGOTHEAD == m_status && n == 200)
			return true;
		if (FIST_DLRECEIVING)
		{
			LOG(n);
			if(n!=206)
			{
				BOUNCE("503 Unexpected remote file state");
			}
		}
		else
		{
			// common message, real problem hard to explain
			BOUNCE("503 Disconnected by peer");
		}
	}
	
	if(m_status<FIST_DLGOTHEAD)
		m_status=FIST_DLGOTHEAD; // assume that for now, may become error state...
	
	if(m_bCheckFreshness)
		m_nTimeExpireAt = GetTime() + MAXTEMPDELAY;

	m_nIncommingCount+=h.m_nEstimLength;

	// optional optimization: hints for the filesystem resp. kernel
	off_t hint_start(0), hint_length(0);
	
	// status will change, most likely... ie. BOUNCE action
	notifyAll();

	m_head.set(header::XORIG, h.h[header::XORIG]);

	string sHeadPath=m_sPathAbs+".head";
	
	switch(h.getStatus())
	{
	case 200:
	{
		if(bForcedRestart && m_nSizeChecked != 0)
		{
			/* shouldn't be here, server should have resumed at the previous position.
			 * Most likely the remote file was modified after the download started.
			 */
			return false;
		}
		m_nSizeChecked=0;
		m_head=h;
		hint_length=atoofft(h.h[header::CONTENT_LENGTH], 0);
		break;
	}
	case 206:
	{

		if(m_nSizeSeen<=0 && m_nRangeLimit<0)
		{
			// wtf? Cannot have requested partial content
			BOUNCE("500 Unexpected Partial Response");
		}
		/*
		 * Range: bytes=453291-
		 * ...
		 * Content-Length: 7271829
		 * Content-Range: bytes 453291-7725119/7725120
		 */
		const char *p=h.h[header::CONTENT_RANGE];
		if(!p)
			BOUNCE("500 Missing Content-Range in Partial Response");
		off_t myfrom, myto, mylen;
		int n=sscanf(p, "bytes " OFF_T_FMT "-" OFF_T_FMT "/" OFF_T_FMT, &myfrom, &myto, &mylen);
		if(n<=0)
			n=sscanf(p, "bytes=" OFF_T_FMT "-" OFF_T_FMT "/" OFF_T_FMT, &myfrom, &myto, &mylen);
		
		ldbg("resuming? n: "<< n << " und myfrom: " <<myfrom << 
				" und myto: " << myto << " und mylen: " << mylen);
		if(n!=3  // check for nonsense
				|| (m_nSizeSeen>0 && myfrom != m_nSizeSeen-1)
				|| (m_nRangeLimit>=0 && myto != m_nRangeLimit)
				|| myfrom<0 || mylen<0
		)
		{
			BOUNCE("500 Server reports unexpected range");
		}

		m_nSizeChecked=myfrom;
		
		hint_start=myfrom;
		hint_length=mylen;

		m_head=h;
		m_head.frontLine="HTTP/1.1 200 OK";
		m_head.del(header::CONTENT_RANGE);
		m_head.set(header::CONTENT_LENGTH, mylen);

		// stored the header before?
		if(bForcedRestart && m_status >= FIST_DLGOTHEAD)
		{
			// also make sure to have the FD position at the right place
			return (m_nSizeChecked == lseek(m_filefd, m_nSizeChecked, SEEK_SET));
		}

		// special optimization; if "-1 trick" was used then maybe don't reopen that file for writing later
		if(m_bCheckFreshness && pNextData && m_nSizeSeen == mylen && m_nSizeChecked == mylen-1)
		{
			int fd=open(m_sPathAbs.c_str(), O_RDONLY);
			if(fd>=0)
			{
				if(m_nSizeChecked==lseek(fd, m_nSizeChecked, SEEK_SET))
				{
					char c;
					if(1 == read(fd, &c, 1) && c == *pNextData)
					{
						aclog::err("known data hit, don't write to...");
						aclog::err(m_sPathAbs);
						m_bAllowStoreData=false;
						m_nSizeChecked=mylen;
					}
				}
				// XXX: optimize that, open as RW if possible and keep the file open for writing
				forceclose(fd);
			}
		}
		break;
	}
	case 416:
		// that's always bad; it cannot have been complete before (the -1 trick)
		// -> kill cached file ASAP
		m_bAllowStoreData=false;
		SETERRORKILLFILE("503 Server disagrees on file size, cleaning up");
		break;
// use the Location header and be sure the server does not mess around with Location and other codes
	case 301:
	case 302:
	case 303:
	case 307:
		m_head.set(header::LOCATION, h.h[header::LOCATION]);
		// fall-through
	default:
		m_bAllowStoreData=false;
		// have a clean header with just the error message
		m_head.frontLine=h.frontLine;
		m_head.type=header::ANSWER;
		m_head.set(header::CONTENT_LENGTH, "0");
		if(m_status>FIST_DLGOTHEAD)
		{
			// oh shit. Client may have already started sending it. Prevent such trouble in future.
			unlink(sHeadPath.c_str());
		}
	}

	if(acfg::debug&LOG_MORE)
		aclog::misc(string("Download of ")+m_sPathRel+" started");

	if(m_bAllowStoreData)
	{

		// using adaptive Delete-Or-Replace-Or-CopyOnWrite strategy
		
		// First opening the file first to be sure that it can be written. Header storage is the critical point,
		// every error after that leads to full cleanup to not risk inconsistent file contents 
		
		int flags = O_WRONLY | O_CREAT | O_BINARY;
		struct stat stbuf;
				
		mkbasedir(m_sPathAbs);
		m_filefd=open(m_sPathAbs.c_str(), flags, acfg::fileperms);
		ldbg("file opened?! returned: " << m_filefd);
		
		// self-recovery from cache poisoned with files with wrong permissions
		if (m_filefd<0)
		{
			if(m_nSizeChecked>0) // OOOH CRAP! CANNOT APPEND HERE! Do what's still possible.
			{
				string temp=m_sPathAbs+".tmp";
				if(FileCopy(m_sPathAbs, temp) && 0==unlink(m_sPathAbs.c_str()) )
				{
					if(0!=rename(temp.c_str(), m_sPathAbs.c_str()))
						BOUNCE("503 Cannot rename files");
					
					// be sure about that
					if(0!=stat(m_sPathAbs.c_str(), &stbuf) || stbuf.st_size!=m_nSizeSeen)
						BOUNCE("503 Cannot copy file parts, filesystem full?");
					
					m_filefd=open(m_sPathAbs.c_str(), flags, acfg::fileperms);
					ldbg("file opened after copying around: ");
				}
				else
					BOUNCE((tSS()<<"503 Cannot store or remove files in "
							<< GetDirPart(m_sPathAbs)).c_str());
			}
			else
			{
				unlink(m_sPathAbs.c_str());
				m_filefd=open(m_sPathAbs.c_str(), flags, acfg::fileperms);
				ldbg("file force-opened?! returned: " << m_filefd);
			}
		}
		
		if (m_filefd<0)
		{
			aclog::errnoFmter efmt("503 Cache storage error - ");
#ifdef DEBUG
			BOUNCE((efmt+m_sPathAbs).c_str());
#else
			BOUNCE(efmt.c_str());
#endif
		}
		
		if(0!=fstat(m_filefd, &stbuf) || !S_ISREG(stbuf.st_mode))
			SETERRORKILLFILE("503 Not a regular file");
		
		// crop, but only if the new size is smaller. MUST NEVER become larger (would fill with zeros)
		if(m_nSizeChecked <= m_nSizeSeen)
		{
			if(0==ftruncate(m_filefd, m_nSizeChecked))
				fdatasync(m_filefd);
			else
				SETERRORKILLFILE("503 Cannot change file size");
		}
		else if(m_nSizeChecked>m_nSizeSeen) // should never happen and caught by the checks above
			SETERRORKILLFILE("503 Internal error on size checking");
		// else... nothing to fix since the expectation==reality

		falloc_helper(m_filefd, hint_start, hint_length);
		
		ldbg("Storing header as "+sHeadPath);
		int count=m_head.StoreToFile(sHeadPath);

		if(count<0)
			SETERRORKILLFILE( (-count!=ENOSPC ? "503 Cache storage error" : "503 OUT OF DISK SPACE"));
			
		// double-check the sane state
		if(0!=fstat(m_filefd, &stbuf) || stbuf.st_size!=m_nSizeChecked)
			SETERRORKILLFILE("503 Inconsistent file state");
			
		if(m_nSizeChecked!=lseek(m_filefd, m_nSizeChecked, SEEK_SET))
			SETERRORKILLFILE("503 IO error, positioning");
	}
	
	m_status=FIST_DLGOTHEAD;
	return true;

	kill_file:
	if(m_filefd>=0)
	{
#if _POSIX_SYNCHRONIZED_IO > 0
		fsync(m_filefd);
#endif
		forceclose(m_filefd);
	}

	LOG("Deleting " << m_sPathAbs);
	unlink(m_sPathAbs.c_str());
	unlink(sHeadPath.c_str());
	
	m_status=FIST_LOCALERROR;
	return false;
}

bool fileitem_with_storage::StoreFileData(const char *data, unsigned int size)
{

	setLockGuard;

	LOGSTART2("fileitem::StoreFileData", "status: " << m_status << ", size: " << size);

	// something might care, most likely... also about BOUNCE action
	notifyAll();
	
	m_nIncommingCount+=size;
	
	if(m_status >= FIST_LOCALERROR || m_status < FIST_DLGOTHEAD)
		return false;
	
	if (size==0)
	{
		if(FIST_COMPLETE == m_status)
		{
			LOG("already completed");
		}
		else
		{
			m_status = FIST_COMPLETE;

			if (acfg::debug & LOG_MORE)
				aclog::misc(tSS() << "Download of " << m_sPathRel << " finished");

			// we are done! Fix header from chunked transfers?
			if (m_filefd >= 0 && !m_head.h[header::CONTENT_LENGTH])
			{
				m_head.set(header::CONTENT_LENGTH, m_nSizeChecked);
				m_head.StoreToFile(m_sPathAbs + ".head");
			}
		}
	}
	else
	{
		m_status = FIST_DLRECEIVING;

		if (m_bAllowStoreData && m_filefd>=0)
		{
			while(size>0)
			{
				int r=write(m_filefd, data, size);
				if(r<0)
				{
					if(EINTR==errno || EAGAIN==errno)
						continue;
					aclog::errnoFmter efmt("503 ");
					BOUNCE(efmt.c_str());
				}
				m_nSizeChecked+=r;
				size-=r;
				data+=r;
			}
		}
	}

	// needs to remember the good size, just in case the DL is resumed (restarted in hot state)
	if(m_nSizeSeen < m_nSizeChecked)
		m_nSizeSeen = m_nSizeChecked;

	return true;
}


struct tFileRefEntry
{
	int nRefCount;
	tFileItemPtr ref;
	bool m_bNeedsDelayedExpiration;
	tFileRefEntry() : nRefCount(0), m_bNeedsDelayedExpiration(false) { };
};
static MYMAP<string, tFileRefEntry> mapItems;
lockable mapLck;

void fileitem::Unreg()
{
	LOGSTART("fileitem::Unreg");

   lockguard managementLock(mapLck); 
	
   MYMAP<string,tFileRefEntry>::iterator entry = mapItems.find(m_sPathRel);
   if(mapItems.end() == entry)
   {
	   aclog::err("INTERNAL ERROR, attempt to unregister non-existing download item");
	   return;
   }
   if(entry->second.ref.get() != this)
   {
	   aclog::err("INTERNAL ERROR, doppelganger running amok");
	   return;
   }
   entry->second.nRefCount--;
   if(entry->second.nRefCount<=0)
   {
	   LOG("*this is last entry, deleting dl/fi mapping");
	  setLockGuard;
	  m_status=FIST_ERRNOUSER;
	  notifyAll();
	  mapItems.erase(entry);
   }
}

tFileItemPtr fileitem::GetRegisteredFileItem(MYSTD::string sPathKey)
{
	LOGSTART2s("fileitem::GetFileItem", sPathKey);
	tFileItemPtr p;
	MYTRY
	{
		p.reset(new fileitem(sPathKey)); // let it create real target path
		lockguard lockGlobalMap(mapLck);

		map<string, tFileRefEntry>::iterator it=mapItems.find(p->m_sPathRel);
		if(it!=mapItems.end())
		{
			it->second.nRefCount++;
			LOG("Sharing existing file item");
			return it->second.ref;
		}
		// ok, keep the new item

		LOG("Created new file item");
		tFileRefEntry & entry = mapItems[p->m_sPathRel];
		LOG("Registering new file item...");
		if(!entry.ref)
		{
			entry.ref=p;
			entry.nRefCount++;
			LOG("New reference count: " << entry.nRefCount);
		}
		return entry.ref;
	}
	MYCATCH(MYSTD::bad_alloc&)
	{
		p.reset();
	}
	return p;
}

bool fileitem::RegisterFileItem(SHARED_PTR<fileitem> spCustomFileItem)
		{
			if (!spCustomFileItem || spCustomFileItem->m_sPathRel.empty())
				return false;

			lockguard lockGlobalMap(mapLck);

			map<string, tFileRefEntry>::iterator it = mapItems.find(spCustomFileItem->m_sPathRel);
			if (it != mapItems.end())
				return false; // conflict, another agent is already active

			tFileRefEntry & entry = mapItems[spCustomFileItem->m_sPathRel];
			entry.ref = spCustomFileItem;
			entry.nRefCount = 1;

			return true;
		}

// Opposite of GetFileItem, unregisters existing file item now or as soon as possible.
void fileitem::UnregOneASAP(tFileItemPtr pItem)
{
	if (!pItem)
		return;
	// download never started or no delayed expiration needed -> release now. Or expired long ago.
	if (!pItem->m_nTimeExpireAt || GetTime() >= pItem->m_nTimeExpireAt)
	{
		pItem->Unreg();
		return;
	}
	lockguard lockGlobalMap(mapLck);
	MYMAP<string, tFileRefEntry>::iterator entry = mapItems.find(pItem->m_sPathRel);
	// not existing? -> not for us. Still enough references? -> let this one go
	if (mapItems.end() == entry || entry->second.nRefCount>1)
	{
		lockGlobalMap.unLock();
		pItem->Unreg();
		return;
	}
	// now the item's refCount is by one higher then it should be, set the flag instead of --
	entry->second.m_bNeedsDelayedExpiration=true;

// this will work as long as the delay is equal for all
	m_nEarliestExpiration=pItem->m_nTimeExpireAt;

}

// this method is supposed to be awaken periodically and detect items with ref count manipulated by
// the request storm prevention mechanism. Items shall be be dropped after some time if no other
// thread but us is using them.
time_t fileitem::DoDelayedUnregAndCheck()
{
	lockguard lockGlobalMap(mapLck);
	MYMAP<string, tFileRefEntry>::iterator it, here;

	time_t now=GetTime();

	if(now<m_nEarliestExpiration) // unreachable anyhow
		return m_nEarliestExpiration;

	m_nEarliestExpiration = cleaner::never; // the correct time will be found soon

	for(it=mapItems.begin(); it!=mapItems.end();)
	{
		here=it++;
		if(!here->second.m_bNeedsDelayedExpiration)
			continue;
		// still active or young item? skip, but find the earliest time to expire
		if(now < here->second.ref->m_nTimeExpireAt || here->second.nRefCount>1)
		{
			m_nEarliestExpiration=min(here->second.ref->m_nTimeExpireAt, m_nEarliestExpiration);
			continue;
		}
		// ok, unused and delay is over. Destroy with the same sequence as Unreg... does.
		lockguard g(here->second.ref.get());
		here->second.ref->m_status=FIST_ERRNOUSER;
		here->second.ref->notifyAll();
		g.unLock();
		mapItems.erase(here);
	}
	return m_nEarliestExpiration;
}

#ifdef DEBUG

void DumpItems()
{
/*	lockguard lockGlobalMap(mapLck);

	map<string, tFileRefEntry>::iterator it=mapItems.begin();
	cout << "Remaining file items:\n";
	for(;it!=mapItems.end(); it++)
	{
		cout << it->second.ref->status << ": " << it->first <<endl;
	}
	*/
}
#endif



ssize_t fileitem_with_storage::SendData(int out_fd, int in_fd, off_t &nSendPos, size_t count)
{
#ifndef HAVE_LINUX_SENDFILE
	return sendfile_generic(out_fd, in_fd, &nSendPos, count);
#else
	ssize_t r=sendfile(out_fd, in_fd, &nSendPos, count);

	if(r<0 && (errno==ENOSYS || errno==EINVAL))
		return sendfile_generic(out_fd, in_fd, &nSendPos, count);
	else
		return r;
#endif
}

#endif // MINIBUILD

FiStatus fileitem_base::WaitForFinish(int *httpCode)
{
	setLockGuard;
	while(m_status<FIST_COMPLETE)
		wait();
	if(httpCode)
		*httpCode=m_head.getStatus();
	return m_status;
}
