// StartUp by Sebastian Roll
// 2010-09-15
// This program reads a config file line by line (filename = "StartUpConfig.txt").
// If a line begins with "wait", StartUp delays specified seconds (example: "wait 5").
// All other lines are interpreted as fully qualified paths to a program to start (example: "C:\WINDOWS\notepad.exe").
// Empty lines are ignored.


#define WIN32_LEAN_AND_MEAN   // exclude rarely-used services from Windows headers
#define NOGDI                 // exclude GDI services from Windows headers
#include <windows.h>          // Sleep()
#include <shellapi.h>         // ShellExecute()
#include <stdio.h>            // FILE, fopen(), fgets(), fclose(), frpintf(), sprintf()
#include <stdlib.h>           // EXIT_SUCCESS, atoi(), malloc(), free()
#include <string.h>           // strlen(), strncmp()
#include <limits.h>           // INT_MAX



// the file to read the config from
#define FILE_NAME                   "StartUpConfig.txt"
// config file: the max size of one line to read AND the max amount of all lines
#define LINES_SIZE                  (1024)
// the text to interpret as "delay command" within the config file
#define WAIT_STRING                 "wait"
// the max "delay" we allow (4294967 seconds = 1193 hours)
#define WAIT_SECONDS_MAX            (INT_MAX/1000)
// CarrierReturn ASCII code
#define CHAR_CODE_CARRIER_RETURN    (0x0D)
// LineFeed ASCII code
#define CHAR_CODE_LINE_FEED         (0x0A)


// the config file is stored to this 2D array
// each line (first[]) holds a command to execute (second[])
// e.g. config[2][0] is the first char of the 3rd command line
// e.g. &(config[2][0]) is the C-string of the 3rd command line

static char config[LINES_SIZE][LINES_SIZE];


// funtion prototypes
int getConfig();

int formatLine(int line, int lineLength);
char* getLine(int line);

void startProcess(int line);
void wait(int seconds);

void printShellExecuteError(int error);


// application entry point
int main(void)
{

   int i = 0;
   int numOfLines = getConfig();

   int waitStringSize = strlen(WAIT_STRING);

   for (i = 0; i < numOfLines; i++)
   {

      // check if we have a "delay command"
      if (!strncmp(WAIT_STRING, getLine(i), waitStringSize))
      {

         wait(atoi(&(config[i][waitStringSize + 1])));
      }

      else
      {
         startProcess(i);
      }
   }

	return EXIT_SUCCESS;
}


// reads file "FILE_NAME" and stores its content to config[][].
// skips empty lines in the file.
// removes LF/CR chars at the end of each line.
int getConfig()
{
   int lineCount = 0;

   int lineLength = 0;
   int errorFlag = 0;

   char* status = 0;
   FILE* file = fopen(FILE_NAME, "r");

   printf("getConfig() reading file %s \n", FILE_NAME);

   if (NULL != file)
   {

      while (!feof(file))
      {
         // read a line from file and write to config[][]
         status = fgets(getLine(lineCount), LINES_SIZE, file);

         if ( (lineCount == 0) && (status == 0) )
         {

            errorFlag = 1;
            printf("getConfig() read file error (empty file?): line %d \n", lineCount);

            // exit while()
            break;
         }

         // check size of the line
         lineLength = strlen(getLine(lineCount));

         if (lineLength >= LINES_SIZE - 1)
         {
            errorFlag = 1;

            printf("getConfig() read file error: string too long: line %d \n", lineCount);
            // exit while()
            break;
         }

         // check if line is empty and
         // check line for CarrierReturn/LineFeed chars

         lineCount += formatLine(lineCount, lineLength);
         if (lineCount >= LINES_SIZE)
         {

            errorFlag = 1;
            printf("getConfig() file too long (max %d lines allowed) \n", LINES_SIZE);

            // exit while()
            break;
         }
      }

      fclose(file);

      if (errorFlag == 0)
      {

         printf("getConfig() found %d commands \n", lineCount);
      }
   }
   else
   {
      printf("getConfig() cannot open file \n");
   }

   if (errorFlag == 1)
   {
      lineCount = 0;
   }

   return lineCount;
}


// overwrites last CR/LF char of given line with 0 (zero)
// returns 1 if given line is a valid command
// returns 0 if given line is emtpy
// returns 0 if given line consists of only one CR/LF char
int formatLine(int line, int lineLength)
{

   int result = 0;

   if (lineLength == 0)
   {

      // line is empty
   }
   else if (lineLength == 1)
   {
      // this line has either one LF/CR char or a valid filename with only one char
      // suppress LF/CR char

      if ( (config[line][lineLength - 1] == CHAR_CODE_CARRIER_RETURN) ||
           (config[line][lineLength - 1] == CHAR_CODE_LINE_FEED) )
      {

         config[line][lineLength - 1] = 0;
      }
      else

      {
         // it is a valid filename with only one char
         result = 1;
      }
   }
   else
   {

      // suppress LF/CR char
      if ( (config[line][lineLength - 1] == CHAR_CODE_CARRIER_RETURN) ||
           (config[line][lineLength - 1] == CHAR_CODE_LINE_FEED) )
      {

         config[line][lineLength - 1] = 0;
      }

      // it is a valid filename with some chars

      result = 1;
   }

   return result;
}


// returns the pointer to the first char of given line
char* getLine(int line)
{
   return &(config[line][0]);
}


// executes the given line of the config file
void startProcess(int line)
{
   HINSTANCE hInst;

   printf("startProcess() \"%s\" \n", getLine(line));

   hInst = ShellExecute(0, "open",  // operation to perform

         getLine(line),             // application name
         0,                         // additional parameters
         0,                         // default directory

         SW_SHOW);

   // MSDN says: return code > 31 is "no error"
   if ((int)hInst < 32)
   {

      printShellExecuteError((int)hInst);
   }
}


// delays execution for given seconds
void wait(int seconds)
{

   if ( (seconds > 0) && (seconds < WAIT_SECONDS_MAX) )
   {

      printf("wait() %d seconds ", seconds);

      Sleep(seconds * 1000);

      printf("end \n");
   }
   else
   {
      printf("wait() warning: cannot wait for %d seconds (invalid number?)\n", seconds);
   }
}


// maps ShellExecute() error codes to text
void printShellExecuteError(int error)
{
   char* standardText = "startProcess() error";

   char* detailText = (char*)malloc(64);

   switch (error)
   {

      case ERROR_FILE_NOT_FOUND:
         detailText = "file not found";
         break;

      case ERROR_PATH_NOT_FOUND:
         detailText = "path not found";
         break;

      case ERROR_BAD_FORMAT:
         detailText = "not an executable";
         break;

      case SE_ERR_NOASSOC:
         detailText = "filetype unknown";
         break;

      case SE_ERR_ACCESSDENIED:
         detailText = "access denied";
         break;

      case SE_ERR_SHARE:
         detailText = "sharing violation";
         break;

      default:
         sprintf(detailText, "%d", error);
         break;
   }

   printf("%s: %s \n", standardText, detailText);
   free(detailText);
}