sisoputnfrba / foro

Foro de consultas para el trabajo práctico
148 stars 7 forks source link

Escrituras de files_metadata en un archivo.bin #1766

Closed guidoenr closed 4 years ago

guidoenr commented 4 years ago

buenas.. estoy teniendo problemas con la escritura de files_metadata a un Metadata.bin de un determinado pokemon, empece a tener problemas desde que manejo el campo blocks de un pokemon como una lista enlazada.. tenia pensarlo hacerlo como array dinamico y asi ser mas facil todo al leer de archivos, pero bueno, tambien es mas facil manejar una lista enlazada que un array dinamico si lo tengo fuera del archivo.

esta es la estructura de un t_file_metadata

typedef struct{
    char directory;
    int size;
    t_list* blocks;
    char open;
}t_file_metadata;

esta es mi funcion para grabarlo en un Metadata.bin

FILE* file = fopen(metaPath,"wb");
    int tam_lista_blocks = size_of_meta_blocks(metadata.blocks);

    fwrite(&metadata.open,sizeof(char),1,file);
    fwrite(&metadata.directory,sizeof(char),1,file);
    fwrite(&metadata.size,sizeof(int),1,file);
    fwrite(&metadata.blocks,tam_lista_blocks,1,file);

    fclose(file);

y esta mi funcion para leerlo

    t_file_metadata fmetadata_leido;
    FILE* file = fopen(path,"rb");

    fread(&fmetadata_leido.open,sizeof(char),1,file);
    fread(&fmetadata_leido.directory,sizeof(char),1,file);
    fread(&fmetadata_leido.size,sizeof(int),1,file);
    fread(&fmetadata_leido.blocks,4,1,file);

    fclose(file);
    return fmetadata_leido;

las funciones size_of_meta_blocks me da el tamaño del t_list* una vez asignados los indices de los blocks de mi pokemon, esta calculado asi porque tomo el tamaño como el size del elements_count de la lista sumado a la cantidad de elementos multiplicados por 4 (porque es de int) supongase que tengo una lista [1,2,3] , el elements count seria 3, y cada int de 4 bytes, por lo tanto la funcion retornaria 3*4 + 4 (por el tamaño del elements counts, que tambien es un int)

int size_of_meta_blocks(t_list* lista){
    return sizeof(int) + (lista->elements_count * 4);
}

creo que el error viene por aca, por eso le doy enfasis.. el resultado que me da al leerlo es basura, lo muestro a continuacion.. nose si esta bien grabar toda una estructura t_list*en un archivo de una, pense al principio grabarlo de a partes pero iba a traerme problemas al momento de asignarlos a mi estructura t_list que lei, puesto que no puedo igualar elemento por elemento.. no puedo encontrar el error, si alguien me da una mano se lo agradezco.

Sin título

mgarciaisaia commented 4 years ago

¡Aloha!

La pregunta es buena.

La respuesta más puntual es que fwrite no tiene idea de cómo funcionan las t_list de las commons, y entonces no tiene chances de recorrer punteros y cosas. La documentación dice que graba una cantidad determinada de objetos de un tamaño determinado, leyendo a partir del puntero que le das. Entonces, nada de recorrer punteros ni blah - escribe en el archivo una cantidad de bytes a partir del puntero que le das.

Las listas enlazadas, como bien mencionás, usan memoria no-contigua para guardar los distintos nodos (bah, al menos las nuestras lo hacen, y en general son así), por lo que no te va a servir ese approach.

Además, el enunciado te dice cómo tenés que escribir esa lista en el archivo: como un string que arranca con el caracter [, seguido de caracteres que representan los números que tenés que guardar, y blah. Dudo que las listas de las commons usen ese formato en memoria, ¿no?

tenia pensarlo hacerlo como array dinamico y asi ser mas facil todo al leer de archivos, pero bueno, tambien es mas facil manejar una lista enlazada que un array dinamico si lo tengo fuera del archivo.

Esto me parece jugosísimo, porque es muy cierto: la forma de expresar la lista en el archivo no es muy cómoda para manejar la lista en memoria. Pero cada una sí es cómoda para su caso: la lista enlazada, porque me permite más o menos fácil agregar y sacar nodos, y es una estructura que banca nodos con distintos tipos de contenidos, y blah; el formato textual del archivo de configuración, porque me es fácil a mí como humano leerlo, entenderlo, y editarlo; e, incluso, hay un tercer formato que mencionás vos (el del array dinámico), que es más fácil para manejar tu lista y persistirla en disco con fwrite, pero que puede estar más atada al tipo de datos que contiene, o que puede necesitar reservar bloques muy grandes de memoria contigua que al sistema operativo le cueste más conseguir.

Entonces, de algún modo, para la misma información podemos tener distintas formas de representarla.

La que el enunciado pide para el archivo de configuración es una representación de la información que no es manejable de manera práctica al momento de ejecutar el programa: tengo que andar leyendo un string, parseando los caracteres, interpretar cuándo hay números, e inferir información a partir de esos datos. Pero es re cómoda para guardar en un archivo o transmitirla fuera de mi programa. A las representaciones de información con formatos poco cómodos para el programa pero piolas para interactuar con el mundo exterior las llamamos "serialización".

Entonces, claro: el fwrite ese no va a andar. Y no es por culpa, precisamente, de tu size_of_meta_blocks. Tiene más que ver con que estás asumiendo que las estructuras de datos que usás en memoria tienen una forma que en realidad no tienen.

Entonces, lo que tenés que hacer es serializar esa estructura con el formato que menciona el enunciado. Recorrer tu lista, entender cuál es el número que representa a cada nodo de tu lista, y escribir eso en el archivo con el formato indicado.

Y, después, claro: hay que deserializar. Porque eventualmente vas a querer leer ese archivo, con la lista serializada, y crear una representación en memoria que sea cómoda para que tu programa trabaje con ella. La ventaja es que el formato elegido no es tan aleatorio: es justo, justo el formato de lista de strings que el t_config de las commons sabe interpretar. Así que una parte de la función te la regalamos: nos das el cacho de texto, y te devolvemos el puñadito de números que representa, en el orden correspondiente. Después sólo te queda la parte de, a partir de ese número, crear en memoria las estructuras correspondientes con las que tu programa sabe operar.

¿Se entiende la idea?

guidoenr commented 4 years ago

Bien, quedo todo claro pero me partiste la cabeza con el ultimo parrafo. Vi que me citaste lo del array dinamico. Yo lo que quiero grabar en ese archivo binario son todos los indices de blocks que tiene un pokemon, osea, en cuales blocks realmente tengo que buscar. Me hizo ruido cuando mencionaste que un array me limita los tipos de datos, y eso aca no es problema, puesto que los indices solamente me van a servir para poder buscar en los verdaderos Blocks.bin de mi filesystem, y por lo tanto podrian ser solamente enteros.

Pense en t_list por el tema de que va a ir variando a lo largo de mi programa. Pero por que me hablas de serializar una estructura? el fwrite y fread (hasta antes de realizar esa modificacion) me tomaban los demas campos de mi t_metadata_file y es mas, podia leerlos en el orden que quiera, sin seguir una serializacion.. yo podia leer de una el campo DIRECTORY x ejemplo, sin importar que el primero que se graba es OPEN.

De todas formas, nombraste serializacion y lo que se me ocurrio, fue tal vez grabar mi lista->elements.count y en base a eso hacer un while para grabar y leer la cantidad de blocks que tiene mi pokemon ;D. Haceme saber si lo que digo esta bien Mati!

Por cierto, gracias!

mgarciaisaia commented 4 years ago

Volvamos un par de pasos atrás, a ver si estoy entendiendo lo que estás queriendo hacer o si te estoy confundiendo al pedo.

¿Qué tiene que haber en tu archivo de resultado? ¿Qué pinta tiene que tener? Contame algún ejemplo, y cómo sería el archivo que querés generar.

guidoenr commented 4 years ago

Supongase que me llega un pokemon NEW PIKACHU EN LA POSICION 3-2=1

eso quiere decir que tengo que guardar a un pikachu en la posicion x=3; y=2 en mi mapa..

El tp pide guardar esa informacion con el siguiente formato 3-2=1

Yo como filesystem, tengo que asignarle espacio a esa informacion para guardarla, y lo hago mediante bloques del tipo binario que estan creados inicialmente (de 64bytes cada uno)

Entonces, lo que tengo que hacer es poder darle algunos blocks que esten libres para que pueda escribirlos y que queden guardados.

Cada pokemon tiene un t_file_metadada en el cual un campo son los BLOCKS que en realidad, es como la tabla de indices en los cuales yo tengo que guardar (o ya guarde) la informacion.

Es asignacion indexada, que lo que quiere decir es que yo la informacion '3-2=1' la tengo distribuida en varios bloques de mi filesystem a leer. (aunque en este caso es un solo bloque, puesto que tengo 64 bytes, y el string de arriba pesa solamente 5bytes.) Pero en el campo blocks, con un pokemon mas grande, un ejemplo podria ser: =[1,42,22,5] de los que me dice "Che bueno, esta informacion la tengo en estos bloques, leelas en orden y te damos la posicion"

Pero esto con el tiempo varia, puede haber mas o menos bloques que los que tengo inicialmente.

Entonces de ahi surgio mi idea de un t_list para poder guardar los indices de mis bloques y manejarlos dinamicamente, y tambien de ahi surgio mi incoveniente de como grabo esta lista en el t_metadata_file que en realidad es un archivo binario de mi pokemon.

mgarciaisaia commented 4 years ago

OK, hablamos de lo mismo, entonces.

El enunciado te da un ejemplo de cómo se tiene que escribir el archivo (en la página 18):

Ej Archivo:

DIRECTORY=N
SIZE=250 
BLOCKS=[40,21,82,3]
OPEN=Y

Entonces, ahí te está diciendo cómo se tendría que ver el archivo de este ejemplo. Por más que se llame metadata.bin, eso es un archivo de texto. El primer byte de ese archivo es el correspondiente a la letra D mayúscula (el byte 67, si la memoria no me falla), el siguiente es la I mayúscula, y así. Y tu lista de bloques se representa como un texto que empieza con el caracter [, y sigue con texto que representa los números de los bloques, separados por comas. Ese [40,21,82,3] es lo mismo que si abrís un editor de texto y escribís esos caracteres. Es decir, no son 4 bytes representando el número 40, seguido de 4 bytes representando el número 21, si no que son el caracter '4' seguido del caracter '0', seguido del caracter ',', seguido del '2', y así. En este caso, el bloque 40 ocupa 2 bytes del archivo, mientras que el bloque 3 ocupa uno sólo (y el bloque 1083581815 ocuparía 10 bytes, ponele).

En tu memoria probablemente no tengas el texto 40. En tu memoria tenés un campo de tipo int (ponele) que va a usar 4 bytes para representar el número de bloque - ya sea el bloque 1 o el bloque 36572. Son distintas representaciones del mismo dato: en el archivo uso una representación textual, porque los humanos solemos leerla mejor, mientras que en memoria uso una representación numérica, porque el compilador sabe hacer unCoso[14] (con 14 siendo un int), pero no sabe resolver (al menos no con el sentido que me gustaría) unCoso["14"] (con "14" siendo un char * que apunta a un byte que representa el caracter '1', seguido de otro que representa al '4', seguido de otro que vale 0).

Entonces, ¿cómo grabás tu t_list de bloques en el archivo?

Buen, si te pidiera que calcules la suma de todos los números de bloque de esa lista, ¿cómo lo harías?

¿Y si tuvieras que imprimir una línea diciendo "Hola" y el número de bloque por cada uno? Onda, para la lista [1, 14, 3, 122], que se imprima por pantalla:

Hola 1
Hola 14
Hola 3
Hola 122

¿Cómo harías?

De forma bastante similar vas a poder imprimir la lista en el archivo.

guidoenr commented 4 years ago

Me perdi totalmente, vos me estas sugiriendo que escriba en un archivo el string entero este por ejemplo : DIRECTORY=Y , y que yo sepa que lo primero que esta es algo de **11 bytes** y lo va a ser siempre porque la otra variante es DIRECTORY=N.. bien, hasta ahi vamos, es una especie de serializacion hard codeada digamos.

con el campo de OPEN=Y/N pasa lo mismo que arriba, no hay problema, los podria leer siempre.. pero el tema esta cuando me meto con los campos SIZE y BLOCKS nuevamente.

El size obviamente varia segun el pokemon, podria ser algo de 1 digito, 2 digitos, 3 .. etc. y BLOCKS ni hablar, volvemos al problema que te plantie inicialmente.. como sabria yo al momento de leer cuantos bytes me va a ocupar el contenido de BLOCKS o cuantos bytes me va a ocupar el contenido de size?

guidoenr commented 4 years ago

Ya lo pense , manejo todo como char* y chau, grabo y leo asi.. con el SIZE lo paso a string, y asi se su longitud, sea 64, 124, 123123213 .. lo que fuese. PEEEERo, cuando quiero leer tengo el mismo problema, como se cuantos voy a tener que leer?

ejemplo

void escribir_campo_size(FILE* f,int metadata_size){
    char* a_escribir = append("SIZE=",string_itoa(metadata_size));
    int tam = strlen("SIZE=") + strlen(string_itoa(metadata_size));
    fwrite(&a_escribir,tam,1,f);
}

voy a intentar con blocks a ver que me sale, de ultima pregunto

mgarciaisaia commented 4 years ago

Cuando yo te muestro esto:

DIRECTORY=N
SIZE=250 
BLOCKS=[40,21,82,3]
OPEN=Y

Vos sabés que el campo DIRECTORY tiene un valor N. Y, por algún motivo, sabés que el valor no es NSIZE=250 ni N SIZE ni ninguna otra cosa. ¿Por qué lo sabés?

Bueno, porque, quizá está tan evidente que no lo vemos: hay un salto de línea entre la N y la S. Son dos líneas distintas, entonces sabemos que son dos claves/valores distintas, indistintamente de si DIRECTORY sólo puede tener un único caracter como valor o si su valor fuera de tamaño variable (ponele, SIZE vale 250, y no 250 BLOCKS).

Entonces, nada - algunas representaciones se basan en tamaños fijos para los campos, y otras representaciones usan valores centinela para separarse unos de otros. Por ejemplo, en la memoria, un int tiene un tamaño fijo, mientras que un "string" usa el valor \0 como centinela para indicar el fin de una cadena de tamaño variable.

La representación de este archivo, más human-friendly, es de tamaño variable, usando el centinela "salto de línea" (\n) para indicar cuándo termina un valor.

Me perdi totalmente, vos me estas sugiriendo que escriba en un archivo el string entero este por ejemplo : DIRECTORY=Y , y que yo sepa que lo primero que esta es algo de **11 bytes** y lo va a ser siempre porque la otra variante es DIRECTORY=N.. bien, hasta ahi vamos, es una especie de serializacion hard codeada digamos.

con el campo de OPEN=Y/N pasa lo mismo que arriba, no hay problema, los podria leer siempre.. pero el tema esta cuando me meto con los campos SIZE y BLOCKS nuevamente.

El size obviamente varia segun el pokemon, podria ser algo de 1 digito, 2 digitos, 3 .. etc. y BLOCKS ni hablar, volvemos al problema que te plantie inicialmente.. como sabria yo al momento de leer cuantos bytes me va a ocupar el contenido de BLOCKS o cuantos bytes me va a ocupar el contenido de size?

Esto... Podrías tomarte el laburo de abrir el archivo, buscar el primer salto de línea, levantar ese string, buscar el signo =, asumir clave por un lado, valor por el otro, y toda esa perorata. Pero... justamente ese mismo formato que tiene ese archivo, ¿no te suena de algún lado? ¿Es muy distinto a este otro?

TAMANO_MEMORIA=2048
TAMANO_MINIMO_PARTICION=32
ALGORITMO_MEMORIA=BS
ALGORITMO_REEMPLAZO=FIFO
ALGORITMO_PARTICION_LIBRE=FF
IP_BROKER=127.0.0.1
PUERTO_BROKER=6009
FRECUENCIA_COMPACTACION=3

¿Será casualidad que se parecen?

Nada: leer esos archivos ya te lo damos más o menos resuelto.


Para escribirlo, insisto: ¿Cómo escribirías los Hola 1, Hola 14, Hola 3 en pantalla?

guidoenr commented 4 years ago

Ya hice todo, manejo todo como char el size, el directory y el open , me arme un parser para los blocks que sigue siendo un `t_list`

parser: (por si a alguno le sirve)

void escribir_campo_blocks(FILE* f ,t_list* blocks){
    char* format = "[";

    while (blocks->head->next != NULL){
        format = concatenar(format,string_itoa(blocks->head->data));
        format = concatenar(format,",");
        blocks->head = blocks->head->next;
        if (blocks->head->next == NULL){
            format=concatenar(format,string_itoa(blocks->head->data));
        }
    }

    format = concatenar(format,"]");

    int size = strlen(format) + 1 ;

    fwrite(&format,size,1,f);

}

hice la prueba con una lista de 4 blocks

t_list* blocks = list_create();
    list_add(blocks,1);
    list_add(blocks,61);
    list_add(blocks,3);
    list_add(blocks,12);

como resultado, el format me da [1,61,3,12] lo cual esta perfecto para grabar, dsp de concatenarlo con BLOCKS= Tengo pensado grabar todo ese char ahora dentro de mi metadata.bin, lo cual es facil porque ahora se el tamaño de ese char* y no tengo ni un problema.

Decime que opinas de esto mati, y la ultima pregunta que te hago:

El '/0'de un char* cuenta como un byte en mi archivo binario? o no? y el '\n' para indicar el fin de linea , la misma pregunta, se graba? como funciona en un archivo? lo tengo que grabar?

GRACIAS CRACK.

mgarciaisaia commented 4 years ago

Ojota: eso no es un parser, eh. Un parser es un coso que te permite entender la información que hay escrita en un texto (en estas latitudes traducen parser como "analizador sintáctico").

Pero sí banco que sea una función que te traduce una lista de bloques a la representación textual que pide el enunciado.

Te pregunté un par de veces cómo harías para imprimir eso en pantalla (un list_iterate con un printf) porque casi casi del mismo modo podés imprimirlo en un archivo (usando fprintf) sin tener que andar armando el string en memoria (seguramente tu función concatenar tenga un realloc, cambiando las asignaciones de memoria varias veces).

Pero, nada - ya estás ahí, o algo.

Respecto a las dos preguntas que hacés... Nada, tenés todas las herramientas para probarlo y contestarlas vos. Probalo, fijate qué te parece, y contame así vemos si se escapó algo más.

¡Abrazo!

mgarciaisaia commented 4 years ago

¿Qué pasó? ¿Cómo solucionaste?

guidoenr commented 4 years ago

Que tal mati. Lo solucione haciendo lo que te dije arriba.

Manejo todo como char* , lo cual es facil de manipular. Vi que en los archivos binarios se graba un . que es como el /0 entonces, esto me facilito miles de cosas.

Tengo mas facilidad al leer, tras el beneficio de que se que por ejemplo OPEN=Y/N y DIRECTORY=Y/N van a tener siempre el mismo size, yo se que desde ahora en mas tengo que leer hasta que aparezca el . o /0

Entonces, obviamente hardcodie el size para estos dos campos al momento de leer, pero con el tema de los BLOCKS por ejemplo, tengo pensado hacer una especie de desarmador inverso del que te mostre arriba, asi obtengo mis numeros sin problemas. Una vez que tengo eso, los meto a un t_list* nuevo, agrego, elimino, hago lo que quiera con mis bloques y una vez que ya tengo el `t_listdefinitivo, los vuelvo a grabar con el """PARSER""" que te mostre arriba, y puedo hacer todo esto, puesto que se que por ejemplo tengo el campo blocks en el espacio **52** (ej..) de mi archivo, entonces leo a partir de ahi hasta que encuentre el.`

y te preguntas, como leo el campo SIZE ahora, que tambien varia porque puede ser un size de 1 o n digitos. Entonces, cuando lea/escriba los blocks de un metadata, la funcion la hago int y se en que posicion termina esos blocks , entonces, al leer el SIZE se de donde empezar :D.

Capaz es medio villero, lo se y capaz me este complicando las cosas.. pero fue lo que se me ocurrio. Aparte no soy el creador de DEBIAN ;D

mgarciaisaia commented 4 years ago

@guidoenr4 me asustaría un poco si estuviera intercambiando mensajes con el creador de Debian. No tengo experiencia con developers viller@s, tampoco - o al menos no sabiéndolo - pero no veo por qué sería relevante acá.

Siento que hay dos o tres cosas que traté de transmitirte y que no te están llegando, o que estás decidiendo que no te son muy relevantes.


La primera, quizá, es que, por más que se llame metadata.bin, llamarlo "archivo binario" a esto es un exceso. Mirá este archivo: metadata.txt. Abrilo con un editor de textos, y contame: ¿es un archivo binario?

Buen, es un archivo binario en tanto a que es un archivo guardado en una computadora binaria, y entonces en el fondo sabemos que hay bits en algún lado. Pero en mi barrio decimos que es un archivo de texto plano: el archivo está formado por bits, sí, que componen bytes, que cuando los interpretamos con cierto encoding me muestra caracteres de texto; entonces, archivo de texto plano.

Archivo binario llamaría a, no sé, un archivo cuyos primeros 4 bytes tienen encodeado un int en una representación entera con signo, ponele. A eso, si lo abro con un editor de textos, me va a intentar mostrar letritas, pero probablemente no sea la información que me interese. Ponele, si hacés less /bin/ls para ver el binario ls. A eso llamamos archivo binario.

Los "campos" de los archivos binarios suelen estar determinados por tamaños fijos. Un poco lo que hacemos en la charla de serialización: pongo primero algunos campos de tamaño fijo que me describan lo que viene después, que puede ser también de un tamaño fijo o ser variable. Qué se yo, en algún lado estará especificado qué significa cada byte del binario de /bin/ls.

Pero con los archivos de texto es distinto. Están pensados para human@s, y entonces la información la separamos con delimitadores que solemos entender los humanos. Entre este párrafo y el anterior hay dos bytes: dos saltos de línea (\n\n). Están tan a la vista (hay un puñado ENORME de píxeles blancos entre párrafo y párrafo) que no los vemos. Pero están ahí. Y, en el metadata.txt que te mencioné recién, también están.

Ni en memoria (ni en disco, claro) existe el concepto de "saltar de línea". No existe poner "una cosa abajo de la otra" en la memoria. Lo que tenemos son esos bytes (el \n, por ejemplo) que representan esa idea. Para verlo (o para ver los bytes que componen cualquier archivo) podés usar bless o cualquier visor/editor hexadecimal. Probalo con el metadata.txt, o con /bin/ls. Vas a ver qué bytes están escritos en el archivo, y si comparás los del metadata.txt con lo que ves al abrirlo con un editor de texto, vas a identificar qué bytes son los saltos de línea.

Intuitivamente entendemos (buena parte de) el formato de los archivos de texto: cada línea es un par clave-valor. Si decimos "cada línea", estamos diciendo que el delimitador de un par clave-valor es el salto de línea, ese byte \n que no vemos porque no se muestra \n si no que se muestra como un salto de línea.

Y cada valor se distingue de la clave porque hay un caracter = en el medio de ambos. A golpe de vista más o menos lo entendemos, y con más o menos dificultad se puede escribir código que entienda lo mismo.

Hecha toda esta digresión, confío en que para esta altura me compres la idea de que el metadata.txt es un archivo de texto. Así que hagamos una prueba: renombrá el archivo como metadata.bin. ¿Cambió algo de todo lo que escribí hasta ahora? ¿Alguna de todas las cosas que postulé sobre el archivo dejó de ser cierta?

Si estamos de acuerdo en que sigue siendo lo mismo, tenga la extensión que tenga, acordamos, entonces, que el metadata.bin es un archivo de texto.


La segunda cosa es que pensar en tamaños de campos cuando hablamos de archivos de texto no es la mejor estrategia.

Como te decía antes, entendemos que los distintos campos están separados en distintas líneas (es decir, separados por saltos de línea), y que las claves y valores se separan con el caracter =.

Entonces, dado que tengo delimitadores de campos, y que los campos son de tamaño variable, encarar el problema de leer/escribir esos archivos como un problema de tamaños de campos es usar la herramienta incorrecta. La convención/especificación del formato de ese archivo (vamos, que tampoco es que tenemos una especificación súúúúúúper específica) no te dice "el primer campo va a ser el campo BLAH, ocupando X bytes, de los cuales el byte número N significa tal cosa". Más bien el formato parece ser "un montón de líneas (separadas por \n, claro) de las cuales, aquellas que contengan el signo =, tendrán como clave el texto que está a la izquierda del primer signo =, y como valor lo que está a la derecha". Más algunas que otras definiciones sobre cómo interpretar los tipos de los valores. Ponerse a averiguar/adivinar los tamaños de los campos y qué sé yo no parece ser la mejor estrategia.

Podés escribir líneas en un archivo (o, incluso, podés escribir pedazos de línea) sin preocuparte por cuánta memoria ocupen. Y podés leer líneas, e ir "interpretándolas" (buscando claves y valores) a medida que las leés, sin tener que pensar en leer una cantidad fija de bytes desde el archivo.

Entonces, eso: no te enfoques en los tamaños de los campos de un archivo que no tiene campos definidos por su tamaño.


La tercera cosa es que el problema es mucho más sencillo de lo que lo estás viendo.

Si querés armar un string en memoria, como venís haciendo, un poco sí te van a importar los tamaños de la información, porque vas a tener que reservarle memoria a tu string. Pero todo eso lo podés evitar, como te mencionaba, escribiendo la información directamente al archivo. No necesitás hacer ningún procesamiento con el string: podés escribir campo por campo, valor por valor en el archivo, y ya. Fijate en tu código: justo después de terminar de armar el string, le hacés un fwrite, y no volvés a usarlo (y te faltó liberarlo 🙄).

Por eso te preguntaba cómo imprimirías esos datos en pantalla: porque casi de la misma manera los podés "imprimir al archivo". Querés imprimir strings a un archivo (en vez de "escribir bytes genéricos"), y entonces tenés (y podés usar) una función que te simplifica trabajar con strings en vez de con bytes genéricos: fprintf en lugar de fwrite (del mismo modo que usás printf para imprimir por pantalla, en vez de hacer write al STDOUT).

Al mismo tiempo, el formato del archivo metadata.bin (o metadata.txt, o como quieras llamarlo) es exactamente el mismo que usan los archivos de configuración del TP, y es exactemente el mismo que el que sabe manejar el TAD t_config de las commons.

Esto no es casualidad: no nos interesa, desde el punto de vista didáctico, que se peleen con parsear strings. Nos interesa que lidien con guardar y levantar información en archivos, y que puedan pasar de las estructuras en memoria a su mínima representación en texto y a prueba de humanos, y que a partir de esa representación puedan rearmar la cosa en memoria. O la función que sea que cumplen estos archivos en el contexto del enunciado del TP.

Entonces, podés pasarte una semana codeando funciones que calculen tamaños de campos o busquen saltos de línea e interpreten cosas. O podés hacer config_create("metadata.bin") seguido de un par de config_has_property(metadata, "BLOCKS") y config_get_array_value(metadata, "BLOCKS") y concentrar el esfuerzo en recrear las estructuras que correspondan a partir de la info que levantás del archivo.


La cuarta cosa (parece que no eran "dos o tres" 🤷) es que trates de parar la pelota, entender qué estás queriendo hacer, leer detenidamente lo que te escribimos, y tener en cuenta esto.

En lo que va del thread te tiré cuatro o cinco (igual ya viste que cuento como el ocote) preguntas/sugerencias de ejercicios, sobre las que nunca contestaste. Entre eso, y que no me pareció hasta ahora que hayas entendido el mensaje que te quería dar con ellas, se me ocurre que es muy probable que no hayas hecho el ejercicio de resolver esas preguntas.

La respuesta a:

si te pidiera que calcules la suma de todos los números de bloque de esa lista, ¿cómo lo harías?

Podría ser algo como:

int suma = 0;
void _sumar_elemento(void *nodo) {
  suma += nodo->num_bloque;
}
list_iterate(bloques, _sumar_elemento);

Y, a:

¿Y si tuvieras que imprimir una línea diciendo "Hola" y el número de bloque por cada uno?

Sería:

void _mostrar_elemento(void *nodo) {
  printf("Hola %d\n", nodo->num_bloque);
}
list_iterate(bloques, _mostrar_elemento);

¿Y para escribir la lista de bloques en el archivo (validaciones no-incluidas)?

fprintf(archivo, "BLOCKS=[");
int el_primero = 1;
void _imprimir_elemento(void *nodo) {
  if (el_primero == 1) {
    fprintf(archivo, "%d", nodo->num_bloque);
  } else {
    fprintf(archivo, ", %d", nodo->num_bloque);
  }
  el_primero = 0;
}
list_iterate(bloques, _imprimir_elemento);
fprintf(archivo, "]\n");

Sí, la función es un poco más complicada porque hay que lidiar con que el primer elemento va sin una , adelante. Pero podés arrancar ignorando ese detalle, y la función tendría la misma pinta que las dos anteriores. Y de ahí la mejorás.

Pero sin resolver los pasos anteriores, pasar de 0 a todo eso, buen, sí - es más complicado.

Entonces, nada, eso: dedicale un rato a procesar las cosas que te contamos.

Un abrazo.

guidoenr commented 4 years ago

Claro, yo lo que hice fue ser bastante cuidadoso con los archivos binarios puesto que no sabia que eran iguales a un archivo de texto plano. Al principio pense que justo esos archivos tenian formato de config, pero lo que menos me imagine era que podiamos usar las funciones de las config, porque primero son archivos .config y cuando vi su estructura, vi algo complejo de como que creaba un direccionario y demas cosas.. entonces dije "che bueno, por aca no es .. olvidate".

Lo de parsear, tamaños y eso no me resulto gran problema.. ya a esta altura venis mas o menos sabiendo como hacer un fseek a un archivo y demas.. pero claro, me llevo tiempo y como decis vos, no es la complicacion de este TP.

Te agradezco mati, voy a probar implementar todo lo de las configs con mis archivos binarios.

Y con respecto a las funciones que me mencionas, obvio que las lei.. y se como se hacen, se reducia simplemente a iterar algo y imprimirlo, pero nunca termine de entender que me queres decir con eso, tirarme un centro como para decirme que lea de a un caracter y ir armando algo? es lo que hago, y tambien cuando escribo.. capaz si te muestro mi modulo lo entenderias mejor, y es mas, en la funcion que te mostre arriba hizo la misma logica que lo tuyo, leo de a uno y armo un char*.

guidoenr commented 4 years ago

Bien.. las funciones de las config para crear me parece que no andan.


    FILE* f = fopen(realPath,"wb");
    fclose(f);

    t_config_metadata* metadata = malloc(sizeof(t_config_metadata));
    metadata = config_create(realPath);

    metadata->blocks = 5192;
    metadata->blocksize = 64;
    metadata->magic_number = "TALL_GRASS";

    config_save(metadata);

    config_destroy(metadata);
    liberar_config(metadata);

    log_info(logger,"Se creo Metadata.bin en: %s",realPath);

ahora me hiciste acordar porque no lo hice.. me acuerdo que pasamos por lo mismo cuando queriamos crear las config por default de los otros modulos, tales como la config de team por ejemplo.

Las funciones estas no andan, no crean nada ni escriben nada.. si la usamos para leer y eso anda perfecto. Me parece que voy a tener que seguir creando los datos como los venia haciendo.

iago64 commented 4 years ago

Aloha, me sumo un toque tarde, ya que claramente las explicaciones de Mati son geniales y poco puedo aportar sobre eso, pero quisiera sumar algo interesante que es que en el TP los archivos "binarios" no existen como tales, que la extensión sea .bin es meramente anecdotico, podrían ser .exe, .txt o .pdf y aun así los seguirían utilizando de la misma forma, ya que la extensión es algo que nos mal acostumbro windows (y un poco mac).

Ahora, respecto al config, estas seguro que funciona asi? revisaste que tenes en la estructura del config? capaz que no te funciona no porque estén "mal las commons" si no porque las estas usando de la forma incorrecta? Fijate que tu t_config_metadata a priori tiene campos que no estan en el t_config de las commons, al menos no veo que en el codigo tengas un blocks, un metadata o un magic_number, capaz que lo que tengas que hacer sea darle el formato de ese t_config_metadata al t_config que tenemos en las commons que es el siguiente:

typedef struct {
    char *path;
    t_dictionary *properties;
} t_config;

Donde posiblemente el properties sea el diccionario con los diferentes valores que queres setear y que le vas a pasar al config_save()

Saludos.-

guidoenr commented 4 years ago

Bien, pero como le das ese formato? agrego los campos a la estructura esa?

typedef struct {
    int tiempo_reintento_conexion;
    int tiempo_reintento_operacion;
    int tiempo_retardo_operacion;
    char* punto_montaje_tallgrass;
    char* ip_broker;
    char* puerto_broker;
    char* ip_gameBoy;
    char* puerto_gameBoy;
    char* ip_gameCard;
    char* puerto_gameCard;
}t_config_game_card;

este por ejemplo es el config de game_card. Y esto funciona obvio, pero pasa como te comente arriba no puede setear, ni guardar , ni nada. Solamente puede leer

mi t_config_metadata es el siguiente :

typedef struct{
    int* blocksize;
    int* blocks;
    char* magic_number;
} t_config_metadata;

y bien , la estructura general t_config es asi:

typedef struct {
        char *path;
        t_dictionary *properties;
    } t_config;

donde t_dictonary


typedef struct {
        t_hash_element **elements;
        int table_max_size;
        int table_current_size;
        int elements_amount;
    } t_dictionary;

y

struct hash_element{
        char *key;
        unsigned int hashcode;
        void *data;
        struct hash_element *next;
    };
    typedef struct hash_element t_hash_element;

veo que es una estructura complicada de manejar por si, como haria para meter yo los campos de mi config_metadata a eso?

guidoenr commented 4 years ago

A lo que voy @iago64 es que, es necesario meterme con todo esto? no tengo nocion de como funciona esta estructura ni como hacen todas las funciones de configs para trabajarlos.

iago64 commented 4 years ago

Bueno, vamos en orden porque me parece que te estas mareando fuerte y claramente la idea es todo lo contrario: Que los tipos se llamen t_config o t_configmetadata no quiere decir que las funciones que empiezan con config* siempre funcionen, porque justamente quienes hicieron dichas funciones (por ejemplo config_save) lo hicieron pensando en su estructura:

typedef struct {
    char *path;
    t_dictionary *properties;
} t_config;

Con lo cual, cualquier cosa que le mandes que no respete esa estructura va a hacer que estas funciones no hagan absolutamente nada

Ahora bien, si frenas la pelota un segundo y revisas las commons no desde el punto de las estructuras, si no de las operaciones, podrias ver que por ejemplo los t_dictionary, tienen funciones asociadas que te permiten por ejemplo, crear el diccionario, agregarle elementos, etc, etc y eso lo podes ver claramente en el archivo dictionary.h

Ahora a este punto ya debes sabes como crear y agregar los datos a esa estructura de diccionario, se te ocurre como podes hacer para cargar los datos en tu metadata.bin?

EDIT La idea de que tratemos con mati de explicartelo es que justamente, un error al operar los archivos, puede hacer que tu tp rompa, siendo que esta pensado para que los archivos no sean tan complejos de operar y no esta bueno que se la compliquen con esto.

Saludos.-

guidoenr commented 4 years ago

Bien.. perdon por venir tan a fondo, se que parece que ni freno la pelota.. es que toda esta parte ya casi la tenia manejando archivos del modo "novato" digamosle, con fput, fseek y demas.. entonces esto me esta llevando tiempo y me esta haciendo volver bastante para atras, por eso la desesperacion.

lo que se me ocurrio fue lo siguiente que es basicamente, crear un dictionary por mi mismo y igualarlo al que tiene el t_config por lo tanto, estaria respetando la estructura para las funciones de config que vos me dijiste, de todas formas, esto no me anda , cuando compilo , rompe en el config_save_in_file , capaz la estoy usando mal.. pero no se, esto fue lo que se me ocurrio de momento

esta funcion crear el metadata inicial digamosle

**void crearMetadata(char* path){**

    char* realPath = concatenar(path,"/Metadata/Metadata.bin");

    t_dictionary* dictionary_metadata = dictionary_create();

    dictionary_put(dictionary_metadata,"BLOCKS",5192);
    dictionary_put(dictionary_metadata,"BLOCKS_SIZE",64);
    dictionary_put(dictionary_metadata,"MAGIC_NUMBER","TALL_GRASS");

    FILE* f = fopen(realPath,"w-b");
    fclose(f); //esto esta para que se cree el archivo.

    t_config* metadata_config = malloc(sizeof(t_config));

    metadata_config = config_create(realPath);

    metadata_config -> properties = dictionary_metadata;

    config_save_in_file(metadata_config,realPath);

    config_destroy(metadata_config);

    log_info(logger,"Se creo Metadata.bin en: %s",realPath);
    free(realPath);

}

probe tambien con config_save sin el path, que dice "Reescribe el archivo de configuracion con los valores del config."

y tambien probe, en vez de igualando, una vez que creo el config hago asi

dictionary_put(metadata_config -> properties,"BLOCKS",5192);
dictionary_put(metadata_config -> properties,"BLOCKS_SIZE",64);
dictionary_put(metadata_config -> properties,"MAGIC_NUMBER","TALL_GRASS");

y tampoco anduvo. rompe al momento de hacer el config_save, y lo que muestra el debugger es esto:

imagen

iago64 commented 4 years ago

Tranqui, esta perfecto que vengas a fondo, acá es donde estamos los ayudantes para servirte de airbag para que no te estampes contra el paredón 😄 Ojo porque ahi tenes un segment fault, te fijaste con valgrind que esta pasando? A priori, te tiro un dato interesante, y es que el config_create() ya te reserva la memoria para el config y ademas es tan copado que te crea el diccionario, así que vos podrías hacer algo similar a lo siguente:

t_config* metadata_config = config_create(realPath);
dictionary_put(metadata_config -> properties,"BLOCKS",5192);
dictionary_put(metadata_config -> properties,"BLOCKS_SIZE",64);
dictionary_put(metadata_config -> properties,"MAGIC_NUMBER","TALL_GRASS");
config_save_in_file(metadata_config,realPath);

Puede que el cachito de codigo anterior no funcione correctamente porque me falte algun que otro cachito de memoria, pero espero se entienda lo que ya las commons te resuelven :)

Saludos.-

guidoenr commented 4 years ago

Te juro por dios que acabo de probar eso y no me anda.. con el tema de segmentation fault lo unico que ejecuta mi main por ahora es esto:

void laboratorio_de_pruebas(){

    char* path = concatenar(config->punto_montaje_tallgrass,"/PRUEBA.bin");

    FILE* f = fopen(path,"wb");
    fclose(f);

    t_config* metadata_config = config_create(path);

    dictionary_put(metadata_config -> properties,"BLOCKS",5192);
    dictionary_put(metadata_config -> properties,"BLOCKS_SIZE",64);
    dictionary_put(metadata_config -> properties,"MAGIC_NUMBER","TALL_GRASS");

    config_save_in_file(metadata_config,path);

}

y rompe en config_save_in_file nuevamente, con los mismos errores que la foto de arriba

valgrind: imagen

guidoenr commented 4 years ago

Tras eso, cuando debugeo y veo lo que se graba en el dictionary me muestra esto:

imagen

guidoenr commented 4 years ago

probe todas las combinaciones posibles para grabar y todas las posibles con el config_save y el config_save_in_file..tambien probe con el archivo (vacio) ya creado y sin crearlo.

probe todo , hasta me fije las commons si estaban bien y no puedo encontrar el error, el debugger me dice como que no puede representar algunas cosas , ( que son todas funciones de las commons ).

ya no se que mas hacer.

iago64 commented 4 years ago

Fijate que valgrind rompe cuando quiere iterar en el diccionario: image

Y por lo que estas mostrando el problema es que el diccionario no esta creándose bien, fijate en el ejemplo de los tests de las commons, que en la linea 159 tiene la parte de escritura en el config desde 0

Saludos.-

guidoenr commented 4 years ago

Como ejecuto ese test? @iago64

iago64 commented 4 years ago

Los test se ejecutando cuando instalas las commons, pero mas que nada te decía que lo revises como para guiarte a la hora de hacerlo arrancar.

guidoenr commented 4 years ago

Lo acabo de ver, pero que diferencia le ves a esto de lo que hago yo? lo unico distinto que veo es que el path que le manda lo manda de una como "unpath" entre comillas

imagen

iago64 commented 4 years ago

En los tests utilizan dictionary_put? o usan otra función? Algo importante que tenes que llevarte de esto, es el poder frenar la pelota e investigar, recurrir a cuanta fuente de info, fijate que yo te dije que podías probar con dictionary put y me comí que hay una función de los config que te permite agregar values al config...

Saludos.-

guidoenr commented 4 years ago

Listo, ya funca.. muchas gracias a ambos @iago64 y @mgarciaisaia perdon por las molestias y ser tan cabeza de termo y que el issue haya durado 3 dias maso.

Un abrazo!

iago64 commented 4 years ago

Oiga! Nada de decir que sos un cabeza de termo, que nadie nace sabiendo y estamos para ayudar y para que todos aprendamos.

Un abrazo!