// rdloglock.cpp
//
// Log locking routines for Rivendell
//
//   (C) Copyright 2017-2019 Fred Gleason <fredg@paravelsystems.com>
//
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License version 2 as
//   published by the Free Software Foundation.
//
//   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.
//

#include <syslog.h>

#include <qdatetime.h>

#include "rdapplication.h"
#include "rddb.h"
#include "rdescape_string.h"
#include "rdloglock.h"

RDLogLock::RDLogLock(const QString &log_name,RDUser *user,RDStation *station,
		     QObject *parent)
  : QObject(parent)
{
  lock_log_name=log_name;
  lock_user=user;
  lock_station=station;
  lock_locked=false;

  lock_timer=new QTimer(this);
  connect(lock_timer,SIGNAL(timeout()),this,SLOT(updateLock()));
}


RDLogLock::~RDLogLock()
{
  if(lock_locked) {
    clearLock();
  }
  delete lock_timer;
}


bool RDLogLock::isLocked() const
{
  return lock_locked;
}


bool RDLogLock::tryLock(QString *username,QString *stationname,
			QHostAddress *addr)
{
  bool ret=false;
  QString guid=RDLogLock::makeGuid(lock_station->name());

  *username=lock_user->name();
  *stationname=lock_station->name();
  addr->setAddress(lock_station->address().toString());
  if(RDLogLock::tryLock(username,stationname,addr,lock_log_name,guid)) {
    lock_timer->start(RD_LOG_LOCK_TIMEOUT/2);
    lock_guid=guid;
    lock_locked=true;
    ret=true;
  }

  return ret;
}


void RDLogLock::clearLock()
{
  RDLogLock::clearLock(lock_guid);
  lock_guid=QString();
  lock_timer->stop();
  lock_locked=false;
}


void RDLogLock::updateLock()
{
  RDLogLock::updateLock(lock_log_name,lock_guid);
}


bool RDLogLock::tryLock(QString *username,QString *stationname,
			QHostAddress *addr,const QString &log_name,
			const QString &guid)
{
  QString sql;
  RDSqlQuery *q;
  RDSqlQuery *q1;
  bool ret=false;
  QDateTime now=QDateTime::currentDateTime();

  sql=QString("update LOGS set ")+
    "LOCK_USER_NAME=\""+RDEscapeString(*username)+"\","+
    "LOCK_STATION_NAME=\""+RDEscapeString(*stationname)+"\","+
    "LOCK_IPV4_ADDRESS=\""+RDEscapeString(addr->toString())+
    "\","+
    "LOCK_GUID=\""+RDEscapeString(guid)+"\","+
    "LOCK_DATETIME=now() where "+
    "(NAME=\""+RDEscapeString(log_name)+"\")&&"+
    "((LOCK_DATETIME is null)||"+
    "(LOCK_DATETIME<\""+RDEscapeString(now.addSecs(-RD_LOG_LOCK_TIMEOUT/1000).toString("yyyy-MM-dd hh:mm:ss"))+"\"))";
  q=new RDSqlQuery(sql);
  if(q->numRowsAffected()>0) {
    ret=true;
  }
  else {
    sql=QString("select ")+
      "LOCK_USER_NAME,"+
      "LOCK_STATION_NAME,"+
      "LOCK_IPV4_ADDRESS "+
      "from LOGS where "+
      "NAME=\""+RDEscapeString(log_name)+"\"";
    q1=new RDSqlQuery(sql);
    if(q1->first()) {
      *username=q1->value(0).toString();
      *stationname=q1->value(1).toString();
      addr->setAddress(q1->value(2).toString());
    }
    delete q1;
  }
  delete q;

  return ret;
}


void RDLogLock::updateLock(const QString &log_name,const QString &guid)
{
  QString sql;
  RDSqlQuery *q;

  sql=QString("update LOGS set ")+
    "LOCK_DATETIME=now() where "+
    "LOCK_GUID=\""+RDEscapeString(guid)+"\"";
  q=new RDSqlQuery(sql);
  if(q->numRowsAffected()==0) {
    rda->syslog(LOG_WARNING,"lock on log \"%s\" has evaporated!",
		(const char *)log_name);
  }
  delete q;
}


void RDLogLock::clearLock(const QString &guid)
{
  QString sql;
  RDSqlQuery *q;

  sql=QString("update LOGS set ")+
    "LOCK_USER_NAME=null,"+
    "LOCK_STATION_NAME=null,"+
    "LOCK_IPV4_ADDRESS=null,"+
    "LOCK_GUID=null,"+
    "LOCK_DATETIME=null where "+
    "LOCK_GUID=\""+RDEscapeString(guid)+"\"";
  q=new RDSqlQuery(sql);
  delete q;
}


bool RDLogLock::validateLock(const QString &log_name,const QString &guid)
{
  QString sql;
  RDSqlQuery *q;
  bool ret=false;
  QDateTime now=QDateTime::currentDateTime();

  sql=QString("select NAME from LOGS where ")+
    "(NAME=\""+RDEscapeString(log_name)+"\")&&"+
    "(LOCK_GUID=\""+RDEscapeString(guid)+"\")&&"+
    "(LOCK_DATETIME>\""+RDEscapeString(now.addSecs(-RD_LOG_LOCK_TIMEOUT/1000).
				       toString("yyyy-MM-dd hh:mm:ss"))+"\")";
  q=new RDSqlQuery(sql);
  ret=q->first();
  delete q;

  return ret;
}


QString RDLogLock::makeGuid(const QString &stationname)
{
  return stationname+QDateTime::currentDateTime().
    toString("yyyyMMddhhmmsszzz");
}