save a file modified using a list (c language) - c

=============================================================
edit : im sorry it seems like i havent explained well
what my program does is
open file for reading => load list from the file => operations chosen by user will be applies to list (not file)(add-search-display-reset)\ => import change to file(in case there is any)
if the user chooses to only add an employee and quit will it be better to append the added node to the end of file, close and free list or its okay to open file for reading and overwrite all the nodes from the beggining
this last option will save me a lot of lines of code but will it save time and energy for the user while execution ?
=============================================================
i am workig on a (c language) school project where we have to use both lists and files to :
display a list of employees
add employees
search an employee
reset list
save and quit
so i am looking for the best way to do it, and as i know a good code is the one that ensures the program uses as less memory as it can and be as fast as it can.
what i am asking you is
after opening the file that contains the infos of employees and loadig it to the list, and modifying the list by add or reset, would it be better to :
open the file for "w" and fill it with the list.
or open it for "a+" to add from last employee.
note that this last way i will have to memorize the initial_number_of_lines (aka nodes) and look for the 'initial last node' to start loading from it.
note : the employee variables are first_name last_name and salary; VERIFY(file) returns 1 i file opened returns 0 if not;
void load_file(list *list, char *filename)
{
if (current_number_of_lines != initial_number_of_lines || list_reset == 1)
{
if (current_number_of_lines > initial_number_of_lines && list_reset == 0)
{
FILE *file = fopen(filename, "a+");
if (VERIFY(file) == 1)
{
for (int i = 0; i < initial_number_of_lines; i++)
{
list = list->next;
}
while (list != NULL)
{
fprintf(file, "%s\t%s\t%lf\n", list->emp.Fname, list->emp.Lname, list->emp.salary);
list = list->next;
}
}
fclose(file);
}
else if (list_reset == 1)
{
FILE *file = fopen(filename, "w");
while (list != NULL)
{
fprintf(file, "%s\t%s\t%lf\n", list->emp.Fname, list->emp.Lname, list->emp.salary);
list = list->next;
}
fclose(file);
}
else
printf("Error\n");
}
freeList(list);
}

If all your add operations adds the new node to the end of the list, you will get better performance using a+. On the other hand, the code will be more simple if you always rewrite the the whole file. So what is most important for you? Performance or simple code? I would start with the simple approach.
Some other notes.
It's pretty strange that you call the function load_file when it actually writes the file.
The variables: current_number_of_lines, initial_number_of_lines, list_reset seem to be global variables. Using global variables is something you should avoid.
The logic (aka if statements) seems too complicated. You can simply do:
void load_file(list *list, char *filename)
{
if (list_reset == 1)
{
...
}
else if (current_number_of_lines > initial_number_of_lines)
{
...
}
else if (current_number_of_lines < initial_number_of_lines)
{
printf("Error\n");
}
freeList(list);
}

If you use "w" to fopen() then it will truncate file to zero length or create text file for writing. The stream is positioned at the beginning of the file. You have to write all records to the file.
If you use "a+" to fopen() then the file will be open for reading and appending (writing at end of file). The file is created if it does not exist. Output is always appended to the end of the file. Warning: POSIX is silent on what the initial read position is when using this mode. In the mode, you will only write records that needs to be added.
Please also note that neither of the methods are well suited for sharing the data between two or more applications.
If new records are added at the end of the list, obviously it is faster to append those records ate the end of the file ("a+" mode).
Since you have a text file (made of text lines), it is unfortunately not possible to update the changed records. In that case, you must write everything from the list to the file ("w" mode).
If you change you file format in order to use a fixed size record then you can optimize the writing by positioning the file on the record and then write. For that, you'll define your record as a struct containing fixed size strings. And of course add an item in that struct to remember where it has been read from the list.

Related

search in a file in c

the data on the file.txt are placed as shown.
My Code is this:
int searchBookname()
{
FILE *myFile=fopen("file.txt","r+");
if (myFile!=NULL) // file exists
{
char tmp1[512];
char tmp2[512];
while(fgets(tmp1,512,myFile)!=EOF)
{
puts("Insert the Book Name: ");
scanf("%s",tmp2);
if(strstr(tmp1,tmp2)!=NULL){
printf("the book is found: %s\n\n",tmp1);
}else{
puts("\nSorry there was no Match with this name! Maybe Book is not recorded yet :(\n");
}
}
}else{ // file doesn't exist
puts("\nDatabase is not created yet. Please record a new book to create a simple database\n");
exit(0);
}
fclose(myFile); // closing the file
}
It keeps skipping the if statement 2 times for some reason and on the
3rd time it prints the correct result.
This happen for whatever book I try to search.
See here
How can I make it find the result without skipping the if statement.
You read the file line by line. So in the third loop/line there is a record with 'book1'. Code is working correctly as it is. Maybe you want to ask the user for a book name outside of the while loop and search in every line for the given book name. If there is, you can print you message and break from the loop.
int searchBookname()
{
FILE *myFile=fopen("file.txt","r+");
if (myFile != NULL) // file exists
{
char tmp1[512], tmp2[512];
puts("Insert the Book Name: ");
scanf("%s",tmp2);
// Skip the first two lines of the file
fgets(tmp1,512,myFile);
fgets(tmp1,512,myFile);
while(fgets(tmp1,512,myFile) != EOF)
{
if(strstr(tmp1,tmp2) != NULL)
{
printf("the book is found: %s\n\n",tmp1);
break;
}
else
{
puts("\nSorry there was no Match with this name! Maybe Book is not recorded yet :(\n");
}
}
}
else
{ // file doesn't exist
puts("\nDatabase is not created yet. Please record a new book to create a simple database\n");
exit(0);
}
fclose(myFile); // closing the file
}
It is obvious.
First 2 lines of file are following:
Record_Date ...
-> (empty line)
You are reading file line by line and check book name in each line. So if must fail for first 2 times.
If you want to find a book in your file, your approach is incorrect. There are different approaches for this, but simplest is reading book records into an structure array, and then look for book name in that array.

Recursive function : abort-condition

We need to create a binary tree which contains content of textfiles. The pointer selection_a and selection_b pointing to another textfile in the directory.
The structure of the textfiles is following:
line: Title
line: OptionA
line: OptionB
line: Text.
The first file is given as parameter while starting the program. All files should be saved at the beginning of the program. Then the text of the first file shows, and the user can input A or B to continue. Based on the selection, the text of File Option A/B is shown and the user can decide again.
The last file of a tree contains no Options: lines 2 and 3 are "-\n".
The problem is, this code only reads all the option A files of the first tree. It doesn't read in any B-Options. In the end, the program shows a memory access error.
I think the problem is that the readingRows function has no abort condition.
current->selection_a = readingRows(input_selection_a);
current->selection_b = readingRows(input_selection_b);
I know the code may be kind of chaotic, but we are beginners in programming. Hope anybody can help us to write an abort-condition.
The function should be aborted if the content of option A (line 3) is "-\n".
Here is the whole function:
struct story_file* readingRows(FILE *current_file)
{
char *buffer = fileSize(current_file);
char *delimiter = "\n";
char *lines = strtok(buffer, delimiter);
int line_counter = 0;
struct story_file *current = malloc(sizeof(struct story_file));
while(lines != NULL)
{
if(line_counter == 0)
{
current->title = lines;
}
else if(line_counter == 1)
{
char *filename_chapter_a = lines;
FILE *input_selection_a = fopen(filename_chapter_a, "r");
if(input_selection_a)
{
current->selection_a = readingRows(input_selection_a);
}
fclose(input_selection_a);
}
else if(line_counter == 2)
{
char *filename_chapter_b = lines;
FILE *input_selection_b = fopen(filename_chapter_b, "r");
if(input_selection_b)
{
current->selection_b = readingRows(input_selection_b);
}
fclose(input_selection_b);
}
else if (line_counter >= 3)
{
current->text = lines;
}
lines = strtok(NULL, delimiter);
line_counter++;
}
return current;
}
There are two items that define a terminating recursive function:
One or more base cases
Recursive calls that move toward a base case
Your code has one base case: while (lines!=NULL) {} return current;, it breaks the while loop when lines is NULL and returns current. In other words, within any particular call to your function, it only terminates when it reaches the end of a file.
Your code moves toward that base case as long as your files do not refer to each other in a loop. We know this because you always read a line, take an action according to your if-else block, and the read the next line. So you always move toward the end of each file you read.
But as you note, the issue is that you don't have a case to handle "no Options", being when lines 2 or 3 are "-\n". So right now, even though you move through files, you are always opening files in line 2. Unless a file is malformed and does not contain a line 2, your recursive call tree never ends. So you just need to add another base case that looks at whether the beginning of lines matches "-\n", and if it does, return before the recursive call. This will end that branch of your recursive tree.
Inside of your while loop, you will need code along the lines of:
if `line_counter` is `2` or `3`
if `lines` starts with your terminating sequence "-\n"
return current
else
`fopen` and make the recursive call
In the parent function that made the recursive call, it will move to the next line and continue as expected.
P.S. Make sure you use free for each malloc you do.

Unique String generator

I want to make a program (network server-client).
One of the specification for this program is next:
The server will receive the sent packages and save it into a file, with a unique name (generated by the server at the moment the transfer starts.
Ex __tf_"unique_random_string".txt
I made a function that returns a pointer to a "unique" string created.
The problem is: If i stop the server and then start it again it will generate the same names.
Ex:this file names were generated and then i stopped the server.
__ft_apqfwk.txt
__ft_arzowk.txt
__ft_cdyggx.txt
I start it again and i try to generate 3 file names. Them will be the same.
Sorry for my english. I'm still learning it.
My function to generate this "unique string" is:
char *create_random_name(void)
{
const char charset[] = "abcdefghijklmnopqrstuvwxyz";
char *file_name;
int i=0;
int key;
if((file_name = malloc(16 * sizeof ( char )) ) == NULL)
{
printf("Failed to alloc memory space\n");
return NULL;
}
strcpy(file_name,"__ft_");
for(i=5 ; i<11 ; i++)
{
key = rand() % (int)(sizeof(charset)-1);
file_name[i]=charset[key];
}
strcat(file_name,".txt");
file_name[15] = '\0';
return file_name;
}
One option is saving to a file the names that have been used, and using them as a checklist. You also want to seed rand with something like srand(time(NULL)).
another is ignoring the randomisation, and just going in order, e.g. aaa, aab aac...aba ,abb etc. Again, save where your cycle is up to on a file.
Your question seems a little bit unclear but if you want to generate a unique string there are a couple of things you can consider:
Get System timestamp ( yyyy-MM-dd-HH-mm-ss-fff-tt)
Use Random function to generate a random number
Combine this with your function and I am sure you will get a unique string.
Hope it helps !
If it's available, you could avoid manually generating random names that might collide and let the system do it for you (and handle collision resolution by creating a new name) by using mkstemps. This is also safer because it opens the file for you, removing the risk of a random name being generated, verified to be unique, then trying to open it and discovering another thread/process raced in and created it.
char name[] = "/path/to/put/files/in/__ft_XXXXXX.txt";
int fd = mkstemps(name, strlen(".txt"));
if (fd == -1) { ... handle error ... }
After mkstemps succeeds, name will hold the path to the file (it's mutated in place, replacing the XXXXXX string), and fd will be an open file descriptor to that newly created file; if you need a FILE*, use fdopen to convert to a stdio type.
Before calling rand(),--- once and only once---, call srand(time()) to initialize the random number generator.
Before settling on any specific file name, call stat() to assure that file name does not already exist.

Reading and selecting files using dirent.h in C

I'm new into programming microcontrollers, and I have a problem.
I'm trying to make a device that plays music from USB. I can read from USB, but I have no idea how to choose a certain file. I'm using dirent.
My code so far:
while (true) {
USBHostMSD msd("usb");
//setup PWM hardware for a Class D style audio output
PWMout.period(1.0/400000.0);
// wait until connected to a USB device
while(!msd.connect()) {
Thread::wait(500);
}
FILE *wave_file;
lcd.cls();
lcd.locate(0,3);
DIR *dir;
struct dirent *ent;
int i=0;
int stevilo_datotek =0;
dir = opendir ("/usb/");
if (dir != NULL)
{
while ((ent = readdir (dir)) != NULL)
{
lcd.printf ("%s\n", ent->d_name[0]);
}
}
Now this code displays what's on USB. How can I navigate through files on USB using the buttons on the device? I'd like to know if there's a way to assign certain song a certain number so I can navigate. I've studied dirent.h file for so long, and I can't find where dirent saves the file order (if it does).
You may be confusing the purpose of dirent.h In short it may be difficult to see the forest for the trees.
You can read the information (ent->d_name, note that ent->d_name is a pointer to an array of chars, often called a 'string') into a data structure (like an array or a list), then use that struct with code that detects button presses to move an index into the array information either up or down (to a greater or lesser index, make sure to check that your index doesn't go outside the range or your structure). Or you can create code where your while loop waits on a button press and only reads the file name then (using seekdir to go backwards).
UPDATE (to response to comment):
Keep in mind that filesystems are tree structures like so:
/ (root) +--- dir1 ----------------------------+- dir1.1
---- dir2 (empty) -- file1.1
---- dir3 ---- dir3.1 +--- file3.1
---- file1 ---- file3.2
---- file2
You have to decide how to handle that, are you going to only support one directory (make them put all their music files in one place), allow them to navigate directories, or look through all directories selecting only files you know how to play?
There is no inherit order to the files (or sub-directories) and in some systems files can be added or deleted at any time.
Here is a very simple-minded example of one way to keep the list of directory entries:
char *names[400]; // make space for 400 names
int ix;
ix = 0;
if (dir != NULL)
{
while ((ent = readdir (dir)) != NULL)
{
lcd.printf ("%s\n", ent->d_name[0]);
// allocate memory to store the name
names[ix] = (char*) malloc(strlen(ent->d_name)); // strlen from string.h
// malloc from stdlib.h
// copy the name from the directory entry
strcpy(names[ix], ent->d_name); // strcpy from string.h
ix++;
if (ix >= 400)
{
// do something because your array isn't big enough
}
}
}
Now you have your names in the array 'names' and can address them by index. The value 'ix-1' is your last name, 0 is your first name. Your button presses can increment/decrement the index into your name array which identifies the name you want. Keep in mind that some of those names might be directory names rather than file names.
Admittedly this is simply-minded, you might want to allocation the array rather than use a fixed value (in fact you have to if you want to pass the 'names' array to a calling function), there are "secure" versions of strcpy (meant to help prevent memory overflow corruption), etc. but it should give you an idea about how you can keep the names in memory.

Why does my program read an extra structure?

I'm making a small console-based rpg, to brush up on my programming skills.
I am using structures to store character data. Things like their HP, Strength, perhaps Inventory down the road. One of the key things I need to be able to do is load and save characters. Which means reading and saving structures.
Right now I'm just saving and loading a structure with first name and last name, and attempting to read it properly.
Here is my code for creating a character:
void createCharacter()
{
char namebuf[20];
printf("First Name:");
if (NULL != fgets(namebuf, 20, stdin))
{
char *nlptr = strchr(namebuf, '\n');
if (nlptr) *nlptr = '\0';
}
strcpy(party[nMember].fname,namebuf);
printf("Last Name:");
if (NULL != fgets(namebuf, 20, stdin))
{
char *nlptr = strchr(namebuf, '\n');
if (nlptr) *nlptr = '\0';
}
strcpy(party[nMember].lname,namebuf);
/*Character created, now save */
saveCharacter(party[nMember]);
printf("\n\n");
loadCharacter();
}
And here is the saveCharacter function:
void saveCharacter(character party)
{
FILE *fp;
fp = fopen("data","a");
fwrite(&party,sizeof(party),1,fp);
fclose(fp);
}
and the loadCharacter function
void loadCharacter()
{
FILE *fp;
character tempParty[50];
int loop = 0;
int count = 1;
int read = 2;
fp= fopen("data","r");
while(read != 0)
{
read=fread(&tempParty[loop],sizeof(tempParty[loop]),1,fp);
printf("%d. %s %s\n",count,tempParty[loop].fname,tempParty[loop].lname);
loop++;
count++;
}
fclose(fp);
}
So the expected result of the program is that I input a name and last name such as 'John Doe', and it gets appended to the data file. Then it is read in, maybe something like
1. Jane Doe
2. John Doe
and the program ends.
However, my output seems to add one more blank structure to the end.
1. Jane Doe
2. John Doe
3.
I'd like to know why this is. Keep in mind I'm reading the file until fread returns a 0 to signify it's hit the EOF.
Thanks :)
Change your loop:
while( fread(&tempParty[loop],sizeof(tempParty[loop]),1,fp) )
{
// other stuff
}
Whenever you write file reading code ask yourself this question - "what happens if I read an empty file?"
You have an algorithmic problem in your loop, change it to:
read=fread(&tempParty[loop],sizeof(tempParty[loop]),1,fp);
while(read != 0)
{
//read=fread(&tempParty[loop],sizeof(tempParty[loop]),1,fp);
printf("%d. %s %s\n",count,tempParty[loop].fname,tempParty[loop].lname);
loop++;
count++;
read=fread(&tempParty[loop],sizeof(tempParty[loop]),1,fp);
}
There are ways to ged rid of the double fread but first get it working and make sure you understand the flow.
Here:
read=fread(&tempParty[loop],sizeof(tempParty[loop]),1,fp);
printf("%d. %s %s\n",count,tempParty[loop].fname,tempParty[loop].lname);
You are not checking whether the read was successful (the return value of fread()).
while( 1==fread(&tempParty[loop],sizeof*tempParty,1,fp) )
{
/* do anything */
}
is the correct way.
use fopen("data","rb")
instead of fopen("data","r") which is equivalent to fopen("data","rt")
You've got the answer to your immediate question but it's worth pointing out that blindly writing and reading whole structures is not a good plan.
Structure layouts can and do change depending on the compiler you use, the version of that compiler and even with the exact compiler flags used. Any change here will break your ability to read files saved with a different version.
If you have ambitions of supporting multiple platforms issues like endianness also come into play.
And then there's what happens if you add elements to your structure in later versions ...
For robustness you need to think about defining your file format independently of your code and having your save and load functions handle serialising and de-serialising to and from this format.

Resources