From 396e2b46e4af7445f7029f97151b391bb0b11f5b Mon Sep 17 00:00:00 2001 From: Matthew Kaschula Date: Thu, 11 Jul 2019 09:19:58 +0100 Subject: [PATCH] Add parser, supporting files and tests --- .gitignore | 2 + README.md | 44 ++++++++++ call-event-file.go | 136 +++++++++++++++++++++++++++++++ call-event-file_test.go | 137 +++++++++++++++++++++++++++++++ call-event-parser.go | 122 ++++++++++++++++++++++++++++ call-event-parser_test.go | 147 ++++++++++++++++++++++++++++++++++ call-event-store.go | 122 ++++++++++++++++++++++++++++ file-service.go | 44 ++++++++++ main.go | 49 ++++++++++++ test/data/emptydir/.gitignore | 0 10 files changed, 803 insertions(+) create mode 100644 .gitignore create mode 100644 call-event-file.go create mode 100644 call-event-file_test.go create mode 100644 call-event-parser.go create mode 100644 call-event-parser_test.go create mode 100644 call-event-store.go create mode 100644 file-service.go create mode 100644 main.go create mode 100644 test/data/emptydir/.gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d52b47e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +running +processed/ diff --git a/README.md b/README.md index e69de29..882397e 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,44 @@ +# Call Event Parser + +## Introduction + +This application processes sets of call data in a specific CSV format. The data is read, validated and then uploaded to a database. Once a file has been successfully processed it moved to a process directory. Any records that can not be processed form files are logged to the syslog. + +This project is designed to be run as a back ground task. + +## Config + +The project is a CLI application and requires 9 arguments to be run. The variables that can be configured are: + +- Database Username +- Database Password +- Database Host +- Database Port +- Database Name +- Database Table +- Process directory path (Where files will be moved when processed) +- Upload directory path (Directory where incoming cvs files are located) +- File running path (Path to a temport file that stops this application running in more than one process at a time) + + +## Build and Run + +To build this project run the following in the root of the project + +`go install` + +from you $GOPATH/bin directory run the application passingin the arguments + +`./call-event-parser username password host port db_name db_table "./processed/" "./uploaded" "/tmp/call_event_parse_running"` + + +### Things to note + +The processed directory argument must include a '/' at the end. This is a bug that needs to be resolved. Make sure the application as read/write access to the `File running path` location. + + +## Tests + +This project contains test which can be run from the project root using + +`go test ./...` \ No newline at end of file diff --git a/call-event-file.go b/call-event-file.go new file mode 100644 index 0000000..5817fe0 --- /dev/null +++ b/call-event-file.go @@ -0,0 +1,136 @@ +package main + +import ( + "fmt" + "strconv" + "strings" + "time" +) + +const ( + DATE_TIME_FORMAT = "2006-02-01 15:04:05" + COL_EVENT_DATETIME = 0 + COL_EVENT_ACTION = 1 + COL_CALL_REF = 2 + COL_EVENT_VAL = 3 + COL_EVENT_CURRENCY_CODE = 4 +) + +type CallEventFile struct { + filenamePath string + validData [][]string + recordErrors []string + numberOfRecords int +} + +func (f *CallEventFile) GetFilename() string { + parts := strings.Split(f.filenamePath, "/") + + return parts[len(parts)-1:][0] +} + +func (f *CallEventFile) RecordErrors() []string { + return f.recordErrors +} + +func (f *CallEventFile) ValidData() [][]string { + return f.validData +} + +func CreateCallEventFileFromRaw(rawData [][]string, filepath string) CallEventFile { + data := removeHeader(rawData) + validRecords := [][]string{} + errorRecords := []string{} + + for i, row := range data { + // COL_EVENT_DATETIME + eventDateTimeValue := row[COL_EVENT_DATETIME] + + if eventDateTimeValue == "" { + errorRecords = append( + errorRecords, + fmt.Sprintf("File: %#v. Record: %#v requires 'eventDatetime' value", filepath, i+1), + ) + continue + } + + _, err := time.Parse(DATE_TIME_FORMAT, eventDateTimeValue) + if err != nil { + errorRecords = append( + errorRecords, + fmt.Sprintf("File: %#v. Record: %#v date format must be yyyy-mm-dd hh:mm:ss", filepath, i+1), + ) + continue + } + + // COL_EVENT_ACTION + if row[COL_EVENT_ACTION] == "" { + errorRecords = append( + errorRecords, + fmt.Sprintf("File: %#v. Record: %#v requires 'eventAction' value", filepath, i+1), + ) + continue + } + + if l := len(row[COL_EVENT_ACTION]); l == 0 || l > 20 { + errorRecords = append( + errorRecords, + fmt.Sprintf("File: %#v. Record: %#v requires 'eventAction' to be between 1 - 20 in length", filepath, i+1), + ) + continue + } + + // COL_CALL_REF + if row[COL_CALL_REF] == "" { + errorRecords = append( + errorRecords, + fmt.Sprintf("File: %#v. Record: %#v requires 'callRef' value", filepath, i+1), + ) + continue + } + + if _, err := strconv.ParseInt(row[COL_CALL_REF], 10, 64); err != nil { + errorRecords = append( + errorRecords, + fmt.Sprintf("File: %#v. Record: %#v requires 'callRef' to be a valid integer", filepath, i+1), + ) + continue + } + + // COL_EVENT_VAL + if row[COL_EVENT_VAL] == "" { + row[COL_EVENT_VAL] = "0.00" + } + + eventValue, err := strconv.ParseFloat(row[COL_EVENT_VAL], 64) + + if err != nil { + errorRecords = append( + errorRecords, + fmt.Sprintf("File: %#v. Record: %#v requires 'eventValue' to be a valid float", filepath, i+1), + ) + continue + } + + // COL_EVENT_CURRENCY_CODE + if row[COL_EVENT_CURRENCY_CODE] == "" && eventValue > 0.0 { + errorRecords = append( + errorRecords, + fmt.Sprintf("File: %#v. Record: %#v requires 'eventCurrencyCode' if event value is more than 0.0", filepath, i+1), + ) + continue + } + + validRecords = append(validRecords, row) + } + + return CallEventFile{filepath, validRecords, errorRecords, len(data)} +} + +func removeHeader(rawCsv [][]string) [][]string { + if len(rawCsv) > 0 { + return rawCsv[1:] + } + + return rawCsv +} diff --git a/call-event-file_test.go b/call-event-file_test.go new file mode 100644 index 0000000..5c98c64 --- /dev/null +++ b/call-event-file_test.go @@ -0,0 +1,137 @@ +package main_test + +import ( + "strings" + "testing" + + parser "github.com/kaschula/call-event-parser" + . "github.com/stretchr/testify/assert" +) + +func TestItReturnsAnErrorWhenNoEventDateTimeGiven(t *testing.T) { + rawCsv := [][]string{ + []string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"}, + []string{""}, + } + + callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file") + + Equal(t, 0, len(callFile.ValidData())) + True(t, strings.Contains(callFile.RecordErrors()[0], "requires 'eventDatetime' value")) +} + +func TestItReturnsAnErrorWhenDateFormatIsIncorrect(t *testing.T) { + rawCsv := [][]string{ + []string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"}, + []string{"2012-01-02 12-01-30"}, + } + + callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file") + + Equal(t, 0, len(callFile.ValidData())) + True(t, strings.Contains(callFile.RecordErrors()[0], "date format must be yyyy-mm-dd hh:mm:ss")) +} + +func TestItReturnsAnErrorWhenEventActionIsMissing(t *testing.T) { + rawCsv := [][]string{ + []string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"}, + []string{"2012-01-02 12:01:30", ""}, + } + + callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file") + + Equal(t, 0, len(callFile.ValidData())) + True(t, strings.Contains(callFile.RecordErrors()[0], "requires 'eventAction' value")) +} + +func TestItReturnsAnErrorWhenEventActionIsMoreThan20CharactersLong(t *testing.T) { + rawCsv := [][]string{ + []string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"}, + []string{"2012-01-02 12:01:30", "abcdefghijklmnopqrstu"}, + } + + callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file") + + Equal(t, 0, len(callFile.ValidData())) + True(t, strings.Contains(callFile.RecordErrors()[0], "requires 'eventAction' to be between 1 - 20 in length")) +} + +func TestItReturnsAnErrorWhenCallRefIsMissing(t *testing.T) { + rawCsv := [][]string{ + []string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"}, + []string{"2012-01-02 12:01:30", "sale", ""}, + } + + callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file") + + Equal(t, 0, len(callFile.ValidData())) + True(t, strings.Contains(callFile.RecordErrors()[0], "requires 'callRef' value")) +} + +func TestItReturnsAnErrorWhenCallRefIsNotAValidInteger(t *testing.T) { + rawCsv := [][]string{ + []string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"}, + []string{"2012-01-02 12:01:30", "sale", "notAnInt"}, + } + + callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file") + + Equal(t, 0, len(callFile.ValidData())) + True(t, strings.Contains(callFile.RecordErrors()[0], "requires 'callRef' to be a valid integer")) +} + +func TestItReturnsAnErrorWhenEventValueIsNoteAValidFloat(t *testing.T) { + rawCsv := [][]string{ + []string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"}, + []string{"2012-01-02 12:01:30", "sale", "1234", "2.1a"}, + } + + callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file") + + Equal(t, 0, len(callFile.ValidData())) + True(t, strings.Contains(callFile.RecordErrors()[0], "requires 'eventValue' to be a valid float")) +} + +func TestItReturnsAnErrorWhenEventCurrencyCodeIsNotSetAndEventValueIsMoreThanZero(t *testing.T) { + rawCsv := [][]string{ + []string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"}, + []string{"2012-01-02 12:01:30", "sale", "1234", "1.0", ""}, + } + + callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file") + + Equal(t, 0, len(callFile.ValidData())) + True(t, strings.Contains(callFile.RecordErrors()[0], "requires 'eventCurrencyCode' if event value is more than 0.0")) +} + +func TestItDoesNotReturnAnErrorWhenEventValueIsZeroAndNoCurrencyCodeSet(t *testing.T) { + rawCsv := [][]string{ + []string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"}, + []string{"2012-01-02 12:01:30", "sale", "1234", "0.0", ""}, + } + + callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file") + + Equal(t, 0, len(callFile.RecordErrors())) + Equal(t, 1, len(callFile.ValidData())) +} + +func TestItReturnsItSetsTheEventValueToZeroFloatIfEmpty(t *testing.T) { + rawCsv := [][]string{ + []string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"}, + []string{"2012-01-02 12:01:30", "sale", "1234", "", "GBP"}, + } + + callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file") + + Equal(t, 1, len(callFile.ValidData())) + Equal(t, "0.00", callFile.ValidData()[0][parser.COL_EVENT_VAL]) +} + +func TestItReturnsTheFileName(t *testing.T) { + rawCsv := [][]string{} + + callFile := parser.CreateCallEventFileFromRaw(rawCsv, "path/to/file.txt") + + Equal(t, "file.txt", callFile.GetFilename()) +} diff --git a/call-event-parser.go b/call-event-parser.go new file mode 100644 index 0000000..72f6531 --- /dev/null +++ b/call-event-parser.go @@ -0,0 +1,122 @@ +package main + +import ( + "encoding/csv" + "errors" + "fmt" + "io" + "os" +) + +type callEventParser struct { + processLockFile string + processedDirectory string + store callEventStore +} + +func NewCallEventParser(store callEventStore, processedDirectory, tempFilePath string) callEventParser { + return callEventParser{tempFilePath, processedDirectory, store} +} + +func (p callEventParser) Parse(directoryPath string) error { + if p.isProcessRunning() { + return errors.New("A parsing process is currently running, exiting") + } + + csvFilePaths, err := getCSVFilePaths(directoryPath) + if err != nil { + return err + } + + if err := p.store.Prepare(); err != nil { + return err + } + + p.startProcessing() + defer p.stopProcessing() + + return p.processFiles(csvFilePaths) +} + +func (p callEventParser) processFiles(csvFilePaths []string) error { + for _, filePath := range csvFilePaths { + file, err := os.Open(filePath) + if err != nil { + log(fmt.Sprintf("could not open %#v", filePath)) + continue + } + + reader := csv.NewReader(file) + + csvFileRecordsRaw := [][]string{} + for { + record, err := reader.Read() + if err == io.EOF { + break + } + if err != nil { + log(fmt.Sprintf("Error reading file '%#v' : %#v", filePath, err.Error())) + } + + csvFileRecordsRaw = append(csvFileRecordsRaw, record) + } + + callFile := CreateCallEventFileFromRaw(csvFileRecordsRaw, filePath) + + err = p.logFileErrorsAndStoreRecords(callFile) + if err != nil { + continue + } + + p.moveFileToProcessed(callFile) + } + + return nil +} + +func (p callEventParser) startProcessing() error { + f, err := os.Create(p.processLockFile) + if f != nil { + f.Close() + } + + return err +} + +func (p callEventParser) stopProcessing() { + os.Remove(p.processLockFile) +} + +func (p callEventParser) isProcessRunning() bool { + if _, err := os.Stat(p.processLockFile); os.IsNotExist(err) { + return false + } + + return true +} + +func (p callEventParser) logFileErrorsAndStoreRecords(processedFile CallEventFile) error { + for _, message := range processedFile.recordErrors { + log(message) + } + + err := p.store.Create(processedFile) + if err != nil { + log(err.Error()) + log(fmt.Sprintf("Database write error for file: %#v", processedFile.filenamePath)) + } + + return err +} + +func (p callEventParser) moveFileToProcessed(file CallEventFile) error { + if !directoryExists(p.processedDirectory) { + err := createDirectory(p.processedDirectory) + + if err != nil { + return err + } + } + + return os.Rename(file.filenamePath, p.processedDirectory+file.GetFilename()) +} diff --git a/call-event-parser_test.go b/call-event-parser_test.go new file mode 100644 index 0000000..f262011 --- /dev/null +++ b/call-event-parser_test.go @@ -0,0 +1,147 @@ +package main_test + +import ( + "encoding/csv" + "errors" + "os" + "strings" + "testing" + + parser "github.com/kaschula/call-event-parser" + . "github.com/stretchr/testify/assert" +) + +const ( + EMPTY_DIR_PATH = "./test/data/emptydir" + CSV_DATA_PATH = "./test/data/callevent" +) + +func TestAnErrorIsReturnedProcessAlreadyRunning(t *testing.T) { + createDummyData(t, "test/data/callevent/complete-data.csv") + + p := parser.NewCallEventParser(nil, "./test/data/processed/", "./running") + runningFile, fErr := os.Create("./running") + if fErr != nil { + t.Fatal("Error creating 'running' file") + } + defer runningFile.Close() + defer removeFile("./running") + + err := p.Parse("path to file") + + Error(t, err) + True(t, strings.Contains("A parsing process is currently running, exiting", err.Error())) +} + +func TestAnErrorIsReturnedIfDirectoryDoesNotExist(t *testing.T) { + createDummyData(t, "test/data/callevent/complete-data.csv") + + p := parser.NewCallEventParser(nil, "./test/data/processed/", "./running") + err := p.Parse("./path/that/does/not/exist") + + Error(t, err) + True(t, strings.Contains("Directory does not exist", err.Error())) +} + +func TestAnErrorIsReturnedIfNoCSVFilesFoundInDirectory(t *testing.T) { + p := parser.NewCallEventParser(nil, "./test/data/processed/", "./running") + err := p.Parse(EMPTY_DIR_PATH) + + Error(t, err) + True(t, strings.Contains("No CSV files found", err.Error())) +} + +func TestAnErrorIsReturnedStoreFailsToPrepare(t *testing.T) { + createDummyData(t, "test/data/callevent/complete-data.csv") + + store := newStoreStub(errors.New("Store error"), nil) + p := parser.NewCallEventParser(store, "./test/data/processed/", "./running") + err := p.Parse(CSV_DATA_PATH) + + Error(t, err) + True(t, strings.Contains("Store error", err.Error())) +} + +func TestCsvDataIsParsedAndStored(t *testing.T) { + createDummyData(t, "test/data/callevent/complete-data.csv") + + store := newStoreStub(nil, nil) + expectedData := [][]string{ + []string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"}, + []string{"2018-01-02 10:27:36", "sale", "4536", "111.00", "GBP"}, + []string{"2018-01-02 11:28:54", "sale", "6257", "240.49", "GBP"}, + []string{"2018-01-02 11:48:23", "lead", "5328", "0", "GBP"}, + []string{"2018-01-02 13:11:17", "sale", "3826", "100.00", "GBP"}, + []string{"2018-01-02 15:37:42", "sale", "9872", "100.00", "GBP"}, + []string{"2018-01-02 16:42:12", "lead", "6271", "0", "GBP"}, + } + expectCallEventFile := parser.CreateCallEventFileFromRaw(expectedData, "test/data/callevent/complete-data.csv") + + p := parser.NewCallEventParser(store, "./test/data/processed/", "./running") + err := p.Parse(CSV_DATA_PATH) + defer removeFile("./test/data/processed/complete-data.csv") + + Nil(t, err) + Equal(t, expectCallEventFile, store.storedData[0]) + True(t, fileExists(t, "./test/data/processed/complete-data.csv")) + False(t, fileExists(t, "./test/data/callevent/complete-data.csv")) +} + +func removeFile(path string) { + os.Remove(path) +} + +func newStoreStub(prepareReturn, createError error) *storeStub { + return &storeStub{prepareReturn, []parser.CallEventFile{}, createError} +} + +type storeStub struct { + prepareReturn error + storedData []parser.CallEventFile + createError error +} + +func (s *storeStub) Prepare() error { + return s.prepareReturn +} + +func (s *storeStub) Create(file parser.CallEventFile) error { + if s.createError != nil { + return s.createError + } + + s.storedData = append(s.storedData, file) + + return nil +} + +func fileExists(t *testing.T, path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } + + return true +} + +func createDummyData(t *testing.T, path string) { + data := [][]string{ + []string{"eventDatetime", "eventAction", "callRef", "eventValue", "eventCurrencyCode"}, + []string{"2018-01-02 10:27:36", "sale", "4536", "111.00", "GBP"}, + []string{"2018-01-02 11:28:54", "sale", "6257", "240.49", "GBP"}, + []string{"2018-01-02 11:48:23", "lead", "5328", "0", "GBP"}, + []string{"2018-01-02 13:11:17", "sale", "3826", "100.00", "GBP"}, + []string{"2018-01-02 15:37:42", "sale", "9872", "100.00", "GBP"}, + []string{"2018-01-02 16:42:12", "lead", "6271", "0", "GBP"}, + } + + file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0777) + defer file.Close() + + if err != nil { + t.Fatal("Failed to set up dummy data") + } + + csvWriter := csv.NewWriter(file) + csvWriter.WriteAll(data) + csvWriter.Flush() +} diff --git a/call-event-store.go b/call-event-store.go new file mode 100644 index 0000000..b507e7a --- /dev/null +++ b/call-event-store.go @@ -0,0 +1,122 @@ +package main + +import ( + "database/sql" + "fmt" + + _ "github.com/go-sql-driver/mysql" +) + +type callEventStore interface { + Prepare() error + Create(CallEventFile) error +} + +type MySqlCallEventStore struct { + user string + password string + host string + port string + database string + table string +} + +func NewMySqlEventStore(user, password, host, port, database, table string) *MySqlCallEventStore { + return &MySqlCallEventStore{user, password, host, port, database, table} +} +func (s *MySqlCallEventStore) Prepare() error { + connection, err := s.getConnection() + if err != nil { + return err + } + defer connection.Close() + + tableExists, err := s.tableExists(connection) + if err != nil { + return err + } + + if tableExists > 0 { + return nil + } + + return s.createTable(connection) +} + +func (s *MySqlCallEventStore) createTable(connection *sql.DB) error { + tableSchema := ` + CREATE TABLE %s ( + id INT NOT NULL AUTO_INCREMENT, + event_date_time DATETIME NOT NULL, + event_action VARCHAR(20) NOT NULL, + call_ref VARCHAR(20) NOT NULL, + event_value DECIMAL(10,2) NULL, + event_currency_code VARCHAR(3) NULL, + PRIMARY KEY (id) + ) + ` + query := fmt.Sprintf(tableSchema, s.table) + _, err := connection.Exec(query) + + return err +} + +func (s *MySqlCallEventStore) getConnection() (*sql.DB, error) { + return sql.Open("mysql", s.user+":"+s.password+"@/"+s.database+"") +} + +func (s *MySqlCallEventStore) tableExists(connection *sql.DB) (int, error) { + tableExistsQuery := fmt.Sprintf("SELECT count(*) FROM information_schema.TABLES WHERE (TABLE_SCHEMA = %#v) AND (TABLE_NAME = %#v)", + s.database, + s.table, + ) + + rows, err := connection.Query(tableExistsQuery) + if err != nil { + return 0, err + } + + var tableExists int + var queryErr error + for rows.Next() { + queryErr = rows.Scan(&tableExists) + } + + return tableExists, queryErr +} + +func (s *MySqlCallEventStore) Create(file CallEventFile) error { + dataRows := file.ValidData() + if len(dataRows) == 0 { + return nil + } + + connection, err := s.getConnection() + if err != nil { + return err + } + + insertTemplate := "INSERT INTO %s(event_date_time, event_action, call_ref, event_value, event_currency_code) VALUES " + query := fmt.Sprintf(insertTemplate, s.table) + vals := []interface{}{} + + lastRowIndex := len(dataRows) - 1 + + for i, row := range dataRows { + if i == lastRowIndex { + query += "(?, ?, ?, ?, ?)" + } else { + query += "(?, ?, ?, ?, ?)," + } + vals = append(vals, row[COL_EVENT_DATETIME], row[COL_EVENT_ACTION], row[COL_CALL_REF], row[COL_EVENT_VAL], row[COL_EVENT_CURRENCY_CODE]) + } + + stmt, err := connection.Prepare(query) + if err != nil { + return err + } + + _, err = stmt.Exec(vals...) + + return err +} diff --git a/file-service.go b/file-service.go new file mode 100644 index 0000000..26527bb --- /dev/null +++ b/file-service.go @@ -0,0 +1,44 @@ +package main + +import ( + "errors" + "os" + "path/filepath" +) + +func directoryExists(path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } + + return true +} + +func getCSVFilePaths(directory string) ([]string, error) { + if !directoryExists(directory) { + return nil, errors.New("Directory does not exist") + } + + csvFiles := []string{} + err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { + if filepath.Ext(path) == ".csv" { + csvFiles = append(csvFiles, path) + } + + return nil + }) + + if err != nil { + return nil, err + } + + if len(csvFiles) == 0 { + return nil, errors.New("No CSV files found") + } + + return csvFiles, nil +} + +func createDirectory(path string) error { + return os.MkdirAll(path, 0755) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..497e2c8 --- /dev/null +++ b/main.go @@ -0,0 +1,49 @@ +package main + +import ( + l "log" + "log/syslog" + "os" +) + +func main() { + // setUp + writer, err := syslog.New(syslog.LOG_NOTICE, "call_event_parser") + if err == nil { + l.SetOutput(writer) + } + + args := os.Args[1:] + + if !validArgs(args) { + log("In valid command line arguments 9 required") + os.Exit(0) + } + + dbUser := args[0] + dbPassword := args[1] + dbHost := args[2] + dbPort := args[3] + database := args[4] + table := args[5] + processedDirectory := args[6] + uploadDirectory := args[7] + fileRunningPath := args[8] + + db := NewMySqlEventStore(dbUser, dbPassword, dbHost, dbPort, database, table) + parser := NewCallEventParser(db, processedDirectory, fileRunningPath) + + parseErr := parser.Parse(uploadDirectory) + + if parseErr != nil { + log("Call Event Parse Error: " + parseErr.Error()) + } +} + +func validArgs(args []string) bool { + return len(args) == 9 +} + +func log(message string) { + l.Print(message) +} diff --git a/test/data/emptydir/.gitignore b/test/data/emptydir/.gitignore new file mode 100644 index 0000000..e69de29