/****************************************************************************
**
** Copyright (C) 2003-2006 Frank Hemer <frank@hemer.org>,
**                         Tilo Riemer <riemer@crossvc.com>
**
**
**----------------------------------------------------------------------------
**
**----------------------------------------------------------------------------
**
** CrossVC is available under two different licenses:
**
** If CrossVC is linked against the GPLed version of Qt 
** CrossVC is released under the terms of GPL also.
**
** If CrossVC is linked against a nonGPLed version of Qt 
** CrossVC is released under the terms of the 
** CrossVC License for non-Unix platforms (CLNU)
**
**
** CrossVC License for non-Unix platforms (CLNU):
**
** Redistribution and use in binary form, without modification, 
** are permitted provided that the following conditions are met:
**
** 1. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 2. It is not permitted to distribute the binary package under a name
**    different than CrossVC.
** 3. The name of the authors may not be used to endorse or promote
**    products derived from this software without specific prior written
**    permission.
** 4. The source code is the creative property of the authors.
**    Extensions and development under the terms of the Gnu Public License
**    are limited to the Unix platform. Any distribution or compilation of 
**    the source code against libraries licensed other than gpl requires 
**    the written permission of the authors.
**
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR 
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
** GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**
**
** CrossVC License for Unix platforms:
**
** 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, version 2 of the License.
** 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 version 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*****************************************************************************/

#include "config.h"
#include "globals.h"

#include "FamConnector.h"

#include <fcntl.h>
#include <qsocketnotifier.h>
#include <qapplication.h>
#include <qdir.h>

#define MAXDIRS 20000

FamConnector::FamConnector() : DirConnector(), famConnection(NULL), socketNotifier(NULL), initiated(false), m_dirCount(0) {

  //  Connect to fam (say FAMConnection *famConnection is a member of our class)
  famConnection = new FAMConnection;

  if (FAMOpen2(famConnection, "LinCVS") != 0) {
    delete famConnection;
    famConnection = NULL;
    return;
  }
  //  Set the connection to be non-blocking
  int famfd = FAMCONNECTION_GETFD(famConnection);
  int flags = fcntl(famfd, F_GETFL);
  fcntl(famfd, F_SETFL, flags | O_NONBLOCK);

  socketNotifier = new QSocketNotifier(famfd, QSocketNotifier::Read, 0);
  connect(socketNotifier, SIGNAL(activated(int)), SLOT(readFam()));
  initiated = true;
  setRunning(true);
  qDebug("fam initiated");
}

FamConnector::~FamConnector() {
  setRunning(false);
  disconnect();
  if (socketNotifier) {
    socketNotifier->setEnabled(false);
  }
  if (famConnection) {
    FAMClose(famConnection);
    delete famConnection;
    famConnection = NULL;
  }
}

void FamConnector::readFam() {

  //  We want to read as many events as are available.
  while (FAMPending(famConnection)) {
//     qDebug("fam pending");
    FAMEvent famEvent;
    if (FAMNextEvent(famConnection, &famEvent) != 1) {
      disconnect();
      if (socketNotifier) {
	socketNotifier->setEnabled(false);
	if (famConnection) {
	  FAMClose(famConnection);
	  delete famConnection;
	  famConnection = NULL;
	}
      }
      qDebug("fam error"+QString::number(FAMErrno));
      qDebug("In words: "+QString(FamErrlist[FAMErrno]));
      DirWatch::b_isActive = FALSE;
      return;
    }
      
//     qDebug("FamEvent: "+QString::number(famEvent.fr.reqnum));
    famData * data = (famData*)famEvent.userdata;
    if (!data) {
//       qDebug("continueing .... event: "+QString(famEvent.filename));
      continue;//allready released
    } else {
//       qDebug("event: "+QString(famEvent.filename)+", id: "+QString::number(famEvent.code));
    }

    QString fileName = QString::null;
    bool deleted = FALSE;
    if (famEvent.code == FAMChanged) {
//       qDebug("change-event: "+QString(famEvent.filename)+", userdata: "+data->dir->fullName()+", val: "+QString::number(famEvent.code));
      if ( bugfixList.findIndex(famEvent.fr.reqnum) == -1) {
	debug("trouble: old dir data should have been removed: "+QString(famEvent.filename)+", req: "+QString::number(famEvent.fr.reqnum));
	FAMRequest req;
	req.reqnum = famEvent.fr.reqnum;
	if (!FAMCancelMonitor(famConnection, &req)) {
	  qDebug("successfully remove from fam: "+QString::number(famEvent.fr.reqnum));
	  return;
	} else {
	  qDebug("remove from fam failed for req: "+QString::number(famEvent.fr.reqnum));
	}
      }
      else {
	addToDirQueue(data->dir, fileName, deleted);
      }
    } else if (famEvent.code == FAMAcknowledge) {
//       qDebug("deleting: "+QString(famEvent.filename)+", id: "+QString::number(famEvent.fr.reqnum));
      delete data;
      famEvent.userdata = 0;
    }
  }
}

bool FamConnector::addMonitoredDir(DirBase * dir) {

  if (!initiated) {
    qDebug("trying to add: "+dir->fullName()+" but fam not initiated");
    return FALSE;
  }
  if (m_dirCount >= MAXDIRS) {
    qDebug("max fd count reached, switching to LinCVS polling");

    QDictIterator<famData> it( famNames );
    for ( ; it.current(); ++it ) {
      int id;
      do {
       id = it.current()->id;
       releaseMonitoredDir(it.current()->dir);
      } while (it.current() && (id != it.current()->id) );
    }
    return FALSE;
  }

  FAMRequest req;
  famData * data = new famData();
  data->dir = dir;
  QString name = dir->fullName();

  if (!FAMMonitorFile( famConnection, name.latin1(), &req, data)) {
    data->id = req.reqnum;
    famNames.insert(name,data);
    bugfixList.append(req.reqnum);
    qApp->processEvents();//fam will crash if its queues get filled up
    ++m_dirCount;
    return TRUE;
  }
  data->dir = NULL;
  delete data;
  return FALSE;
}

void FamConnector::releaseMonitoredDir(DirBase * dir) {

  if (!initiated) {
    qDebug("trying to release: "+dir->fullName()+", but fam not initiated");
    return;
  }

  FAMRequest req;
  famData * data = NULL;
  QString name = dir->fullName();
  data = famNames.find(name);
  if (data) {
    req.reqnum = data->id;
    if (!FAMCancelMonitor(famConnection, &req)) {
      famNames.remove(name);
      const int idx = data->id;
      bugfixList.remove(idx);
      qApp->processEvents();//fam will crash if its queues get filled up
      --m_dirCount;
    } else {
      qDebug("release failed on: "+name);
    }
  }
  return;
}

