
//#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
mstring sReplDir("_altStore" SZPATHSEP);

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(END_OF_TIME);

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),
	m_nTimeDlDone(END_OF_TIME)
{

	// 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),
	m_nTimeDlDone(END_OF_TIME)
{
}

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+"], last errno: "+f).c_str());
}

#ifndef MINIBUILD

/*
#ifdef DEBUG
#define SET_FRONTLINE(ret, x) tSS __fbuf; __fbuf << "HTTP/1.1 " << x << " @@ " << __FILE__ ":" \
	<< __LINE__ << " /" << __FUNCTION__; ret=__fbuf;
#else
#endif
*/

#define SET_FRONTLINE(ret, x) ret="HTTP/1.1 "; ret+=x;
#define SETERROR(x) { m_bAllowStoreData=false; SET_FRONTLINE(m_head.frontLine, x); \
	m_head.set(header::XORIG, h.h[header::XORIG]); \
    m_status=FIST_LOCALERROR; m_nTimeDlDone=GetTime(); _LogWithErrno(x, m_sPathRel); }
#define SETERRORKILLFILE(x) { SETERROR(x); goto kill_file; }
#define BOUNCE(x) { SETERROR(x); return false; }

fileitem::SuccessWithTransErrorFlag
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_COMPLETE)
	{
		USRDBG(0, "Error: item finished and idle, refusing download start");
		return false;
	}

	// conflict with another thread's initial attempt? Deny, except for a forced restart
	if (m_status > FIST_DLPENDING && !bForcedRestart)
		return false;
	
	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();

	string sHeadPath=m_sPathAbs+".head";
	int serverStatus = h.getStatus();
#if 0
#warning FIXME
	static UINT fc=1;
	if(!(++fc % 4))
	{
		serverStatus = 416;
	}
#endif
	switch(serverStatus)
	{
	case 200:
	{
		if(m_status < FIST_DLGOTHEAD)
			bForcedRestart = false; // behave normally, set all data

		if (bForcedRestart)
		{
			if (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.
				 */
				//USRDBG(0, "state: " << m_status << ", m_nsc: " << m_nSizeChecked);
				BOUNCE("500 Failed to resume remote download");
			}
			if(h.h[header::CONTENT_LENGTH] && atoofft(h.h[header::CONTENT_LENGTH])
					!= atoofft(h.h[header::CONTENT_LENGTH], -2))
			{
				BOUNCE("500 Failed to resume remote download, bad length");
			}
			m_head.set(header::XORIG, h.h[header::XORIG]);
		}
		else
		{
			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);
		m_head.set(header::XORIG, h.h[header::XORIG]);

		// target opened before? close it, will reopen&seek later
		if (bForcedRestart)
			checkforceclose(m_filefd);

		// 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 bad; it cannot have been complete before (the -1 trick)
		// however, proxy servers with v3r4 cl3v3r caching strategy can cause that
		// if if-mo-since is used and they don't like it, so attempt a retry in this case
		if(m_nSizeChecked == 0)
		{
			USRDBG(0, "Peer denied to resume previous download (transient error) " << m_sPathRel );
			m_nSizeSeen = 0;
			return SuccessWithTransErrorFlag(false, true);
		}
		else
		{
		// -> kill cached file ASAP
			m_bAllowStoreData=false;
			m_head.copy(h, header::XORIG);
			SETERRORKILLFILE("503 Server disagrees on file size, cleaning up");
		}
		break;
	default:
		m_head.type=header::ANSWER;
		m_head.copy(h, header::XORIG);
		m_head.copy(h, header::LOCATION);
		if(bForcedRestart)
		{
			// got an error from the replacement mirror? cannot handle it properly
			// because some job might already have started returning the data
			USRDBG(0, "Cannot restart, HTTP code: " << serverStatus);
			BOUNCE(h.getCodeMessage());
		}

		m_bAllowStoreData=false;
		// have a clean header with just the error message
		m_head.frontLine=h.frontLine;
		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))
      {
#if ( (_POSIX_C_SOURCE >= 199309L || _XOPEN_SOURCE >= 500) && _POSIX_SYNCHRONIZED_IO > 0)
				fdatasync(m_filefd);
#endif
      }
			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;
	m_nTimeDlDone=GetTime();
	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;
			m_nTimeDlDone=GetTime();

			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("HTTP/1.1 503 ");
					m_head.frontLine = efmt;
					m_status=FIST_LOCALERROR;
					m_nTimeDlDone=GetTime();
					_LogWithErrno(efmt.c_str(), m_sPathRel);
					return false;
				}
				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;
}

//typedef MYSTD::pair<int,tFileItemPtr> tFileRefEntry;
//typedef MYSTD::multimap<string, tFileRefEntry> tFiGlobMap;
static tFiGlobMap mapItems;
lockable mapLck;

fileitem::fileitem(cmstring &s) : fileitem_with_storage(s), m_globRef(mapItems.end())
{};

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

	lockguard managementLock(mapLck);
	setLockGuard;

	if(m_globRef == mapItems.end())
	{
		// not globally registered, just ignore
		return;
	}

	m_globRef->second.first--;
	if (m_globRef->second.first <= 0)
	{
		// use the delay timer only if late expiration is needed and it isn't expired already
		if (m_nTimeExpireAt && GetTime() < m_nTimeExpireAt)
		{
			m_nEarliestExpiration = MYSTD::min(m_nTimeExpireAt, m_nEarliestExpiration);
			g_victor.RunLatestAt(m_nEarliestExpiration);
			return;
		}

		LOG("*this is last entry, deleting dl/fi mapping");
		m_status = FIST_ERRNOUSER;
		notifyAll();
		mapItems.erase(m_globRef);
		m_globRef = mapItems.end();
	}
}


tFileItemPtr fileitem::GetRegisteredFileItem(cmstring &sPathKey, bool bConsiderAltStore)
{
	LOGSTART2s("fileitem::GetFileItem", sPathKey);

	MYTRY
	{
		fileitem *p = new fileitem(sPathKey); // need one because it will convert the path string to the proper value needed as key
		tFileItemPtr sp(p);
		lockguard lockGlobalMap(mapLck);

		tFiGlobMap::iterator it=mapItems.find(p->m_sPathRel);
		if(it!=mapItems.end())
		{
			if (bConsiderAltStore)
			{
				// detect items that got stuck somehow
				time_t now(GetTime());
				time_t extime(now - acfg::stucksecs);
				if (it->second.second.get()->m_nTimeDlDone < extime)
				{
					// try to find its sibling which is in good state?
					for (; it!=mapItems.end() && it->first == p->m_sPathRel; ++it)
					{
						if (it->second.second.get()->m_nTimeDlDone >= extime)
						{
							it->second.first++;
							LOG("Sharing an existing REPLACEMENT file item");
							return it->second.second;
						}
					}
					// ok, then create a modded name version in the replacement directory
					mstring sKeyPathOrig(p->m_sPathRel);
					replaceChars(p->m_sPathRel, "/\\", '_');
					p->m_sPathRel.insert(0, sReplDir + ltos(getpid()) + "_" + ltos(now))+"_";
					p->m_sPathAbs = acfg::cacheDirSlash+p->m_sPathRel;

					LOG("Registering a new REPLACEMENT file item...");
					p->m_globRef = mapItems.insert(make_pair(sKeyPathOrig, tFileRefEntry(1,sp)));
					return sp;
				}
			}
			LOG("Sharing existing file item");
			it->second.first++;
			return it->second.second;
		}
		LOG("Registering the NEW file item...");
		p->m_globRef = mapItems.insert(make_pair(p->m_sPathRel, tFileRefEntry(1,sp)));
		return sp;
	}
	MYCATCH(MYSTD::bad_alloc&)
	{
	}
	return tFileItemPtr();
}

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

	lockguard lockGlobalMap(mapLck);

	if(ContHas(mapItems, spCustomFileItem->m_sPathRel))
		return false; // conflict, another agent is already active

	spCustomFileItem->m_globRef = mapItems.insert(make_pair(spCustomFileItem->m_sPathRel,
			tFileRefEntry(1, spCustomFileItem)));

	return true;
}


// 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);
	tFiGlobMap::iterator it, here;

	time_t now=GetTime();

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

	m_nEarliestExpiration = END_OF_TIME; // the correct time will be found below

	for(it=mapItems.begin(); it!=mapItems.end();)
	{
		here=it++;

		// not needed or not ready yet
		if (!here->second.second->m_nTimeExpireAt)
			continue;

		m_nEarliestExpiration=min(here->second.second->m_nTimeExpireAt, m_nEarliestExpiration);

		if (now < here->second.second->m_nTimeExpireAt)
			continue;

		// became busy again? Ignore this entry, it's new master will take care of the deletion ASAP
		if(here->second.first>0)
			continue;

		// ok, unused and delay is over. Destroy with the same sequence as Unreg does, care about
		// live time.
		tFileItemPtr local_ptr(here->second.second);
		fileitem *p = static_cast<fileitem*>(local_ptr.get());
		{
			lockguard g(p);
			p->m_status = FIST_ERRNOUSER;
			p->m_globRef = mapItems.end();
			mapItems.erase(here);
		}
		// p still alive, through local_ptr or external references to it
		p->notifyAll();

	}
	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
}

void dump_handler(int) {
	fileitem::dump_status();
}

void fileitem::dump_status()
{
	tSS fmt;
	aclog::err("File descriptor table:\n", NULL);
	for(tFiGlobMap::iterator it=mapItems.begin(); it!=mapItems.end(); ++it)
	{
		fmt.clear();
		fmt << "FREF: " << it->first << " [" << it->second.first << "]:\n";
		if(! it->second.second)
		{
			fmt << "\tBAD REF!\n";
			continue;
		}
		else
		{
			fmt << "\t" << it->second.second->m_sPathRel
					<< "\n\t" << it->second.second->m_sPathAbs
					<< "\n\tPos.dl.agents: " << it->second.second->m_nDlRefsCount
					<< "\n\tState: " << it->second.second->m_status
					<< "\n\tPos: " << it->second.second->m_nIncommingCount << " , "
					<< it->second.second->m_nRangeLimit << " , "
					<< it->second.second->m_nSizeChecked << " , "
					<< it->second.second->m_nSizeSeen
			<< "\n\tExp: " << it->second.second->m_nEarliestExpiration << " , "
			<< it->second.second->m_nTimeExpireAt << "\n\n";
		}
		aclog::err(fmt.c_str(), NULL);
	}
}

fileitem_with_storage::~fileitem_with_storage()
{
	if(startsWith(m_sPathRel, sReplDir))
	{
		unlink(m_sPathAbs.c_str());
		unlink((m_sPathAbs+".head").c_str());
	}
}

#endif // MINIBUILD

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

