/*
 * videodir.c: Functions to maintain the video directory
 *
 * See the main source file 'vdr.c' for copyright information and
 * how to reach the author.
 *
 * $Id: videodir.c 5.2 2021/12/24 10:56:47 kls Exp $
 */

#include "videodir.h"
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "recording.h"
#include "tools.h"

cMutex cVideoDirectory::mutex;
cString cVideoDirectory::name;
cVideoDirectory *cVideoDirectory::current = NULL;

cVideoDirectory::cVideoDirectory(void)
{
  mutex.Lock();
  delete current;
  current = this;
  mutex.Unlock();
}

cVideoDirectory::~cVideoDirectory()
{
  mutex.Lock();
  current = NULL;
  mutex.Unlock();
}

cVideoDirectory *cVideoDirectory::Current(void)
{
  mutex.Lock();
  if (!current)
     new cVideoDirectory;
  mutex.Unlock();
  return current;
}

void cVideoDirectory::Destroy(void)
{
  delete current;
}

int cVideoDirectory::FreeMB(int *UsedMB)
{
  return FreeDiskSpaceMB(Name(), UsedMB);
}

const char *cVideoDirectory::Name(void)
{
  return name;
}

void cVideoDirectory::SetName(const char *Name)
{
  name = Name;
}

bool cVideoDirectory::Register(const char *FileName)
{
  // Incoming name must be in base video directory:
  if (strstr(FileName, Name()) != FileName) {
     esyslog("ERROR: %s not in %s", FileName, Name());
     errno = ENOENT; // must set 'errno' - any ideas for a better value?
     return false;
     }
  return true;
}

bool cVideoDirectory::Rename(const char *OldName, const char *NewName)
{
  dsyslog("renaming '%s' to '%s'", OldName, NewName);
  if (rename(OldName, NewName) == -1) {
     LOG_ERROR_STR(NewName);
     return false;
     }
  return true;
}

bool cVideoDirectory::Move(const char *FromName, const char *ToName)
{
  dsyslog("moving '%s' to '%s'", FromName, ToName);
  if (EntriesOnSameFileSystem(FromName, ToName)) {
     if (rename(FromName, ToName) == -1) {
        LOG_ERROR_STR(ToName);
        return false;
        }
     // detect whether it's a real recording move inside same file system or a recording rename
     if (strcmp(strgetbefore(FromName, '/', 2), strgetbefore(ToName, '/', 2)))
        cRecordingUserCommand::InvokeCommand(RUC_MOVEDRECORDING, ToName, FromName);
     else
        cRecordingUserCommand::InvokeCommand(RUC_RENAMEDRECORDING, ToName, FromName);
     }
  else
     return RecordingsHandler.Add(ruMove, FromName, ToName);
  return true;
}

bool cVideoDirectory::Remove(const char *Name)
{
  return RemoveFileOrDir(Name);
}

void cVideoDirectory::Cleanup(const char *IgnoreFiles[])
{
  RemoveEmptyDirectories(Name(), false, IgnoreFiles);
}

bool cVideoDirectory::Contains(const char *Name)
{
  return EntriesOnSameFileSystem(this->Name(), Name);
}

cUnbufferedFile *cVideoDirectory::OpenVideoFile(const char *FileName, int Flags)
{
  if (Current()->Register(FileName))
     return cUnbufferedFile::Create(FileName, Flags, DEFFILEMODE);
  return NULL;
}

bool cVideoDirectory::RenameVideoFile(const char *OldName, const char *NewName)
{
  return Current()->Rename(OldName, NewName);
}

bool cVideoDirectory::MoveVideoFile(const char *FromName, const char *ToName)
{
  return Current()->Move(FromName, ToName);
}

bool cVideoDirectory::RemoveVideoFile(const char *FileName)
{
  return Current()->Remove(FileName);
}

bool cVideoDirectory::VideoFileSpaceAvailable(int SizeMB)
{
  return Current()->FreeMB() >= SizeMB;
}

int cVideoDirectory::VideoDiskSpace(int *FreeMB, int *UsedMB)
{
  int used = 0;
  int free = Current()->FreeMB(&used);
  LOCK_DELETEDRECORDINGS_READ;
  int deleted = DeletedRecordings->TotalFileSizeMB();
  if (deleted > used)
     deleted = used; // let's not get beyond 100%
  free += deleted;
  used -= deleted;
  if (FreeMB)
     *FreeMB = free;
  if (UsedMB)
     *UsedMB = used;
  return (free + used) ? round(double(used) * 100 / (free + used)) : 0;
}

cString cVideoDirectory::PrefixVideoFileName(const char *FileName, char Prefix)
{
  char PrefixedName[strlen(FileName) + 2];

  const char *p = FileName + strlen(FileName); // p points at the terminating 0
  int n = 2;
  while (p-- > FileName && n > 0) {
        if (*p == '/') {
           if (--n == 0) {
              int l = p - FileName + 1;
              strncpy(PrefixedName, FileName, l);
              PrefixedName[l] = Prefix;
              strcpy(PrefixedName + l + 1, p + 1);
              return PrefixedName;
              }
           }
        }
  return NULL;
}

void cVideoDirectory::RemoveEmptyVideoDirectories(const char *IgnoreFiles[])
{
  Current()->Cleanup(IgnoreFiles);
}

bool cVideoDirectory::IsOnVideoDirectoryFileSystem(const char *FileName)
{
  return Current()->Contains(FileName);
}

// --- cVideoDiskUsage -------------------------------------------------------

#define DISKSPACECHEK     5 // seconds between disk space checks
#define MB_PER_MINUTE 25.75 // this is just an estimate!

int cVideoDiskUsage::state = 0;
time_t cVideoDiskUsage::lastChecked = 0;
int cVideoDiskUsage::usedPercent = 0;
int cVideoDiskUsage::freeMB = 0;
int cVideoDiskUsage::freeMinutes = 0;

bool cVideoDiskUsage::HasChanged(int &State)
{
  if (time(NULL) - lastChecked > DISKSPACECHEK) {
     int FreeMB;
     int UsedPercent = cVideoDirectory::VideoDiskSpace(&FreeMB);
     if (FreeMB != freeMB) {
        usedPercent = UsedPercent;
        freeMB = FreeMB;
        LOCK_RECORDINGS_READ;
        double MBperMinute = Recordings->MBperMinute();
        if (MBperMinute <= 0)
           MBperMinute = MB_PER_MINUTE;
        freeMinutes = int(double(FreeMB) / MBperMinute);
        state++;
        }
     lastChecked = time(NULL);
     }
  if (State != state) {
     State = state;
     return true;
     }
  return false;
}

cString cVideoDiskUsage::String(void)
{
  HasChanged(state);
  return cString::sprintf("%s %d%%  -  %2d:%02d %s", tr("Disk"), usedPercent, freeMinutes / 60, freeMinutes % 60, tr("free"));
}
