// rdimport.cpp
//
// A Batch Importer for Rivendell.
//
//   (C) Copyright 2002-2022 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 <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <glob.h>
#include <limits.h>
#include <math.h>
#include <sched.h>
#include <signal.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <QApplication>

#include <rd.h>
#include <rdaudioimport.h>
#include <rdconfig.h>
#include <rdconf.h>
#include <rdcut.h>
#include <rddatedecode.h>
#include <rddisclookup.h>
#include <rdescape_string.h>
#include <rdlibrary_conf.h>
#include <rdstringlist.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_syslog=false;
  import_log_file=false;
  import_log_filename="";
  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;
  import_failed_imports=0;
  import_send_mail=false;
  import_mail_per_file=false;
  import_journal=NULL;
  import_dump_isci_xref=false;
  import_by_isci_program_code="";
  import_by_isci=false;
 
  //
  // Open the Database
  //
  rda=static_cast<RDApplication *>(new RDCoreApplication("rdimport","rdimport",
							 RDIMPORT_USAGE,this));
  if(!rda->open(&err_msg)) {
    fprintf(stderr,"rdimport: %s\n",err_msg.toUtf8().constData());
    ErrorExit(RDApplication::ExitNoDb);
  }

  //
  // Read Command Options
  //
  if(rda->cmdSwitch()->keys()<2) {
    Log(LOG_ERR,QString::asprintf("\n%s\n",RDIMPORT_USAGE));
    ErrorExit(RDApplication::ExitInvalidOption);
  }
  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-syslog") {
      import_log_syslog=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--log-filename") {
      import_log_filename=rda->cmdSwitch()->value(i);
      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)) {
	Log(LOG_ERR,QString("rdimport: invalid cart number\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      if(import_use_cartchunk_cutid) {
	Log(LOG_ERR,QString("rdimport: '--to-cart' and '--use-cartchunk-cutid' are mutually exclusive\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      import_single_cart=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--use-cartchunk-cutid") {
      if(import_cart_number!=0) {
	Log(LOG_ERR,QString("rdimport: '--to-cart' and '--use-cartchunk-cutid' are mutually exclusive\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      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) {
	Log(LOG_ERR,QString("rdimport: invalid --cart-number-offset\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      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)=="--dump-isci-xref") {
      import_dump_isci_xref=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--by-isci") {
      import_by_isci=true;
      if(!rda->cmdSwitch()->value(i).isEmpty()) {
	RDSvc *svc=new RDSvc(rda->cmdSwitch()->value(i),
			     rda->station(),rda->config(),this);
	if(!svc->exists()) {
	  Log(LOG_ERR,QString("rdimport: no such service\n"));
	  ErrorExit(RDApplication::ExitNoSvc);
	}
	import_by_isci_program_code=svc->programCode();
	delete svc;
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--dump-isci-xref") {
      import_dump_isci_xref=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--by-isci") {
      import_by_isci=true;
      if(!rda->cmdSwitch()->value(i).isEmpty()) {
	RDSvc *svc=new RDSvc(rda->cmdSwitch()->value(i),
			     rda->station(),rda->config(),this);
	if(!svc->exists()) {
	  Log(LOG_ERR,QString("rdimport: no such service\n"));
	  ErrorExit(RDApplication::ExitNoSvc);
	}
	import_by_isci_program_code=svc->programCode();
	delete svc;
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--startdate-offset") {
      import_startdate_offset=rda->cmdSwitch()->value(i).toInt(&ok);
      if(!ok) {
	Log(LOG_ERR,QString("rdimport: invalid startdate-offset\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--enddate-offset") {
      import_enddate_offset=rda->cmdSwitch()->value(i).toInt(&ok);
      if(!ok) {
	Log(LOG_ERR,QString("rdimport: invalid enddate-offset\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      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=rda->cmdSwitch()->value(i).split(",");
      if(f0.size()!=2) {
	Log(LOG_ERR,QString("rdimport: invalid argument to --set-datetimes\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      for(unsigned j=0;j<2;j++) {
	if((f0[j].length()!=15)||(f0[j].mid(8,1)!="-")) {
	  Log(LOG_ERR,QString("rdimport: invalid argument to --set-datetimes\n"));
	  ErrorExit(RDApplication::ExitInvalidOption);
	}
	unsigned year=f0[j].left(4).toUInt(&ok);
	if(!ok) {
	  Log(LOG_ERR,QString("rdimport: invalid year argument to --set-datetimes\n"));
	  ErrorExit(RDApplication::ExitInvalidOption);
	}
	unsigned month=f0[j].mid(4,2).toUInt(&ok);
	if((!ok)||(month>12)) {
	  Log(LOG_ERR,QString("rdimport: invalid month argument to --set-datetimes\n"));
	  ErrorExit(RDApplication::ExitInvalidOption);
	}
	unsigned day=f0[j].mid(6,2).toUInt(&ok);
	if((!ok)||(day>31)) {
	  Log(LOG_ERR,QString("rdimport: invalid day argument to --set-datetimes\n"));
	  ErrorExit(RDApplication::ExitInvalidOption);
	}
	unsigned hour=f0[j].mid(9,2).toUInt(&ok);
	if((!ok)||(hour>23)) {
	  Log(LOG_ERR,QString("rdimport: invalid hour argument to --set-datetimes\n"));
	  ErrorExit(RDApplication::ExitInvalidOption);
	}
	unsigned min=f0[j].mid(11,2).toUInt(&ok);
	if((!ok)||(min>59)) {
	  Log(LOG_ERR,QString("rdimport: invalid minute argument to --set-datetimes\n"));
	  ErrorExit(RDApplication::ExitInvalidOption);
	}
	unsigned sec=f0[j].right(2).toUInt(&ok);
	if((!ok)||(sec>59)) {
	  Log(LOG_ERR,QString("rdimport: invalid seconds argument to --set-datetimes\n"));
	  ErrorExit(RDApplication::ExitInvalidOption);
	}
	import_datetimes[j]=QDateTime(QDate(year,month,day),
				      QTime(hour,min,sec));
	if(!import_datetimes[j].isValid()) {
	  Log(LOG_ERR,QString("rdimport: invalid argument to --set-datetimes\n"));
	}
      }
      if(import_datetimes[0]>=import_datetimes[1]) {
	Log(LOG_ERR,QString("rdimport: datetime cannot end before it begins\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-daypart-times") {
      QStringList f0=rda->cmdSwitch()->value(i).split(",");
      if(f0.size()!=2) {
	Log(LOG_ERR,QString("rdimport: invalid argument to --set-daypart-times\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      for(unsigned j=0;j<2;j++) {
	if(f0[j].length()!=6) {
	  Log(LOG_ERR,QString("rdimport: invalid argument to --set-daypart-times\n"));
	  ErrorExit(RDApplication::ExitInvalidOption);
	}
	unsigned hour=f0[j].left(2).toUInt(&ok);
	if((!ok)||(hour>23)) {
	  Log(LOG_ERR,QString("rdimport: invalid hour argument to --set-daypart-times\n"));
	  ErrorExit(RDApplication::ExitInvalidOption);
	}
	unsigned min=f0[j].mid(2,2).toUInt(&ok);
	if((!ok)||(min>59)) {
	  Log(LOG_ERR,QString("rdimport: invalid minute argument to --set-daypart-times\n"));
	  ErrorExit(RDApplication::ExitInvalidOption);
	}
	unsigned sec=f0[j].right(2).toUInt(&ok);
	if((!ok)||(sec>59)) {
	  Log(LOG_ERR,QString("rdimport: invalid seconds argument to --set-daypart-times\n"));
	  ErrorExit(RDApplication::ExitInvalidOption);
	}
	import_dayparts[j].setHMS(hour,min,sec);
      }
      if(import_dayparts[0]>=import_dayparts[1]) {
	Log(LOG_ERR,QString("rdimport: daypart cannot end before it begins\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      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)) {
	Log(LOG_ERR,QString("rdimport: invalid --metadata-pattern\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--output-pattern") {
      import_output_pattern=rda->cmdSwitch()->value(i);
      if(!VerifyPattern(import_output_pattern)) {
	Log(LOG_ERR,QString("rdimport: invalid --output-pattern\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      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) {
	Log(LOG_ERR,QString("rdimport: invalid persistent dropbox id\n"));
	ErrorExit(RDApplication::ExitNoDropbox);
      }
      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) {
	Log(LOG_ERR,QString("rdimport: invalid create-startddate-offset\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      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 )) {
	Log(LOG_ERR,QString("rdimport: invalid create-enddate-offset\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      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) {
	Log(LOG_ERR,QString("rdimport: invalid value for --set-string-bpm\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      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-isci") {
      import_string_isci=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-isrc") {
      if(RDDiscLookup::isrcIsValid(rda->cmdSwitch()->value(i))) {
	import_string_isrc=rda->cmdSwitch()->value(i);
	rda->cmdSwitch()->setProcessed(i,true);
      }
      else {
	Log(LOG_ERR,"invalid ISRC \""+rda->cmdSwitch()->value(i)+"\"\n");
	ErrorExit(RDApplication::ExitInvalidOption);
      }
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-recording-mbid") {
      import_string_recording_mbid=rda->cmdSwitch()->value(i);
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--set-string-release-mbid") {
      import_string_release_mbid=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()) {
	Log(LOG_ERR,QString("rdimport: title field cannot be empty\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      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) {
	Log(LOG_ERR,QString("rdimport: invalid value for --set-string-year\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--xml") {
      import_xml=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--send-mail") {
      import_send_mail=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
    if(rda->cmdSwitch()->key(i)=="--mail-per-file") {
      import_mail_per_file=true;
      import_send_mail=true;
      rda->cmdSwitch()->setProcessed(i,true);
    }
  }

  //
  // Sanity Checks
  //
  if(import_datetimes[0].isValid()&&import_clear_datetimes) {
    Log(LOG_ERR,QString("rdimport: --set-datetimes and --clear-datetimes are mutually exclusive\n"));
    ErrorExit(RDApplication::ExitInvalidOption);
  }
  if((!import_dayparts[1].isNull())&&import_clear_dayparts) {
    Log(LOG_ERR,QString("rdimport: --set-daypart-times and --clear-daypart-times are mutually exclusive\n"));
    ErrorExit(RDApplication::ExitInvalidOption);
  }
  if((!import_metadata_pattern.isEmpty())&&import_xml) {
    Log(LOG_ERR,QString::asprintf("rdimport: --metadata-pattern and --xml are mutually exclusive\n"));
    ErrorExit(RDApplication::ExitInvalidOption);
  }
  if((!import_log_filename.isEmpty())&&import_log_syslog) {
    Log(LOG_ERR,QString::asprintf("rdimport: --log-filename and --log-syslog are mutually exclusive\n"));
    ErrorExit(RDApplication::ExitInvalidOption);
  }
  if((import_cart_number>0)&&import_by_isci) {
    Log(LOG_ERR,QString().sprintf("rdimport: --to-cart and --by-isci are mutually exclusive\n"));
    ErrorExit(RDApplication::ExitInvalidOption);
  }
 
  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()) {
	Log(LOG_ERR,QString::asprintf("rdimport: invalid group specified\n"));
	delete import_group;
	ErrorExit(RDApplication::ExitNoGroup);
      }
      import_file_key=i+1;
      i=rda->cmdSwitch()->keys();
    }
  }
  if(import_group==NULL) {
    Log(LOG_ERR,QString::asprintf("rdimport: invalid group specified\n"));
    ErrorExit(RDApplication::ExitNoGroup);
  }
  if(import_cart_number>0) {
    if(!import_group->cartNumberValid(import_cart_number)) {
      Log(LOG_ERR,QString::asprintf("rdimport: invalid cart number for group\n"));
      delete import_group;
      ErrorExit(RDApplication::ExitInvalidCart);
    }
  }

  //
  // Verify Scheduler Codes
  //
  for(unsigned i=0;i<import_add_scheduler_codes.size();i++) {
    if(!SchedulerCodeExists(import_add_scheduler_codes[i])) {
      Log(LOG_ERR,QString::asprintf("rdimport: scheduler code \"%s\" does not exist\n",
	      (const char *)import_add_scheduler_codes[i].toUtf8()));
      ErrorExit(RDApplication::ExitNoSchedCode);
    }
  }

  //
  // 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 {
	Log(LOG_ERR,QString("rdimport: invalid normalization level\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      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 {
	Log(LOG_ERR,QString("rdimport: invalid autotrim level\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      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 {
	Log(LOG_ERR,QString("rdimport: invalid segue level\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      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 {
	Log(LOG_ERR,QString("rdimport: invalid segue length\n"));
	ErrorExit(RDApplication::ExitInvalidOption);
      }
      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)=="--")) {
      Log(LOG_ERR,QString::asprintf("rdimport: unknown command option \"%s\"\n",
			       rda->cmdSwitch()->key(i).toUtf8().constData()));
      ErrorExit(RDApplication::ExitInvalidOption);
    }
  }
  if(import_to_mono) {
    import_channels=1;
  }

  //
  // Print Status Messages
  //
  Log(LOG_INFO,QString("rdimport started"));

  Log(LOG_INFO,QString::asprintf("RDImport v%s\n",VERSION));
  if(import_to_mono) {
    Log(LOG_INFO,QString(" Force to Mono is ON\n"));
  }
  else {
    Log(LOG_INFO,QString(" Force to Mono is OFF\n"));
  }
  if(import_normalization_level==0) {
    Log(LOG_INFO,QString(" Normalization is OFF\n"));
  }
  else {
    Log(LOG_INFO,QString::asprintf(" Normalization level = %d dB\n",import_normalization_level/100));
  }
  if(import_autotrim_level==0) {
    Log(LOG_INFO,QString(" AutoTrim is OFF\n"));
  }
  else {
    Log(LOG_INFO,QString::asprintf(" AutoTrim level = %d dB\n",import_autotrim_level/100));
  }
  if(import_cart_number==0) {
    if(import_use_cartchunk_cutid) {
      Log(LOG_INFO,QString(" Destination cart is taken from CartChunk CutID\n"));
    }
    else {
      Log(LOG_INFO,QString(" Destination cart is AUTO\n"));
    }
  }
  else {
    Log(LOG_INFO,QString::asprintf(" Destination cart is %06u\n",import_cart_number));
  }
  if(import_single_cart) {
    Log(LOG_INFO,QString(" Single cart mode is ON\n"));
  }
  else {
    Log(LOG_INFO,QString(" Single cart mode is OFF\n"));
  }
  if(import_title_from_cartchunk_cutid) {
    Log(LOG_INFO,QString(" Destination cart title is taken from CartChunk CutID\n"));
  }
  if(import_cart_number_offset!=0) {
    Log(LOG_INFO,QString::asprintf(" Cart number offset is %d\n",import_cart_number_offset));
  }
  if(import_delete_source) {
    Log(LOG_INFO,QString(" Delete source mode is ON\n"));
  }
  else {
    Log(LOG_INFO,QString(" Delete source mode is OFF\n"));
  }
  if(import_delete_cuts) {
    Log(LOG_INFO,QString(" Delete cuts mode is ON\n"));
  }
  else {
    Log(LOG_INFO,QString(" Delete cuts mode is OFF\n"));
  }
  if(import_dump_isci_xref) {
    Log(LOG_INFO,QString(" Dump ISCI Cross Reference File is ON\n"));
  }
  else {
    Log(LOG_INFO,QString(" Dump ISCI Cross Reference File is OFF\n"));
  }
  if(import_by_isci) {
    Log(LOG_INFO,QString(" Import by ISCI Code is ON\n"));
  }
  else {
    Log(LOG_INFO,QString(" Import by ISCI Code is OFF\n"));
  }
  if(import_drop_box) {
    Log(LOG_INFO,QString(" DropBox mode is ON\n"));
  }
  else {
    Log(LOG_INFO,QString(" DropBox mode is OFF\n"));
  }
  if(import_add_scheduler_codes.size()>0) {
    Log(LOG_INFO,QString(" Adding Scheduler Code(s):\n"));
    for(unsigned i=0;i<import_add_scheduler_codes.size();i++) {
      Log(LOG_INFO,QString::asprintf(" %s\n",(const char *)import_add_scheduler_codes[i].toUtf8()));
    }
  }
  if(!import_set_user_defined.isEmpty()) {
    Log(LOG_INFO,QString::asprintf(" Setting the User Defined field to \"%s\"\n",
			   import_set_user_defined.toUtf8().constData()));
  }
  if(!import_metadata_pattern.isEmpty()) {
    Log(LOG_INFO,QString::asprintf(" Using metadata pattern: %s\n",
				import_metadata_pattern.toUtf8().constData()));
  }
  if(!import_output_pattern.isEmpty()) {
    Log(LOG_INFO,QString::asprintf(" Using output pattern: %s\n",
				import_output_pattern.toUtf8().constData()));
  }
  Log(LOG_INFO,QString::asprintf(" Start Date Offset = %d days\n",import_startdate_offset));
  Log(LOG_INFO,QString::asprintf(" End Date Offset = %d days\n",import_enddate_offset));
  if((!import_dayparts[0].isNull())||(!import_dayparts[1].isNull())) {
    Log(LOG_INFO,QString::asprintf(" Start Daypart = %s\n",
				   import_dayparts[0].toString("hh:mm:ss").
				   toUtf8().constData()));
    Log(LOG_INFO,QString::asprintf(" End Daypart = %s\n",
				   import_dayparts[1].toString("hh:mm:ss").
				   toUtf8().constData()));
  }
  if(import_clear_dayparts) {
    Log(LOG_INFO,QString(" Clearing daypart times\n"));
  }
  if((!import_datetimes[0].isNull())||(!import_datetimes[1].isNull())) {
    Log(LOG_INFO,QString::asprintf(" Start DateTime = %s\n",
				   import_datetimes[0].toString("MM/dd/yyyy hh:mm:ss").toUtf8().constData()));
    Log(LOG_INFO,QString::asprintf(" End DateTime = %s\n",
				   import_datetimes[1].toString("MM/dd/yyyy hh:mm:ss").toUtf8().constData()));
  }
  if(import_clear_datetimes) {
    Log(LOG_INFO,QString(" Clearing datetimes\n"));
  }
  if(import_fix_broken_formats) {
    Log(LOG_INFO,QString(" Broken format workarounds are ENABLED\n"));
  }
  else {
    Log(LOG_INFO,QString(" Broken format workarounds are DISABLED\n"));
  }
  if(import_send_mail) {
    if(import_mail_per_file) {
      Log(LOG_INFO,QString(" E-mail report per file is ENABLED\n"));
    }
    else {
      Log(LOG_INFO,QString(" Summary e-mail report is ENABLED\n"));
    }
  }
  else {
    Log(LOG_INFO,QString(" E-mail reporting is DISABLED\n"));
  }
  if(import_create_dates) {
    Log(LOG_INFO,QString(" Import Create Dates mode is ON\n"));
    Log(LOG_INFO,QString::asprintf(" Import Create Start Date Offset = %d days\n",import_create_startdate_offset));
    Log(LOG_INFO,QString::asprintf(" Import Create End Date Offset = %d days\n",import_create_enddate_offset));
  }
  else {
    Log(LOG_INFO,QString(" Import Create Dates mode is OFF\n"));
  }
  if(import_persistent_dropbox_id>=0) {
    Log(LOG_INFO,QString::asprintf(" Persistent DropBox ID = %d\n",import_persistent_dropbox_id));
  }
  if(!import_string_agency.isNull()) {
    Log(LOG_INFO,QString::asprintf(" Agency set to: %s\n",import_string_agency.
				   toUtf8().constData()));
  }
  if(!import_string_album.isNull()) {
    Log(LOG_INFO,QString::asprintf(" Album set to: %s\n",
				   import_string_album.toUtf8().constData()));
  }
  if(!import_string_artist.isNull()) {
    Log(LOG_INFO,QString::asprintf(" Artist set to: %s\n",
				   import_string_artist.toUtf8().constData()));
  }
  if(import_string_bpm!=0) {
    Log(LOG_INFO,QString::asprintf(" BPM set to: %d\n",import_string_bpm));
  }
  if(!import_string_client.isNull()) {
    Log(LOG_INFO,QString::asprintf(" Client set to: %s\n",
				   import_string_client.toUtf8().constData()));
  }
  if(!import_string_composer.isNull()) {
    Log(LOG_INFO,QString::asprintf(" Composer set to: %s\n",
       			   import_string_composer.toUtf8().constData()));
  }
  if(!import_string_conductor.isNull()) {
    Log(LOG_INFO,QString::asprintf(" Conductor set to: %s\n",
				   import_string_conductor.toUtf8().constData()));
  }
  if(!import_string_description.isNull()) {
    Log(LOG_INFO,QString::asprintf(" Description set to: %s\n",
				   import_string_description.toUtf8().constData()));
  }
  if(!import_string_label.isNull()) {
    Log(LOG_INFO,QString::asprintf(" Label set to: %s\n",
				   import_string_label.toUtf8().constData()));
  }
  if(!import_string_outcue.isNull()) {
    Log(LOG_INFO,QString::asprintf(" Outcue set to: %s\n",
				   import_string_outcue.toUtf8().constData()));
  }
  if(!import_string_isci.isNull()) {
    Log(LOG_INFO,QString::asprintf(" ISCI set to: %s\n",
				   import_string_isci.toUtf8().constData()));
  }
  if(!import_string_isrc.isNull()) {
    Log(LOG_INFO,QString::asprintf(" ISRC set to: %s\n",
				   import_string_isrc.toUtf8().constData()));
  }
  if(!import_string_recording_mbid.isNull()) {
    Log(LOG_INFO,QString::asprintf(" MusicBrainz recording ID set to: %s\n",
				   import_string_recording_mbid.toUtf8().constData()));
  }
  if(!import_string_release_mbid.isNull()) {
    Log(LOG_INFO,QString::asprintf(" MusicBrainz release ID set to: %s\n",
				   import_string_release_mbid.toUtf8().constData()));
  }
  if(!import_string_publisher.isNull()) {
    Log(LOG_INFO,QString::asprintf(" Publisher set to: %s\n",
				   import_string_publisher.toUtf8().constData()));
  }
  if(!import_string_song_id.isNull()) {
    Log(LOG_INFO,QString::asprintf(" Song ID set to: %s\n",
				   import_string_song_id.toUtf8().constData()));
  }
  if(!import_string_title.isNull()) {
    Log(LOG_INFO,QString::asprintf(" Title set to: %s\n",
				   import_string_title.toUtf8().constData()));
  }
  if(!import_string_user_defined.isNull()) {
    Log(LOG_INFO,QString::asprintf(" User Defined set to: %s\n",
				   import_string_user_defined.toUtf8().constData()));
  }
  if(import_string_year!=0) {
    Log(LOG_INFO,QString::asprintf(" Year set to: %d\n",import_string_year));
  }
  if(import_xml) {
    Log(LOG_INFO,QString::asprintf(" 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();
  Log(LOG_INFO,QString(" Files to process:\n"));
  for(unsigned i=import_file_key;i<rda->cmdSwitch()->keys();i++) {
    if(rda->cmdSwitch()->key(i)=="-") {
      if(!import_stdin_specified) {
	Log(LOG_INFO,"   [stdin]\n");
	import_stdin_specified=true;
      }
    }
    else {
      Log(LOG_INFO,QString::asprintf("   \"%s\"\n",
			     rda->cmdSwitch()->key(i).toUtf8().constData()));
    }
  }

  //
  // ISCI Code Lookups
  //
  if(import_dump_isci_xref) {
    QString err_msg;
    if(LoadIsciXref(&err_msg,rda->system()->isciXreferencePath())) {
      for(QMap<QString,RDWaveData *>::const_iterator it=
	    import_isci_xref.begin();
	  it!=import_isci_xref.end();it++) {
	printf("%32s : %06u | %s\n",it.key().toUtf8().constData(),
	       it.value()->cartNumber(),
	       it.value()->title().toUtf8().constData());
      }
      exit(RDApplication::ExitOk);
    }
    else {
      fprintf(stderr,"rdimport: isci Xref load failed [%s]\n",
	      err_msg.toUtf8().constData());
      exit(RDApplication::ExitImportFailed);
    }
  }
  if(import_by_isci) {
    QString err_msg;
    if(!LoadIsciXref(&err_msg,rda->system()->isciXreferencePath())) {
      fprintf(stderr,"rdimport: isci Xref load failed [%s]\n",
	      err_msg.toUtf8().constData());
      exit(RDApplication::ExitImportFailed);
    }
  }

  //
  // Start the email journal
  //
  import_journal=new Journal(import_mail_per_file);

  // 
  // 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()->createCarts()) {
    Log(LOG_ERR,
	QString::asprintf("rdimport: user \"%s\" has no Create Carts permission\n",
			  rda->user()->name().toUtf8().constData()));
    ErrorExit(RDApplication::ExitNoPerms);
  }

  if(import_verbose) {
    printf(" running as user \"%s\"\n",rda->user()->name().toUtf8().constData());
  }

  //
  // Process Files
  //
  if(import_drop_box) {
    RunDropBox();
  }
  else {
    for(unsigned i=import_file_key;i<rda->cmdSwitch()->keys();i++) {
      ProcessFileEntry(rda->cmdSwitch()->key(i));
    }
    if(import_stdin_specified) {
      QTextStream in_stream(stdin,QIODevice::ReadOnly);
      QString line=in_stream.readLine();
      while(!line.isNull()) {
	ProcessFileEntry(line);
	line=in_stream.readLine();
      }
    }
  }

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

  Log(LOG_INFO,QString("rdimport finished\n"));

  NormalExit();
}


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++) {
      ProcessFileEntry(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);
  Log(LOG_INFO,QString("rdimport stopped\n"));
}


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).toUtf8(),gflags,NULL,&globbuf);
    if((globbuf.gl_pathc==0)&&(gflags==GLOB_MARK)&&(!import_drop_box)) {
      Log(LOG_WARNING,QString::asprintf(" Unable to open \"%s\", skipping...\n",
					entry.toUtf8().constData()));
      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 {
	  switch(ImportFile(QString::fromUtf8(globbuf.gl_pathv[i]),&import_cart_number)) {
	    case MainObject::Success:
	      break;
	      
 	    case MainObject::DuplicateTitle:
	      break;

	    case MainObject::FileBad:
	      break;
	      
	    case MainObject::NoCart:
	      break;

	    case MainObject::NoCut:
	      break;
	  }
	}
      }
    }
    gflags=GLOB_MARK|GLOB_APPEND;
  }
  globfree(&globbuf);
}


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

  //
  // Open the file
  //
  if(!OpenAudioFile(&wavefile,wavedata)) {
    delete wavedata;
    delete wavefile;
    return MainObject::FileBad;
  }

  //
  // Get file-sourced metadata
  //
  if(!import_metadata_pattern.isEmpty()) {
    QString groupname=effective_group->name();
    found_cart=RunPattern(import_metadata_pattern,RDGetBasePart(filename),
			  wavedata,&groupname);
    if(wavedata->validateDateTimes()) {
      Log(LOG_WARNING,QString::asprintf(
	      " File \"%s\": End date/time cannot be prior to start date/time, ignoring...\n",
	      filename.toUtf8().constData()));
    }

    //
    // Process Group override
    //
    if(groupname!=effective_group->name()) {
      delete effective_group;
      effective_group=new RDGroup(groupname);
      if(!effective_group->exists()) {
	Log(LOG_WARNING,QString::asprintf(" Specified group \"%s\" from file \"%s\" does not exist, using default group...\n",
					  groupname.toUtf8().constData(),
					  filename.toUtf8().constData()));
	delete effective_group;
	effective_group=new RDGroup(import_group->name());
      }
    }
  }

  //
  // Get external XML metadata
  //
  if(import_xml) {
    ReadXmlFile(filename,wavedata);
  }

  //
  // Get CartChunk metadata
  //
  if(import_use_cartchunk_cutid||found_cart) {
    Log(LOG_INFO," found CartChunk CutID \""+wavedata->cutId()+"\"\n");
    *cartnum=0;
    *cartnum=wavedata->cutId().toInt(&ok);
    (*cartnum)+=import_cart_number_offset;
    if((!ok)||(*cartnum==0)||(*cartnum>999999)||
       (effective_group->enforceCartRange()&&
      	(!effective_group->cartNumberValid(*cartnum)))) {
      Log(LOG_WARNING,QString::asprintf(
	      " File \"%s\" has an invalid or out of range Cart Number \"%s\", skipping...\n",
	      RDGetBasePart(filename).toUtf8().constData(),
	      wavedata->cutId().toUtf8().constData()));
      wavefile->closeWave();
      import_failed_imports++;
      import_journal->addFailure(effective_group->name(),filename,
				 tr("invalid/out-of-range cart number"));
      delete wavefile;
      delete wavedata;
      delete effective_group;
      return MainObject::FileBad;
    }
  }

  //
  // Ensure that we have a valid title
  //
  bool cart_exists=false;
  if(*cartnum!=0) {
    cart_exists=RDCart::exists(*cartnum);
  }
  if(import_string_title.isEmpty()) {
    //
    // If the cart already exists and no title was found in metadata,
    // then keep the existing title. Otherwise, generate a default title.
    //
    if((!cart_exists)&&wavedata->metadataFound()&&wavedata->title().isEmpty()) {
      wavedata->setTitle(effective_group->generateTitle(filename));
    }
  } 
  else {  // Use specified title
    wavedata->setTitle(import_string_title);
  }

  //
  // If the cart already exists and no title was found in metadata,
  // then keep the existing title. Otherwise, generate a default title.
  //
  if((!cart_exists)&&wavedata->metadataFound()&&wavedata->title().isEmpty()) {
    wavedata->setTitle(effective_group->generateTitle(filename));
  }

  /*
  //
  // Attempt to find a free cart
  //
  if(*cartnum==0) {
    *cartnum=effective_group->nextFreeCart();
  }
  if(*cartnum==0) {
    Log(LOG_ERR,QString::asprintf("rdimport: no free carts available in specified group\n"));
    wavefile->closeWave();
    import_failed_imports++;
    import_failed_imports++;
    if(import_drop_box) {
      if(!import_run) {
	NormalExit();
      }
      if(!import_temp_fix_filename.isEmpty()) {
	QFile::remove(import_temp_fix_filename);
	import_temp_fix_filename="";
  */
  if(import_by_isci) {
    QString isci=wavedata->isci().trimmed();
    if(isci.isEmpty()) {
      isci=import_string_isci.trimmed();
      if(isci.isEmpty()) {
	Log(LOG_WARNING,QString().sprintf(" File \"%s\" has no ISCI code, skipping...\n",
					  RDGetBasePart(filename).toUtf8().constData()));
	wavefile->closeWave();
	import_failed_imports++;
	import_journal->addFailure(effective_group->name(),filename,
				   tr("no isci code"));
	delete wavefile;
	delete wavedata;
	delete effective_group;
	return MainObject::FileBad;
      }
    }
    if(!import_by_isci_program_code.isEmpty()) {
      isci=import_by_isci_program_code+"_"+isci;
    }
    RDWaveData *wd=import_isci_xref.value(isci,NULL);
    if(wd==NULL) {
      *cartnum=0;
    }
    else {
      *cartnum=wd->cartNumber();
    }
    if(*cartnum==0) {
      Log(LOG_WARNING,QString().sprintf(" File \"%s\" has no ISCI xreference entry, skipping...\n",
					RDGetBasePart(filename).toUtf8().constData()));
      wavefile->closeWave();
      import_failed_imports++;
      import_journal->addFailure(effective_group->name(),filename,
				 tr("no isci xref entry"));
      delete wavefile;
      delete wavedata;
      delete effective_group;
      return MainObject::FileBad;
    }
    if(!effective_group->cartNumberValid(*cartnum)) {
      Log(LOG_WARNING,QString().sprintf(" File \"%s\" cart number %06u is is not valid in group \"%s\", skipping...\n",
		     RDGetBasePart(filename).toUtf8().constData(),
		     *cartnum,
		     effective_group->name().toUtf8().constData()));
      wavefile->closeWave();
      import_failed_imports++;
      import_journal->addFailure(effective_group->name(),filename,
				 tr("forbidden cart number"));
      delete wavefile;
      delete wavedata;
      delete effective_group;
      return MainObject::FileBad;
    }
    if(import_string_title.isEmpty()) {
      wavedata->setTitle(import_isci_xref.value(isci)->title());
    }
  }
  else {
    //
    // Attempt to find a free cart
    //
    if(*cartnum==0) {
      *cartnum=effective_group->nextFreeCart();
    }
    if(*cartnum==0) {
      Log(LOG_ERR,QString().sprintf("rdimport: no free carts available in specified group\n"));
      wavefile->closeWave();
      import_failed_imports++;
      import_failed_imports++;
      if(import_drop_box) {
	if(!import_run) {
	  NormalExit();
	}
	if(!import_temp_fix_filename.isEmpty()) {
	  QFile::remove(import_temp_fix_filename);
	  import_temp_fix_filename="";
	}
	import_journal->addFailure(effective_group->name(),filename,
                                  tr("no free cart available in group"));
	delete wavefile;
	delete wavedata;
	delete effective_group;
	return MainObject::NoCart;
      }
      ErrorExit(RDApplication::ExitImportFailed);
    }
  }

  //
  // Prepare destination cart/cut
  //
  if(import_delete_cuts) {
    DeleteCuts(import_cart_number);
  }
  if(RDCart::exists(*cartnum)) {
    cart_created=false;
  }
  else {
    if((wavedata->title().length()==0)||
       ((wavedata->title().length()>0)&&(wavedata->title()[0] == '\0'))) {
      wavedata->setTitle(effective_group->generateTitle(filename));
    }

    if(!rda->system()->allowDuplicateCartTitles()) {
      if(rda->system()->fixDuplicateCartTitles()) {
	wavedata->setTitle(RDCart::ensureTitleIsUnique(0,wavedata->title()));
      }
      else {
	sql=QString("select ")+
	  "`NUMBER` "+  // 00
	  "from `CART` where "+
	  "`TITLE`='"+RDEscapeString(wavedata->title())+"'";
	q=new RDSqlQuery(sql);
	if(q->first()) {
	  QString err_msg=QString().
	    sprintf(" File \"%s\" has duplicate title \"%s\", skipping...\n",
		    RDGetBasePart(filename).toUtf8().constData(),
		    wavedata->title().toUtf8().constData());
	  Log(LOG_WARNING,err_msg);
	  import_journal->addFailure(effective_group->name(),filename,
				     tr("duplicate title"));
	  if(!import_output_pattern.isEmpty()) {
	    printf("Import failed: duplicate title \"%s\"\n",
		   wavedata->title().toUtf8().constData());
	  }
	  delete q;
	  delete wavedata;
	  delete wavefile;
	  return MainObject::DuplicateTitle;
	}
	delete q;
      }
    }
    cart_created=
      RDCart::create(effective_group->name(),RDCart::Audio,&err_msg,*cartnum)!=0;
  }

  //
  // Create cart/cut
  //
  RDCart *cart=new RDCart(*cartnum);
  int cutnum=
    cart->addCut(import_format,import_bitrate,import_channels);
  if(cutnum<0) {
    Log(LOG_WARNING,QString::asprintf("rdimport: no free cuts available in cart %06u\n",*cartnum));
    import_failed_imports++;
    import_journal->addFailure(effective_group->name(),filename,
			       tr("no free cut available in cart"));
    delete cart;
    return MainObject::NoCut;
  }
  RDCut *cut=new RDCut(*cartnum,cutnum);

  //
  // Import audio
  //
  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(false);
  Log(LOG_INFO,QString().
      sprintf(" Importing file \"%s\" [%s] to cart %06u ... ",
	      RDGetBasePart(filename).toUtf8().constData(),
	      wavedata->title().trimmed().toUtf8().constData(),
	      *cartnum));
  switch(conv_err=conv->runImport(rda->user()->name(),rda->user()->password(),
				  &audio_conv_err)) {
  case RDAudioImport::ErrorOk:
    Log(LOG_INFO,QString::asprintf("done.\n"));
    break;

  default:
    Log(LOG_INFO,QString::asprintf(" %s, skipping %s...\n",
	 RDAudioImport::errorText(conv_err,audio_conv_err).toUtf8().constData(),
				   filename.toUtf8().constData()));
    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();
    if(!import_run) {
      NormalExit();
    }
    if(!import_temp_fix_filename.isEmpty()) {
      QFile::remove(import_temp_fix_filename);
      import_temp_fix_filename="";
    }
    import_failed_imports++;
    import_journal->addFailure(effective_group->name(),filename,
			       tr("corrupt audio file"));
    delete wavefile;
    delete wavedata;
    delete effective_group;
    return MainObject::FileBad;
    break;
  }

  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(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())&&(!wavedata->title().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_isci.isNull()) {
    cut->setIsci(import_string_isci);
  }
  if(!import_string_isrc.isNull()) {
    cut->setIsrc(RDDiscLookup::normalizedIsrc(import_string_isrc));
  }
  if(!import_string_recording_mbid.isNull()) {
    cut->setRecordingMbId(import_string_recording_mbid);
  }
  if(!import_string_release_mbid.isNull()) {
    cut->setReleaseMbId(import_string_release_mbid);
  }
  if(!import_string_publisher.isNull()) {
    cart->setPublisher(import_string_publisher);
  }
  if(!import_string_song_id.isNull()) {
    cart->setSongId(import_string_song_id);
  }
  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());
  }

  if(!import_output_pattern.isEmpty()) {
    RDLogLine *ll=new RDLogLine(cart->number(),cut->cutNumber());
    printf("%s\n",
	   ll->resolveWildcards(import_output_pattern).toUtf8().constData());
    delete ll;
  }

  import_journal->
    addSuccess(effective_group->name(),filename,*cartnum,cart->title());

  delete settings;
  delete conv;
  delete cut;
  delete cart;
  wavefile->closeWave();
  delete wavefile;
  delete wavedata;
  delete effective_group;

  if(import_delete_source) {
    unlink(filename.toUtf8());
    Log(LOG_INFO,QString::asprintf(" Deleted file \"%s\"\n",(const char *)RDGetBasePart(filename).toUtf8()));
  }
  if(!import_run) {
    NormalExit();
  }
  if(!import_temp_fix_filename.isEmpty()) {
    QFile::remove(import_temp_fix_filename);
    import_temp_fix_filename="";
  }

  return MainObject::Success;
}


bool MainObject::OpenAudioFile(RDWaveFile **wavefile,RDWaveData *wavedata)
{
  QString orig_filename=(*wavefile)->getName();

  if(!(*wavefile)->openWave(wavedata)) {
    if(import_fix_broken_formats) {
      Log(LOG_WARNING,QString::asprintf(" File \"%s\" appears to be malformed, trying workaround ... ",
		   RDGetBasePart(orig_filename).toUtf8().constData()));
      delete *wavefile;
      if(((*wavefile)=FixFile(orig_filename,wavedata))==NULL) {
	Log(LOG_WARNING,QString::asprintf("failed.\n"));
	Log(LOG_WARNING,QString::asprintf(
		" File \"%s\" is not readable or not a recognized format, skipping...\n",
		RDGetBasePart(orig_filename).toUtf8().constData()));
	if(!import_run) {
	  NormalExit();
	}
	if(!import_temp_fix_filename.isEmpty()) {
	  QFile::remove(import_temp_fix_filename);
	  import_temp_fix_filename="";
	}
	import_failed_imports++;
	import_journal->addFailure(import_group->name(),orig_filename,
				   tr("unknown/unrecognized file format"));
	return false;
      }
      Log(LOG_WARNING,QString::asprintf("success.\n"));
    }
    else {
      Log(LOG_WARNING,QString::asprintf(
        " File \"%s\" is not readable or not a recognized format, skipping...\n",
        RDGetBasePart((*wavefile)->getName()).toUtf8().constData()));
      if(!import_run) {
	NormalExit();
      }
      if(!import_temp_fix_filename.isEmpty()) {
	QFile::remove(import_temp_fix_filename);
	import_temp_fix_filename="";
      }
      import_failed_imports++;
      import_journal->addFailure(import_group->name(),orig_filename,
				 tr("unknown/unrecognized file format"));
      return false;
    }
  }

  return true;
}


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().toSecsSinceEpoch()>dt.toSecsSinceEpoch())) {
	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::DuplicateTitle:
	    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.toUtf8(),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
  //
  char tempfile[PATH_MAX];
  strncpy(tempfile,(RDTempDirectory::basePath()+
		    QString::asprintf("/rdimport%dXXXXXX",getpid())).toUtf8(),
	  PATH_MAX-1);
  int dest_fd=mkstemp(tempfile);
  if(dest_fd<0) {
    return NULL;
  }
  import_temp_fix_filename=tempfile;
  if(!RDCopy(filename,dest_fd)) {
    return NULL;
  }
  close(dest_fd);

  //
  // 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.toUtf8(),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);
      RDCheckReturnCode("FixChunkSizes() read",read(fd,buf,4),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);
      RDCheckReturnCode("FixChunkSizes() write",write(fd,buf,4),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)
{
  //  MAINTAINERS'S NOTE: These mappings must be kept in sync with those
  //                      of the 'resolvePadFields()' method in
  //                      'apis/PyPAD/api/PyPAD.py', as well as the
  //                      'resolveWildcards()' method in the
  //                      'lib/rdlog_line.cpp' file and the
  //                      'VerifyPattern()' method in this file.

  bool macro_active=false;
  int ptr=0;
  QChar field;
  QChar subfield;
  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;

  //
  // Sanity Check
  //
  if(pattern.length()<2) {
    return false;
  }

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

  for(int i=0;i<=filename.length();i++) {
    if(macro_active) {
      if((i==filename.length())||
	 ((!delimiter.isNull())&&(filename.at(i)==delimiter))) {
	switch(field.cell()) {
	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 'w':
	  switch(subfield.cell()) {
	  case 'c':
	    wavedata->setIsci(value);
	    wavedata->setMetadataFound(true);
	    break;

	  case 'i':
	    if(RDDiscLookup::isrcIsValid(value)) {
	      wavedata->setIsrc(RDDiscLookup::normalizedIsrc(value));
	      wavedata->setMetadataFound(true);
	    }
	    else {
	      Log(LOG_ERR,"invalid ISRC \""+value+"\"\n");
	      ErrorExit(RDApplication::ExitInvalidOption);
	    }
	    break;

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

	  case 'r':
	    wavedata->setReleaseMbId(value);
	    wavedata->setMetadataFound(true);
	    break;
	  }
	  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);
	  if(field==QChar('w')) {
	    if(pattern.length()>(ptr+3)) {
	      delimiter=pattern.at(ptr+3);
	    }
	    else {
	      delimiter=QChar();
	    }
	    subfield=pattern.at(ptr+2);
	    ptr+=4;
	    macro_active=true;
	  }
	  else {
	    if(pattern.length()>(ptr+2)) {
	      delimiter=pattern.at(ptr+2);
	    }
	    else {
	      delimiter=QChar();
	    }
	    ptr+=3;
	    macro_active=true;
	  }
	}
	else {
	  if(pattern.length()>ptr) {
	    delimiter=pattern.at(ptr);
	  }
	  else {
	    delimiter=QChar();
	  }
	  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);
	if(field==QChar('w')) {
	  delimiter=pattern.at(ptr+3);
	  subfield=pattern.at(ptr+2);
	  ptr+=4;
	  macro_active=true;
	}
	else {
	  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)
{
  //  MAINTAINERS'S NOTE: These mappings must be kept in sync with those
  //                      of the 'resolvePadFields()' method in
  //                      'apis/PyPAD/api/PyPAD.py', as well as the
  //                      'resolveWildcards()' method in the
  //                      'lib/rdlog_line.cpp' file and the
  //                      'RunPattern()' method in this file.

  bool macro_active=false;
  for(int i=0;i<pattern.length();i++) {
    if(pattern.at(i)==QChar('%')) {
      if(macro_active) {
	return false;
      }
      macro_active=true;
      if(i>=(pattern.length()-1)) {
	return false;
      }
      switch(pattern.at(++i).cell()) {
      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;

      case 'w':
	if(i>=(pattern.length()-1)) {
	  return false;
	}
	switch(pattern.at(++i).cell()) {
	case 'c':
	case 'i':
	case 'm':
	case 'r':
	  break;
	}
	break;

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


void MainObject::DeleteCuts(unsigned cartnum)
{
  Log(LOG_INFO,QString::asprintf(" Deleting cuts from cart %06u\n",cartnum));

  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::asprintf("select `FILE_DATETIME` from `DROPBOX_PATHS` where ")+
    QString::asprintf("(`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;

  if(import_persistent_dropbox_id<0) {
    return;
  }
  if(GetCachedTimestamp(filename).isNull()) {
    sql=QString("insert into `DROPBOX_PATHS` set ")+
      QString::asprintf("`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::asprintf("(`DROPBOX_ID`=%d)&&",import_persistent_dropbox_id)+
      "(`FILE_PATH`='"+RDEscapeString(filename)+"')";
  }
  RDSqlQuery::apply(sql);
}


bool MainObject::LoadIsciXref(QString *err_msg,const QString &filename)
{
  char line[1024];
  FILE *f=NULL;
  bool ok=false;

  *err_msg=tr("OK");
  for(QMap<QString,RDWaveData *>::const_iterator it=import_isci_xref.begin();
      it!=import_isci_xref.end();it++) {
    delete it.value();
  }
  import_isci_xref.clear();

  if((f=fopen(filename.toUtf8(),"r"))==NULL) {
    *err_msg=strerror(errno);
    return false;
  }

  //
  // Skip Header
  //
  fgets(line,1024,f);
  fgets(line,1024,f);

  //
  // Load Records
  //
  while(fgets(line,1024,f)!=NULL) {
    RDStringList fields=fields.split(',',line,"\"");
    if(fields.size()==9) {
      for(int i=0;i<fields.size();i++) {
	fields[i]=fields[i].replace("\"","").trimmed();
      }
      unsigned cartnum=fields[3].right(fields[3].length()-1).toUInt(&ok);
      if(ok&&(cartnum<=RD_MAX_CART_NUMBER)) {
	if(!fields[4].isEmpty()) {
	  RDWaveData *wd=new RDWaveData();
	  wd->setCartNumber(cartnum);
	  wd->setIsci(fields[4].trimmed());
	  if(!fields[1].trimmed().isEmpty()) {
	    if(fields[7].isEmpty()) {
	      wd->setTitle(fields[1].trimmed());
	    }
	    else {
	      wd->setTitle(fields[1].trimmed()+" - "+fields[7]);
	    }
	  }
	  import_isci_xref[fields[4].trimmed()]=wd;
	}
      }
    }
    else {
      *err_msg=tr("invalid/corrupt data at line")+
	QString().sprintf("%d",3+fields.size());
      return false;
    }
  }

  return true;
}


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=basename.split(".");
  for(int i=0;i<f0.size()-1;i++) {
    xmlname+=f0[i]+".";
  }
  xmlname+="xml";
  Log(LOG_INFO,QString::asprintf(" Reading xml metadata from \"%s\": ",
				 xmlname.toUtf8().constData()));

  //
  // Read XML
  //
  wavedata->clear();
  if((f=fopen(xmlname.toUtf8(),"r"))==NULL) {
    Log(LOG_WARNING,QString::asprintf("failed [%s]\n",strerror(errno)));
    return;
  }
  Log(LOG_INFO,QString("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;
}


void MainObject::Log(int prio,const QString &msg) const
{
  QString m=msg;
  FILE *f=NULL;

  if(import_log_syslog) {
    rda->syslog(prio,"%s",(const char *)msg.trimmed().toUtf8());
  }
  if(!import_log_filename.isEmpty()) {
    QDateTime now=QDateTime::currentDateTime();
    QString filename=RDDateDecode(import_log_filename,now.date(),
				  rda->station(),rda->config());
    if((f=fopen(filename.toUtf8(),"a"))!=NULL) {
      fprintf(f,"%s: %s",
	      (const char *)now.toString("MMM dd hh:mm:ss").toUtf8(),
	      (const char *)msg.toUtf8());
      fflush(f);
      fclose(f);
    }
  }

  if(prio==LOG_ERR) {
    fprintf(stderr,"%s",(const char *)msg.toUtf8());
    fflush(stderr);
  }
  else {
    if(import_verbose) {
      fprintf(stdout,"%s",(const char *)msg.toUtf8());
      fflush(stdout);
    }
  }
}


void MainObject::NormalExit() const
{
  if((import_journal!=NULL)&&(import_send_mail)) {
    import_journal->sendAll();
  }
  if(import_failed_imports>0) {
    exit(RDApplication::ExitImportFailed);
  }
  exit(RDApplication::ExitOk);
}


void MainObject::ErrorExit(RDApplication::ExitCode code) const
{
  if((import_journal!=NULL)&&(import_send_mail)) {
    import_journal->sendAll();
  }
  exit(code);
}


int main(int argc,char *argv[])
{
  QCoreApplication::setSetuidAllowed(true);
  QCoreApplication a(argc,argv);
  new MainObject();
  return a.exec();
}
