/*
 * dirwatch.cpp - detect changes of directory content
 * Copyright (C) 2003  Justin Karneges
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include"dirwatch.h"

#include<qdir.h>
#include<qfileinfo.h>
#include<qvaluelist.h>
#include<qstringlist.h>
#include<qtimer.h>

static DirWatchPlatform *platform = 0;
static int platform_ref = 0;

struct file_info
{
	QString name;
	uint size;
	QDateTime lastModified;
};

typedef QValueList<file_info> DirInfo;

static DirInfo getDirInfo(const QString &dir)
{
	DirInfo info;
	QDir d(dir);
	if(!d.exists())
		return info;
	QStringList el = d.entryList();
	for(QStringList::ConstIterator it = el.begin(); it != el.end(); ++it) {
		if(*it == "." || *it == "..")
			continue;
		QFileInfo fi(d.filePath(*it));
		file_info i;
		i.name = fi.fileName();
		i.size = fi.size();
		i.lastModified = fi.lastModified();
		info += i;
	}
	return info;
}

class DirWatch::Private
{
public:
	Private() {}

	QString dir;

	// for non-platform watching
	DirInfo info;
	QTimer checkTimer;
	int freq;
	int id;
};

DirWatch::DirWatch(const QString &dir, QObject *parent)
:QObject(parent)
{
	d = new Private;
	d->freq = 5000;
	d->id = -1;

	// try to use platform
	if(!platform) {
		DirWatchPlatform *p = new DirWatchPlatform;
		if(p->init())
			platform = p;
		else
			delete p;
	}
	if(platform) {
		++platform_ref;
		connect(platform, SIGNAL(dirChanged(int)), SLOT(pf_dirChanged(int)));
	}
	else {
		connect(&d->checkTimer, SIGNAL(timeout()), SLOT(doCheck()));
	}

	if(!dir.isEmpty())
		setDir(dir);
}

DirWatch::~DirWatch()
{
	setDir("");

	if(platform) {
		--platform_ref;
		if(platform_ref == 0) {
			delete platform;
			platform = 0;
		}
	}
	delete d;
}

bool DirWatch::usingPlatform() const
{
	return (platform ? true: false);
}

QString DirWatch::dir() const
{
	return d->dir;
}

void DirWatch::setDir(const QString &s)
{
	if(d->dir == s)
		return;

	if(!d->dir.isEmpty()) {
		if(platform) {
			if(d->id != -1)
				platform->removeDir(d->id);
		}
		else
			d->checkTimer.stop();

		d->dir = "";
	}

	if(s.isEmpty())
		return;

	d->dir = s;

	if(platform) {
		d->id = platform->addDir(d->dir);
	}
	else {
		d->info = getDirInfo(d->dir);
		d->checkTimer.start(d->freq);
	}
}

void DirWatch::doCheck()
{
	DirInfo newInfo = getDirInfo(d->dir);

	bool info_changed = false;
	QDir dir(d->dir);
	file_info ni;

	// look for files that are missing or modified
	for(DirInfo::ConstIterator it = d->info.begin(); it != d->info.end(); ++it) {
		const file_info &i = *it;

		bool found = false;
		for(DirInfo::ConstIterator nit = newInfo.begin(); nit != newInfo.end(); ++nit) {
			const file_info &tmp = *nit;
			if(i.name == tmp.name) {
				found = true;
				ni = tmp;
				break;
			}
		}
		if(!found) {
			//printf("%s: not found\n", i.name.latin1());
			info_changed = true;
			break;
		}

		if(i.lastModified != ni.lastModified || i.size != ni.size) {
			//printf("%s: modified (is: %s, was: %s)\n", i.name.latin1(), ni.lastModified.toString().latin1(), i.lastModified.toString().latin1());
			info_changed = true;
			break;
		}
		//printf("%s: no change\n", i.name.latin1());
	}

	// look for files that have been added
	for(DirInfo::ConstIterator nit = newInfo.begin(); nit != newInfo.end(); ++nit) {
		const file_info &i = *nit;
		bool found = false;
		for(DirInfo::ConstIterator it = d->info.begin(); it != d->info.end(); ++it) {
			const file_info &tmp = *it;
			if(i.name == tmp.name) {
				found = true;
				break;
			}
		}
		if(!found) {
			info_changed = true;
			break;
		}
	}

	d->info = newInfo;

	if(info_changed)
		changed();
}

void DirWatch::pf_dirChanged(int id)
{
	if(d->id != id)
		return;

	changed();
}


class FileWatch::Private
{
public:
	Private() {}

	QString fname;
	DirWatch *dw;

	uint lastSize;
	QDateTime lastModified;
};

FileWatch::FileWatch(const QString &fname, QObject *parent)
:QObject(parent)
{
	d = new Private;
	d->dw = 0;
	if(!fname.isEmpty())
		setFileName(fname);
}

FileWatch::~FileWatch()
{
	delete d;
}

QString FileWatch::fileName() const
{
	return d->fname;
}

void FileWatch::setFileName(const QString &s)
{
	if(d->fname == s)
		return;

	// remove old watch if necessary
	if(d->dw) {
		delete d->dw;
		d->dw = 0;
	}
	d->fname = s;

	if(d->fname.isEmpty())
		return;

	// setup the watch
	d->dw = new DirWatch("", this);
	connect(d->dw, SIGNAL(changed()), SLOT(dirChanged()));
	QFileInfo fi(d->fname);
	d->lastSize = fi.size();
	d->lastModified = fi.lastModified();
	d->dw->setDir(fi.dirPath());
}

void FileWatch::dirChanged()
{
	bool doChange = false;
	QFileInfo fi(d->fname);
	if(fi.size() != d->lastSize || fi.lastModified() != d->lastModified)
		doChange = true;
	d->lastSize = fi.size();
	d->lastModified = fi.lastModified();

	if(doChange)
		changed();
}
