// fgfshelp.cc

#include "fgfshelp.h"

#ifndef _FGDLIST_H
#include "fgdlist.h"
#endif

#ifndef _FGSTRING_H
#include "fgstring.h"
#endif

#ifndef _FGEXC_H
#include "fgexc.h"
#endif

#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/file.h>

#include <assert.h>
#include <string.h>

FGDirListing
FGFileSystemHelper::GetDirListing(const FGString& dir)
{
  FGDirListing ret(dir);

  DIR* pDir = opendir(dir);
  if (pDir == NULL) {
    // Oh dear
    throw FGException(FGException::kLocalNoSuchDir, dir);
  }

  struct dirent* pDirEnt;
  while ((pDirEnt = readdir(pDir)) != NULL) {
    // Only report the file if it's not partially downloaded
    // (this is indicated by permissions ---------T)
    struct stat statBuf;
    FGString fname(dir);
    fname += '/';
    fname += pDirEnt->d_name;
    if (stat(fname, &statBuf) != -1) {
      if (statBuf.st_mode != (S_ISVTX | S_IWUSR)) {
        FGFileInfo fileInfo(pDirEnt->d_name, statBuf.st_size,
                            S_ISDIR(statBuf.st_mode),
                            S_ISREG(statBuf.st_mode));
        ret.push_back(fileInfo);
      }
    }
  }

  closedir(pDir);

  return ret;
}

void
FGFileSystemHelper::TransferRemoteToLocal(int fromFD, int toFD,
                                          int bytes)
{
  // Preconditions
  assert(fromFD >= 0);
  assert(toFD >= 0);

  // Transfer buffer
  char buf[msTransferBufSize];

  // Accounting
  int bytesWritten = 0;
  bool hitEOF = false;

  // Monitoring
  fd_set remoteFDset;
  FD_ZERO(&remoteFDset);
  FD_SET(fromFD, &remoteFDset);

  try {
    while ((bytes == -1 || bytesWritten < bytes) && !hitEOF) {
      // Timeout on remote read
      // For now we will use 5 minutes
      struct timeval tv;
      tv.tv_usec = 0;
      tv.tv_sec = 60 * 5;

      int selectRet = select(fromFD+1, &remoteFDset, NULL, NULL, &tv);

      // Did we timeout
      if (selectRet == 0) {
        // How irritating
        throw FGException(FGException::kDownloadTimeout);
      }

      int numRead = read(fromFD, buf, sizeof(buf));
      if (!numRead) {
        hitEOF = true;
        // Throw exception if expecting specific number of bytes
        if (bytes != -1 && bytesWritten < bytes) {
          throw FGException(FGException::kFileTooShort);
        }
      } else if (numRead == -1) {
        throw FGException(FGException::kNetworkReadError, strerror(errno));
      } else {
        int writeRet = write(toFD, buf, numRead);
        if (writeRet == -1) {
          throw FGException(FGException::kWriteFailed, strerror(errno));
        }
        bytesWritten += numRead;
      }
    } // end: while (want more bytes)
  }
  catch (FGException& e) {
    close(toFD);
    throw e;
  }

  // If we completed the transfer fully, update file permissions to
  // reflect this
  if (bytes == -1 || bytes == bytesWritten) {
    fchmod(toFD, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  }

  // We are no longer writing to the file - so lose the lock
  flock(toFD, LOCK_UN);

  // And close it
  close(toFD);
}

int
FGFileSystemHelper::CreateLocalFile(const FGString& file)
{
  // Create with permissions "1400" == "---w-----T"
  // This flags file as "in transit"
  int theFD = open(file, O_CREAT | O_WRONLY,
                   S_ISVTX | S_IWUSR);
  if (theFD == -1) {
    FGString details(file);
    details += ": ";
    details += strerror(errno);
    throw FGException(FGException::kFileCreateFailed, details);
  }

  // Before truncating, lock the file - in case another thread / process
  // is currently downloading the file
  int lockRet = flock(theFD, LOCK_EX | LOCK_NB);
  if (lockRet == -1) {
    close(theFD);
    throw FGException(FGException::kConcurrentGrabs);
  }

  int ret = ftruncate(theFD, 0);
  (void) ret;

  return theFD;
}

void
FGFileSystemHelper::DeleteFile(const FGString& file)
{
  int delRet = unlink(file);
  if (delRet == -1) {
    switch (errno) {
    case EACCES:
    case EPERM:
    case EROFS:
      throw FGException(FGException::kLocalPermissionDenied);
    case ENOENT:
      throw FGException(FGException::kLocalNoSuchFile);
    default:
      throw FGException(FGException::kUnlinkFailed);
    }
  }
}
