Dateibaum



  • Hallo,
    da ich in meinem Buch mit den Kapiteln zu den grundlegenden Sachen in C fertig bin, habe ich mich entschlossen, ein eigenes Programm zu schreiben. Es geht darum, (rekursiv) einen Dateibaum zu erstellen, der ab einem Startverzeichnis alle Dateien und Ordner verkettet. Diesen lasse ich (bis jetzt einfach etwas formatiert) ausgeben. Meine Bitte wäre, dass sich jemand den Sourcecode mal angucken könnte und konstruktive Verbesserungsvorschläge (zu allen Bereichen, wenn nötig 😃 ) geben könnte.

    Noch ein paar Informationen vorweg: Ich habe extra darauf verzichtet, einzelne Funktionen in eine header-file auszulagern, weil das Thema in meinem Buch nur kurz angeschnitten wurde. Auch auf Kommentare habe ich verzichtet, weil ich denke, dass der Quellcode noch recht einfach ist und deshalb selbsterklärend. Falls dem nicht so ist, kann ich auch noch eine Version mit Kommentaren posten.

    Das Programm funktioniert nur unter Windows, weil ich keine Bibliothek gefunden habe, die OS-unabhängig Funktionen zum Auslesen von Dateien und Ordnern zu verfügung stellt. Deshalb habe ich auf die "windows.h" header-file zurückgegriffen und die "WIN32_FIND_DATA" Struktur verwendet (http://msdn.microsoft.com/en-us/library/aa365740%28v=vs.85%29.aspx). Die .exe muss über die Kommandozeilenebene gestartet und als einziges Argument das Startverzeichnis angegeben werden. Wenn die Standardausgabe in eine .txt Datei umgeleitet wird, ist der Filetree auch einigermaßen formatiert, jedoch wollte ich dafür ein weiteres Programm schreiben (in meinem Buch wurde gesagt, dass man für ein "Problem" ein Programm schreiben soll, was auch nur dieses Problem löst. Ich hab zur Kontrolle trotzdem schon die Ausgabe implementiert), was auch sotieren, filtern usw. ermöglicht.

    Das will ich aber erst wagen, wenn ich mir sicher sein kann, dass das Programm einigermaßen verwendbar ist :).

    /*
     * Creates a linked list containing all files and directories from a start directory. 
     */
    
     #include <stdio.h>
     #include <stdlib.h>
     #include <string.h>
     #include <windows.h>
     #include <dirent.h>
    
     #define MAX_SIZE 512
    
     typedef struct _node {
    	WIN32_FIND_DATA find_file_data;
    	struct _node *next_file;
    	struct _node *next_directory;
    } node;
    
    struct _node* allocate_file_node(void);
    struct _node* create_file_tree(char start_directory[MAX_SIZE]);
    void release_file_tree(struct _node *start_node);
    
    void display_file_tree(struct _node *start_node, short offset);
    void display_file_time_information(struct _node *current_node);
    
     int main(int argc, char *argv[])
     {
    	if(argc != 2) {
    		printf("You have to type the start directory: LinkedFileList.exe [start directory]!\n");
    		return 1;
    	}
    
    	DIR *test = opendir(argv[1]);
    	if(test) {
    		closedir(test);
    		node *start_node;
    
    		start_node = create_file_tree(argv[1]);
    
    		printf("Information -- file size [bytes] | creation time | last access time | last write time [mm/dd/yy]\nStart directory: %s\n", argv[1]);
    		display_file_tree(start_node, 4);
    
    		release_file_tree(start_node);
    	} else {
    		printf("%s doesn't exist!\n", argv [1]);
    	}
    
    	return 0;
     }
    
     struct _node* allocate_file_node(void)
    {
    	struct _node* node_buffer = malloc(sizeof(node));
    
    	node_buffer->next_file = NULL;
    	node_buffer->next_directory = NULL;
    
    	return node_buffer;
    }
    
    struct _node* create_file_tree(char start_directory[MAX_SIZE])
    {
    	struct _node *current_node = NULL;
    	struct _node *next_node = allocate_file_node();
    	struct _node *start_node = allocate_file_node();
    
    	HANDLE handle_find = FindFirstFile(strcat(start_directory, "\\*"), &(start_node->find_file_data));
    	start_directory[strlen(start_directory) - 1] = '\0';
    
    	current_node = start_node;
    	while(FindNextFile(handle_find, &(next_node->find_file_data))) {
    
    		if(current_node->find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
    			if(!(strcmp(current_node->find_file_data.cFileName, ".") == 0) && !(strcmp(current_node->find_file_data.cFileName, "..") == 0)) {
    				char path_buffer[MAX_SIZE];
    				strcpy(path_buffer, start_directory);
    				current_node->next_directory = create_file_tree(strcat(path_buffer, current_node->find_file_data.cFileName)); 
    			}
    		}
    
    		current_node->next_file = next_node;
    		current_node = next_node;
    		next_node = allocate_file_node();
    	}
    
    	free(next_node);
    	FindClose(handle_find);
    
    	return start_node;
    }
    
    void release_file_tree(struct _node *start_node)
    {
    	if(start_node) {
    
    		if(start_node->next_file) {
    			release_file_tree(start_node->next_file);
    		}
    
    		if(start_node->next_directory) {
    			release_file_tree(start_node->next_directory);
    		}
    
    		free(start_node);
    	}
    }
    
    void display_file_tree(struct _node *start_node, short offset) 
    {		
    	struct _node *i = start_node;
    
    	for(; i != NULL; i = i->next_file) {
    		fseek(stdout, offset, SEEK_CUR);
    		printf("- %s   -- %u | ", i->find_file_data.cFileName, i->find_file_data.nFileSizeLow);
    		display_file_time_information(i);
    
    		if(i->next_directory) {
    			display_file_tree(i->next_directory, offset+4);
    		}
    	}
    }
    
    void display_file_time_information(struct _node *current_node)
    {
    	SYSTEMTIME creation_time, last_access_time, last_write_time;
    
    	FileTimeToSystemTime(&(current_node->find_file_data.ftCreationTime), &creation_time);
    	FileTimeToSystemTime(&(current_node->find_file_data.ftLastAccessTime), &last_access_time);
    	FileTimeToSystemTime(&(current_node->find_file_data.ftLastWriteTime), &last_write_time);
    
    	printf("%02d/%02d/%02d | ", creation_time.wMonth, creation_time.wDay, creation_time.wYear);
    	printf("%02d/%02d/%02d | ", last_access_time.wMonth, last_access_time.wDay, last_access_time.wYear);
    	printf("%02d/%02d/%02d\n", last_write_time.wMonth, last_write_time.wDay, last_write_time.wYear);
    }
    

    Zu mir noch kurz ein paar Informationen: Das ist mein erstes größeres C-Programm und deswegen bin ich über Kritik auch sehr dankbar (und bloß nicht zu sanft :D). Ansonsten ist das Programm für mich nur eine Übung und soll nicht weiter ausgebaut werden.

    Danke für die Zeit zum drübergucken,
    Jaob


  • Mod

    Ich kann es mangels Windows nicht testen, aber bei ersten querlesen sieht das sehr gut aus. Sauber designte Datenstrukturen, sauber implementiert. Kein einziger der üblichen Fehler, die ich erwartet und gesucht habe. Wenn da dran was falsch ist, dann braucht es eine tiefergehende Analyse um es zu finden, als ich kurz nach 0 Uhr machen kann.

    👍 😋



  • ...
    start_node = create_file_tree(argv[1]);  // Zeile 38
    ....
    struct _node* create_file_tree(char start_directory[MAX_SIZE]) // Zeile 61
    {
     ... FirstFile(strcat(start_directory, "\\*"),.... // Zeile 67
    

    Du weißt nicht wie groß der Speicher von argv[1] ist.
    Behauptest aber bei der Übergabe an create_file_tree er wäre vom Typ char[MAX_SIZE]
    Zudem hängst du noch Zeichen dran.

    Der Speicher von argv gehört dir nicht. Du kannst nicht wissen wie groß der Platz dahinter ist. (Warum sollte er größer sein als benötigt?)

    Zu Zeile 29: Der Programmname steht in argv[0] (nicht unbedingt, aber bei Windows ist dies auch so). Nimm diesen, falls du das Programm mal umbenennst.

    Überleg dir mal, ob du das Datum nicht nach ISO_8601 ausgibst.



  • Eine winzige Anmerkung: Namen mit führendem Unterstrich im globalen Namensraum sind für die Implementation (Compiler+Bibliothek) reserviert. Es gibt auch überhaupt keinen Grund, warum deine Struktur anders heißen muss als der dazugehörige Typedef. Nenne beides node und fertig. Übrigens benutzt du an so vielen Stellen struct _node statt node , dass ich mich frage, wozu du überhaupt ein Typedef eingeführt hast. node kommt nur einmal vor, oder?



  • Hallo,

    erstmal vielen Dank für die Verbesserungsvorschläge! 🙂

    @SeppJ
    Ich hab selber noch einen großen Fehler gefunden, undzwar habe ich die Rekursion doch falsch implementiert, denn es wurden immer die letzten Verzeichnisse übersprungen (das ist mir gestern gar nicht aufgefallen). Das habe ich jetzt behoben. 😃

    @DirkB
    Du hast recht, ich wusste nicht genau wie ich das regeln sollte, da man ja nicht einfach beliebig Zeichen an den String hängen kann (das ging mit Java einfacher :D). Ich habe das jetzt aber lösen können, könntest du dir das nochmal genauer angucken und mir sagen, ob es auch einen einfacheren Weg gibt (ich finde das noch eher klobig gelöst)?

    Das mit argv[0] und die Darstellung des Datums habe ich geändert.

    @Bashar
    Ich habe jetzt die Struktur und den Typedef gleich benannt. Das ich so häufig struct _node statt node genommen habe, lag daran, dass in dem Buch geschrieben wurde, dass man bei verketteten Listen struct _node (anstatt den Typedef) als Zeiger auf die nächste Struktur verwenden muss.

    Hier ist der neue Code:

    /*
     * Creates a linked list containing all files and directories from a start directory. 
     */
    
     #include <stdio.h>
     #include <stdlib.h>
     #include <string.h>
     #include <windows.h>
     #include <dirent.h>
    
     typedef struct node {
    	WIN32_FIND_DATA find_file_data;
    	struct node *next_file;
    	struct node *next_directory;
    } node;
    
    node* allocate_file_node(void);
    node* create_file_tree(char *start_directory);
    void release_file_tree(node *start_node);
    
    void display_file_tree(node *start_node, short offset);
    void display_file_time_information(node *current_node);
    
     int main(int argc, char *argv[])
     {
    	if(argc != 2) {
    		fprintf(stderr, "You have to type the start directory: %s [start directory]!\n", argv[0]);
    		return 1;
    	}
    
    	DIR *test = opendir(argv[1]);
    	if(test) {
    		closedir(test);
    		node *start_node;
    
    		start_node = create_file_tree(argv[1]);
    
    		printf("Information -- file size [bytes] | creation time | last access time | last write time [YYYY-MM-TT]\nStart directory: %s\n", argv[1]);
    		display_file_tree(start_node, 4);
    
    		release_file_tree(start_node);
    	} else {
    		fprintf(stderr, "%s doesn't exist!\n", argv[1]);
    	}
    
    	return 0;
     }
    
    node* allocate_file_node(void)
    {
    	node* node_buffer = malloc(sizeof(node));
    
    	node_buffer->next_file = NULL;
    	node_buffer->next_directory = NULL;
    
    	return node_buffer;
    }
    
    node* create_file_tree(char *start_directory)
    {
    	node *current_node = NULL;
    	node *next_node = allocate_file_node();
    	node *start_node = allocate_file_node();
    
    	char search_buffer[strlen(start_directory) + 3];
    	strcpy(search_buffer, start_directory);
    	HANDLE handle_find = FindFirstFile(strcat(search_buffer, "\\*"), &(start_node->find_file_data));
    	search_buffer[strlen(start_directory) + 1] = '\0';
    
    	current_node = start_node;
    	while(next_node != NULL) {
    		if(current_node->find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
    			if(!(strcmp(current_node->find_file_data.cFileName, ".") == 0) && !(strcmp(current_node->find_file_data.cFileName, "..") == 0)) {
    				char path_buffer[strlen(search_buffer) + strlen(current_node->find_file_data.cFileName) + 1];
    				strcpy(path_buffer, search_buffer);
    				current_node->next_directory = create_file_tree(strcat(path_buffer, current_node->find_file_data.cFileName)); 
    			}
    		}
    
    		if(FindNextFile(handle_find, &(next_node->find_file_data))) {
    			current_node->next_file = next_node;
    			current_node = next_node;
    			next_node = allocate_file_node();
    		} else {
    			free(next_node);
    			next_node = NULL;
    		}
    	}
    
    	FindClose(handle_find);
    
    	return start_node;
    }
    
    void release_file_tree(node *start_node)
    {
    	if(start_node) {
    
    		if(start_node->next_file) {
    			release_file_tree(start_node->next_file);
    		}
    
    		if(start_node->next_directory) {
    			release_file_tree(start_node->next_directory);
    		}
    
    		free(start_node);
    	}
    }
    
    void display_file_tree(node *start_node, short offset) 
    {		
    	node *i = start_node;
    
    	for(; i != NULL; i = i->next_file) {
    		fseek(stdout, offset, SEEK_CUR);
    		printf("- %s   -- %u | ", i->find_file_data.cFileName, i->find_file_data.nFileSizeLow);
    		display_file_time_information(i);
    
    		if(i->next_directory) {
    			display_file_tree(i->next_directory, offset+4);
    		}
    	}
    }
    
    void display_file_time_information(node *current_node)
    {
    	SYSTEMTIME creation_time, last_access_time, last_write_time;
    
    	FileTimeToSystemTime(&(current_node->find_file_data.ftCreationTime), &creation_time);
    	FileTimeToSystemTime(&(current_node->find_file_data.ftLastAccessTime), &last_access_time);
    	FileTimeToSystemTime(&(current_node->find_file_data.ftLastWriteTime), &last_write_time);
    
    	printf("%02d-%02d-%02d | ", creation_time.wYear, creation_time.wMonth, creation_time.wDay);
    	printf("%02d-%02d-%02d | ", last_access_time.wYear, last_access_time.wMonth, last_access_time.wDay);
    	printf("%02d-%02d-%02d\n", last_write_time.wYear, last_write_time.wMonth, last_write_time.wDay);
    }
    

    Über weitere Kritik oder Hinweise, wie man manche Sachen hätte eleganter lösen können, bin ich immernoch dankbar 🙂

    Mfg Jaob



  • Jaob schrieb:

    @Bashar
    Ich habe jetzt die Struktur und den Typedef gleich benannt. Das ich so häufig struct _node statt node genommen habe, lag daran, dass in dem Buch geschrieben wurde, dass man bei verketteten Listen struct _node (anstatt den Typedef) als Zeiger auf die nächste Struktur verwenden muss.

    Das stimmt auch, allerdings nur innerhalb der Struktur, weil an der Stelle der Typedef noch nicht bekannt ist.



  • Was soll die Zeile 68 bewirken?

    search_buffer[strlen(start_directory) + 1] = '\0';
    

    Wenn dein String nur ein Zeichen enthält, so belegt er zwei Elemente.
    Das Zeichen liegt beim Index 0
    Die terminierende '\0' liegt beim Index 1
    Mehr gibt es nicht

    strlen liefert 1 (Ein Zeichen)

    Durch das +1 schreibst du an den Index 2.



  • @Bashar
    Danke für die Erklärung, das macht natürlich Sinn (wurde in dem Buch leider nicht erklärt).

    @DirkB
    Laut der Referenz für die FindFirstFile() Funktion muss der Dateipfad mit einem "\" enden (also C:\Windows\"), damit Dateien und Unterverzeichnisse in diesem Verzeichnis gesucht werden. Deswegen muss ich auch als erstes einen char-Array erstellen, der so groß ist wie der Dateipfad + 2 weiter Zeichen "\*" (und die terminierende '\0').

    char search_buffer[strlen(start_directory) + 3];
    

    Danach können diese Zeichen mit strcat zusammengesetzt werden und die Funktion kann korrekt arbeiten. Da der Pfad dann aber ein '*' enthält, muss dieses ja wieder entfernt werden und das mache ich mit der von dir genannten Zeile, wobei die besser Variante diese hier wäre(fällt mir gerade so ein):

    search_buffer[strlen(search_buffer) - 1] = '\0';
    

    Ansonsten soweit alles gut?



  • Jaob schrieb:

    Das Programm funktioniert nur unter Windows, weil ich keine Bibliothek gefunden habe, die OS-unabhängig Funktionen zum Auslesen von Dateien und Ordnern zu verfügung stellt.

    https://svn.apache.org/repos/asf/avro/trunk/lang/c/tests/msdirent.h
    http://www.two-sdg.demon.co.uk/curbralan/code/dirent/dirent.html

    Außerdem bietet MinGW diese POSIX-Funktionen (opendir/readdir/closedir) als Erweiterung an.



  • @Wutz
    Danke für die Links, aber laut dem zweiten Link verwendet die readdir() Funktion die selbe Funktion von Microsoft (FindNextFile()) wie auch ich genommen habe. Außerdem wird ein Zeiger auf die Struktur dirent zurückgegeben, die Informationen wie Größe der Datei und verschiedene Zeitangeben nicht enthält. Ich glaube da hätte ich keinen großen Gewinn gemacht. Oder verstehe ich da was falsch?



  • Die Idee ist, dass diese Funktionen auch unter Unix zur Verfügung stehen, du also ein portables Programm hinbekommst. Logischerweise benutzt die Windows-Implementierung unter der Haube Windows-spezifische Funktionen, aber das muss dich als Benutzer ja nicht interessieren.


Anmelden zum Antworten