/*
    mylockfile.* - c++ wrapper for file locks (for easy unlocking due to destructors, and abstraction from lowlevel)
    Copyright (C) 1999-2003  Matthew Mueller <donut AT dakotacom.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 option) 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef _MYLOCKFILE_H_
#define _MYLOCKFILE_H_

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string>

#include <errno.h>
#include "log.h"

//liblockfile doesn't support anything but exclusive locks, so these are only "wants"
#define WANT_SH_LOCK 1
#define WANT_EX_LOCK 0
#define FLOCK_DEBUG_LEV DEBUG_MED

#ifdef HAVE_LIBLOCKFILE
#include <lockfile.h>
class c_lockfile{
	public:
		string file;

		c_lockfile(string filename,int flag){
			file=filename;
			file.append(".lock");
			PDEBUG(FLOCK_DEBUG_LEV,"attempting to lock %s",file.c_str());
			int ret=lockfile_create(file.c_str(),10,0);
			if (ret){
				file = "";
				throw ApplicationExFatal(Ex_INIT,"lockfile_create %s: %i (%s)",filename.c_str(),ret,strerror(errno));
			}
			PDEBUG(FLOCK_DEBUG_LEV,"locked %s",file.c_str());
		}
		~c_lockfile(){
			if (!file.empty()) {
				lockfile_remove(file.c_str());
				PDEBUG(FLOCK_DEBUG_LEV,"unlocked %s",file.c_str());
			}
		}
};
#elif HAVE_FLOCK
#include <unistd.h>
#include <sys/file.h>
class c_lockfile{
	public:
		int fd;

		c_lockfile(string filename,int flag){
			PDEBUG(FLOCK_DEBUG_LEV,"attempting to flock %s",filename.c_str());
			fd=open(filename.c_str(),O_RDONLY);
			if (fd<0){
				if (errno==ENOENT){
//					if (flag&WANT_SH_LOCK)
					return;
				} else
					throw ApplicationExFatal(Ex_INIT,"c_lockfile: open %s (%s)",filename.c_str(),strerror(errno));
			}
			int ret=flock(fd,(flag&WANT_SH_LOCK)?LOCK_SH:LOCK_EX);
			if (ret) {
				int savederrno = errno;
				close(fd); fd = -1;
				throw ApplicationExFatal(Ex_INIT,"c_lockfile: flock %s: %i (%s)",filename.c_str(),ret,strerror(savederrno));
			}
			PDEBUG(FLOCK_DEBUG_LEV,"flocked %s (%i)",filename.c_str(),fd);
//			sleep(10);
		}
		~c_lockfile(){
			if (fd>=0) {
				flock(fd,LOCK_UN);
				close(fd);
				PDEBUG(FLOCK_DEBUG_LEV,"unflocked %i",fd);
			}
		}
};
#elif HAVE_LOCKFILE
#define WIN32_LEAN_AND_MEAN
#undef NOMINMAX
#define NOMINMAX 1
#include <windows.h>
class c_lockfile{
	public:
		HANDLE hFile;
		string filename;

		c_lockfile(string infilename,int flag): hFile(INVALID_HANDLE_VALUE){
			filename = infilename + ".lock";//Windows does not allow removing the locked file, (when we rename the .tmp to the real name), so create a seperate lock file instead.
			PDEBUG(FLOCK_DEBUG_LEV,"attempting to LockFile %s",filename.c_str());
			//if (!fexists(filename.c_str()))
			//	return;
			//hFile = CreateFile(filename.c_str(),GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
			hFile = CreateFile(filename.c_str(), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
			if (hFile==INVALID_HANDLE_VALUE){
				throw ApplicationExFatal(Ex_INIT,"c_lockfile: open %s (%i)",filename.c_str(),GetLastError());
			}
			int tries=1;
			while (!LockFile(hFile,0,0,0xFFFFFFFF,0)) {
				if (tries++ >= 10) {
					CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE;
					throw ApplicationExFatal(Ex_INIT,"c_lockfile: LockFile %s: giving up after %i tries",filename.c_str(), tries);
				}
				sleep(1);
				PDEBUG(FLOCK_DEBUG_LEV,"try %i LockFile %s",tries,filename.c_str());
			}
			PDEBUG(FLOCK_DEBUG_LEV,"locked %s (%p)",filename.c_str(),hFile);
		}
		~c_lockfile(){
			if (hFile!=INVALID_HANDLE_VALUE) {
				UnlockFile(hFile,0,0,0xFFFFFFFF,0);
				CloseHandle(hFile);
				DeleteFile(filename.c_str());
				PDEBUG(FLOCK_DEBUG_LEV,"unlocked %s(%p)",filename.c_str(),hFile);
			}
		}
};
#else
#warning building without any sort of locking at all
class c_lockfile{
	public:
		c_lockfile(string filename,int flag){}
		~c_lockfile(){}
};
#endif


#endif
