/***************************************************************************
                  filebrowser.c - eboxy file browser plugin
                             -------------------
    begin                : Sun Oct 13 2002
    copyright            : (C) 2002 by Paul Eggleton
    email                : bluelightning@bluelightning.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <limits.h>
#include <pwd.h>
#include <unistd.h>
#include <regex.h>

#include "eboxyplugin.h"
#include "pluginconstants.h"


/* Constants */

/* Sort order */
static const int SORT_FORWARD = 0;		/* sort in forward */
static const int SORT_REVERSE = 1;		/* sort in reverse order */

/* Sort item */
static const int SORT_NAME    = 0;		/* sort by file name */
static const int SORT_SIZE    = 1;		/* sort by file size */
static const int SORT_ATIME   = 2;		/* sort by last access time */
static const int SORT_CTIME   = 3;		/* sort by last change time */
static const int SORT_MTIME   = 4;		/* sort by last modification time */
static const int SORT_VERSION = 5;		/* sort by version */
static const int SORT_EXT     = 6;		/* sort by file name extension */
static const int SORT_DIR     = 7;		/* sort by file or directory */


/* Globals */

char *listboxname;
char *dirpath;
char *rootpath;
char *pattern;
int showhidden;
int sort_opts;
int sort_order;


/* Function prototypes */

char *fbp_setup(const char *sender, int numargs, const char *args[]);
char *fbp_refresh(const char *sender, int numargs, const char *args[]);
char *fbp_getpath(const char *sender);
int fbp_setpath(const char *sender, const char *value);
char *fbp_getroot(const char *sender);
int fbp_setroot(const char *sender, const char *value);
char *fbp_getpattern(const char *sender);
int fbp_setpattern(const char *sender, const char *value);
char *fbp_getshowhidden(const char *sender);
int fbp_setshowhidden(const char *sender, const char *value);
char *fbp_getfilename(const char *sender);
char *fbp_listchoose(const char *sender);
int fbp_setsort(const char *sender, const char *value);
char *fbp_getsort(const char *sender);
int fbp_setsortdirection(const char *sender, const char *value);
char *fbp_getsortdirection(const char *sender);

int refresh_dir(void);
char *expandPath(const char *filename);
int strtobool(const char *str);
char *patternToRegEx(const char *pattern);
static int sortcmp(const void * a, const void * b);


/* Implementation */

int ebplugin_init(void) {
  int rc = 0;

  /* Set version info for the plugin */
  setPluginInfo("FileBrowser", "0.4");

  /* Register an object to be used in scripts */
  rc = registerObject("filebrowser");
  if(rc)
    return rc;
  rc = registerMethodDL("filebrowser", "setup", 1, "fbp_setup");
  if(rc)
    return rc;
  rc = registerMethodDL("filebrowser", "refresh", 0, "fbp_refresh");
  if(rc)
    return rc;
  rc = registerPropertyDL("filebrowser", "path", "fbp_getpath", "fbp_setpath");
  if(rc)
    return rc;
  rc = registerPropertyDL("filebrowser", "rootpath", "fbp_getroot", "fbp_setroot");
  if(rc)
    return rc;
  rc = registerPropertyDL("filebrowser", "pattern", "fbp_getpattern", "fbp_setpattern");
  if(rc)
    return rc;
  rc = registerPropertyDL("filebrowser", "showhidden", "fbp_getshowhidden", "fbp_setshowhidden");
  if(rc)
    return rc;
  rc = registerPropertyDL("filebrowser", "filename", "fbp_getfilename", NULL);
  if(rc)
    return rc;
  rc = registerPropertyDL("filebrowser","sort","fbp_getsort", "fbp_setsort");
  if(rc)
    return rc;
  rc = registerPropertyDL("filebrowser","sortdirection","fbp_getsortdirection", "fbp_setsortdirection");
  if(rc)
    return rc;
   
  listboxname = NULL;
  rootpath = strdup("/");
  dirpath = expandPath(".");
  pattern = strdup("*");
  showhidden = 0;
  sort_opts = SORT_DIR;
  sort_order = SORT_FORWARD;

  /* Success */
  return 0;
}

int ebplugin_message(int msgcode, void *msgdata) {
  if(msgcode == PLMSG_BEFOREPAGECHANGE) {
    /* Page is changing */
    if(listboxname) {
      free(listboxname);
      listboxname = NULL;
    }
  }
  return 0;     /* return 0 here for future compatibility */
}

void ebplugin_deinit(void) {
  free(dirpath);
  free(pattern);
  free(rootpath);
  if(listboxname)
    free(listboxname);
}

char *fbp_setup(const char *sender, int numargs, const char *args[]) {
  listboxname = strdup(args[0]);
  refresh_dir();
  registerEventHandlerDL(listboxname, "OnChoose", "fbp_listchoose");
  return NULL;
}

char *fbp_refresh(const char *sender, int numargs, const char *args[]) {
  refresh_dir();
  return NULL;
}

char *fbp_getpath(const char *sender) {
  return strdup(dirpath);
}

int fbp_setpath(const char *sender, const char *value) {
  char *newpath;
  int rc;

  newpath = expandPath(value);
  if(!strncmp(rootpath, newpath, strlen(rootpath))) {
    free(dirpath);
    dirpath = newpath;
    rc = refresh_dir();
  }
  else {
    fprintf(stderr, "filebrowser: new path %s is outside root path %s", newpath, rootpath);
    free(newpath);
    rc = -1;
  }
  return rc;
}

char *fbp_getroot(const char *sender) {
  return strdup(rootpath);
}

int fbp_setroot(const char *sender, const char *value) {
  char *newroot;
  struct stat statbuf;
  int rc;

  newroot = expandPath(value);

  rc = lstat(newroot,&statbuf);
  if(rc != 0 || !S_ISDIR(statbuf.st_mode)) {
    fprintf(stderr, "filebrowser: specified root path %s could not be accessed\n", newroot);
    free(newroot);
    return -2;
  }
  /* Make sure path has a trailing slash */
  free(rootpath);
  if(newroot[strlen(newroot)-1] != '/') {
    rootpath = malloc(strlen(newroot)+2);
    strcpy(rootpath, newroot);
    strcat(rootpath, "/");
    free(newroot);
  }
  else
    rootpath = newroot;
  newroot = NULL;

  rc = 0;
  if(strncmp(rootpath, dirpath, strlen(rootpath))) {
    /* Current path is outside root path */
    free(dirpath);
    dirpath = strdup(rootpath);
    rc = refresh_dir();
  }
  return rc;
}

char *fbp_getpattern(const char *sender) {
  return strdup(pattern);
}

int fbp_setpattern(const char *sender, const char *value) {
  int rc;
  
  free(pattern);
  pattern = strdup(value);
  rc = refresh_dir();
  return rc;
}

char *fbp_getshowhidden(const char *sender) {
  if(showhidden)
    return strdup("true");
  else
    return strdup("false");
}

int fbp_setshowhidden(const char *sender, const char *value) {
  showhidden = strtobool(value);
  return 0;
}

char *fbp_getfilename(const char *sender) {
  char *fullname;
  const char *selectedtext;

  if(listboxname && dirpath) {
    selectedtext = getPropertyAsString(listboxname, "selectedtext");
    fullname = malloc(PATH_MAX);
    /* Build full name of file */
    strcpy(fullname, dirpath);
    if(dirpath[strlen(dirpath)-1] != '/')
      strcat(fullname, "/");
    strcat(fullname, selectedtext);
    return fullname;
  }
  return NULL;
}

char *fbp_getsort(const char *sender) {
  char *ret;

  if(sort_opts == SORT_NAME)
    ret = "name";
  else if(sort_opts == SORT_SIZE)
    ret = "size";
  else if(sort_opts == SORT_ATIME)
    ret = "atime";
  else if(sort_opts ==  SORT_CTIME)
    ret = "ctime";
  else if(sort_opts == SORT_MTIME)
    ret = "mtime";
  else if(sort_opts == SORT_VERSION)
    ret = "version";
  else if(sort_opts == SORT_EXT)
    ret = "ext";
  else if(sort_opts == SORT_DIR)
    ret = "dir";
  else
    ret = "none";

  return strdup(ret);
}

int fbp_setsort(const char *sender, const char *value) {
  int rc;
  
  if(strcasecmp(value, "name")==0)
    sort_opts = SORT_NAME;
  else if(strcasecmp(value, "size")==0)
    sort_opts = SORT_SIZE;
  else if(strcasecmp(value, "atime")==0)
    sort_opts = SORT_ATIME;
  else if(strcasecmp(value, "ctime")==0)
    sort_opts = SORT_CTIME;
  else if(strcasecmp(value, "mtime")==0)
    sort_opts = SORT_MTIME;
  else if(strcasecmp(value, "version")==0)
    sort_opts = SORT_VERSION;
  else if(strcasecmp(value, "ext")==0)
    sort_opts = SORT_EXT;
  else if(strcasecmp(value, "dir")==0)
    sort_opts = SORT_DIR;

  rc = refresh_dir();
  return rc;
}

char *fbp_getsortdirection(const char *sender) {
  if(sort_order==SORT_FORWARD)
    return strdup("forward");
  else
    return strdup("reverse");
}

int fbp_setsortdirection(const char *sender, const char *value) {
  int rc;
  
  if(strcasecmp(value, "forward")==0)
    sort_order = SORT_FORWARD;
  else
    sort_order = SORT_REVERSE;   

  rc = refresh_dir();
  return rc;
}

int refresh_dir(void) {
  DIR *dp;
  struct dirent *entry;
  struct stat statbuf;
  const char *margs[1];
  char *fullname;
  char *listitem;
  int rc;
  int count;
  int i;
  regex_t *regex = NULL;
  struct dirent **files;

  if(listboxname) {

    if(strcmp(pattern, "*")) {
      int rc;
      char *regstr;
      /* Make space for the regular expression */
      regex = (regex_t *) malloc(sizeof(regex_t));
      memset(regex, 0, sizeof(regex_t));
      /* Convert pattern to regular expression */
      regstr = patternToRegEx(pattern);
      if(!regstr) {
        /* Malloc error */ 
        return 1;
      }
      /* Compile regular expression */
      if((rc=regcomp(regex, regstr, REG_EXTENDED))!=0) {
        /* An error occurred, get and print the error */
        size_t length;
        char *buffer;
        length = regerror(rc, regex, NULL, 0);
        buffer = malloc(length);
        regerror(rc, regex, buffer, length);
        fprintf(stderr, "%s\n", buffer);
        free(buffer);
        regfree(regex); 
        free(regstr);
        return 1;
      }
      free(regstr);
    }

    /* Clear listbox */
    callMethod(listboxname, "clear", 0, NULL);

    fullname = malloc(PATH_MAX);

    /* Get list of files, sorted using sortcmp() function */
    count = scandir(dirpath, &files, NULL, sortcmp);

    /* Test for further requirements and display file */
    for(i=0; i<count; ++i) {
      int showentry = 0;
 
      if(!strcmp(files[i]->d_name, ".."))   /* Only show .. if we aren't at the root */
        showentry = !(!strcmp(dirpath, rootpath));
      else {
        showentry = !(!strcmp(files[i]->d_name, ".")) && (showhidden || files[i]->d_name[0] != '.');
      }

      if(showentry) {
        /* Build full name of file */
        strcpy(fullname, dirpath);
        if(dirpath[strlen(dirpath)-1] != '/')
          strcat(fullname, "/");
        strcat(fullname, files[i]->d_name);
        /* Get info on file entry */
        rc = lstat(fullname, &statbuf);
        if(rc == 0) {
          if(S_ISDIR(statbuf.st_mode)) {
            /* Directory - append a / onto the name */
            listitem = malloc(strlen(files[i]->d_name)+2);
            strcpy(listitem, files[i]->d_name);
            strcat(listitem, "/");
          }
          else {
            /* Normal file */
            if(regex == NULL || regexec(regex, files[i]->d_name, 0, NULL, REG_NOSUB)==0)
              listitem = strdup(files[i]->d_name);
            else
              listitem = NULL;
          }

          if(listitem) {
            margs[0] = listitem;
            callMethod(listboxname, "additem", 1, margs);
            free(listitem);
          }
        }
        else {
          perror("filebrowser");
          printf("filebrowser: file was %s\n", fullname);
        }
      }
    }
    free(fullname);
    if(regex)
      regfree(regex);
  }
  return 0;
}

/* Called whenever the user chooses an item from the list */
char *fbp_listchoose(const char *sender) {
  const char *selectedtext;
  char *newpath;
  char *oldpath;
  char *backpos;

  if(!strcmp(sender, listboxname)) {
    selectedtext = getPropertyAsString(sender, "selectedtext");
    if(strlen(selectedtext)>0) {
      if(selectedtext[strlen(selectedtext)-1] == '/') {
        /* Change directory */
        if(!strcmp(selectedtext, "../")) {
          /* Up to parent */
          if(dirpath[strlen(dirpath)-1] == '/')
            dirpath[strlen(dirpath)-1] = '\0';
          backpos = strrchr(dirpath, '/');
          if(backpos) {
            newpath = malloc((backpos-dirpath) + 2);
            strncpy(newpath, dirpath, (backpos-dirpath));
            newpath[backpos-dirpath] = '/';
            newpath[backpos-dirpath+1] = '\0';
          }
          else
            newpath = strdup(dirpath);
        }
        else {
          /* Enter directory */
          newpath = malloc(strlen(selectedtext) + strlen(dirpath) + 2);
          strcpy(newpath, dirpath);
          if(dirpath[strlen(dirpath)-1] != '/')
            strcat(newpath, "/");
          strcat(newpath, selectedtext);
        }
        oldpath = dirpath;
        dirpath = newpath;
        if(refresh_dir() == 0) {
          /* Change of path succeeded */
          free(oldpath);
          fireEvent("filebrowser", "OnPathChange");
        }
        else {
          dirpath = oldpath;
          free(newpath);
        }
      }
      else {
        /* A file was chosen */
        fireEvent("filebrowser", "OnChooseFile");
      }
    }
  }
  return NULL;
}

/* Expands any environment variables in the specified path
   Note: you should free what this returns when you're done with it */
char *expandPath(const char *filename) {
  const int bufsize = PATH_MAX;
  char *path = NULL;
  char *buf = NULL;
  char *bufptr = NULL;
  char *expandbuf = NULL;
  int i=0, j=0, k=0;
  int expandstart = 0;
  int expanding = 0;
  int length = 0;
  struct passwd *userinfo;

  if(filename == NULL)
    return NULL;

  path = strdup(filename);
  if(strlen(path) == 0)
    return path;

  buf = malloc(bufsize+1);
  expandbuf = malloc(bufsize+1);

  while(1) {
    if(expanding) {
      if(i >= strlen(path) ||
          k >= bufsize ||
          !(isalnum(path[i]) || path[i] == '_')) {
        /* End of variable, try to dereference it */
        if(path[expandstart] == '$') {
          if(k==0 && i < strlen(path) && path[i] == '$') {
            /* Special variable (process ID) */
            bufptr = malloc(11);
            snprintf(bufptr, 10, "%d", getpid());
            length = strlen(bufptr);
            if(bufsize-j < length)
              length = bufsize-j;
            strncat(buf, bufptr, length);
            j += length;
            free(bufptr);
            i++; /* need to skip second $ */
          }
          else {
            /* Get value of specified environment variable */
            expandbuf[k] = '\0';
            bufptr = getenv(expandbuf);
            if(bufptr) {
              /* getenv returned something, add it to the string */
              int length = strlen(bufptr);
              if(bufsize-j < length)
                length = bufsize-j;
              buf[j] = '\0';
              strncat(buf, bufptr, length);
              j += length;
              /* we DO NOT free bufptr here because it's a pointer into the user's env table */
            }
          }
        }
        else if(path[expandstart] == '~') {
          /* Note that we always expect j==0 here because anything else is not allowed */
          if(i >= strlen(path) || path[i] == '/') {
            /* Home directory */
            if(k==0) {
              /* Current user's home directory */
              bufptr = getenv("HOME");
              if(bufptr) {
                strncpy(buf, bufptr, bufsize);
                j = strlen(bufptr);
                /* we DO NOT free bufptr here because it's a pointer into the user's env table */
              }
            }
            else {
              /* Some other user's home directory */
              expandbuf[k] = '\0';
              userinfo = getpwnam(expandbuf);
              if(userinfo) {
                strncpy(buf, userinfo->pw_dir, bufsize);
                j = strlen(userinfo->pw_dir);
              }
              else {
                j = i;
                strncpy(buf, path, j);
              }
            }
          }
          else {
            j = i;
            strncpy(buf, path, j);
          }
        }
        expanding = 0;
      }
      else
        expandbuf[k++] = path[i];
    }

    if(i >= strlen(path))
      break;

    if(!expanding) {
      if(path[i] == '$' || (i==0 && path[i] == '~')) {
        /* Start of something to expand */
        k = 0;
        expandstart = i;
        expanding = 1;
      }
      else {
        /* FIXME: this needs work (.. (parent), /./ in middle of path, etc.) */
        if(path[i] == '.' && path[i+1] != '.' && (i==0 || path[i] == '/') && (path[i+1] == '\0' || path[i+1] == '/')) {
          if(i == 0) {
            /* Get current directory */
            if(getcwd(buf, bufsize))
              j+=strlen(buf);
          }
        }
        else {
          buf[j] = path[i];
          j++;
        }
      }
    }

    i++;
  }

  /* Cleanup */
  free(expandbuf);
  free(path);

  buf[j] = '\0';
  return buf;
}

/* Convert a string value into a boolean */
int strtobool(const char *str) {
  if(strcasecmp(str, "true") == 0 || strcasecmp(str, "on") == 0 || strcasecmp(str, "yes") == 0 || atoi(str) != 0)
    return 1;
  else
    return 0;
}

/* Convert a simple file pattern into a basic regular expression */
char *patternToRegEx(const char *pattern) {
  int i, j=0;
  char *buf;

  buf = malloc(strlen(pattern) * 2);
  if(!buf) {
    perror("filebrowser");
    return NULL;
  }

  for(i=0; i<strlen(pattern); i++) {
    if(pattern[i] == '?') {
      buf[j++] = '.';
    }
    else if(pattern[i] == '*') {
      strcpy(buf+j, ".*");
      j+=2;
    }
    else if(pattern[i] == '.') {
      strcpy(buf+j, "\\.");
      j+=2;
    }
    else if(pattern[i] == ';') {  // Multiple patterns
      buf[j++] = '|';
    }
    else {
      buf[j++] = pattern[i];
    }
  }

  buf[j] = '\0';
  return buf;
}

/* Custom comparison function to use with scandir() */
static int sortcmp(const void *a, const void *b) {
	int cmp, dif;
	struct stat stat_a;
	struct stat stat_b;
  char *fullname_a;
  char *fullname_b;
	
  /* Get full name for both files */
  fullname_a = malloc(PATH_MAX);
  fullname_b = malloc(PATH_MAX);

  strcpy(fullname_a, dirpath);
  if(dirpath[strlen(dirpath)-1] != '/')
    strcat(fullname_a, "/");
  strcat(fullname_a, (*(const struct dirent **) a)->d_name);
  strcpy(fullname_b, dirpath);
  if(dirpath[strlen(dirpath)-1] != '/')
    strcat(fullname_b, "/");
  strcat(fullname_b, (*(const struct dirent **) b)->d_name);

  /* Obtain file details */
  lstat(fullname_a, &stat_a);
  lstat(fullname_b, &stat_b);
	
  cmp = 0;
  dif = 0;
 	if (sort_opts == SORT_SIZE) {
		dif = (int)(stat_a.st_size - stat_b.st_size);
	}
  else if (sort_opts == SORT_ATIME) {
		dif = (int)(stat_a.st_atime - stat_b.st_atime);
	}
  else if (sort_opts == SORT_CTIME) {
		dif = (int)(stat_a.st_ctime - stat_b.st_ctime);
	}
  else if (sort_opts == SORT_MTIME) {
		dif = (int)(stat_a.st_mtime - stat_b.st_mtime);
	}
  else if (sort_opts == SORT_DIR) {
    if(S_ISDIR(stat_a.st_mode) && ! S_ISREG(stat_a.st_mode))
       dif = 1;
    if(S_ISDIR(stat_b.st_mode) && ! S_ISREG(stat_b.st_mode))
       dif -= 1;
  /* FIXME: implement these or remove them */
	/* } else if (sort_opts == SORT_VERSION) { */
	/* } else if (sort_opts == SORT_EXT) { */
	} else {    /* assume SORT_NAME */
		dif = 0;
	}

	if (dif > 0) cmp = -1;
	if (dif < 0) cmp =  1;
	if (dif == 0) {
		/* sort by name- may be a tie_breaker for time or size cmp */
		dif = strcasecmp((*(const struct dirent **) a)->d_name, (*(const struct dirent **) b)->d_name);
		if (dif > 0) cmp =  1;
		if (dif < 0) cmp = -1;
	}

	if (sort_order == SORT_REVERSE) {
		cmp = -1 * cmp;
	}

  free(fullname_a);
  free(fullname_b);

	return(cmp);
}



syntax highlighted by Code2HTML, v. 0.9.1