My application reads a TextFile of floating point numbers slowly. From time to time circumstances change and the file has to be rewritten. Some example code:
procedure TAMI_Column_Selector.read_continuously (file_name: string);
var infile: TextFile;
f: Double;
begin
AssignFile (infile, file_name);
Reset (infile);
try
while not EOF (infile) do
begin
Read (infile, f);
process (f); // this may take quite some time, seconds or even minutes
end; // while
finally
CloseFile (infile);
end; // try..finally
end; // read_continuously //
How can I write a file that is open for reading? More specifically:
how can I write a file that is open for reading?
how to prevent a crash when the application tries to read a file that at that moment
is being written
how does my application know that the file has been rewritten?
I think I could solve the first to questions be reading the file into memory and read that (is there a TextFile that can be written to and read from memory)? Then still remains how I can test a file has been written over.
Anyone having an (elegant) solution to this problem?
Thanks in advance.
Using Delphi XE on windows 7
To write to a file that's also open for reading, there's generally not anything special the writer needs to do. If everyone else who has the file open has allowed writing to the file, then the intended writer can open the file for writing, write to the file, and close it. If the others haven't allowed writing to the file, then the intended writer won't be allowed to open the file in the first place, and there's nothing it can do about it.
How to open a file for reading while also allowing writing depends on the opening method being used. With CreateFile, the dwDesiredAccess parameter is the usual GENERIC_READ, and the dwShareMode parameter is FILE_SHARE_READ or FILE_SHARE_WRITE. If you're using a TFileStream, then the mode parameter of the constructor should be fmOpenWrite or fmShareDenyNone. If you're using AssignFile and Reset, then you'd need to set the FileMode global variable, but that doesn't support any sharing modes, so you can't use Pascal-style I/O.
Reading a file that is at the same time being written does not inherently cause a crash. It certainly doesn't cause problems at the OS level. If your program crashes, it's because it wasn't written to anticipate reading failures. When you read something, check the API result to confirm that you read as many bytes as you requested. You can also have the reading and writing applications communicate with each other. You might use a synchronization object to serialize access to the file, or the writer might send the reader a signal to indicate that the file has changed, and that the previous read might not be accurate anymore. It's up to you to work the details.
If the reader is going to keep a copy of the file in memory, then it probably doesn't need to bother sharing write access. Instead, it can open the file and only share read access, make a copy of the file in memory, and the close the file. The writer can then open the file without any worries of trampling on the reader process because there's nothing to trample. It can notify the reader that something changed, and the reader can either reload the entire file or just load the part that changed. (The writer will have to tell the reader which part changed, though; there's no other way for the reader to detect that without reading the entire file and seeing how it differs from the memory copy.)
Another way to keep writes from interfering with reads is to use transactions. Transactional NTFS is being phased out, though. Microsoft has published a list of alternatives, so you can try to find something that matches your needs.
Related
I'm writing Trace output to a file, but I can't leave it open because the system never hits a point where I am sure everything is finished; i.e., it hangs.
I don't want to keep opening new files with every TRACE.
Can't I reopen the TraceListener so I can append to the file?
Much appreciated.
Chuck
ANSWER: it turns out that Tracing can be done easier than I thought. I wrote a little C# application so I could try every combination of uses.
Once you open the listener in the application, you can write to it at will. Flushing empties buffers (in my case I write to a file) and the output all goes into the file.
Now you can CLOSE the file and at that point you can either OPEN it again to the same place with the same name etc (WITHOUT losing anything in the file) or you can just go ahead the write to it. Again, you can flush if you like and it is all appended to the file. OR you can Close again and it is appended to the file (Closing automatically does a flush).
It certainly answers all my questions. I hope it does yours as well.
Well, I'm making a program in Delphi that uses a TValueListEditor object to store Keys and Values and save them to a .txt file. I used this functions to do so:
procedure TfrmInserir.FormClose(Sender: TObject; var Action: TCloseAction);
begin
vlePalavras.Strings.SaveToFile('C:\Users\Felipe Knop\Desktop\Felipe\Algoritmos\Delphi\Projetos\Palavras Japonês\Lista.txt');
end;
procedure TfrmInserir.FormCreate(Sender: TObject);
begin
vlePalavras.Strings.LoadFromFile('C:\Users\Felipe Knop\Desktop\Felipe\Algoritmos\Delphi\Projetos\Palavras Japonês\Lista.txt');
end;
In my computer it works fine because the saving path is in the code, but I wanted to share the program with my friends and wanted them to be able to choose the path. Thought about using a TSaveDialog, but since I never used, I don't know if it would do what I want. I need a way to make the user able to choose a path just once and the file will be saved there every time he closes the form. Any ideas?
EDIT: Thank you both Jason and Sean for you answers. Both helped me alot and I figured out a way to make the program more interactive and give my friends the possibility to even share their lists. May sound dumb but it's one of my first programs and I really thank you alot.
Firstly I would suggest not using the FormCreate to load things from file. If its excepts or fails for any reason your form won't load. Create a method called something like "FormInit" and call this after your have created the form, then show it. Allows for better handling of FormInit issues. Same for closing down the form, have a FormDeInit and call it in the "CloseQuery".
Onto your question. I use "ForceDirectories" to ensure that the path chosen exists for saving. This will return false if the directory couldn't be created, true in all other cases. Again you can handle the error nicely when you can't create the folder.
The save dialog is fine for saying where to store the file, however when you run up again how do you know where they set this the last time? Answer you don't, you need to store that somewhere. So the answer here is to simply store your initial configuration in a known place or ask them where it is. I prefer to have base configurations stored in a known place, and store the location of other configs in there. For simplicity lets stay at one level.
If you need to have a known location then use the following calls
ExtractFileDir(Application.ExeName);
This will give you the directory in which the application executable is running. From here you can attach any directory structure you see fit.
If you choose to use the save dialog to get the directory or the application path, make sure to still use the forceDirectory call to make sure the path exists.
I hava an application that reads a file from a ZIP archive and saves it to file on file system. After writing it to file system I start start immediately to read this file with a SAX2 reader. On bigger files (300+ MB) it sometimes occures, that SAX2 stops parsing because of an unclosed tag. But when I check the file (or even try to read it again later) it works, so the file it self it OK.
FZipKit.ExtractToStream(LFileName, LStream);
LStream.SaveToFile(OutputFilename);
SAX2.processUrl(OutputFilename);
My assumption is, that the file was not yet fully written to file system when I started the parsing process.
Is there a way to ensure, that the file was written or the steam has been flushed to file system?
thx
I'm going to first of all assume that the XML parser operates correctly. If it is incapable of reading files, well the solution is obvious.
Which leads us to look at how the file is created. When you call SaveToFile, the file is opened, written, closed and buffers are flushed. In a plain vanilla system, your XML parser will see the entire content of the file. The only conclusion is that something is interfering. The most like suspect is your virus scanner. Many scanners, even the most respected ones, cannot properly handle a file being closed and then immediately re-opened.
The bottom line is that your code is fine and the problem almost certainly lies with your local environment.
I'm developing a download manager using Indy and Delphi XE (The application uses Multithreading to attempt several connections to the server). Everything works fine but sometimes the final downloaded file is broken and when I check downloaded temp files I see that 2 or 3 of them is filled with zero at their end. (Each temp file is download result of each connection).
The larger the file is, the more broken temp files I get as the result.
For example in one of the temp files which was 65,536,000 bytes, only the range of 0-34,359,426 was valid and from 34,359,427 to 64,535,999 it was full of zeros. If I delete those zeros, application will automatically download the missing segments and what I get as the result, well if the problem wouldn't happen again, is the healthy downloaded file.
I want to get rid of those zeros at the end of the temp files without having a lost in download speed.
P.S. I'm using TFileStream and I'm sending it directly to TIdHTTP and downloading the files using GET method.
Additional Info: I handle OnWork event which assigns AWorkCount to a public int64 variable. Each time the file is downloaded, the downloaded file size (That Int64 variable) is logged to a text file and from what the log says is that the file has been downloaded completely (even those zero bytes).
Make sure the server actually supports downloading byte ranges before you request a range to download. If the server does not support ranges, a requested range will be ignored by the server and the entire file will be sent instead. If you are not already doing so, you should be using TIdHTTP.Head() to text for range support before then calling TIdHTTP.Get(). You also need to do this anyway to detect if the remote file has been altered since the last time you downloaded it. Any decent download manager needs to be able to handle things like that.
Also keep in mind that if TIdHTTP knows up front how many bytes are being transferred, it will pre-allocate the size of the destination TStream before then downloading data into it. This is to speed up the transfer and optimize disc I/O when using a TFileStream. So you should NOT use TFileStream to access the same file as the destination for multiple simultaneous downloads, even if they are writing to different areas of the file. Pre-allocating multiple TFileStream objects will likely trample over each other trying to set the file size to different positions. If you need to download a file in multiple pieces simultaneously then either:
1) download each piece to a separate file and copy them into the final file as needed once you have all of the pieces that you need.
2) use a custom TStream class, or Indy's TIdEventStream class, to manage the file I/O yourself so you can ignore TIdHTTP's pre-allocation attempts and ensure that multiple file I/O operatons do not overlap each other incorrectly.
I have an application that have a log system that create a file and keep it handle with a TFileStream, that is created with this way:
FFileStream := TFileStream.Create(FFilename, fmOpenWrite);
Ok. When I try to open this file with notepad, no problem, with notepad++ no problem. When I try to load the file with other application that I created it raise my an error that says the file is in used by other process.
I tried TStringList, LoadFromFile and TFileStream.Create(LFile, fmOpenRea);.
Some one knows how I can read this like the notepad and notepad++?
Tks.
Erik got there first but uses fmShareDenyNone which would allow other processes to write to the same file. If you only want to allow reading by other processes, use:
FFileStream := TFileStream.Create(FFilename, fmOpenWrite or fmShareDenyWrite);
Use fmOpenWrite or fmShareDenyNone to enable sharing.
I am using Delphi7 and I was experiencing, that TFileStream sometimes fail to open a file for reading, while it is only locked for writing. (Before one would start nagging about the sharing parameters of the TFileStream class; I know about them, and set them right.) While I was not yet able to find out the reasons of this clearly buggy behavior, I found that it can be worked around by using some other means of file handling:
While the file can not be opened with TFileStream - even with the right sharing settings (yes, I know what I am doing) - it could easily be opened using the appropriate WinAPI calls: CreateFile/ReadFile/SetFilePointer/CloseHandle wrapped in the Windows unit.