// rdimport.cpp
//
// A Batch Importer for Rivendell.
//
//   (C) Copyright 2002-2014,2016-2018 Fred Gleason <fredg@paravelsystems.com>
//
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License version 2 as
//   published by the Free Software Foundation.
//
//   This program is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public
//   License along with this program; if not, write to the Free Software
//   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//

#include <limits.h>
#include <glob.h>
#include <signal.h>
#include <math.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <sched.h>
#include <errno.h>

#include <qapplication.h>
#include <qdir.h>
#include <qstringlist.h>

#include <rd.h>
#include <rdapplication.h>
#include <rdaudioimport.h>
#include <rdconf.h>
#include <rdcut.h>
#include <rdescape_string.h>
#include <rdlibrary_conf.h>
#include <rdtempdirectory.h>

#include "rdimport.h"

volatile bool import_run=true;

void SigHandler(int signo)
{
  switch(signo) {
  case SIGTERM:
  case SIGINT:
  case SIGHUP:
    import_run=false;
    break;
  }
}


MainObject::MainObject(QObject *parent)
  :QObject(parent)
{
  bool ok=false;
  int n=0;
  QString err_msg;

  import_file_key=0;
  import_group=NULL;
  import_verbose=false;
  import_log_mode=false;
  import_single_cart=false;
  import_use_cartchunk_cutid=false;
  import_cart_number_offset=0;
  import_cart_number=0;
  import_title_from_cartchunk_cutid=false;
  import_delete_source=false;
  import_delete_cuts=false;
  import_drop_box=false;
  import_set_user_defined="";
  import_stdin_specified=false;
  import_startdate_offset=0;
  import_enddate_offset=0;
  import_fix_broken_formats=false;
  import_persistent_dropbox_id=-1;
  import_create_dates=false;
  import_create_startdate_offset=0;
  import_create_enddate_offset=0;
  import_string_bpm=0;
  import_string_year=0;
  import_clear_datetimes=false;
  import_clear_dayparts=false;
  import_xml=false;
  import_to_mono=false;

  //
  // Open the Database
  //
  rda=new RDApplication("rdimport","rdimport",RDIMPORT_USAGE,this);
  if(!rda->open(&err_msg)) {
    fprintf(stderr,"rdimport: %s\n",(const char *)err_msg);
    exit(1);
  }

  //
  // Read Command Options
  //
  if(rda->cmdSwitch()->keys()<2) {
    fprintf(stderr,"\n");
    fprintf(stderr,"%s",RDIMPORT_USAGE);
    fprintf(stderr,"\n");
    exit(2);
  }
  for(unsigned i=0;i<rda->cmdSwitch()->keys()-2;i++) {
    if(rda->cmdSwitch()->key(i)=="--verbose") {
      import_verbose=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--log-mode") {
      import_verbose=true;
      import_log_mode=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--to-cart") {
      import_cart_number=rda->cmdSwitch()->value(i).toUInt(&ok);
      if((!ok)||(import_cart_number<1)||(import_cart_number>999999)) {
	fprintf(stderr,"rdimport: invalid cart number\n");
	exit(2);
      }
      if(import_use_cartchunk_cutid) {
	fprintf(stderr,"rdimport: '--to-cart' and '--use-cartchunk-cutid' are mutually exclusive\n");
	exit(2);
      }
      import_single_cart=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--use-cartchunk-cutid") {
      if(import_cart_number!=0) {
	fprintf(stderr,"rdimport: '--to-cart' and '--use-cartchunk-cutid' are mutually exclusive\n");
	exit(2);
      }
      import_use_cartchunk_cutid=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--cart-number-offset") {
      import_cart_number_offset=rda->cmdSwitch()->value(i).toInt(&ok);
      if(!ok) {
	fprintf(stderr,"rdimport: invalid --cart-number-offset\n");
	exit(2);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--title-from-cartchunk-cutid") {
      import_title_from_cartchunk_cutid=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--delete-source") {
      import_delete_source=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--delete-cuts") {
      import_delete_cuts=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--startdate-offset") {
      import_startdate_offset=rda->cmdSwitch()->value(i).toInt(&ok);
      if(!ok) {
	fprintf(stderr,"rdimport: invalid startdate-offset\n");
	exit(2);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--enddate-offset") {
      import_enddate_offset=rda->cmdSwitch()->value(i).toInt(&ok);
      if(!ok) {
	fprintf(stderr,"rdimport: invalid enddate-offset\n");
	exit(2);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--clear-datetimes") {
      import_clear_datetimes=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-datetimes") {
      QStringList f0=QStringList().split(",",rda->cmdSwitch()->value(i));
      if(f0.size()!=2) {
	fprintf(stderr,"rdimport: invalid argument to --set-datetimes\n");
	exit(2);
      }
      for(unsigned j=0;j<2;j++) {
	if((f0[j].length()!=15)||(f0[j].mid(8,1)!="-")) {
	  fprintf(stderr,"rdimport: invalid argument to --set-datetimes\n");
	  exit(2);
	}
	unsigned year=f0[j].left(4).toUInt(&ok);
	if(!ok) {
	  fprintf(stderr,"rdimport: invalid year argument to --set-datetimes\n");
	  exit(2);
	}
	unsigned month=f0[j].mid(4,2).toUInt(&ok);
	if((!ok)||(month>12)) {
	  fprintf(stderr,"rdimport: invalid month argument to --set-datetimes\n");
	  exit(2);
	}
	unsigned day=f0[j].mid(6,2).toUInt(&ok);
	if((!ok)||(day>31)) {
	  fprintf(stderr,"rdimport: invalid day argument to --set-datetimes\n");
	  exit(2);
	}
	unsigned hour=f0[j].mid(9,2).toUInt(&ok);
	if((!ok)||(hour>23)) {
	  fprintf(stderr,"rdimport: invalid hour argument to --set-datetimes\n");
	  exit(2);
	}
	unsigned min=f0[j].mid(11,2).toUInt(&ok);
	if((!ok)||(min>59)) {
	  fprintf(stderr,"rdimport: invalid minute argument to --set-datetimes\n");
	  exit(2);
	}
	unsigned sec=f0[j].right(2).toUInt(&ok);
	if((!ok)||(sec>59)) {
	  fprintf(stderr,"rdimport: invalid seconds argument to --set-datetimes\n");
	  exit(2);
	}
	import_datetimes[j]=QDateTime(QDate(year,month,day),
				      QTime(hour,min,sec));
	if(!import_datetimes[j].isValid()) {
	  fprintf(stderr,"rdimport: invalid argument to --set-datetimes\n");
	}
      }
      if(import_datetimes[0]>=import_datetimes[1]) {
	fprintf(stderr,"rdimport: datetime cannot end before it begins\n");
	exit(2);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-daypart-times") {
      QStringList f0=QStringList().split(",",rda->cmdSwitch()->value(i));
      if(f0.size()!=2) {
	fprintf(stderr,"rdimport: invalid argument to --set-daypart-times\n");
	exit(2);
      }
      for(unsigned j=0;j<2;j++) {
	if(f0[j].length()!=6) {
	  fprintf(stderr,"rdimport: invalid argument to --set-daypart-times\n");
	  exit(2);
	}
	unsigned hour=f0[j].left(2).toUInt(&ok);
	if((!ok)||(hour>23)) {
	  fprintf(stderr,"rdimport: invalid hour argument to --set-daypart-times\n");
	  exit(2);
	}
	unsigned min=f0[j].mid(2,2).toUInt(&ok);
	if((!ok)||(min>59)) {
	  fprintf(stderr,"rdimport: invalid minute argument to --set-daypart-times\n");
	  exit(2);
	}
	unsigned sec=f0[j].right(2).toUInt(&ok);
	if((!ok)||(sec>59)) {
	  fprintf(stderr,"rdimport: invalid seconds argument to --set-daypart-times\n");
	  exit(2);
	}
	import_dayparts[j].setHMS(hour,min,sec);
      }
      if(import_dayparts[0]>=import_dayparts[1]) {
	fprintf(stderr,"rdimport: daypart cannot end before it begins\n");
	exit(2);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--to-mono") {
      import_to_mono=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--clear-daypart-times") {
      import_clear_dayparts=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--drop-box") {
      import_drop_box=true;
      if(import_persistent_dropbox_id<0) {
	import_delete_source=true;
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--add-scheduler-code") {
      import_add_scheduler_codes.push_back(rda->cmdSwitch()->value(i));
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-user-defined") {
      import_set_user_defined=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--metadata-pattern") {
      import_metadata_pattern=rda->cmdSwitch()->value(i);
      if(!VerifyPattern(import_metadata_pattern)) {
	fprintf(stderr,"rdimport: invalid metadata pattern\n");
	exit(2);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--fix-broken-formats") {
      import_fix_broken_formats=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--persistent-dropbox-id") {
      import_persistent_dropbox_id=rda->cmdSwitch()->value(i).toInt(&ok);
      if(!ok) {
	fprintf(stderr,"rdimport: invalid persistent dropbox id\n");
	exit(2);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--create-startdate-offset") {
      import_create_startdate_offset=rda->cmdSwitch()->value(i).toInt(&ok);
      if(!ok) {
	fprintf(stderr,"rdimport: invalid create-startddate-offset\n");
	exit(2);
      }
      import_create_dates=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--create-enddate-offset") {
      import_create_enddate_offset=rda->cmdSwitch()->value(i).toInt(&ok);
      if((!ok) || 
         (import_create_startdate_offset > import_create_enddate_offset )) {
	fprintf(stderr,"rdimport: invalid create-enddate-offset\n");
	exit(2);
      }
      import_create_dates=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-agency") {
      import_string_agency=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-album") {
      import_string_album=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-artist") {
      import_string_artist=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-bpm") {
      import_string_bpm=rda->cmdSwitch()->value(i).toInt(&ok);
      if(!ok) {
	fprintf(stderr,"rdimport: invalid value for --set-string-bpm\n");
	exit(255);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-client") {
      import_string_client=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-composer") {
      import_string_composer=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-conductor") {
      import_string_conductor=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-description") {
      import_string_description=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-label") {
      import_string_label=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-outcue") {
      import_string_outcue=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-publisher") {
      import_string_publisher=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-song-id") {
      import_string_song_id=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-title") {
      if(rda->cmdSwitch()->value(i).isEmpty()) {
	fprintf(stderr,"title field cannot be empty\n");
	exit(255);
      }
      import_string_title=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-user-defined") {
      import_string_user_defined=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-year") {
      import_string_year=rda->cmdSwitch()->value(i).toInt(&ok);
      if(!ok) {
	fprintf(stderr,"rdimport: invalid value for --set-string-year\n");
	exit(255);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--xml") {
      import_xml=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
  }

  //
  // Sanity Checks
  //
  if(import_datetimes[0].isValid()&&import_clear_datetimes) {
    fprintf(stderr,"rdimport: --set-datetimes and --clear-datetimes are mutually exclusive\n");
    exit(255);
  }
  if((!import_dayparts[1].isNull())&&import_clear_dayparts) {
    fprintf(stderr,"rdimport: --set-daypart-times and --clear-daypart-times are mutually exclusive\n");
    exit(255);
  }
  if((!import_metadata_pattern.isEmpty())&&import_xml) {
    fprintf(stderr,"rdimport: --metadata-pattern and --xml are mutually exclusive\n");
    exit(255);
  }

  import_cut_markers=new MarkerSet();
  import_cut_markers->loadMarker(rda->cmdSwitch(),"cut");
  import_talk_markers=new MarkerSet();
  import_talk_markers->loadMarker(rda->cmdSwitch(),"talk");
  import_hook_markers=new MarkerSet();
  import_hook_markers->loadMarker(rda->cmdSwitch(),"hook");
  import_segue_markers=new MarkerSet();
  import_segue_markers->loadMarker(rda->cmdSwitch(),"segue");
  import_fadedown_marker=new MarkerSet();
  import_fadedown_marker->loadFade(rda->cmdSwitch(),"fadedown");
  import_fadeup_marker=new MarkerSet();
  import_fadeup_marker->loadFade(rda->cmdSwitch(),"fadeup");

  //
  // RIPC Connection
  //
  connect(rda,SIGNAL(userChanged()),this,SLOT(userData()));
  rda->ripc()->
    connectHost("localhost",RIPCD_TCP_PORT,rda->config()->password());

  //
  // Verify Group
  //
  for(unsigned i=0;i<rda->cmdSwitch()->keys();i++) {
    if(rda->cmdSwitch()->key(i).left(2)!="--") {
      import_group=new RDGroup(rda->cmdSwitch()->key(i));
      if(!import_group->exists()) {
	fprintf(stderr,"rdimport: invalid group specified\n");
	delete import_group;
	exit(2);
      }
      import_file_key=i+1;
      i=rda->cmdSwitch()->keys();
    }
  }
  if(import_group==NULL) {
    fprintf(stderr,"rdimport: invalid group specified\n");
    exit(2);
  }
  if(import_cart_number>0) {
    if(!import_group->cartNumberValid(import_cart_number)) {
      fprintf(stderr,"rdimport: invalid cart number for group\n");
      delete import_group;
      exit(2);
    }
  }

  //
  // Verify Scheduler Codes
  //
  for(unsigned i=0;i<import_add_scheduler_codes.size();i++) {
    if(!SchedulerCodeExists(import_add_scheduler_codes[i])) {
      fprintf(stderr,"scheduler code \"%s\" does not exist\n",
	      (const char *)import_add_scheduler_codes[i].utf8());
      exit(2);
    }
  }

  //
  // Get Audio Parameters
  //
  import_format=rda->libraryConf()->defaultFormat();
  import_samprate=rda->system()->sampleRate();
  import_bitrate=rda->libraryConf()->defaultBitrate();
  import_channels=rda->libraryConf()->defaultChannels();
  import_normalization_level=rda->libraryConf()->ripperLevel();
  import_autotrim_level=rda->libraryConf()->trimThreshold();
  import_src_converter=rda->libraryConf()->srcConverter();
  import_segue_level=0;
  import_segue_length=0;

  for(unsigned i=0;i<rda->cmdSwitch()->keys()-2;i++) {
    if(rda->cmdSwitch()->key(i)=="--normalization-level") {
      n=rda->cmdSwitch()->value(i).toInt(&ok);
      if(ok&&(n<=0)) {
	import_normalization_level=100*n;
      }
      else {
	fprintf(stderr,"rdimport: invalid normalization level\n");
	exit(2);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--autotrim-level") {
      n=rda->cmdSwitch()->value(i).toInt(&ok);
      if(ok&&(n<=0)) {
	import_autotrim_level=100*n;
      }
      else {
	fprintf(stderr,"rdimport: invalid autotrim level\n");
	exit(2);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--segue-level") {
      n=rda->cmdSwitch()->value(i).toInt(&ok);
      if(ok&&(n<=0)) {
	import_segue_level=n;
      }
      else {
	fprintf(stderr,"rdimport: invalid segue level\n");
	exit(2);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--segue-length") {
      n=rda->cmdSwitch()->value(i).toInt(&ok);
      if(ok&&(n>=0)) {
	import_segue_length=n;
      }
      else {
	fprintf(stderr,"rdimport: invalid segue length\n");
	exit(2);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--single-cart") {
      import_single_cart=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if((!rda->cmdSwitch()->processed(i))&&
       (rda->cmdSwitch()->key(i).left(2)=="--")) {
      fprintf(stderr,"rdimport: unknown command option \"%s\"\n",
	      (const char *)rda->cmdSwitch()->key(i));
      exit(2);
    }
  }
  if(import_to_mono) {
    import_channels=1;
  }

  //
  // Print Status Messages
  //
  if(import_verbose) {
    printf("\n");
    if(import_log_mode) {
      PrintLogDateTime(stdout);
      printf("rdimport started\n");
    }
    printf("RDImport v%s\n",VERSION);
    if(import_log_mode) {
      printf(" Log mode is ON\n");
    }
    else {
      printf(" Log mode is OFF\n");
    }
    if(import_to_mono) {
      printf(" Force to Mono is ON\n");
    }
    else {
      printf(" Force to Mono is OFF\n");
    }
    if(import_normalization_level==0) {
      printf(" Normalization is OFF\n");
    }
    else {
      printf(" Normalization level = %d dB\n",import_normalization_level/100);
    }
    if(import_autotrim_level==0) {
      printf(" AutoTrim is OFF\n");
    }
    else {
      printf(" AutoTrim level = %d dB\n",import_autotrim_level/100);
    }
    if(import_cart_number==0) {
      if(import_use_cartchunk_cutid) {
	printf(" Destination cart is taken from CartChunk CutID\n");
      }
      else {
	printf(" Destination cart is AUTO\n");
      }
    }
    else {
      printf(" Destination cart is %06u\n",import_cart_number);
    }
    if(import_single_cart) {
      printf(" Single cart mode is ON\n");
    }
    else {
      printf(" Single cart mode is OFF\n");
    }
    if(import_title_from_cartchunk_cutid) {
      printf(" Destination cart title is taken from CartChunk CutID\n");
    }
    if(import_cart_number_offset!=0) {
      printf(" Cart number offset is %d\n",import_cart_number_offset);
    }
    if(import_delete_source) {
      printf(" Delete source mode is ON\n");
    }
    else {
      printf(" Delete source mode is OFF\n");
    }
    if(import_delete_cuts) {
      printf(" Delete cuts mode is ON\n");
    }
    else {
      printf(" Delete cuts mode is OFF\n");
    }
    if(import_drop_box) {
      printf(" DropBox mode is ON\n");
    }
    else {
      printf(" DropBox mode is OFF\n");
    }
    if(import_add_scheduler_codes.size()>0) {
      printf(" Adding Scheduler Code(s):");
      for(unsigned i=0;i<import_add_scheduler_codes.size();i++) {
	printf(" %s",(const char *)import_add_scheduler_codes[i].utf8());
      }
      printf("\n");
    }
    if(!import_set_user_defined.isEmpty()) {
      printf(" Setting the User Defined field to \"%s\"\n",
	     (const char *)import_set_user_defined);
    }
    if(!import_metadata_pattern.isEmpty()) {
      printf(" Using metadata pattern: %s\n",
	     (const char *)import_metadata_pattern);
    }
    printf(" Start Date Offset = %d days\n",import_startdate_offset);
    printf(" End Date Offset = %d days\n",import_enddate_offset);
    if((!import_dayparts[0].isNull())||(!import_dayparts[1].isNull())) {
      printf(" Start Daypart = %s\n",
	     (const char *)import_dayparts[0].toString("hh:mm:ss"));
      printf(" End Daypart = %s\n",
	     (const char *)import_dayparts[1].toString("hh:mm:ss"));
    }
    if(import_clear_dayparts) {
      printf(" Clearing daypart times\n");
    }
    if((!import_datetimes[0].isNull())||(!import_datetimes[1].isNull())) {
      printf(" Start DateTime = %s\n",
	     (const char *)import_datetimes[0].toString("MM/dd/yyyy hh:mm:ss"));
      printf(" End DateTime = %s\n",
	     (const char *)import_datetimes[1].toString("MM/dd/yyyy hh:mm:ss"));
    }
    if(import_clear_datetimes) {
      printf(" Clearing datetimes\n");
    }
    if(import_fix_broken_formats) {
      printf(" Broken format workarounds are ENABLED\n");
    }
    else {
      printf(" Broken format workarounds are DISABLED\n");
    }
    if(import_create_dates) {
      printf(" Import Create Dates mode is ON\n");
      printf(" Import Create Start Date Offset = %d days\n",import_create_startdate_offset);
      printf(" Import Create End Date Offset = %d days\n",import_create_enddate_offset);
    }
    else {
      printf(" Import Create Dates mode is OFF\n");
    }
    if(import_persistent_dropbox_id>=0) {
      printf(" Persistent DropBox ID = %d\n",import_persistent_dropbox_id);
    }
    if(!import_string_agency.isNull()) {
      printf(" Agency set to: %s\n",(const char *)import_string_agency);
    }
    if(!import_string_album.isNull()) {
      printf(" Album set to: %s\n",(const char *)import_string_album);
    }
    if(!import_string_artist.isNull()) {
      printf(" Artist set to: %s\n",(const char *)import_string_artist);
    }
    if(import_string_bpm!=0) {
      printf(" BPM set to: %d\n",import_string_bpm);
    }
    if(!import_string_client.isNull()) {
      printf(" Client set to: %s\n",(const char *)import_string_client);
    }
    if(!import_string_composer.isNull()) {
      printf(" Composer set to: %s\n",(const char *)import_string_composer);
    }
    if(!import_string_conductor.isNull()) {
      printf(" Conductor set to: %s\n",(const char *)import_string_conductor);
    }
    if(!import_string_description.isNull()) {
      printf(" Description set to: %s\n",
	     (const char *)import_string_description);
    }
    if(!import_string_label.isNull()) {
      printf(" Label set to: %s\n",(const char *)import_string_label);
    }
    if(!import_string_outcue.isNull()) {
      printf(" Outcue set to: %s\n",(const char *)import_string_outcue);
    }
    if(!import_string_publisher.isNull()) {
      printf(" Publisher set to: %s\n",(const char *)import_string_publisher);
    }
    if(!import_string_song_id.isNull()) {
      printf(" Song ID set to: %s\n",(const char *)import_string_song_id);
    }
    if(!import_string_title.isNull()) {
      printf(" Title set to: %s\n",(const char *)import_string_title);
    }
    if(!import_string_user_defined.isNull()) {
      printf(" User Defined set to: %s\n",
	     (const char *)import_string_user_defined);
    }
    if(import_string_year!=0) {
      printf(" Year set to: %d\n",import_string_year);
    }
    if(import_xml) {
      printf(" Importing RDXML metadata from external file\n");
    }
    import_cut_markers->dump();
    import_talk_markers->dump();
    import_hook_markers->dump();
    import_segue_markers->dump();
    import_fadedown_marker->dump();
    import_fadeup_marker->dump();
    printf(" Files to process:\n");
    for(unsigned i=import_file_key;i<rda->cmdSwitch()->keys();i++) {
      printf("   \"%s\"\n",(const char *)rda->cmdSwitch()->key(i));
    }
    printf("\n");
    fflush(stdout);
  }

  // 
  // Setup Signal Handling 
  //
  ::signal(SIGTERM,SigHandler);
  ::signal(SIGINT,SigHandler);
  ::signal(SIGHUP,SigHandler);
}


void MainObject::userData()
{
  //
  // Get User Context
  //
  disconnect(rda->ripc(),SIGNAL(userChanged()),this,SLOT(userData()));

  //
  // Verify Permissions
  //
  if(!rda->user()->editAudio()) {
    fprintf(stderr,"rdimport: user \"%s\" has no edit audio permission\n",
	    (const char *)rda->user()->name());
    exit(256);
  }

  //
  // Process Files
  //
  if(import_drop_box) {
    RunDropBox();
  }
  else {
    for(unsigned i=import_file_key;i<rda->cmdSwitch()->keys();i++) {
      ProcessFileList(rda->cmdSwitch()->key(i));
    }
    if(import_stdin_specified) {
      bool quote_mode=false;
      bool escape_mode=false;
      char buffer[PATH_MAX];
      unsigned ptr=0;
      while((ptr<PATH_MAX)&&(read(0,buffer+ptr,1)==1)) {
	if(quote_mode) {
	  if(buffer[ptr]=='\"') {
	    quote_mode=false;
	  }
	  else {
	    ptr++;
	  }
	}
	else {
	  if(escape_mode) {
	    ptr++;
	    escape_mode=false;
	  }
	  else {
	    if(buffer[ptr]=='\"') {
	      quote_mode=true;
	    }
	    else {
	      if(buffer[ptr]=='\\') {
		escape_mode=true;
	      }
	      else {
		if(isspace(buffer[ptr])) {
		  buffer[ptr]=0;
		  ProcessFileList(buffer);
		  ptr=0;
		}
		else {
		  ptr++;
		}
	      }
	    }
	  }
	}
      }
      if(ptr>0) {
	buffer[ptr]=0;
	ProcessFileList(buffer);
      }
    }
  }

  //
  // Clean Up and Exit
  //
  delete import_group;
  //  delete import_cmd;

  exit(0);
}


void MainObject::RunDropBox()
{
  //
  // Set Process Priority
  //
  struct sched_param sp;
  memset(&sp,0,sizeof(sp));
  if(sched_setscheduler(getpid(),SCHED_BATCH,&sp)!=0) {
    printf(" Unable to set batch permissions, %s",strerror(errno));
  }

  do {
    //
    // Clear the Checked Flag
    //
    for(std::list<struct DropboxList *>::const_iterator 
	  ci=import_dropbox_list.begin();
	ci!=import_dropbox_list.end();ci++) {
      (*ci)->checked=false;
    }

    //
    // Scan for Eligible Imports
    //
    for(unsigned i=import_file_key;i<rda->cmdSwitch()->keys();i++) {
      ProcessFileList(rda->cmdSwitch()->key(i));
    }

    //
    // Take Out the Trash
    //
    for(std::list<struct DropboxList *>::iterator 
	  ci=import_dropbox_list.begin();
	ci!=import_dropbox_list.end();ci++) {
      if(!(*ci)->checked) {
	delete *ci;
	import_dropbox_list.erase(ci);
	ci=import_dropbox_list.end();
      }
    }

    sleep(RDIMPORT_DROPBOX_SCAN_INTERVAL);
  } while(import_run);
  if(import_log_mode) {
    PrintLogDateTime();
    printf("rdimport stopped\n");
    fflush(stdout);
  }
}


void MainObject::ProcessFileList(const QString &flist)
{
  QString entry;

  for(unsigned i=0;i<flist.length();i++) {
    entry+=flist.at(i);
  }
  ProcessFileEntry(entry);
}


void MainObject::ProcessFileEntry(const QString &entry)
{
  glob_t globbuf;
  int gflags=GLOB_MARK;

  if(entry=="-") {
    import_stdin_specified=true;
    return;
  }
  globbuf.gl_offs=RDIMPORT_GLOB_SIZE;
  while((globbuf.gl_pathc==RDIMPORT_GLOB_SIZE)||(gflags==GLOB_MARK)) {
    glob(RDEscapeString(entry),gflags,NULL,&globbuf);
    if((globbuf.gl_pathc==0)&&(gflags==GLOB_MARK)&&(!import_drop_box)) {
      PrintLogDateTime(stderr);
      fprintf(stderr," Unable to open \"%s\", skipping...\n",
	      (const char *)entry);
      fflush(stderr);
      globfree(&globbuf);
    }
    for(size_t i=0;i<globbuf.gl_pathc;i++) {
      if(globbuf.gl_pathv[i][strlen(globbuf.gl_pathv[i])-1]!='/') {
	if(!import_single_cart) {
	  import_cart_number=0;
	}
	if(import_drop_box) {
	  VerifyFile(QString::fromUtf8(globbuf.gl_pathv[i]),&import_cart_number);
	}
	else {
	  ImportFile(QString::fromUtf8(globbuf.gl_pathv[i]),&import_cart_number);
	}
      }
    }
    gflags=GLOB_MARK|GLOB_APPEND;
  }
  globfree(&globbuf);
}


MainObject::Result MainObject::ImportFile(const QString &filename,
					  unsigned *cartnum)
{
  bool cart_created=false;
  QString effective_filename;
  bool found_cart=false;
  QDateTime dt;
  bool ok=false;
  RDAudioImport::ErrorCode conv_err;
  RDAudioConvert::ErrorCode audio_conv_err;
  RDGroup *effective_group=new RDGroup(import_group->name());
  RDWaveData *wavedata=new RDWaveData();
  RDWaveFile *wavefile=new RDWaveFile(filename);
  QString err_msg;

  if(wavefile->openWave(wavedata)) {
    effective_filename=filename;
  }
  else {
    if(import_fix_broken_formats) {
      if(import_verbose) {
	PrintLogDateTime();
	printf(" File \"%s\" appears to be malformed, trying workaround ... ",
	       (const char *)RDGetBasePart(filename).utf8());
	fflush(stdout);
      }
      delete wavefile;
      if((wavefile=FixFile(filename,wavedata))==NULL) {
	if(import_verbose) {
	  printf("failed.\n");
	}
	PrintLogDateTime(stderr);
	fprintf(stderr,
		" File \"%s\" is not readable or not a recognized format, skipping...\n",
		(const char *)RDGetBasePart(filename).utf8());
	fflush(stderr);
	delete wavefile;
	delete wavedata;
	delete effective_group;
	if(!import_run) {
	  exit(0);
	}
	if(!import_temp_fix_filename.isEmpty()) {
//	  printf("Fixed Name: %s\n",(const char *)import_temp_fix_filename);
	  QFile::remove(import_temp_fix_filename);
	  import_temp_fix_filename="";
	}
	return MainObject::FileBad;
      }
      if(import_verbose) {
	printf("success.\n");
	fflush(stdout);
      }
      effective_filename=import_temp_fix_filename;
    }
    else {
      PrintLogDateTime(stderr);
      fprintf(stderr,
      " File \"%s\" is not readable or not a recognized format, skipping...\n",
      (const char *)RDGetBasePart(filename).utf8());
      fflush(stderr);
      delete wavefile;
      delete wavedata;
      delete effective_group;
      if(!import_run) {
	exit(0);
      }
      if(!import_temp_fix_filename.isEmpty()) {
	QFile::remove(import_temp_fix_filename);
	import_temp_fix_filename="";
      }
      return MainObject::FileBad;
    }
  }
  if(!import_metadata_pattern.isEmpty()) {
    QString groupname=effective_group->name();
    found_cart=RunPattern(import_metadata_pattern,RDGetBasePart(filename),
			  wavedata,&groupname);
    if(!wavedata->checkDateTimes()) {
      PrintLogDateTime(stderr);
      fprintf(stderr,
	      " File \"%s\": End date/time cannot be prior to start date/time, ignoring...\n",
	      (const char *)filename.utf8());
    }
    if(groupname!=effective_group->name()) {
      delete effective_group;
      effective_group=new RDGroup(groupname);
      if(!effective_group->exists()) {
	PrintLogDateTime(stderr);
	fprintf(stderr," Specified group \"%s\" from file \"%s\" does not exist, using default group...\n",
		(const char *)groupname,(const char *)filename.utf8());
	fflush(stderr);
	delete effective_group;
	effective_group=new RDGroup(import_group->name());
      }
    }
    if(wavedata->metadataFound()&&wavedata->title().isEmpty()) {
      wavedata->setTitle(effective_group->generateTitle(filename));
    }
  }

  if(import_xml) {
    ReadXmlFile(filename,wavedata);
  }

  if(import_use_cartchunk_cutid||found_cart) {
    *cartnum=0;
    sscanf(wavedata->cutId(),"%u",cartnum);
    (*cartnum)+=import_cart_number_offset;
    if((*cartnum==0)||(*cartnum>999999)||
       (effective_group->enforceCartRange()&&
	(!effective_group->cartNumberValid(*cartnum)))) {
      PrintLogDateTime(stderr);
      fprintf(stderr,
	      " File \"%s\" has an invalid or out of range Cart Number, skipping...\n",
	      (const char *)RDGetBasePart(filename).utf8());
      fflush(stderr);
      wavefile->closeWave();
      delete wavefile;
      delete wavedata;
      delete effective_group;
      return MainObject::FileBad;
    }
  }
  if(*cartnum==0) {
    *cartnum=effective_group->nextFreeCart();
  }
  if(*cartnum==0) {
    PrintLogDateTime(stderr);
    fprintf(stderr,"rdimport: no free carts available in specified group\n");
    fflush(stderr);
    wavefile->closeWave();
    delete wavefile;
    delete wavedata;
    delete effective_group;
    if(import_drop_box) {
      if(!import_run) {
	exit(0);
      }
      if(!import_temp_fix_filename.isEmpty()) {
	QFile::remove(import_temp_fix_filename);
	import_temp_fix_filename="";
      }
      return MainObject::NoCart;
    }
    exit(256);
  }
  if(import_delete_cuts) {
    DeleteCuts(import_cart_number);
  }
  cart_created=
    RDCart::create(effective_group->name(),RDCart::Audio,&err_msg,*cartnum)!=0;
  RDCart *cart=new RDCart(*cartnum);
  int cutnum=
    cart->addCut(import_format,import_bitrate,import_channels);
  if(cutnum<0) {
    fprintf(stderr,"rdimport: no free cuts available in cart %06u\n",*cartnum);
    delete cart;
    return MainObject::NoCut;
  }
  RDCut *cut=new RDCut(*cartnum,cutnum);
  RDAudioImport *conv=new RDAudioImport(this);
  conv->setCartNumber(cart->number());
  conv->setCutNumber(cutnum);
  conv->setSourceFile(wavefile->getName());
  RDSettings *settings=new RDSettings();
  settings->setChannels(import_channels);
  switch(import_format) {
  case 0:
    settings->setFormat(RDSettings::Pcm16);
    break;

  case 1:
    settings->setFormat(RDSettings::MpegL2Wav);
    break;
  }
  settings->setNormalizationLevel(import_normalization_level/100);
  settings->setAutotrimLevel(import_autotrim_level/100);
  conv->setDestinationSettings(settings);
  conv->setUseMetadata(cart_created);
  if(import_verbose) {
    PrintLogDateTime();
    if(wavedata->title().length()==0 || ( (wavedata->title().length()>0) && (wavedata->title()[0] == '\0')) ) {
      printf(" Importing file \"%s\" to cart %06u ... ",
	     (const char *)RDGetBasePart(filename).utf8(),*cartnum);
    }
    else {
      if(import_string_title.isNull()) {
	printf(" Importing file \"%s\" [%s] to cart %06u ... ",
	       (const char *)RDGetBasePart(filename).utf8(),
	       (const char *)wavedata->title().stripWhiteSpace(),*cartnum);
      }
      else {
	printf(" Importing file \"%s\" [%s] to cart %06u ... ",
	       (const char *)RDGetBasePart(filename).utf8(),
	       (const char *)import_string_title.stripWhiteSpace(),*cartnum);
      }
    }
    fflush(stdout);
  }

  switch(conv_err=conv->runImport(rda->user()->name(),rda->user()->password(),
				  &audio_conv_err)) {
  case RDAudioImport::ErrorOk:
    if(import_verbose) {
      printf("done.\n");
    }
    break;

  default:
    PrintLogDateTime(stderr);
    fprintf(stderr," %s, skipping %s...\n",
	    (const char *)RDAudioImport::errorText(conv_err,audio_conv_err),
	    (const char *)filename.utf8());
    fflush(stderr);
    if(cart_created) {
      cart->remove(rda->station(),rda->user(),rda->config());
    }
    else {
      cart->removeCut(rda->station(),rda->user(),cut->cutName(),rda->config());
    }
    delete cut;
    delete cart;
    wavefile->closeWave();
    delete wavefile;
    delete wavedata;
    delete effective_group;
    if(!import_run) {
      exit(0);
    }
    if(!import_temp_fix_filename.isEmpty()) {
      QFile::remove(import_temp_fix_filename);
      import_temp_fix_filename="";
    }
    return MainObject::FileBad;
    break;
  }
  if(wavedata->metadataFound()) {
    if(import_autotrim_level!=0) {
      wavedata->setStartPos(-1);
      wavedata->setEndPos(-1);
    }
    if(cart_created) {
      cart->setMetadata(wavedata);
    }
    cut->setMetadata(wavedata);
  }
  cut->autoSegue(import_segue_level,import_segue_length,rda->station(),
		 rda->user(),rda->config());
  if((wavedata->title().length()==0)||
     ((wavedata->title().length()>0)&&(wavedata->title()[0] == '\0'))) {
    QString title=effective_group->generateTitle(filename);
    if((!wavedata->metadataFound())&&(!wavedata->description().isEmpty())) {
      cut->setDescription(title.utf8());
    }
    if(cart_created) {
      cart->setTitle(title.utf8());
    }
  }
  if(import_title_from_cartchunk_cutid) {    
    if((wavedata->cutId().length()>0)&&(wavedata->cutId()[0]!='\0')) {
      if(cut->description().isEmpty()&&
	 (!wavedata->metadataFound())&&
	 (!wavedata->description().isEmpty())) {
	cut->setDescription(wavedata->cutId());
      }
      cart->setTitle(wavedata->cutId());
    }
  }
  if(cut->description().isEmpty()) {      // Final backstop, so we don't end up
    cut->setDescription(cart->title());   // with an empty description field.
  }
  if(!import_metadata_pattern.isEmpty()) {
    cart->setTitle(wavedata->title());
  }
  if(import_startdate_offset!=0) {
    dt=cut->startDatetime(&ok);
    if(ok) {
      cut->setStartDatetime(dt.addDays(import_startdate_offset),true);
    }
  }
  if(import_enddate_offset!=0) {
    dt=cut->endDatetime(&ok);
    if(ok) {
      cut->setEndDatetime(dt.addDays(import_enddate_offset),true);
    }
  }
  if(import_create_dates)  {
    dt=cut->startDatetime(&ok);
    if (!ok){
      dt=QDateTime(QDate::currentDate(), QTime(0,0,0));
      cut->setStartDatetime(dt.addDays(import_create_startdate_offset),true);
    }
    dt=cut->endDatetime(&ok);
    if(!ok) {
      dt=QDateTime(QDate::currentDate(), QTime(23,59,59));
      cut->setEndDatetime(dt.addDays(import_create_enddate_offset),true);
    }
  }
  cut->setOriginName(rda->station()->name());
  for(unsigned i=0;i<import_add_scheduler_codes.size();i++) {
    cart->addSchedCode(import_add_scheduler_codes[i]);
  }
  if(!import_string_agency.isNull()) {
    cart->setAgency(import_string_agency);
  }
  if(!import_string_album.isNull()) {
    cart->setAlbum(import_string_album);
  }
  if(!import_string_artist.isNull()) {
    cart->setArtist(import_string_artist);
  }
  if(import_string_bpm!=0) {
    cart->setBeatsPerMinute(import_string_bpm);
  }
  if(!import_string_client.isNull()) {
    cart->setClient(import_string_client);
  }
  if(!import_string_composer.isNull()) {
    cart->setComposer(import_string_composer);
  }
  if(!import_string_conductor.isNull()) {
    cart->setConductor(import_string_conductor);
  }
  if(!import_string_description.isNull()) {
    cut->setDescription(import_string_description);
  }
  if(!import_string_label.isNull()) {
    cart->setLabel(import_string_label);
  }
  if(!import_string_outcue.isNull()) {
    cut->setOutcue(import_string_outcue);
  }
  if(!import_string_publisher.isNull()) {
    cart->setPublisher(import_string_publisher);
  }
  if(!import_string_song_id.isNull()) {
    cart->setSongId(import_string_song_id);
  }
  if(!import_string_title.isNull()) {
    cart->setTitle(import_string_title);
  }
  if(!import_set_user_defined.isEmpty()) {
    cart->setUserDefined(import_set_user_defined);
  }
  if(!import_string_user_defined.isNull()) {
    cart->setUserDefined(import_string_user_defined);
  }
  if(import_string_year!=0) {
    cart->setYear(import_string_year);
  }
  if((!import_dayparts[0].isNull())||(!import_dayparts[1].isNull())) {
    cut->setStartDaypart(import_dayparts[0],true);
    cut->setEndDaypart(import_dayparts[1],true);
  }
  if(import_clear_dayparts) {
    cut->setStartDaypart(QTime(),false);
    cut->setEndDaypart(QTime(),false);
  }
  if((!import_datetimes[0].isNull())||(!import_datetimes[1].isNull())) {
    cut->setStartDatetime(import_datetimes[0],true);
    cut->setEndDatetime(import_datetimes[1],true);
  }
  if(import_clear_datetimes) {
    cut->setStartDatetime(QDateTime(),false);
    cut->setEndDatetime(QDateTime(),false);
  }
  import_cut_markers->setAudioLength(wavefile->getExtTimeLength());
  if(import_cut_markers->hasStartValue()) {
    cut->setStartPoint(import_cut_markers->startValue());
    cut->setEndPoint(import_cut_markers->endValue());
    cut->setLength(cut->endPoint()-cut->startPoint());
  }
  int lo=cut->startPoint();
  int hi=cut->endPoint();
  import_talk_markers->setAudioLength(wavefile->getExtTimeLength());
  if(import_talk_markers->hasStartValue()) {
    cut->setTalkStartPoint(import_talk_markers->startValue(lo,hi));
    cut->setTalkEndPoint(import_talk_markers->endValue(lo,hi));
  }
  import_hook_markers->setAudioLength(wavefile->getExtTimeLength());
  if(import_hook_markers->hasStartValue()) {
    cut->setHookStartPoint(import_hook_markers->startValue(lo,hi));
    cut->setHookEndPoint(import_hook_markers->endValue(lo,hi));
  }
  import_segue_markers->setAudioLength(wavefile->getExtTimeLength());
  if(import_segue_markers->hasStartValue()) {
    cut->setSegueStartPoint(import_segue_markers->startValue(lo,hi));
    cut->setSegueEndPoint(import_segue_markers->endValue(lo,hi));
  }
  import_fadedown_marker->setAudioLength(wavefile->getExtTimeLength());
  if(import_fadedown_marker->hasFadeValue()) {
    cut->setFadedownPoint(import_fadedown_marker->fadeValue(lo,hi));
  }
  import_fadeup_marker->setAudioLength(wavefile->getExtTimeLength());
  if(import_fadeup_marker->hasFadeValue()) {
    cut->setFadeupPoint(import_fadeup_marker->fadeValue(lo,hi));
  }
  cart->updateLength();
  if(cart_created) {
    SendNotification(RDNotification::AddAction,cart->number());
  }
  else {
    SendNotification(RDNotification::ModifyAction,cart->number());
  }
  delete settings;
  delete conv;
  delete cut;
  delete cart;
  wavefile->closeWave();
  delete wavefile;
  delete wavedata;
  delete effective_group;

  if(import_delete_source) {
    unlink(filename.utf8());
    if(import_verbose) {
      PrintLogDateTime();
      printf(" Deleted file \"%s\"\n",(const char *)RDGetBasePart(filename).utf8());
      fflush(stdout);
    }
  }
  if(!import_run) {
    exit(0);
  }
  if(!import_temp_fix_filename.isEmpty()) {
    QFile::remove(import_temp_fix_filename);
    import_temp_fix_filename="";
  }

  return MainObject::Success;
}


void MainObject::VerifyFile(const QString &filename,unsigned *cartnum)
{
  bool found=false;
  QDateTime dt;

  for(std::list<struct DropboxList *>::const_iterator 
	ci=import_dropbox_list.begin();
      ci!=import_dropbox_list.end();ci++) {
    if((*ci)->filename==filename) {
      found=true;
      QFileInfo *file=new QFileInfo(filename);
      dt=GetCachedTimestamp(filename);
      if(dt.isNull()||(file->lastModified()>dt)) {
	if((file->size()==(*ci)->size)&&(!(*ci)->failed)) {
	  (*ci)->pass++;
	}
	else {
	  (*ci)->size=file->size();
	  (*ci)->pass=0;
	}
	if((*ci)->failed) {
	  (*ci)->checked=true;
	  if(file->size()!=(*ci)->size) {
	    (*ci)->failed=false;
	    (*ci)->size=file->size();
	    (*ci)->pass=0;
	  }
	}
	if((*ci)->pass>=RDIMPORT_DROPBOX_PASSES) {
	  switch(ImportFile(filename,cartnum)) {
	    case MainObject::Success:
	      WriteTimestampCache(filename,file->lastModified());
	      break;
	      
	    case MainObject::FileBad:
	      (*ci)->failed=true;
	      (*ci)->checked=true;
	      (*ci)->pass=0;
	      WriteTimestampCache(filename,file->lastModified());
	      break;
	      
	    case MainObject::NoCart:
	    case MainObject::NoCut:
	      (*ci)->pass=0;
	      (*ci)->checked=true;
	      break;
	  }
	}
	else {
	  (*ci)->checked=true;
	}
      }
      delete file;
    }
  }
  if(!found) {
    QFile *file=new QFile(filename);
    import_dropbox_list.push_back(new struct DropboxList());
    import_dropbox_list.back()->filename=filename;
    import_dropbox_list.back()->size=file->size();
    import_dropbox_list.back()->pass=0;
    import_dropbox_list.back()->checked=true;
    import_dropbox_list.back()->failed=false;
    delete file;
  }
}


RDWaveFile *MainObject::FixFile(const QString &filename,RDWaveData *wavedata)
{
  bool fix_needed=false;

  //
  // Determine Fixability
  //
  int fd=open(filename,O_RDONLY);
  if(fd<0) {
    return NULL;
  }
  if(!IsWav(fd)) {
    return NULL;
  }
  if(!FindChunk(fd,"fmt ",&fix_needed)) {
    return NULL;
  }
  if(!FindChunk(fd,"data",&fix_needed)) {
    return NULL;
  }
  if(!fix_needed) {  // This shouldn't ever happen!
    return NULL;
  }
  ::close(fd);

  //
  // Copy File
  //
  import_temp_fix_filename=
    QString(tempnam(RDTempDirectory::basePath(),"rdfix"))+QString(".wav");
  if(import_temp_fix_filename.isNull()) {
    return NULL;
  }
  if(!RDCopy(filename,import_temp_fix_filename)) {
    return NULL;
  }

  //
  // Apply Fix
  //
  if(!FixChunkSizes(import_temp_fix_filename)) {
    return NULL;
  }
  RDWaveFile *wf=new RDWaveFile();
  wf->nameWave(import_temp_fix_filename);
  if(!wf->openWave(wavedata)) {
    delete wf;
    return NULL;
  }
  return wf;
}


bool MainObject::IsWav(int fd)
{
  int i;
  char buffer[5];
  
  //
  // Is this a riff file? 
  //
  lseek(fd,0,SEEK_SET);
  i=read(fd,buffer,4);
  if(i==4) {
    buffer[4]=0;
    if(strcmp("RIFF",buffer)!=0) {
      return false;
    }
  }
  else {
    return false;
  }

  // 
  // Is this a WAVE file? 
  //
  if(lseek(fd,8,SEEK_SET)!=8) {
    return false;
  }
  i=read(fd,buffer,4);
  if(i==4) {
    buffer[4]=0;
    if(strcmp("WAVE",buffer)!=0) {
      return false;
    }
  }
  else {
    return false;
  }
  return true;
}


bool MainObject::FindChunk(int fd,const char *chunk_name,bool *fix_needed)
{
  int i;
  char name[5]={0,0,0,0,0};
  unsigned char buffer[4];
  unsigned chunk_size;

  lseek(fd,12,SEEK_SET);
  i=read(fd,name,4);
  i=read(fd,buffer,4);
  chunk_size=buffer[0]+(256*buffer[1])+(65536*buffer[2])+(16777216*buffer[3]);
  while(i==4) {
    if(strcasecmp(chunk_name,name)==0) {
      return true;
    }
    lseek(fd,chunk_size,SEEK_CUR);
    i=read(fd,name,4);
    if(name[0]==0) {  // Possible chunk size error
      if(isalpha(name[1])==0) {
	return false;
      }
      name[0]=name[1];
      name[1]=name[2];
      name[2]=name[3];
      if(read(fd,name+3,1)<1) {
	return false;
      }
      *fix_needed=true;
    }
    i=read(fd,buffer,4);
    chunk_size=
      buffer[0]+(256*buffer[1])+(65536*buffer[2])+(16777216*buffer[3]);
  }
  return false;
}


bool MainObject::FixChunkSizes(const QString &filename)
{
  int i;
  char name[5]={0,0,0,0,0};
  unsigned char buffer[4];
  unsigned chunk_size;
  int fd;

  //
  // Open File
  //
  if((fd=open(filename,O_RDWR))<0) {
    return false;
  }
  lseek(fd,12,SEEK_SET);
  i=read(fd,name,4);
  i=read(fd,buffer,4);
  off_t last_offset=lseek(fd,0,SEEK_CUR);
  chunk_size=buffer[0]+(256*buffer[1])+(65536*buffer[2])+(16777216*buffer[3]);
  while(i==4) {
    lseek(fd,chunk_size,SEEK_CUR);
    i=read(fd,name,4);
    if(name[0]==0) {  // Possible chunk size error
      if(isalpha(name[1])==0) {
	return false;
      }
      //
      // Fix It Up
      //
      name[0]=name[1];
      name[1]=name[2];
      name[2]=name[3];
      if(read(fd,name+3,1)<1) {
	::close(fd);
	return false;
      }
      off_t pos=lseek(fd,0,SEEK_CUR);
      char buf[4];
      lseek(fd,last_offset,SEEK_SET);
      read(fd,buf,4);
      unsigned size=(0xff&buf[0])+(0xff&(256*buf[1]))+
	(0xff&(65536*buf[2]))+(0xff&(16777216*buf[3]))+1;
      buf[0]=size&0xff;
      buf[1]=(size>>8)&0xff;
      buf[2]=(size>>16)&0xff;
      buf[3]=(size>>24)&0xff;
      lseek(fd,last_offset,SEEK_SET);
      write(fd,buf,4);
      lseek(fd,pos,SEEK_SET);
    }
    last_offset=lseek(fd,0,SEEK_CUR);
    i=read(fd,buffer,4);
    chunk_size=
      buffer[0]+(256*buffer[1])+(65536*buffer[2])+(16777216*buffer[3]);
  }
  ::close(fd);
  return true;
}


bool MainObject::RunPattern(const QString &pattern,const QString &filename,
			    RDWaveData *wavedata,QString *groupname)
{
  bool macro_active=false;
  unsigned ptr=0;
  QChar field;
  QString value;
  QChar delimiter;
  bool found_cartnum=false;
  bool found_end_date=false;
  bool found_start_time=false;
  bool found_end_time=false;
  QTime time;
  QDate date;

  //
  // Initialize Pattern Parser
  //
  if((pattern.at(0)=='%')&&(pattern.at(1)!='%')) {
    field=pattern.at(1);
    value="";
    delimiter=pattern.at(2);
    ptr=3;
    macro_active=true;
  }
  else {
    delimiter=pattern.at(0);
    ptr=1;
  }

  for(unsigned i=0;i<=filename.length();i++) {
    if(macro_active) {
      if((filename.at(i)==delimiter)||(i==filename.length())) {
	switch(field) {
	case 'a':
	  wavedata->setArtist(value);
	  wavedata->setMetadataFound(true);
	  break;
  
	case 'b':
	  wavedata->setLabel(value);
	  wavedata->setMetadataFound(true);
	  break;

	case 'c':
	  wavedata->setClient(value);
	  wavedata->setMetadataFound(true);
	  break;

	case 'e':
	  wavedata->setAgency(value);
	  wavedata->setMetadataFound(true);
	  break;

	case 'g':
	  *groupname=value;
	  break;

	case 'i':
	  wavedata->setDescription(value);
	  wavedata->setMetadataFound(true);
	  break;

	case 'k':
	  time=QTime::fromString(value);
	  if(time.isValid()) {
	    wavedata->setStartTime(time);
	    found_start_time=true;
	    wavedata->setMetadataFound(true);
	  }
	  break;

	case 'K':
	  time=QTime::fromString(value);
	  if(time.isValid()) {
	    wavedata->setEndTime(time);
	    found_end_time=true;
	    wavedata->setMetadataFound(true);
	  }
	  break;

	case 'l':
	  wavedata->setAlbum(value);
	  wavedata->setMetadataFound(true);
	  break;

	case 'm':
	  wavedata->setComposer(value);
	  wavedata->setMetadataFound(true);
	  break;

	case 'n':
	  wavedata->setCutId(value);
	  wavedata->setMetadataFound(true);
	  found_cartnum=true;
	  break;

	case 'o':
	  wavedata->setOutCue(value);
	  wavedata->setMetadataFound(true);
	  break;

	case 'p':
	  wavedata->setPublisher(value);
	  wavedata->setMetadataFound(true);
	  break;

	case 'q':
	  date=QDate::fromString(value,Qt::ISODate);
	  if(date.isValid()) {
	    wavedata->setStartDate(date);
	    wavedata->setMetadataFound(true);
	    if(!found_end_date) {
	      wavedata->setEndDate(date);
	      if(!found_end_time) {
		wavedata->setEndTime(QTime(23,59,59));
	      }
	    }
	    if(!found_start_time) {
	      wavedata->setStartTime(QTime(0,0,0,1));
	    }
	  }
	  break;

	case 'Q':
	  date=QDate::fromString(value,Qt::ISODate);
	  if(date.isValid()) {
	    wavedata->setEndDate(date);
	    found_end_date=true;
	    wavedata->setMetadataFound(true);
	    if(!found_end_time) {
	      wavedata->setEndTime(QTime(23,59,59));
	    }
	  }
	  break;

	case 'r':
	  wavedata->setConductor(value);
	  wavedata->setMetadataFound(true);
	  break;

	case 's':
	  wavedata->setTmciSongId(value);
	  wavedata->setMetadataFound(true);
	  break;

	case 't':
	  wavedata->setTitle(value);
	  wavedata->setMetadataFound(true);
	  break;

	case 'u':
	  wavedata->setUserDefined(value);
	  wavedata->setMetadataFound(true);
	  break;

	case 'y':
	  wavedata->setReleaseYear(value.toInt());
	  wavedata->setMetadataFound(true);
	  break;
	}
	value="";
	if((ptr>=pattern.length())||(i==filename.length())) {
	  return found_cartnum;
	}
	if((pattern.at(ptr)=='%')&&(pattern.at(ptr+1)!='%')) {
	  field=pattern.at(ptr+1);
	  delimiter=pattern.at(ptr+2);
	  ptr+=3;
	  macro_active=true;
	}
	else {
	  delimiter=pattern.at(ptr);
	  ptr++;
	  macro_active=false;
	}
      }
      else {
	value+=filename.at(i);
      }
    }
    else {
      if((ptr>=pattern.length())||(i==filename.length())) {
	return found_cartnum;
      }
      if(filename.at(i)!=delimiter) {
	return found_cartnum;
      }
      if((pattern.at(ptr)=='%')&&(pattern.at(ptr+1)!='%')) {
	field=pattern.at(ptr+1);
	delimiter=pattern.at(ptr+2);
	ptr+=3;
	macro_active=true;
      }
      else {
	delimiter=pattern.at(ptr);
	ptr++;
	macro_active=false;
      }
    }
  }

  return found_cartnum;
}


bool MainObject::VerifyPattern(const QString &pattern)
{
  bool macro_active=false;
  for(unsigned i=0;i<pattern.length();i++) {
    if(pattern.at(i)==QChar('%')) {
      if(macro_active) {
	return false;
      }
      macro_active=true;
      switch(pattern.at(++i)) {
      case 'a':
      case 'b':
      case 'c':
      case 'e':
      case 'g':
      case 'i':
      case 'k':
      case 'K':
      case 'l':
      case 'm':
      case 'n':
      case 'o':
      case 'p':
      case 'q':
      case 'Q':
      case 'r':
      case 's':
      case 't':
      case 'u':
      case 'y':
      case '%':
	break;

      default:
	return false;
      }
    }
    else {
      macro_active=false;
    }
  }
  return true;
}


void MainObject::PrintLogDateTime(FILE *f)
{
  if(import_log_mode) {
    fprintf(f,"%s - %s : ",
	   (const char *)QDate::currentDate().toString("MM-dd-yyyy"),
	   (const char *)QTime::currentTime().toString("hh:mm:ss"));
  }
}


void MainObject::DeleteCuts(unsigned cartnum)
{
  if(import_verbose) {
    PrintLogDateTime();
    printf(" Deleting cuts from cart %06u\n",cartnum);
    fflush(stdout);
  }
  unsigned dev;
  RDCart *cart=new RDCart(cartnum);
  cart->removeAllCuts(rda->station(),rda->user(),rda->config());
  cart->updateLength();
  cart->resetRotation();
  cart->calculateAverageLength(&dev);
  cart->setLengthDeviation(dev);
  delete cart;
}


QDateTime MainObject::GetCachedTimestamp(const QString &filename)
{
  QString sql;
  RDSqlQuery *q;
  QDateTime dt;
  if(import_persistent_dropbox_id<0) {
    return dt;
  }
  sql=QString().sprintf("select FILE_DATETIME from DROPBOX_PATHS where ")+
    QString().sprintf("(DROPBOX_ID=%d)&&",import_persistent_dropbox_id)+
    "(FILE_PATH=\""+RDEscapeString(filename)+"\")";
  q=new RDSqlQuery(sql);
  if(q->first()) {
    dt=q->value(0).toDateTime();
  }
  delete q;
  return dt;
}


void MainObject::WriteTimestampCache(const QString &filename,
				     const QDateTime &dt)
{
  QString sql;
  RDSqlQuery *q;
  if(import_persistent_dropbox_id<0) {
    return;
  }
  if(GetCachedTimestamp(filename).isNull()) {
    sql=QString("insert into DROPBOX_PATHS set ")+
      QString().sprintf("DROPBOX_ID=%d,",import_persistent_dropbox_id)+
      "FILE_PATH=\""+RDEscapeString(filename)+"\","+
      "FILE_DATETIME="+RDCheckDateTime(dt,"yyyy-MM-dd hh:mm:ss");
  }
  else {
    sql=QString("update DROPBOX_PATHS set ")+
      "FILE_DATETIME="+RDCheckDateTime(dt,"yyyy-MM-dd hh:mm:ss")+" where "+
      QString().sprintf("(DROPBOX_ID=%d)&&",import_persistent_dropbox_id)+
      "(FILE_PATH=\""+RDEscapeString(filename)+"\")";
  }
  q=new RDSqlQuery(sql);
  delete q;
}


bool MainObject::SchedulerCodeExists(const QString &code) const
{
  QString sql;
  RDSqlQuery *q;
  bool ret=false;

  sql=QString("select CODE from SCHED_CODES where CODE=\"")+
    RDEscapeString(code)+"\"";
  q=new RDSqlQuery(sql);
  ret=q->first();
  delete q;

  return ret;
}


void MainObject::ReadXmlFile(const QString &basename,RDWaveData *wavedata) const
{
  QString xmlname="";
  FILE *f=NULL;
  char line[1024];
  QString xml="";
  std::vector<RDWaveData> wavedatas;

  //
  // Get XML Filename
  //
  QStringList f0=f0.split(".",basename);
  for(unsigned i=0;i<f0.size()-1;i++) {
    xmlname+=f0[i]+".";
  }
  xmlname+="xml";
  if(import_verbose) {
    printf(" Reading xml metadata from \"%s\": ",(const char *)xmlname);
  }

  //
  // Read XML
  //
  wavedata->clear();
  if((f=fopen(xmlname,"r"))==NULL) {
    if(import_verbose) {
      printf("failed [%s]\n",strerror(errno));
      return;
    }
  }
  if(import_verbose) {
    printf("success\n");
  }
  while(fgets(line,1024,f)!=NULL) {
    xml+=line;
  }
  fclose(f);

  //
  // Parse
  //
  if(RDCart::readXml(&wavedatas,xml)>0) {
    *wavedata=wavedatas[1];
  }
}


void MainObject::SendNotification(RDNotification::Action action,
				  unsigned cartnum)
{
  RDNotification *notify=
    new RDNotification(RDNotification::CartType,action,QVariant(cartnum));
  rda->ripc()->sendNotification(*notify);
  qApp->processEvents();
  delete notify;
}


int main(int argc,char *argv[])
{
  QApplication a(argc,argv,false);
  new MainObject();
  return a.exec();
}