I am trying to use NVelocity templates in a .Net application: using a template to output results to a file. It all seems to work fine except for the fact that the output is never fully overwritten. If my file is 100 characters long and the template only renders 20 characters, the last 80 characters are never altered!
Code sample:
FileStream fileStream = new FileStream(outputPath, FileMode.OpenOrCreate, FileAccess.Write);
using (StreamWriter streamWriter = new StreamWriter(fileStream))
{
velocityEngine.MergeTemplate(templateName, Encoding.Default.WebName, velocityContext, streamWriter);
}
So if my template outputs AAAA and the file already contains BBBBBBBB then at the end, the file contains AAAABBBB at the end of the op.
Any clue how I can get it to fully overwrite the file? - e.g. in the above example the final output should be AAAA. Not too sure whether this is just pure stream-related stuff - but I haven't had this problem before with filestreams.
Happy to write a reset method, or just output to a memorystream and overwrite the file, but I would like to get it working like this if possible!
**EDIT:'' got it working by calling
fileStream.SetLength(0);
when I open the file. But would appreciate knowing if there was a better way!
I think the solution is to change the FileMode.OpenOrCreate to simply FileMode.Create in the first line
From the MSDN Article on System.IO.FileMode..
FileMode.Create
Specifies that the operating system should create a new file. If the file already exists, it will be overwritten.
FileMode.OpenOrCreate
Specifies that the operating system should open a file if it exists; otherwise, a new file should be created.
If you don't know, at open time, that you may be truncating the file, you can use the SetLength method on the Stream to truncate it.
http://msdn.microsoft.com/en-us/library/system.io.stream.setlength.aspx
For this to work, the Stream must be writable and seekable.
Related
I have a string with a C# program that I want to write to a file and always overwrite the existing content. If the file isn't there, the program should create a new file instead of throwing an exception.
System.IO.File.WriteAllText (#"D:\path.txt", contents);
If the file exists, this overwrites it.
If the file does not exist, this creates it.
Please make sure you have appropriate privileges to write at the location, otherwise you will get an exception.
Use the File.WriteAllText method. It creates the file if it doesn't exist and overwrites it if it exists.
Generally, FileMode.Create is what you're looking for.
Use the file mode enum to change the File.Open behavior. This works for binary content as well as text.
Since FileMode.Open and FileMode.OpenOrCreate load the existing content to the file stream, if you want to replace the file completely you need to first clear the existing content, if any, before writing to the stream. FileMode.Truncate performs this step automatically
// OriginalFile:
oooooooooooooooooooooooooooooo
// NewFile:
----------------
// Write to file stream with FileMode.Open:
----------------oooooooooooooo
var exists = File.Exists(path);
var fileMode = exists
? FileMode.Truncate // overwrites all of the content of an existing file
: FileMode.CreateNew // creates a new file
using (var destinationStream = File.Open(path, fileMode)
{
await newContentStream.CopyToAsync(destinationStream);
}
FileMode Enum
If your code doesn't require the file to be truncated first, you can use the FileMode.OpenOrCreate to open the filestream, which will create the file if it doesn't exist or open it if it does. You can use the stream to point at the front and start overwriting the existing file?
I'm assuming your using a streams here, there are other ways to write a file.
I am using DotNetZip 1.9.6 in my application which uses a file structure similar to e.g. *.docx: Zip file containing XML files.
Now every module of the application can store such XML files to my custom file management and on "save" they are serialized to streams which are then saved to the Zip file via DotNetZip.
To update the entries I use ZipFile.UpdateEntry(path, stream).
This works fine and the first time I save my file via calling ZipFile.Save() everything works.
But If I do this a second time (first some UpdateEntrycalls then Save) on the same instance the Zip file is corrupted: The file structure and meta-data (e.g. uncompressed size of each file) is still there, but all files are 0 byte in compressed size.
If I create a new instance from the just saved file after saving everything works fine, but shouldn't it be possible to avoid that and "reuse" the same instance?
The following example (also see https://dotnetfiddle.net/mHxEIy) can be used to reproduce the problem:
using System.IO;
using System.Text;
public class Program
{
public static void Main()
{
var zipFile = new Ionic.Zip.ZipFile();
var content1 = new MemoryStream(Encoding.Default.GetBytes("Content 1"));
zipFile.UpdateEntry("test.txt", content1);
zipFile.Save("test.zip"); // here the Zip file is correct
//zipFile = new Ionic.Zip.ZipFile("test.zip"); // uncomment and it works too
var content2 = new MemoryStream(Encoding.Default.GetBytes("Content 2"));
zipFile.UpdateEntry("test.txt", content2);
zipFile.Save(); // after that it is corrupt
}
}
To run this you need to add the "DotNetZip 1.9.6" NuGet package.
After the first save, this is what you get:
and after the second save:
This looks like it's a bug in the library, around removing an entry. If you just remove an entry and then save again, it correctly removes the file.
However, if you remove an entry and then add another one with the same name - which is what UpdateEntry is documented to do if the entry already exists - the old entry appears to be used instead.
The reason you're ending up with an empty file the second time is that the original MemoryStream is being read again - but by now, it's positioned at the end of the data, so there's no data to read. If you reset the position to the start of the stream (content1.Position = 0;) it will rewrite the original data. If you modify the data within content1, you end up with invalid compressed data.
The only workaround I can immediately think of is to keep your own map from filename to MemoryStream, and replace the contents of each MemoryStream when you want to update it... or just load the file each time, as per your existing workaround.
It's definitely worth filing a bug around this though, as it should work as far as I can tell.
As already suspected this was a bug in DotNetZip up to version 1.9.6.
I think I was able to fix this with THIS change which was just released as version 1.9.7 on NuGet. At least for me the problem does not happen anymore.
Some background what happend as far as I found out:
When you call Save the library sets an internal flag which remembers that the ZIP file was just save and on the second Save call instead of "recompressing" all entries in the ZIP file it copies them from the just saved file.
This works fine for adding/removing entries, but breaks when one of the entries was changed as then it "mixes" the old and the new entry and produces the inconsisten ZIP file.
My fix basically disables that "copy from old file" logic if an entry was changed.
This is a question which was asked to me in an interview and still could not find a way to do it-
Suppose I have a .txt file and I want to delete the last 4 characters from the content of that file without opening the file. The first question is- Is it really doable? If yes, what is the way to do it?
I guess you can't read the content of the file. So if you can "open" it with write only access you could do:
using (var fileStream = File.Open("initDoc.txt", FileMode.Open, FileAccess.Write))
{
fileStream.SetLength(fileStream.Length - 4);
}
Of course you would need additional checks to make sure you are subtracting the correct number of bytes depending on the encoding, not subtracting more than the length etc.
If you can't use FileMode.Open, you can use an overload of the FileStream constructor that uses a SafeFileHandle. To acquire a SafeFileHandle to a file, you need to use C# Interop. In this example below i have wrapped the interop code to get a file handle in a class called "UnmanagedFileLoader":
var unmanagedFileLoader = new UnmanagedFileLoader("initDoc.txt");
using (var fileStream = new FileStream(unmanagedFileLoader.Handle, FileAccess.Write))
{
fileStream.SetLength(fileStream.Length - 4);
}
The UnmanagedFileLoader internally uses the unmanaged CreateFile function to open an existing file with write permissions:
handleValue = CreateFile(Path, GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
For more info how to acquire a SafeFileHandle you can check out this link:
http://msdn.microsoft.com/en-us/library/microsoft.win32.safehandles.safefilehandle%28v=vs.110%29.aspx
If you want to skip the FileStream ways, the third way to do it would be to use StreamReader and StreamWriter, and then read a file with StreamReader without the last 4 bytes, and then write it using a StreamWriter. But i would still recommend using the FileStream examples above.
EDIT: I assume "opening the file" means "getting a handle to the file".
Sure it's possible :
Open a handle to the drive that contains the file
Get the file system type
Scan the content of the structure that contains information about all files: the MFT (for NTFS), the FAT records..etc.
Find the entry that corresponds to your file
Updates the entry (write) by subtracting 4 to the value that stores the "file size" information :)
If your concern is about reading all the data for a long file: that isn't necessary. If we assume you really do mean bytes, simply:
using (var file = File.Open(path, FileMode.Open, FileAccess.Write)) {
file.SetLength(file.Length - 4);
}
this does not read the contents of the file.
If you mean characters, then you need to think very carefully about the encoding - 4 characters is not necessarily 4 bytes.
I am using the StreamWriter to create a file and to write some text to that file. In some cases I have no text to write via StreamWriter, but the file was already created when StreamWriter was initialized.
using (StreamWriter sw = new StreamWriter(#"C:\FileCreated.txt"))
{
}
Currently I am using the following code, when StreamWriter is closed, to check if the FileCreated.txt content is empty, if it is delete it. I am wondering if there is a more elegant approach than this (an option within StreamWriter perhaps)?
if (File.Exists(#"C:\FileCreated.txt"))
{
if (new FileInfo(#"C:\FileCreated.txt").Length == 0)
{
File.Delete(#"C:\FileCreated.txt");
}
}
By the way, I must open a stream to write before I can check if there is any text because of some other logic in the code.
If you want to take input from the user bit by bit, you can make your source a StringBuilder, and then just commit to disk when you're done
StringBuilder SB = new StringBuilder();
...
SB.AppendLine("text");
...
if(SB.Length > 0)
File.WriteAllLines(SB.ToString());
Delaying opening the file until the first output would solve this problem, but it might create a new one (if there's a permission error creating the file, you won't find out until later, maybe when the operator is no longer at the computer).
Your current approach is decent. I don't see the need to test File.Exists, though, if you just closed a stream to it. Also consider the race condition:
You find that the file is zero-length
Another process writes to the file
You delete the file
Also consider that you might have permission to create a file, and not to delete it afterwards!
Doing this correctly requires using the raw Win32 API, as I described in a previous answer. Do note that a .NET stream could be used for the first file handle, as long as you specify the equivalent of FILE_SHARE_WRITE.
Revisit your assumptions, i.e. that you must open the stream before checking for content. Simply reorganize your logic.
Ok, so here's a bit of code. I'm having an issue where I want to save to a pre-defined location, and I want to have a pre-defined name for a file. Neither FileStream nor StreamWriter allows you to set both of those paramters as far as I can tell, based on what I've seen on MSDN.
FileStream fs = new FileStream("PermaServerList", FileMode.Create, FileAccess.Write);
StreamWriter hiddensw = new StreamWriter(#"Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments", false);
So, if you look at that, how would I get it to save a file called "PermaServerList" to the location "My Documents", regardless of the version of Windows they're using? I don't want to hard code in a location, I want it to always be whatever My Documents is in their particular version.
Alternatively, the idea behind this is that every time the program starts, I want it to load the list they last saved automatically. Is there a -simple- way to do this? Right now, the idea is that I'll just save to their chosen location, and then make a second copy in my pre-defined location and just load that on program startup. Ideas?
Yes, you're simply trying to store and read user data, which can be easily dealt with using app.config settings file.
string fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "PermaServerList.txt");
using (StreamWriter writer = new StreamWriter(fileName)) {
writer.WriteLine("wooo");
}
That's how you'd write to the file, for example. The SpecialFolfer enum will get you the location of the "My Documents" directory every time, regardless of what version of Windows they're using, or whether the folder is mapped to a network location, etc.
I'm not sure what you mean by "load the file when the program starts"; I assume your issue is that you need the directory location, beyond that it's just a question of opening it as a stream and working with it.