Inserting text into a file only works once - c++

My Goal:
I am trying to write an application in C++ where the user can ask for a a certain weather parameter over a certain date range and this program will find that information from the internet and write it out to a text file. So the user can ask for something like high temperature for every day between August 2, 2009 and August 10, 2009. The application will then spit out a text file something like this:
Month, Date, Year, High
8 2 2009 80.3
8 3 2009 76.9
...
8 10 2009 68.4
I already have getting the webpages, parsing the HTML into meaningful values, and writing these values into a database (txt file) working. I also wrote a function
insert(std::iostream& database, Day day); //Day is a class I defined that contains all the weather information
that will find where this day belongs to stay in order, and insert it into the middle. I have tested this function and it works exactly like it should.
My Problem:
I am now trying to write a function that does this:
void updateDatabase(std::iostream& database, Day start, Day end)
{
Day currentDay = start;
while (currentDay.comesBefore(end))
{
if (currentDay.notInDatabase(database))
insert(database, currentDay);
currentDay = currentDay.nextDay();
}
}
But unfortunately, the insert() function only works correctly if I call it once per program. If I try to call insert() twice in a row, (or three, or four, or five) only the last day will show up on my text file.
Here is the smallest possible amount of code that reproduces my problem but still runs.
#include <iostream>
#include <fstream>
#include <string>
const std::string FOLDER = "/Users/Jimmy/Desktop/WeatherApp/";
const std::string DATABASE_NAME = FOLDER + "database.txt";
class day
{
public:
int date;
int month;
int year;
bool comesBefore(int month, int date, int year);
day(int month, int date, int year)
{
this->month = month;
this->date = date;
this->year = year;
}
};
void writeToDatabase(std::iostream& file, day today, bool end = true);
void insertDay(std::iostream& file, day today);
int main()
{
std::fstream database;
database.open(DATABASE_NAME);
if (database.fail())
{
std::cout << "Cannot find database.\n";
exit(1);
}
day second(1, 2, 2000);
insertDay(database, second);
std::cout << "First day inserted. Press enter to insert second day.\n";
std::cin.get();
day third(1, 3, 2000);
insertDay(database, third);
std::cout << "Done!\n";
return 0;
}
bool day::comesBefore(int month, int day, int year)
{
if (this->year < year)
return true;
if (this->year > year)
return false;
//We can assume this->year == year.
if (this->month < month)
return true;
if (this->month > month)
return false;
//We can also assume this->month == month
return (this->date < day);
}
void writeToDatabase(std::iostream& file, day today, bool end)
{
if (end) //Are we writing at the current cursor position or the end of the file?
file.seekg(0, std::ios::end);
file << today.month << '\t' << today.date << '\t' << today.year << '\n';
return;
}
void insertDay(std::iostream& file, day today)
{
//Clear flags, and set cursor at beggining
file.clear();
file.seekg(0, std::ios::beg);
int date, month, year;
long long positionToInsert = 0;
while (!file.eof())
{
file >> month >> date >> year;
//std::cout << month << date << year << '\n';
if (today.comesBefore(month, date, year))
{
//We found the first day that comes after the day we are inserting
//Now read backwards until we hit a newline character
file.unget();
char c = '\0';
while (c != '\n')
{
file.unget();
c = file.get();
file.unget();
}
positionToInsert = file.tellg();
break;
}
}
if (file.eof())
{
//We hit the end of the file. The day we are inserting is after every day we have. Write at the end.
file.clear();
writeToDatabase(file, today);
return;
}
file.clear();
file.seekg(0, std::ios::beg);
std::fstream tempFile;
std::string tempFileName = FOLDER + "tempfile.txt";
std::string terminalCommand = "> " + tempFileName;
//Send the command "> /Users/Jimmy/Desktop/WeatherApp/tempfile.txt" to the terminal.
//This will empty the file if it exists, and create it if it does not.
system(terminalCommand.c_str());
tempFile.open(tempFileName);
if (tempFile.fail())
{
std::cout << "Failure!\n";
exit(1);
}
int cursorPos = 0;
while (cursorPos++ < positionToInsert)
{
char c = file.get();
tempFile.put(c);
}
tempFile.put('\n'); //To keep the alignment right.
writeToDatabase(tempFile, today, false);
file.get();
char c = file.get();
while (!file.eof())
{
tempFile.put(c);
c = file.get();
}
terminalCommand = "mv " + tempFileName + " " + DATABASE_NAME;
//Sends the command "mv <tempFileName> <databaseName>" to the terminal.
//This command will move the contents of the first file (tempfile) into the second file (database)
//and then delete the old first file (tempfile)
system(terminalCommand.c_str());
return;
}
I added that the cin.get() part in main so I could look at my database before and after each insert() call. Here is the database before compiling/running:
1 1 2000
1 4 2000
Here is the database before hitting enter/moving on past cin.get():
1 1 2000
1 2 2000
1 4 2000
And here is the database after I move on past cin.get() and my program exits:
1 1 2000
1 3 2000
1 4 2000
I have changed the dates that are being inserted, how many dates are being inserted, how far apart the two dates are and the initial size of the database before running the program, but I always get the same result. After every call to insert(), the database acts as if that was the only call to insert that was ever made. However, if I run the program many times, the text file continues to grow. I only get this problem if I try to call insert more than once per compiling/running. So If I were to run this program 5 times:
int main()
{
std::fstream database;
database.open(DATABASE_NAME);
if (database.fail())
{
std::cout << "Cannot find database.\n";
exit(1);
}
day today(1, 2, 2000);
insertDay(database, today);
std::cout << "Done!\n";
return 0;
}
My database would end up looking like this:
1 1 2000
1 2 2000
1 2 2000
1 2 2000
1 2 2000
1 2 2000
1 4 2000
I suspect it's a problem either with fstream.clear(), fstream.seekg() and fstream.eof(), or maybe about closing/reopening the file. But nothing that I have done to fix it has helped.
Also, it is worth noting that this will not run on a windows computer. It should be fine on linux, but I have only tested it on Mac, so I could be wrong. It uses bash for creating/deleting/renaming/moving files.
Any help (even just a nudge in the right direction) is HUGELY appreciated. I've been pulling my hair out over this one for a while. Also, I know SO dislikes code dumps, so I have massively simplified the problem. My full program is 700+ lines and 10 different files, and this is about as short as I could get it while still getting the idea across.

The problem you have here has to do with the way you handle the file: when you mv a file, the old file is not overwritten per se; instead it is unlinked ("deleted") and a new file is created in is place.
On Unix-like operating systems, you can still retain a handle to an unlinked file: it's just not accessible using a path. This is why on Unix it's perfectly okay to delete a file that's still open, unlike on Windows: the file still exists after you have unlinked it, at least until all the file descriptors have been closed. This means that database has not changed at all: it is still pointing to your old file and contains the same contents.
A simple workaround would be to close and reopen the file. (From a practical perspective, it's probably much better to just use a readily available solution such as Sqlite.)

Related

Trying to read from a text file to an object array

so guys i have to create 5 job objects from the information from a text file which looks like this
A 0 3
B 2 6
C 4 4
D 6 5
E 8 2
the left column is its name, the next is arrival time, and the final one is the duration
this is what i have right now
#include <iostream>
#include <fstream>
#include "Job.hpp"
using namespace std;
int main(int argc, const char * argv[]) {
string fileinfo;
string programname;
//gets Jobs.txt from commandline
programname.append(argv[1]);
Job jobs[5];
fstream afile;
//reads in from the file to create job objects
afile.open(programname);
if(afile.fail()){
cout <<"file could not be opened " << programname <<endl;
}
while(!afile.eof())
{
getline(afile,fileinfo);
int i = 0;
//cout <<fileinfo[0] <<endl;//gets next letter
//cout <<fileinfo[2] <<endl;//gets next arrival time
//cout << fileinfo[4] <<endl;//gets next duration time
jobs[i].setletter(fileinfo[0]);
jobs[i].setarrivaltime(fileinfo[2]);
jobs[i].setduration(fileinfo[4]);
i++;
}
afile.close();
cout << jobs[1].getletter() <<endl;
cout << jobs[2].getletter() <<endl;
cout << jobs[3].getduration() <<endl;
right now when i go in and print out the values in my different objects(like at the bottom of the code)after i close the file they all contain the information from the first line of the file.
i dont know why because technically i increase 'i' after i set all the values of the job and then fileinfo gets the nextline of the file, so this to me seems like it should work.
But like the values i get from those 3 couts at the bottom the results are
A
A
0
the Job class
Job::Job(){}
Job::Job(char let, int arrive, int dura){
letter = let;
arrivaltime = arrive;
duration = dura;
}
and it also has all its get and sets defined
so can u guys please help me be able to read in from the file correctly and create my object array
int i = 0;
Each time through the loop, i gets initialized to zero. Immediately after initializing i to 0, your code does this:
jobs[i].setletter(fileinfo[0]);
jobs[i].setarrivaltime(fileinfo[2]);
jobs[i].setduration(fileinfo[4]);
i will always be zero, here. This is what you're seeing. Your computer will always do exactly what you tell it to do, instead of what you want it to do. This is a good rule of thumb for you to keep in mind, going forward.
i++;
This doesn't matter, because on the next iteration of the while loop, i will be initialized to 0 again. See above.
while(!afile.eof())
And, this is going to be your second bug, also, which you will immediately discover after fixing your first bug (initialize i before the loop, not inside it).

C++ reading certain lines from output file

I am making a program for class that needs to read certain lines from an output file based on what "data set" a person chooses. For example, if a person inputs "1" for the desired data set, I need it to use lines 1 through 8 of the data file (inclusively). If they input "2" for the desired data set, I need the program to use lines 9 through 16 from the data file (inclusively), and if "3", then lines 17 through 24 (inclusively).
Here is the code I have so far-
int main()
{
int latA, latB, latC, latD;
int longA, longB, longC, longD;
int AtoB, BtoC, CtoD, threeFlightTotal, nonStop;
int dataSet;
string cityA, cityB, cityC, cityD;
intro();
cout << "Which data set do you wish to use? 1, 2, or 3? ";
cin >> dataSet;
while(dataSet < 1 || dataSet > 3)
{
cout << "Sorry, that is not a valid choice. Please choose again." << endl;
cin >> dataSet;
}
ifstream dataIn;
dataIn.open("cities.txt");
if (dataIn.fail())
{
cout << "File does not exist " << endl;
system("pause");
exit(1);
}
else
{
cout << "File opened successfully" << endl;
}
dataIn.close();
system("pause");
return 0;
}
Here is my data file-
43.65 79.4
Toronto
40.75 74
New York
33.64 84.43
Atlanta
51.5 0
London
37.78 122.42
San Francisco
47.61 122.33
Seattle
44.88 93.22
Minneapolis
41.88 87.63
Chicago
21.19 157.5
Honolulu
45.31 122.41
Portland
42.2 83.03
Detroit
25.47 80.13
Miami
How would I go about doing this? I've looked at other posts but I am having a hard time understanding how to implement their solutions to mine. Thank you for any help in advance. If I'm not giving enough information let me know.
You can simply skip the unneeded lines:
//here you calculate the amount of lines to skip.
//if dataSet=1 --> linesToSkip=0, if dataSet=2 --> linesToSkip=8...
int linesToSkipt = (dataSet-1) * 8;
//getline Needs a string to load the Content.
//So we don't use the data but wee Need to store it somewhere
std::string helper;
//We use a for Loop to skip the desired amount of lines
for(int i = 0; i < linesToSkip; ++i)
std::getline(dataIn, helper); //Skip the unneeded lines
If you knew the exact length of one line you could simply seek to desired Position. But from your example data set it seems like you don't. So you Need to read the file line by line until you reach the desired Position.

update records in file (c++)

I wanted to ask how can I append strings to the end of fixed number of lines (fixed position). I am trying and searching books and websites for my answer but I couldn't find what I am doing wrong.
My structure :
const int numberofdays=150 ;
const int numberofstudents=2;
struct students
{
char attendance[numberofdays]; int rollno;
char fullname[50],fathersname[50];
}
Creating a text file
ofstream datafile("data.txt", ios::out );
Then I take input from the user and save it to the file.
How I save my data to text files :
datafile <<attendance <<endl<< rollno <<endl<<
fullname <<endl<< fathersname <<endl ;
How it looks like in text files :
p // p for present - 1st line
1 // roll number
Monte Cristo // full name
Black Michael // Fathers name
a // a for absent - 5th line
2 // roll number
Johnson // full name
Nikolas // Fathers name
How I try to update the file. (updating attendance for everyday)
datafile.open("data.txt", ios::ate | ios::out | ios::in);
if (!datafile)
{
cerr <<"File couldn't be opened";
exit (1);
}
for (int i=1 ; i<=numberofstudents ; i++)
{
long int offset = ( (i-1) * sizeof(students) );
system("cls");
cout <<"\t\tPresent : p \n\t\t Absent : a"<<endl;
cout <<"\nRoll #"<<i<<" : ";
cin >> ch1;
if (ch1 != 'p')
ch1 = 'a';
datafile.seekp(offset);
datafile <<ch1;
datafile.seekg(0);
}
I just want to add (append) characters 'p' or 'a' to the first or fifth line, I tried every possible way but I am unable to do it.
What you are doing is fairly common, but as you say it is inefficient if the size of data grows. Two solutions are to have fixed size records and index files.
For fixed-size records, in the file write the exact bytes of your data structure rather than a variable length text. This would mean you don't have a text file any more, but a binary file. You can then calculate the position to seek to easily.
To create an index file, write two files at once, one your variable size record file, and the other write a binary value with either the offset of the data from the start of the file. Since the index is a fixed size, you can seek to the index, read it, then seek to the position in the data file. If the new record will fit, you can update it in place, otherwise fill in with blanks and put the updated record at the end of the data file, then update the index file to point to the new location. This is basically how early PC databases worked.
Fixed size records are rather inflexible, and by the time you've implemented the index file system and tested it, now-a-days you probably would use a in-process database instead.
I came up with my own (easy & inefficient) logic to copy every line (except the line I want to update) to the another file.
I made my text file to be created like this :
=p // p for present - 1st line
1 // roll number
Monte Cristo // full name
Black Michael // Fathers name
=a // a for absent - 5th line
2 // roll number
Johnson // full name
Nikolas // Fathers name
Then I made the following code to update 1st and 5th line :
ifstream datafile("data.txt", ios ::in);
ofstream tempfile("temp.txt" , ios ::out);
string data, ch1;
while (getline(datafile,data))
{
if (data[0]=='=')
{
system("cls");
cout <<"\t\tPresent : p\n\t\tAbsent : a"<<endl;
cout <<"\nRoll #"<<i<<" : ";
cin >> ch1;
++i;
if (ch1 != "p")
ch1 = "a";
data=data+ch1; // Appending (updating) lines.
}
tempfile <<data <<endl; // If it was 1st or 5th line, it got updated
}
datafile.close(); tempfile.close();
remove("data.txt"); rename("temp.txt" , "data.txt");
But as you can see, this logic is inefficient. I will still wait for someone to inform me if I could somehow move my file pointer to exact location (1st and 5th line) and update them.
Cheers!

Using seekg() in text mode

While trying to read in a simple ANSI-encoded text file in text mode (Windows), I came across some strange behaviour with seekg() and tellg(); Any time I tried to use tellg(), saved its value (as pos_type), and then seek to it later, I would always wind up further ahead in the stream than where I left off.
Eventually I did a sanity check; even if I just do this...
int main()
{
std::ifstream dataFile("myfile.txt",
std::ifstream::in);
if (dataFile.is_open() && !dataFile.fail())
{
while (dataFile.good())
{
std::string line;
dataFile.seekg(dataFile.tellg());
std::getline(dataFile, line);
}
}
}
...then eventually, further into the file, lines are half cut-off. Why exactly is this happening?
This issue is caused by libstdc++ using the difference between the current remaining buffer with lseek64 to determine the current offset.
The buffer is set using the return value of read, which for a text mode file on windows returns the number of bytes that have been put into the buffer after endline conversion (i.e. the 2 byte \r\n endline is converted to \n, windows also seems to append a spurious newline to the end of the file).
lseek64 however (which with mingw results in a call to _lseeki64) returns the current absolute file position, and once the two values are subtracted you end up with an offset that is off by 1 for each remaining newline in the text file (+1 for the extra newline).
The following code should display the issue, you can even use a file with a single character and no newlines due to the extra newline inserted by windows.
#include <iostream>
#include <fstream>
int main()
{
std::ifstream f("myfile.txt");
for (char c; f.get(c);)
std::cout << f.tellg() << ' ';
}
For a file with a single a character I get the following output
2 3
Clearly off by 1 for the first call to tellg. After the second call the file position is correct as the end has been reached after taking the extra newline into account.
Aside from opening the file in binary mode, you can circumvent the issue by disabling buffering
#include <iostream>
#include <fstream>
int main()
{
std::ifstream f;
f.rdbuf()->pubsetbuf(nullptr, 0);
f.open("myfile.txt");
for (char c; f.get(c);)
std::cout << f.tellg() << ' ';
}
but this is far from ideal.
Hopefully mingw / mingw-w64 or gcc can fix this, but first we'll need to determine who would be responsible for fixing it. I suppose the base issue is with MSs implementation of lseek which should return appropriate values according to how the file has been opened.
Thanks for this , though it's a very old post. I was stuck on this problem for more then a week. Here's some code examples on my site (the menu versions 1 and 2). Version 1 uses the solution presented here, in case anyone wants to see it .
:)
void customerOrder::deleteOrder(char* argv[]){
std::fstream newinFile,newoutFile;
newinFile.rdbuf()->pubsetbuf(nullptr, 0);
newinFile.open(argv[1],std::ios_base::in);
if(!(newinFile.is_open())){
throw "Could not open file to read customer order. ";
}
newoutFile.open("outfile.txt",std::ios_base::out);
if(!(newoutFile.is_open())){
throw "Could not open file to write customer order. ";
}
newoutFile.seekp(0,std::ios::beg);
std::string line;
int skiplinesCount = 2;
if(beginOffset != 0){
//write file from zero to beginoffset and from endoffset to eof If to delete is non-zero
//or write file from zero to beginoffset if to delete is non-zero and last record
newinFile.seekg (0,std::ios::beg);
// if primarykey < largestkey , it's a middle record
customerOrder order;
long tempOffset(0);
int largestKey = order.largestKey(argv);
if(primaryKey < largestKey) {
//stops right before "current..." next record.
while(tempOffset < beginOffset){
std::getline(newinFile,line);
newoutFile << line << std::endl;
tempOffset = newinFile.tellg();
}
newinFile.seekg(endOffset);
//skip two lines between records.
for(int i=0; i<skiplinesCount;++i) {
std::getline(newinFile,line);
}
while( std::getline(newinFile,line) ) {
newoutFile << line << std::endl;
}
} else if (primaryKey == largestKey){
//its the last record.
//write from zero to beginoffset.
while((tempOffset < beginOffset) && (std::getline(newinFile,line)) ) {
newoutFile << line << std::endl;
tempOffset = newinFile.tellg();
}
} else {
throw "Error in delete key"
}
} else {
//its the first record.
//write file from endoffset to eof
//works with endOffset - 4 (but why??)
newinFile.seekg (endOffset);
//skip two lines between records.
for(int i=0; i<skiplinesCount;++i) {
std::getline(newinFile,line);
}
while(std::getline(newinFile,line)) {
newoutFile << line << std::endl;
}
}
newoutFile.close();
newinFile.close();
}
beginOffset is a specific point in the file (beginning of each record) , and endOffset is the end of the record, calculated in another function with tellg (findFoodOrder) I did not add this as it may become very lengthy, but you can find it on my site (under: menu version 1 link) :
http://www.buildincode.com

Trying to extract only part of a string from a single line from a text file in c++

Okay this is the second time Ive looked for help for my program, I know I nearly got it but I cant figure it out. So right now Im trying to write a program that has the user input the date in the format dd/mm/yyyy and return it as month date, year. So 01/01/1990 becomes January 1st, 1990. I need to use a text file that has the names of the months beside their corresponding numbers. So the list of the text file looks like this:
01January
02February
03March
.. and so on.
So far I have this:
// reading a text file
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main ()
{
string thedate; //string to enter the date
string month; // this string will hold the month
ifstream myfile ("months.txt");
cout << "Please enter the date in the format dd/mm/yyyy, include the slashes: " << endl;
cin >> thedate;
month = thedate.substr( 3, 2 );
string newmonth;
if (myfile.is_open())
{
while ( myfile.good() )
{
getline (myfile,newmonth);
newmonth.find(month);
cout << newmonth << endl;
}
myfile.close();
}
else cout << "Unable to open file";
return 0;
}
So I have been able to extract the month from the user input, and store that as month, Im just not too sure how search the text file for that month, and return only the name of the month into a new string, only from that line. Right now if I enter 02/05/1990, it will output
05
05
05
05
05
.. for 12 lines.
I'm new to programming so any help is appreciated.
Also, I am only about 3 weeks into my programming course, and we haven't really learned functions, or arrays yet. So if you do have any help to offer, please avoid arrays and functions. I also understand it is easier to not read from a text file, but this is a requirement from my class to read it from the text file, so I need it.
Thanks.
The string::find function (that you already used) returns the position of the search string (-> month). You can now use the string::substr function to extract the part of the string you are looking for.
Hope that helps for a start.