//=============================================================================
//
//   File : KviSharedFilesManager.cpp
//   Creation date : Wed Aug 27 2000 10:33:11 CEST by Szymon Stefanek
//
//   This file is part of the KVIrc irc client distribution
//   Copyright (C) 2000-2010 Szymon Stefanek (pragma at kvirc dot net)
//
//   This program is FREE software. You can redistribute it and/or
//   modify it under the terms of the GNU General Public License
//   as published by the Free Software Foundation; either version 2
//   of the License, or (at your opinion) any later version.
//
//   This program is distributed in the HOPE that it will be USEFUL,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//   See the GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with this program. If not, write to the Free Software Foundation,
//   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//=============================================================================

#include "KviSharedFilesManager.h"
#include "KviConfigurationFile.h"
#include "KviFileUtils.h"
#include "KviIrcMask.h"

#include <QFileInfo>

// TODO: Match servers that the file requests come from
// TODO: Max number of downloads ?

// FIXME: MD5SUM ?

/*
	@doc: shared_files
	@title:
		Sharing files with KVIrc
	@type:
		generic
	@short:
		Automatically sharing your files with other IRC users
	@keyterms:
		file sharing
	@body:
		[big]What is this ?[/big]
		The "file offers" are a simple way to share your files with other IRC users.[br]
		Basically, you setup an offer by selecting a local file, choosing a "visible name" for it.
		Remote users will be able to request you the file and download it automatically by
		issuing a simple DCC GET request.[br]
		[big]Details[/big]
		Each offer refers to an existing file on one of your locally mounted file systems.
		The offer is given a visible name that the remote users will effectively request.
		To share the file /usr/arch/mp3/SonataArctica_SingInSilence_Live.mp3 you will add a file offer
		with /usr/arch/mp3/SonataArctica_SingInSilence_Live.mp3 as real file path, something like
		"SonataArctica_SingInSilence.mp3". A remote user will then request you a DCC GET SonataArctica_SingInSilence.mp3
		and KVIrc will automatically send the file.[br]
		Each file offer has an "user mask" that the requesting remote users must match to
		obtain the file: *!*@* matches any user, Pragma!*@* matches any user with nickname pragma,
		*!*@*.omnikron.net matches any user coming from the omnikron.net domain.[br]
		Each offer can have an expire time: the offer will be automatically removed after
		a defined number of seconds. An expire time of '0' seconds means that the offer should never expire.[br]
		If you have two file offers with the same name and different file, the remote user can
		use an additional "size" parameter in the DCC GET request.[br]
		[big]Security issues[/big]
		This is a nice but unsecure method of sharing files.[br]
		The user mask is a good protection but you have to use it properly!.[br]
		Setting the user mask to Nick!*@* can be easily exploited (just by making an user disconnect
		in one of the well known ways and then by using his nickname).[br]
		On the other side, the remote end must know exactly the visible name of the offer to request
		and noone but you will tell him that name.[br]
		In sum:[br]
		Don't share any really important files: this *might* be like putting it on your webpage :D[br]
		Please don't send complains if someone stoles your /etc/passwd : it is because you have permitted that.[br]
*/


KviSharedFilesManager::KviSharedFilesManager()
: QObject()
{
	m_pSharedListDict = new KviPointerHashTable<QString,KviSharedFileList>();
	m_pSharedListDict->setAutoDelete(true);
	m_pCleanupTimer = new QTimer();
	connect(m_pCleanupTimer,SIGNAL(timeout()),this,SLOT(cleanup()));
}

KviSharedFilesManager::~KviSharedFilesManager()
{
	if(m_pCleanupTimer->isActive())m_pCleanupTimer->stop();
	delete m_pCleanupTimer;
	delete m_pSharedListDict;
}

void KviSharedFilesManager::cleanup()
{
	KviPointerHashTableIterator<QString,KviSharedFileList> it(*m_pSharedListDict);
	time_t curTime = time(0);

	bool bOtherStuffToCleanup = false;
	//bool bChanged = false;

	KviPointerList<QString> lDying;
	lDying.setAutoDelete(true);

	while(KviSharedFileList * l = it.current())
	{
		KviPointerList<KviSharedFile> tmp;
		tmp.setAutoDelete(false);
		for(KviSharedFile * o = l->first();o;o = l->next())
		{
			if(o->expireTime() > 0)
			{
				if(((int)o->expireTime()) <= ((int)curTime))
				{
					tmp.append(o);
					//bChanged = true;
				} else {
					bOtherStuffToCleanup = true;
				}
			}
		}
		for(KviSharedFile * fo = tmp.first();fo;fo = tmp.next())
		{
			l->removeRef(fo);
			emit sharedFileRemoved(fo);
		}
		if(l->count() == 0)
			lDying.append(new QString(it.currentKey()));

		++it;
	}

	for(QString * pDyingKey = lDying.first();pDyingKey;pDyingKey = lDying.next())
		m_pSharedListDict->remove(*pDyingKey);

	if(!bOtherStuffToCleanup)m_pCleanupTimer->stop();
	//if(bChanged)emit sharedFilesChanged();
}

void KviSharedFilesManager::clear()
{
	m_pSharedListDict->clear();
	emit sharedFilesChanged();
}

void KviSharedFilesManager::doInsert(KviSharedFileList * l, KviSharedFile * o)
{
	int index = 0;
	for(KviSharedFile * fo =l->first();fo;fo = l->next())
	{
		if(o->wildcardCount() > 0)
		{
			// the new mask has wildcards... if the current one has none, skip it
			if(fo->wildcardCount() > 0)
			{
				// the one in the list has wildcards too...
				// the ones with more non-wild chars go first...
				if(fo->nonWildcardCount() < o->nonWildcardCount())
				{
					// ok...the new one has more non-wildcards, insert
					l->insert(index,o);
					return;
				} else {
					if(o->nonWildcardCount() == fo->nonWildcardCount())
					{
						// the same number of non-wildcards
						// let the number of wildcards decide (it will be eventually equal)
						if(o->wildcardCount() < fo->wildcardCount())
						{
							// the new one has less wildcards... goes first
							l->insert(index,o);
							return;
						} // else the same number of wildcards and non-wildcards...skip
					} // else the existing one has more non-wildcards...skip
				}
			} // else the current has no wildcards...skip
		} else {
			// the new mask has no wildcards....
			if(fo->wildcardCount() > 0)
			{
				// current one has wildcards...insert
				l->insert(index,o);
				return;
			}
			// the current one has no wildcards...
			// the longer masks go first....
			if(fo->maskLength() < o->maskLength())
			{
				// the current one is shorter than the new one...insert
				l->insert(index,o);
				return;
			} // else current one is longer...skip
		}
		index++;
	}
	l->append(o);
}

void KviSharedFilesManager::addSharedFile(KviSharedFile * f)
{
	// First find the list
	KviSharedFileList * l = m_pSharedListDict->find(f->name());
	if(!l)
	{
		l = new KviSharedFileList;
		l->setAutoDelete(true);
		m_pSharedListDict->replace(f->name(),l);
	}

	doInsert(l,f);

	if(((int)f->expireTime()) > 0)
	{
		if(!m_pCleanupTimer->isActive())m_pCleanupTimer->start(60000);
	}

	emit sharedFileAdded(f);
}

KviSharedFile * KviSharedFilesManager::addSharedFile(const QString &szName,const QString &szAbsPath,const QString &szMask,int timeoutInSecs)
{
	QFileInfo inf(szAbsPath);
	if(inf.exists() && inf.isFile() && inf.isReadable() && (inf.size() > 0))
	{
		// First find the list
		KviSharedFileList * l = m_pSharedListDict->find(szName);
		if(!l)
		{
			l = new KviSharedFileList;
			l->setAutoDelete(true);
			m_pSharedListDict->replace(szName,l);
		}

		// Now insert
		KviSharedFile * o = new KviSharedFile(szName,szAbsPath,szMask,timeoutInSecs > 0 ? (((int)(time(0))) + timeoutInSecs) : 0,inf.size());

		doInsert(l,o);

		if(((int)o->expireTime()) > 0)
		{
			if(!m_pCleanupTimer->isActive())m_pCleanupTimer->start(60000);
		}

		emit sharedFileAdded(o);

		return o;
	} else {
		qDebug("File %s unreadable: can't add offer",szAbsPath.toUtf8().data());
		return 0;
	}
}

KviSharedFile * KviSharedFilesManager::lookupSharedFile(const QString &szName,KviIrcMask * mask,unsigned int uFileSize)
{
	KviSharedFileList * l = m_pSharedListDict->find(szName);
	if(!l)return 0;

	for(KviSharedFile * o = l->first();o;o = l->next())
	{
		bool bMatch;
		if(mask)
		{
			KviIrcMask umask(o->userMask());
			bMatch = mask->matchedBy(umask);
		} else bMatch = KviQString::equalCS(o->userMask(),"*!*@*");
		if(bMatch)
		{
			if(uFileSize > 0)
			{
				if(uFileSize == o->fileSize())return o;
			} else return o;
		}
	}

	return 0;
}
bool KviSharedFilesManager::removeSharedFile(const QString &szName,const QString &szMask,unsigned int uFileSize)
{
	KviSharedFileList * l = m_pSharedListDict->find(szName);
	if(!l)return false;
	for(KviSharedFile * o = l->first();o;o = l->next())
	{
		if(KviQString::equalCI(szMask,o->userMask()))
		{
			bool bMatch = uFileSize > 0 ? uFileSize == o->fileSize() : true;
			if(bMatch)
			{
				QString save = szName; // <-- szName MAY Be a pointer to o->name()
				l->removeRef(o);
				if(l->count() == 0)m_pSharedListDict->remove(save);
				emit sharedFileRemoved(o);
				return true;
			}
		}
	}
	return false;
}

bool KviSharedFilesManager::removeSharedFile(const QString &szName,KviSharedFile * off)
{
	KviSharedFileList * l = m_pSharedListDict->find(szName);
	if(!l)return false;
	for(KviSharedFile * o = l->first();o;o = l->next())
	{
		if(off == o)
		{
			QString save = szName; // <-- szName MAY Be a pointer to o->name()
			l->removeRef(o);
			if(l->count() == 0)m_pSharedListDict->remove(save);
			emit sharedFileRemoved(off);
			return true;
		}
	}
	return false;
}


void KviSharedFilesManager::load(const QString & szFilename)
{
	KviConfigurationFile cfg(szFilename,KviConfigurationFile::Read);
	cfg.setGroup("PermanentFileOffers");
	int iNum = cfg.readIntEntry("NEntries",0);
	for(int i=0; i < iNum; i++)
	{
		QString szTmp;
		szTmp = QString("%1FName").arg(i);
		QString szName = cfg.readEntry(szTmp,"");
		szTmp = QString("%1FilePath").arg(i);
		QString szPath = cfg.readEntry(szTmp,"");
		szTmp = QString("%1UserMask").arg(i);
		QString szMask = cfg.readEntry(szTmp,"");
		if(!szMask.isEmpty() && !szPath.isEmpty() && !szName.isEmpty())
			addSharedFile(szName,szPath,szMask,0);
	}
}

void KviSharedFilesManager::save(const QString & szFilename)
{
	KviConfigurationFile cfg(szFilename,KviConfigurationFile::Write);
	cfg.clear();
	cfg.setGroup("PermanentFileOffers");

	KviPointerHashTableIterator<QString,KviSharedFileList> it(*m_pSharedListDict);
	int i = 0;
	while(KviSharedFileList * pList = it.current())
	{
		for(KviSharedFile * pFile = pList->first(); pFile; pFile = pList->next())
		{
			if(((int)(pFile->expireTime())) == 0)
			{
				QString szTmp;
				szTmp = QString("%1FName").arg(i);
				cfg.writeEntry(szTmp,it.currentKey());
				szTmp = QString("%1FilePath").arg(i);
				cfg.writeEntry(szTmp,pFile->absFilePath());
				szTmp = QString("%1UserMask").arg(i);
				cfg.writeEntry(szTmp,pFile->userMask());
				++i;
			}
		}
		++it;
	}
	cfg.writeEntry("NEntries",i);
}
