9511_project03

Command line program that parses a bank .csv file and generates and output file in format .csv or .xml containing the debits and credits of every user.
Index Commits Files Refs README LICENSE
commit 5f508c961494cb93003b230621e5a7706f450416
parent 6b416dc25a00a138a8d458d092ec067917d62755
Author: klewer-martin <martin.cachari@gmail.com>
Date:   Thu, 29 Jul 2021 16:43:34 -0300

Updated source code and input file generator

Diffstat:
MMakefile | 3+++
Mexamples/input_gen.py | 90++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Minclude/status.h | 10+++++-----
Minclude/utils.h | 9++++++++-
Minclude/vector.h | 2+-
Msource/cla.c | 52+++++++++++++++++++++++++++++++++-------------------
Msource/main.c | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msource/status.c | 23-----------------------
Msource/utils.c | 29+++++++++++++++++++++++++++++
Msource/vector.c | 6++++--
10 files changed, 239 insertions(+), 86 deletions(-)
diff --git a/Makefile b/Makefile
@@ -51,6 +51,9 @@ run5k:
 run50k:
     ./main -fmt csv -out output.csv -in examples/test_file_50k.csv -ti 1320498000 -tf 1420529000
 
+run100k:
+    ./main -fmt csv -out output.csv -in examples/test_file_100k.csv -ti 1320498000 -tf 1420529000
+
 run500k:
     ./main -fmt csv -out output.csv -in examples/test_file_500k.csv -ti 1320498000 -tf 1420529000
 
diff --git a/examples/input_gen.py b/examples/input_gen.py
@@ -2,7 +2,7 @@ from string import digits
 from time import strftime, gmtime
 from random import randint, choice
 
-LINES = 5
+LINES = 100000
 
 # OUTPUT:
 #     ID_TRANSACCION, ID_USUARIO, FECHA, MONTO, NUMERO DE TRAJETA, DESCRIPCION
@@ -10,19 +10,99 @@ LINES = 5
 
 descs = [ "Compras supermercado", "Pago tarjeta", "Compras libreria", "Pago Mecanico", "Pago Dentista", "Pago Servicios online", "Compras Ferreteria",  "Compras Accesorios Informatica", "Compras farmacia", "Ventas online", "Extraccion cajero" ]
 
-def card_number_generator():
-    return ''.join(choice(digits) for i in range(1, 16))
+def calculate_luhn(cc):
+    num = list(map(int, str(cc)))
+    check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
+    return 0 if check_digit == 10 else check_digit
+
+def generate_card(type):
+    """
+    Prefill some values based on the card type
+    """
+    card_types = ["americanexpress","visa13", "visa16","mastercard","discover"]
+
+    def prefill(t):
+        # typical number of digits in credit card
+        def_length = 16
+
+        """
+        Prefill with initial numbers and return it including the total number of digits
+        remaining to fill
+        """
+        if t == card_types[0]:
+            # american express starts with 3 and is 15 digits long
+            # override the def lengths
+            return [3, randint(4,7)], 13
+
+        elif t == card_types[1] or t == card_types[2]:
+            # visa starts with 4
+            if t.endswith("16"):
+                return [4], def_length - 1
+            else:
+                return [4], 12
+
+        elif t == card_types[3]:
+            # master card start with 5 and is 16 digits long
+            return [5, randint(1,5)], def_length - 2
+
+        elif t == card_types[4]:
+            # discover card starts with 6011 and is 16 digits long
+            return [6, 0, 1, 1], def_length - 4
+
+        else:
+            # this section probably not even needed here
+            return [], def_length
+
+    def finalize(nums):
+        """
+        Make the current generated list pass the Luhn check by checking and adding
+        the last digit appropriately bia calculating the check sum
+        """
+        check_sum = 0
+
+        #is_even = True if (len(nums) + 1 % 2) == 0 else False
+
+        """
+        Reason for this check offset is to figure out whether the final list is going
+        to be even or odd which will affect calculating the check_sum.
+        This is mainly also to avoid reversing the list back and forth which is specified
+        on the Luhn algorithm.
+        """
+        check_offset = (len(nums) + 1) % 2
+
+        for i, n in enumerate(nums):
+            if (i + check_offset) % 2 == 0:
+                n_ = n*2
+                check_sum += n_ -9 if n_ > 9 else n_
+            else:
+                check_sum += n
+        return nums + [10 - (check_sum % 10) ]
+
+    # main body
+    t = type.lower()
+    if t not in card_types:
+        print("Unknown type: '%s'" % type)
+        print("Please pick one of these supported types: %s" % card_types)
+        return
+
+    initial, rem = prefill(t)
+    so_far = initial + [randint(1,9) for x in range(rem - 1)]
+    return ("".join(map(str,finalize(so_far))))
 
 def generate_file(max_lines):
     id_transaction_base = 123400
     id_user_base = 1
-    id_user_max = 20000
+    id_user_max = 30000
     amount_base = 1
     amount_max = 10000
     for i in range(max_lines):
         id_transaction = (i + id_transaction_base)
         id_user = randint(id_user_base, id_user_max)
-        card_nr = card_number_generator() 
+
+        card_nr = generate_card("visa16")
+        while calculate_luhn(card_nr) != 0:
+            card_nr = generate_card("visa16")
+
         date = strftime("%d.%m.%Y %H:%M:%S", gmtime(1320487200 + i))
         
         j = randint(1, len(descs) - 1)
diff --git a/include/status.h b/include/status.h
@@ -5,10 +5,11 @@
 #include <stdlib.h>
 #include <stdarg.h>
 
-#define ERROR_RETRY_MSG "Verifique y vuelva a intentar"
-#define EXIT_SUCCESS_MSG "Ejecución terminada exitosamente"
-#define USERS_REGISTERED_MSG    "Usuarios registrados: "
-#define PROCESED_LINES_MSG        "Lineas procesadas: "
+#define MSG_ERROR_RETRY                "Verifique y vuelva a intentar"
+#define MSG_EXIT_SUCCESS            "Ejecución terminada exitosamente"
+#define MSG_USERS_REGISTERED        "Usuarios registrados"
+#define MSG_PROCESED_LINES            "Lineas procesadas"
+#define STR_INVALID_CARD_NUMBER        "Número no válido"
 
 #define STATUS_T_MAX 12
 
@@ -38,7 +39,6 @@ typedef enum {
 } csv_pos_t;
 
 void show_status(status_t st);
-void free_arrays(size_t num,...);
 
 extern const char *status_strings[STATUS_T_MAX];
 
diff --git a/include/utils.h b/include/utils.h
@@ -4,6 +4,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <stdbool.h>
 
 #include "cla.h"
 #include "user.h"
@@ -18,6 +19,11 @@
 #define IN_FILE_FIELDS    6
 #define IN_FILE_FIELDS_MAX_LEN    50
 
+#define STR_FMT_CSV "csv"
+#define STR_FMT_XML "xml"
+
+#define CARD_NO_VALID_LEN 16
+
 #define XML_STR_HEADER    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
 #define XML_STR_ROOT    "root"
 #define XML_STR_ROW        "row"
@@ -29,8 +35,9 @@ status_t get_date(time_t *, char **);
 status_t array_destroy(char **, size_t);
 status_t string_split(char *, char **, char *);
 
+bool is_valid_card(char *);
+
 void clean_array(char **);
 void clean_buffer(char *);
 
-
 #endif
diff --git a/include/vector.h b/include/vector.h
@@ -26,7 +26,7 @@ status_t ADT_Vector_add(ADT_Vector_t **, void *);
 status_t ADT_Vector_destroy(ADT_Vector_t **);
 
 status_t ADT_Vector_set(ADT_Vector_t **, void *, size_t);
-status_t ADT_Vector_print(const ADT_Vector_t *, FILE *, printer_t);
+status_t ADT_Vector_print(const ADT_Vector_t *, FILE *);
 status_t ADT_Vector_sort(ADT_Vector_t *, comparator_t);
 
 void *ADT_Vector_get_elem(const ADT_Vector_t *v, void *e);
diff --git a/source/cla.c b/source/cla.c
@@ -74,23 +74,34 @@ status_t cla_setup(int argc, char **argv, cla_t cla)
         for(f = FLAG_FMT; f < FLAGS_MAX; f++) {
             if(!strcmp(available_flags[f], argv[i])) {
                 switch (f) {
-                    case FLAG_FMT: strcpy(cla->fmt, argv[i + 1]); break;
-                    case FLAG_OUT: if((fp = fopen(argv[i + 1], "wt")) == NULL)
-                                       return ERROR_OPENING_FILE;
-
-                                   cla->fo = fp; 
-                                   break;
-                    case FLAG_IN: if((fp = fopen(argv[i + 1], "rt")) == NULL)
-                                      return ERROR_OPENING_FILE;
-
-                                   cla->fi = fp; 
-                                   break;
-                    case FLAG_TI: cla->ti = strtoul(argv[i + 1], &endptr, 10); 
-                                  if(*endptr != '\0') return ERROR_WRONG_TIME;
-                                  break;
-                    case FLAG_TF: cla->tf = strtoul(argv[i + 1], &endptr, 10); 
-                                  if(*endptr != '\0') return ERROR_WRONG_TIME;
-                                  break;
+                    case FLAG_FMT: 
+                        strcpy(cla->fmt, argv[i + 1]);
+                       break;
+
+                    case FLAG_OUT:
+                       if((fp = fopen(argv[i + 1], "wt")) == NULL)
+                           return ERROR_OPENING_FILE;
+
+                       cla->fo = fp; 
+                       break;
+
+                    case FLAG_IN:
+                       if((fp = fopen(argv[i + 1], "rt")) == NULL)
+                           return ERROR_OPENING_FILE;
+
+                       cla->fi = fp; 
+                       break;
+
+                    case FLAG_TI:
+                       cla->ti = strtoul(argv[i + 1], &endptr, 10); 
+                       if(*endptr != '\0') return ERROR_WRONG_TIME;
+                       break;
+
+                    case FLAG_TF:
+                       cla->tf = strtoul(argv[i + 1], &endptr, 10); 
+                       if(*endptr != '\0') return ERROR_WRONG_TIME;
+                       break;
+
                     default: return ERROR_FLAG_NOT_FOUND;
                 }
             }
@@ -113,6 +124,9 @@ status_t cla_create(cla_t *cla)
         return ERROR_MEMORY;
     }
 
+    (*cla)->fi = NULL;
+    (*cla)->fo = NULL;
+
     return OK;
 }
 
@@ -120,8 +134,8 @@ status_t cla_destroy(cla_t cla)
 {
     if(cla == NULL) return ERROR_NULL_POINTER;
 
-    fclose(cla->fo);
-    fclose(cla->fi);
+    if(cla->fi != NULL) fclose(cla->fi);
+    if(cla->fo != NULL)    fclose(cla->fo);
 
     free(cla->fmt);
     free(cla);
diff --git a/source/main.c b/source/main.c
@@ -4,11 +4,6 @@
 #include "../include/status.h"
 #include "../include/vector.h"
 
-#define IN_FILE_MAX_LEN    100
-#define IN_FILE_DELIM    ","
-#define IN_FILE_FIELDS    6
-#define IN_FILE_FIELDS_MAX_LEN    50
-
 int main (int argc, char *argv[])
 {
     status_t st;
@@ -68,6 +63,26 @@ int main (int argc, char *argv[])
         return st;
     }
 
+    /* Setea el impresor a ADT_Vector */
+    if(!strcmp(cla->fmt, STR_FMT_CSV)) {
+        if((st = ADT_Vector_set_printer(v, user_print_as_csv)) != OK) {
+            show_status(st);
+            cla_destroy(cla);
+            ADT_Vector_destroy(&v);
+            array_destroy(data, IN_FILE_FIELDS);
+            return st;
+        }
+    }
+    else if(!strcmp(cla->fmt, STR_FMT_XML)) {
+        if((st = ADT_Vector_set_printer(v, user_print_as_xml)) != OK) {
+            show_status(st);
+            cla_destroy(cla);
+            ADT_Vector_destroy(&v);
+            array_destroy(data, IN_FILE_FIELDS);
+            return st;
+        }
+    }
+
     /* Crea un usuario temporal */
     if((st = user_create(&user_tmp)) != OK) {
         show_status(st);
@@ -81,10 +96,22 @@ int main (int argc, char *argv[])
     while(fgets(buffer, IN_FILE_MAX_LEN, cla->fi)) {
 
         /* Separa la linea leida segun un caracter delimitador */
-        string_split(buffer, data, IN_FILE_DELIM);
+        if((st = string_split(buffer, data, IN_FILE_DELIM)) != OK) {
+            show_status(st);
+            cla_destroy(cla);
+            ADT_Vector_destroy(&v);
+            array_destroy(data, IN_FILE_FIELDS);
+            return st;
+        }
 
         /* Setea el usuario temporal con los datos obtenidos de la linea */
-        user_set_data(user_tmp, data);
+        if((st = user_set_data(user_tmp, data)) != OK) {
+            show_status(st);
+            cla_destroy(cla);
+            ADT_Vector_destroy(&v);
+            array_destroy(data, IN_FILE_FIELDS);
+            return st;
+        }
 
         amount = strtol(data[POS_AMOUNT], &endptr, 10);
         if(*endptr != '\0') return ERROR_CORRUPT_DATA;
@@ -96,15 +123,41 @@ int main (int argc, char *argv[])
         if(epoch < cla->ti) continue;
         else if(epoch > cla->tf) break;
 
+        /* Solo imprime en el archivo de salida las transacciones realizadas con una tarjeta valida */
+        if(!is_valid_card(data[POS_CARD_NUMBER])) {
+            fprintf(stderr, "%s: %s\n",STR_INVALID_CARD_NUMBER, data[POS_CARD_NUMBER]);
+            continue;
+        }
+
         /* Busca el id del usuario en el vector */
         if((user = ADT_Vector_get_elem(v, user_tmp)) != NULL) {
             /* Si lo encuentra le suma el monto correspondiente */
-            user_add_amount(user, amount);
+            if((st = user_add_amount(user, amount)) != OK) {
+                show_status(st);
+                cla_destroy(cla);
+                ADT_Vector_destroy(&v);
+                array_destroy(data, IN_FILE_FIELDS);
+                return st;
+            }
         }
+
+        /* Si no lo encuentra crea un usuario nuevo */
         else { 
-            /* Si no lo encuentra crea un usuario nuevo */
-            user_create(&user);
-            user_set_data(user, data);
+            if((st = user_create(&user)) != OK) {
+                show_status(st);
+                cla_destroy(cla);
+                ADT_Vector_destroy(&v);
+                array_destroy(data, IN_FILE_FIELDS);
+                return st;
+            }
+
+            if((st = user_set_data(user, data))) {
+                show_status(st);
+                cla_destroy(cla);
+                ADT_Vector_destroy(&v);
+                array_destroy(data, IN_FILE_FIELDS);
+                return st;
+            }
 
             /* Y lo agrega al vector */
             if((st = ADT_Vector_add(&v, user)) != OK){
@@ -131,25 +184,13 @@ int main (int argc, char *argv[])
     }
 
     /* Imprime el vector con los usuarios de acuerdo al argumento recibido */
-    if(!strcmp(cla->fmt, "xml")) {
-        if((st = ADT_Vector_export_as_xml(v, cla->fo, user_print_as_xml)) != OK) {
-            show_status(st);
-            free(user_tmp);
-            cla_destroy(cla);
-            ADT_Vector_destroy(&v);
-            array_destroy(data, IN_FILE_FIELDS);
-            return st;
-        }
-    }
-    else if(!strcmp(cla->fmt,"csv")) {
-        if((st = ADT_Vector_export_as_csv(v, cla->fo, user_print_as_csv)) != OK) {
-            show_status(st);
-            free(user_tmp);
-            cla_destroy(cla);
-            ADT_Vector_destroy(&v);
-            array_destroy(data, IN_FILE_FIELDS);
-            return st;
-        }
+    if((st = ADT_Vector_print(v, cla->fo)) != OK) {
+        show_status(st);
+        free(user_tmp);
+        cla_destroy(cla);
+        ADT_Vector_destroy(&v);
+        array_destroy(data, IN_FILE_FIELDS);
+        return st;
     }
 
     free(user_tmp);
diff --git a/source/status.c b/source/status.c
@@ -18,27 +18,4 @@ const char *status_strings[] = {
 void show_status(status_t st)
 {
     fprintf(stderr, "\n%s\n", status_strings[st]);
-    fprintf(stderr, "%s\n", ERROR_RETRY_MSG);
-}
-
-void free_arrays(size_t num,...)
-{
-    va_list valist;
-    size_t i;
-
-    va_start(valist, num);
-
-    for(i = 0; i < num; i++)
-        free(va_arg(valist, char *));
-}
-
-void close_streams(size_t num,...)
-{
-    va_list valist;
-    size_t i;
-
-    va_start(valist, num);
-
-    for(i = 0; i < num; i++)
-        free(va_arg(valist, FILE *));
 }
diff --git a/source/utils.c b/source/utils.c
@@ -5,6 +5,9 @@ status_t string_split(char *s, char **data, char *delim)
     char *p, *tmp;
     size_t fields = 0;
 
+    if(s == NULL || data == NULL || delim == NULL)
+        return ERROR_NULL_POINTER;
+
     for(p = s; (tmp = strtok(p, delim)); p = NULL)
         strcpy(data[fields++], tmp);
 
@@ -75,3 +78,29 @@ status_t get_date(time_t *e, char **data)
     }
     return OK;
 }
+
+bool is_valid_card(char *card_no)
+{
+    size_t i, j, k;
+    int arr[4][4], sum;
+    char tmp[2];
+
+    if(card_no == NULL) return false;
+    if(strlen(card_no) != CARD_NO_VALID_LEN) return false;
+
+    for(i = 0, k = 0; i < 4; i++) {
+        for(j = 0; j < 4; j++) {
+            arr[i][j] = (card_no[k++] - '0');
+            if(j % 2) {
+                arr[i][j] *= 2;
+                sprintf(tmp, "%d", arr[i][j]);
+                if(!(strlen(tmp) - 1)) arr[i][j] = (tmp[0] - '0');
+                else arr[i][j] = ((tmp[0] - '0') + (tmp[1] - '0'));
+
+            }
+            sum += arr[i][j];
+        }
+    }
+
+    return (sum % 10) ? false : true;
+}
diff --git a/source/vector.c b/source/vector.c
@@ -108,14 +108,16 @@ void *ADT_Vector_get_elem(const ADT_Vector_t *v, void *e)
     return NULL;
 }
 
-status_t ADT_Vector_print(const ADT_Vector_t *v, FILE *fp, printer_t pf)
+status_t ADT_Vector_print(const ADT_Vector_t *v, FILE *fp)
 {
+    status_t st;
     size_t i;
 
     if(v == NULL) return ERROR_NULL_POINTER;
 
     for(i = 0; i < v->size; i++)
-        (*pf)(v->a[i], fp);
+        if((st = v->printer(v->a[i], fp)) != OK)
+            return st;
 
     putchar('\n');
     return OK;