/* -*- Mode: C ; c-basic-offset: 2 -*- */
/*****************************************************************************
 *
 * DESCRIPTION:
 *  Portable frontend to MediaBase 1.2 and 1.3
 *
 * COMPILATION:
 *  gcc addcd.c -lmysqlclient -o addcd
 *
 * AUTHOR:
 *  Nedko Arnaudov <nedko@users.sourceforge.net>
 *
 * LICENSE:
 *  GNU GENERAL PUBLIC LICENSE verssion 2
 *  
 *****************************************************************************/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <mysql/mysql.h>
#include <errno.h>
#include <time.h>
#include <sys/wait.h>
#include <pwd.h>
#include <unistd.h>

#define MB_T_AUDIO      1
#define MB_T_DATA       2
#define MB_T_EMPTY      3

#define MAX_TITLE_SIZE 128      /* in db */
#define MAX_PASS_SIZE 1024
#define DEFAULT_MYSQL_USER      "mediabase"
#define DEFAULT_MYSQL_PASS      "mediabase"
#define DEFAULT_MYSQL_HOST      "localhost"
#define DEFAULT_MYSQL_DB        "mediabase"
#define DEFAULT_MOUNT_CMD       "sudo /sbin/mount /cdrom"
#define DEFAULT_UNMOUNT_CMD     "sudo /sbin/umount /cdrom"
#define DEFAULT_MOUNTDIR        "/cdrom"
#define MAX_PATH_DEPTH          65535
#define INITIAL_BUFFER_ADD  4096
#define BUFFER_ADD          4096

//#define DUMMY_SQL_QUERIES
//#define PRINT_FILENAMES

char strTitle[MAX_TITLE_SIZE+2]; /* + newline + null */
char strPass[MAX_PASS_SIZE];
char *pszPathBuffer = NULL;
size_t nPathBufferSize = 0;
size_t nMountPrefixLength;
unsigned long nTotalFiles;
unsigned long long nTotalSize;
my_ulonglong nMediaID = 0ULL;
char *pszSQLQuery = NULL;
size_t nSQLQueryBufferSize = 0;
MYSQL *pMYSQL = NULL;
int blnMediaMounted = 0;
int blnExiting = 0;
const char *pszHost = DEFAULT_MYSQL_HOST;
const char *pszUser = DEFAULT_MYSQL_USER;
const char *pszPass = DEFAULT_MYSQL_PASS;
const char *pszDB = DEFAULT_MYSQL_DB;
const char *pszMountMediaCommand = DEFAULT_MOUNT_CMD;
const char *pszUnmountMediaCommand = DEFAULT_UNMOUNT_CMD;
const char *pszMountDir = DEFAULT_MOUNTDIR;
char *pszEscapeBuffer1 = NULL;
size_t nEscapeBuffer1Size = 0;
char *pszEscapeBuffer2 = NULL;
size_t nEscapeBuffer2Size = 0;

void
ExecuteCommand(const char *pszCommand);

void
MountMedia()
{
  printf("--> Mounting media ...\n");
  ExecuteCommand(pszMountMediaCommand);
  printf("--> Media mounted.\n");
  blnMediaMounted = 1;
}

void
UnmountMedia()
{
  printf("--> Unmounting media ...\n");
  ExecuteCommand(pszUnmountMediaCommand);
  printf("--> Media unmounted.\n");
  blnMediaMounted = 0;
}
 
void
Exit(int nRet)
{
  if (blnExiting)
    return;

  blnExiting = 1;

  if (pMYSQL != NULL)
  {
    mysql_close(pMYSQL);
  }

  if (blnMediaMounted)
    UnmountMedia();

  exit(nRet);
}

void
ExecuteCommand(const char *pszCommand)
{
  int nRet;

  nRet = system(pszCommand);
  if (nRet == -1)
  {
    fprintf(stderr,
            "Cannot execute command \"%s\". "
            "system() returned -1, error is %d (%s)\n",
            pszCommand,
            errno,
            strerror(errno));
    Exit(1);
  }

  if (WIFEXITED(nRet))
  {
    if (WEXITSTATUS(nRet) != 0)
    {
      fprintf(stderr,
              "Cannot execute command \"%s\". "
              "Command returned %d\n",
              pszCommand,
              (int)WEXITSTATUS(nRet));
      Exit(1);
    }
  }
  else if (WIFSIGNALED(nRet))
  {
    fprintf(stderr,
            "Cannot execute command \"%s\". "
            "Cammand was terminated because of signal %d\n",
            pszCommand,
            (int)WTERMSIG(nRet));
    Exit(1);
  }
  else
  {
    fprintf(stderr,
            "Cannot execute command \"%s\". "
            "system() returned %d\n",
            pszCommand,
            nRet);
    Exit(1);
  }
}

void
ReadTitle()
{
  char *pch;

  printf("Title: ");
  if (fgets(strTitle, sizeof(strTitle), stdin) == NULL)
  {
    printf("\n");

    if (feof(stdin))
    {
      Exit(0);
    }

    fprintf(stderr, "Error occured while reading standard input.\n");
    Exit(1);
  }
  else if (strTitle[0] == '\n')
  {
    Exit(0);
  }

  if ((pch = strchr(strTitle, '\n')) == NULL)
  {
    fprintf(stderr, "input line too long.\n");
    Exit(1);
  }

  *pch = '\0';
}

void
AskForPassword()
{
  char *pszPass;

  pszPass = getpass("MySQL password: ");

  strcpy(strPass, pszPass);
}

void
MaybeEnlargeBuffer(char ** ppBuffer,
                   size_t *pnBufferSize,
                   size_t nSizeRequired)
{
  char *pNewBuffer;
  size_t nNewBufferSize;

  if (nSizeRequired <= *pnBufferSize)
    return;

  if (*pnBufferSize == 0 || *ppBuffer == NULL)
  {
    /* Allocate */

    nNewBufferSize = nSizeRequired + INITIAL_BUFFER_ADD;

    pNewBuffer = (char *)malloc(nNewBufferSize);
    if (pNewBuffer == NULL)
    {
      fprintf(stderr,
              "Cannot allocate %u bytes of memory.\n",
              (unsigned int)nNewBufferSize);
      Exit(0);
    }
  }
  else
  {
    /* Reallocate */

    nNewBufferSize = nSizeRequired + BUFFER_ADD;

    pNewBuffer = (char *)malloc(nNewBufferSize);
    if (pNewBuffer == NULL)
    {
      fprintf(stderr,
              "Cannot allocate %u bytes of memory.\n",
              (unsigned int)nNewBufferSize);
      Exit(0);
    }

    memcpy(pNewBuffer, *ppBuffer, *pnBufferSize);

    free(*ppBuffer);
  }


  *pnBufferSize = nNewBufferSize;
  *ppBuffer = pNewBuffer;
}

void
ScanDir(unsigned long nDepth)
{
  DIR *pDir;
  struct dirent *pDE;
  size_t nCurrentPathLength;
  struct stat st;
  size_t nEscapedSize;

  if (nDepth >= MAX_PATH_DEPTH)
  {
    fprintf(stderr,
            "Max path depth of reached \"%u\"\n",
            (unsigned int)MAX_PATH_DEPTH);
    Exit(1);
  }

  nCurrentPathLength = strlen(pszPathBuffer);

  pDir = opendir(pszPathBuffer);
  if (pDir == NULL)
  {
    fprintf(stderr, "Cannot open \"%s\"\n", pszPathBuffer);
    Exit(1);
  }

  if (pDir)
  {
    while ((pDE = readdir(pDir)) != NULL)
    {
      if (strcmp(pDE->d_name, ".") == 0)
        continue;

      if (strcmp(pDE->d_name, "..") == 0)
        continue;

      MaybeEnlargeBuffer(&pszPathBuffer,
                         &nPathBufferSize,
                         nCurrentPathLength + pDE->d_namlen + 1);

      memcpy(pszPathBuffer + nCurrentPathLength, pDE->d_name, pDE->d_namlen);
      pszPathBuffer[nCurrentPathLength + pDE->d_namlen] = 0;

      if (lstat(pszPathBuffer, &st) != 0)
      {
        fprintf(stderr,
                "Cannot stat() %s. Error is %d (%s)\n",
                pszPathBuffer,
                errno,
                strerror(errno));
      }

      pszPathBuffer[nCurrentPathLength] = 0;

      MaybeEnlargeBuffer(&pszEscapeBuffer1,
                         &nEscapeBuffer1Size,
                         (nCurrentPathLength - nMountPrefixLength)*2+1);

      nEscapedSize = mysql_real_escape_string(
        pMYSQL,
        pszEscapeBuffer1,
        pszPathBuffer + nMountPrefixLength,
        nCurrentPathLength - nMountPrefixLength);

      MaybeEnlargeBuffer(&pszEscapeBuffer2,
                         &nEscapeBuffer2Size,
                         pDE->d_namlen*2+1);

      nEscapedSize += mysql_real_escape_string(
        pMYSQL,
        pszEscapeBuffer2,
        pDE->d_name,
        pDE->d_namlen);

      MaybeEnlargeBuffer(&pszSQLQuery,
                         &nSQLQueryBufferSize,
                         nEscapedSize + 1024);

      sprintf(pszSQLQuery,
              "INSERT INTO mb_files "
              "(mediaid,size,time,path,name) "
              "VALUES (%u,%u,%u,'%s','%s%s')",
              (unsigned int)nMediaID,
              (unsigned int)((S_ISDIR(st.st_mode))?0:st.st_size),
              (unsigned int)st.st_ctimespec.tv_sec,
              pszEscapeBuffer1,
              pszEscapeBuffer2,
              (S_ISDIR(st.st_mode))?"/":"");
#ifdef DUMMY_SQL_QUERIES
      printf("MySQL query \"%s\"\n", pszSQLQuery);
#else
      if (mysql_query(pMYSQL, pszSQLQuery) != 0)
      {
        fprintf(stderr, "Failed to execute query \"%s\" Error: %s\n",
                pszSQLQuery,
                mysql_error(pMYSQL));
        Exit(1);
      }
#endif

      memcpy(pszPathBuffer + nCurrentPathLength, pDE->d_name, pDE->d_namlen);
      pszPathBuffer[nCurrentPathLength + pDE->d_namlen] = 0;

#ifdef PRINT_FILENAMES
      printf("%s", pszPathBuffer + nMountPrefixLength);
#endif

      nTotalFiles++;

      if (S_ISDIR(st.st_mode))
      {
#ifdef PRINT_FILENAMES
        printf("/\n");
#else
        printf("/");
#endif
        fflush(stdout);
        memcpy(pszPathBuffer + nCurrentPathLength + pDE->d_namlen, "/", 2);
        ScanDir(nDepth + 1);
      }
      else
      {
        nTotalSize += st.st_size;
#ifdef PRINT_FILENAMES
        printf("\n");
#else
        printf(".");
#endif
        fflush(stdout);
      }
    }

    closedir(pDir);
  }
}

void
Help(char *arg0)
{
  char *pszExecutable;

  pszExecutable = strrchr(arg0, '/');

  if (pszExecutable == NULL)
  {
    pszExecutable = arg0;
  }
  else
  {
    pszExecutable++;
  }

  printf("Usage:\n");
  printf("[<path_to>]%s [options]\n", pszExecutable);
  printf("  Options can be:\n");
  printf("  -h <mysql_host> - MySQL server host, default is \"%s\"\n", pszHost);
  printf("  -u <mysql_user> - MySQL user, default is \"%s\"\n", pszUser);
  printf("  -p [mysql_pass] - MySQL password, default is \"%s\"\n", pszPass);
  printf("  -b <mysql_database> - MySQL database, default is \"%s\"\n", pszDB);
  printf("  -m <mount_cmd> - command to mount media, default is \"%s\"\n", pszMountMediaCommand);
  printf("  -n <unmount_cmd> - command to unmount, default is \"%s\"\n", pszUnmountMediaCommand);
  printf("  -d <mount_dir> - where media is mounted, default is \"%s\"\n", pszMountDir);
  Exit(0);
}

int
main(
  int argc,
  char ** argv
  )
{
  time_t timeAdded;
  int i;
  size_t nTitleSize;

  printf("Adding media to MediaBase\n");
  printf("=========================\n");

  /* Process options from command line */

  for (i = 1; i < argc ; i++)
  {
    if (strcmp(argv[i],"-h") == 0)
    {
      i++;
      if (i < argc)
      {
        pszHost = argv[i];
      }
      else
      {
        fprintf(stderr,
                "Missing parameter of -h option\n");
        Help(argv[0]);
      }
    }
    else if (strcmp(argv[i],"-u") == 0)
    {
      i++;
      if (i < argc)
      {
        pszUnmountMediaCommand = argv[i];
      }
      else
      {
        fprintf(stderr,
                "Missing parameter of -u option\n");
        Help(argv[0]);
      }
    }
    else if (strcmp(argv[i],"-p") == 0)
    {
      if (i+1 < argc && argv[i+1][0] != '-')
      {
        i++;
        pszPass = argv[i];
      }
      else
      {
        AskForPassword();
        pszPass = strPass;
      }
    }
    else if (strcmp(argv[i],"-b") == 0)
    {
      i++;
      if (i < argc)
      {
        pszDB = argv[i];
      }
      else
      {
        fprintf(stderr,
                "Missing parameter of -b option\n");
        Help(argv[0]);
      }
    }
    else if (strcmp(argv[i],"-m") == 0)
    {
      i++;
      if (i < argc)
      {
        pszMountMediaCommand = argv[i];
      }
      else
      {
        fprintf(stderr,
                "Missing parameter of -m option\n");
        Help(argv[0]);
      }
    }
    else if (strcmp(argv[i],"-n") == 0)
    {
      i++;
      if (i < argc)
      {
        pszUnmountMediaCommand = argv[i];
      }
      else
      {
        fprintf(stderr,
                "Missing parameter of -n option\n");
        Help(argv[0]);
      }
    }
    else if (strcmp(argv[i],"-d") == 0)
    {
      i++;
      if (i < argc)
      {
        pszMountDir = argv[i];
      }
      else
      {
        fprintf(stderr,
                "Missing parameter of -d option\n");
        Help(argv[0]);
      }
    }
    else if (strcmp(argv[i],"-h") == 0)
    {
      Help(argv[0]);
    }
    else
    {
      fprintf(stderr,
              "Unknown option \"%s\"\n",
              argv[i]);
      Help(argv[0]);
    }
  }

  printf("NOTE: Currently audio media is not supported.\n");

  MaybeEnlargeBuffer(&pszSQLQuery,
                     &nSQLQueryBufferSize,
                     MAX_TITLE_SIZE + 1024);

  pMYSQL = mysql_init(NULL);
  if (pMYSQL == NULL)
  {
    fprintf(stderr,
            "Cannot create MYSQL object. Error is \"%s\"\n",
            mysql_error(pMYSQL));
    Exit(1);
  }

  if (mysql_real_connect(pMYSQL,
                         pszHost,
                         pszUser,
                         pszPass,
                         pszDB,
                         0,
                         NULL,
                         0) == NULL)
  {
    fprintf(stderr, "Failed to connect to database. Error: %s\n",
            mysql_error(pMYSQL));
    Exit(1);
  }

  nMountPrefixLength = strlen(pszMountDir);

  MaybeEnlargeBuffer(&pszPathBuffer,
                     &nPathBufferSize,
                     nMountPrefixLength + 2);

  memcpy(pszPathBuffer, pszMountDir, nMountPrefixLength);

Loop:
  memcpy(pszPathBuffer + nMountPrefixLength, "/", 2);

  printf("--> Please insert media and enter title.\n");

  ReadTitle();

  nTitleSize = strlen(strTitle);

  MaybeEnlargeBuffer(&pszEscapeBuffer1,
                     &nEscapeBuffer1Size,
                     nTitleSize*2+1);

  mysql_real_escape_string(pMYSQL,
                           pszEscapeBuffer1,
                           strTitle,
                           nTitleSize);

  MountMedia();

  nTotalFiles = 0UL;
  nTotalSize = 0ULL;

  printf("--> Processing media ...\n");

  sprintf(pszSQLQuery,
          "INSERT INTO mb_media (name,type,comment) VALUES ('%s',%u,'')",
          pszEscapeBuffer1,
          (unsigned int)MB_T_DATA);
#ifdef DUMMY_SQL_QUERIES
  printf("MySQL query \"%s\"\n", pszSQLQuery);
#else
  if (mysql_query(pMYSQL, pszSQLQuery) != 0)
  {
    fprintf(stderr, "Failed to execute query \"%s\" Error: %s\n",
            pszSQLQuery,
            mysql_error(pMYSQL));
    Exit(1);
  }

  nMediaID = mysql_insert_id(pMYSQL);
  if (nMediaID == 0ULL)
  {
    fprintf(stderr, "Invalid media ID 0 after media INSERT\n");
    Exit(1);
  }
#endif

  ScanDir(0);

  printf("\n");

  timeAdded = time(NULL);

  sprintf(pszSQLQuery,
          "UPDATE mb_media SET added=%u, info1=%u, info2=%u WHERE mediaid = %u",
          (unsigned int)timeAdded,
          (unsigned int)nTotalFiles,
          (unsigned int)nTotalSize,
          (unsigned int)nMediaID);
#ifdef DUMMY_SQL_QUERIES
  printf("MySQL query \"%s\"\n", pszSQLQuery);
#else
  if (mysql_query(pMYSQL, pszSQLQuery) != 0)
  {
    fprintf(stderr, "Failed to execute query \"%s\" Error: %s\n",
            pszSQLQuery,
            mysql_error(pMYSQL));
    Exit(1);
  }
#endif

  UnmountMedia();

  printf("--> SUCCESS - Media added.\n");
  printf("Media title: %s\n", strTitle);
  printf("Media ID: %u\n", (unsigned int)nMediaID);
  printf("Added: %s", ctime(&timeAdded));
  printf("Total files: %u\n", (unsigned int)nTotalFiles);
  printf("Total size: %u\n", (unsigned int)nTotalSize);

  goto Loop;
}