I have a very strange problem indeed! I wonder if the problem is in the framework, OS or maybe it's just me, misunderstanding things...
I have a file, which might be created a long time ago, I use the file, and then I want to archive it, by changing it's name. Then I want to create a new file, with the same name as the old file had, before it was renamed. Easy enough!
The problem that really puzzles me, is that the newly created file gets wrong "created"-timestamp! That's a problem since it's that timestamp that I want to use for determing when to archive and create a new file.
I've created a very small sample that shows the problem. For the sample to work, there must be a file 1.txt in the Files folder. Also, the file attribute must also be set back in time (with one of the tools available, I use Nomad.NET).
static void Main(string[] args)
{
// Create a directory, if doesnt exist.
string path = Path.GetDirectoryName(Application.ExecutablePath) + "\\Files";
Directory.CreateDirectory(path);
// Create/attach to the 1.txt file
string filename = path + "\\1.txt";
StreamWriter sw = File.AppendText(filename);
sw.WriteLine("testing");
sw.Flush();
sw.Close();
// Rename it...
File.Move(filename, path + "\\2.txt");
// Create a new 1.txt
sw = File.AppendText(filename);
FileInfo fi = new FileInfo(filename);
// Observe, the old files creation date!!
Console.WriteLine(String.Format("Date: {0}", fi.CreationTime.Date));
Console.ReadKey();
}
This is the result of an arcane "feature" going way back to the old days of Windows. The core details are here:
Windows NT Contains File System Tunneling Capabilities (Archive)
Basically, this is on purpose. But it's configurable, and an anachronism in most of today's software.
I think you can create a new filename first, then rename old->old.1, then new->old, and it'll "work". I don't remember honestly what we did when we ran into this last a few years back.
I recently ran into the same problem described in the question. In our case, if our log file is older than a week, we delete it and start a new one. However, it's been keeping the same date created since 2008.
One answer here describes renaming the old file and then creating a new one, hopefully picking up the proper Creation Date. However, that was unsuccessful for us, it kept the old date still.
What we used was the File.SetCreationTime method, and as its name suggests, it easily let us control the creation date of the file, allowing us to set it to DateTime.Now. The rest of our logic worked correctly afterwards.
File.SetCreationTime("file", DateTime.Now);
Related
I need to write a big file in my project.
What I learned:
I should NOT write the big file directly to the destination path,
because this may leave a incomplete file in case the app crash while writing it.
Instead, I should write to a temporary file and move (rename) it. (called atomic file operation)
My code snippet:
[NotNull]
public static async Task WriteAllTextAsync([NotNull] string path, [NotNull] string content)
{
string temporaryFilePath = null;
try {
temporaryFilePath = Path.GetTempFileName();
using (var stream = new StreamWriter(temporaryFilePath, true)) {
await stream.WriteAsync(content).ConfigureAwait(false);
}
File.Delete(path);
File.Move(temporaryFilePath, path);
}
finally {
if (temporaryFilePath != null) File.Delete(temporaryFilePath);
}
}
My Question:
The file will be missing if the app crashes between File.Delete and File.Move. Can I avoid this?
Is there any other best practice for writing big files?
Is there any suggestion on my code?
The file will be missing if the app crashes between File.Delete and File.Move. Can I avoid this?
Not that I'm aware of, but you can detect it - and if you use a more predictable filename, you can recover from that. It helps if you tweak the process somewhat to use three file names: the target, a "new" file and an "old" file. The process becomes:
Write to "new" file (e.g. foo.txt.new)
Rename the target file to the "old" file (e.g. foo.txt.old)
Rename the "new" file to the target file
Delete the "old" file
You then have three files, each of which may be present or absent. That can help you to detect the situation when you come to read the new file:
No files: Nothing's written data yet
Just target: All is well
Target and new: App crashed while writing new file
Target and old: App failed to delete old file
New and old: App failed after the first rename, but before the second
All three, or just old, or just new: Something very odd is going on! User may have interfered
Note: I was unaware of File.Replace before, but I suspect it's effectively just a simpler and possibly more efficient way of doing the code you're already doing. (That's great - use it!) The recovery process would still be the same though.
You can use File.Replace instead of deleting and moving files. In case of hard fault (electricity cut or something like this) you will always lost data, you have to count with that.
After a lot of searching on the internet without any success, I'm looking here for some help.
The problem seems to be quiet simple, but unfortunately I'm not able to solve it.
I want to change the default-application to open .txt-files. For example instead of using notepad I want to use Wordpad which is located at C:\Program Files\Windows NT\Accessories\wordpad.exe
So I've tried to change the registry at: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.txt\OpenWithProgids with no success.
I've also found a solution which tries to change the group policy. This code looks like:
string tempFile = Path.GetTempFileName();
string xmlFile = tempFile.Replace(".tmp", ".xml");
File.Move(tempFile, xmlFile);
XDocument document = new XDocument(new XElement("DefaultAssociations",
new XElement("Association",
new XAttribute("Identifier", ".txt"),
new XAttribute("ProgId", "txtFile"),
new XAttribute("ApplicationName", "Editor"))));
document.Save(xmlFile);
ComputerGroupPolicyObject.SetPolicySetting(#"HKLM\Software\Policies\Microsoft\Windows\System!DefaultAssociationsConfiguration",
xmlFile, RegistryValueKind.String);
But this also doesn't work.
I also tried to use the command-line with ftype but that also didn't work.
Can anybody tell me how to change the assoziated application for a given filetype?
I guess you want to this because you have some kind of Set as default option in your program, by the way I have spent the last hour trying to figure out why it doesn't work and here it is what I've found so far.
The step you need to take are the following:
Creates a registry key in ClassesRoot for the .custom extension.
(Period is important)
Code:
Registry.ClassesRoot.CreateSubKey(".custom").SetValue("", "customfile", Microsoft.Win32.RegistryValueKind.String);`
Creating the "Customfile" sub-key and the "customfile\open\command"
subkey that is needed to store the path to the application that will
open this file type.
Code:
Registry.ClassesRoot.CreateSubKey("Customfile\shell\open\command").SetValue("", PATH_TO_YOUR_EXE, Microsoft.Win32.RegistryValueKind.String);
And now the association has been made, your app will be registered as one of those which can open that extention.
The case of .txt (or other already associated extentions)
After messing a little bit around i found out that in order to do changes to an already associated extention you also need to edit the registry
Example (with .txt ext.)
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.txt\UserChoice
This key has an ProgId value which actually contains the default application set by the user, the value is a string. So you will also have do edit/delete this Registry as well.
I hope it helps :)!
I have a log object that writes daily log files with a relative path. It's fairly simple (.NET 4.0, VS 2010).
public void LogLine(string txt)
{
DateTime dt = DateTime.Now;
if (CurrentDay != dt.Day)
{
string newFileName = "..\\Log\\" + programName + dt.Day + ".log";
fs = new FileStream(newFileName, FileMode.Create, FileAccess.Write);
sw = new StreamWriter(fs);
CurrentDay = dt.Day;
}
sw.WriteLine(txt);
}
This works well almost all the time. However, sometimes I get what seems to be a random DirectoryNotFoundException with a totally different path. For example, when I first run the program, it creates a file:
C:\MyFiles\Log\MyApp19.log
After using the program some and letting it run overnight so a new file and stream are created (at the first log after midnight), I come back to the DirectoryNotFoundException stating something like:
C:\MyFiles\MyOtherFiles\Resources\Log\MyApp20.log
The only thing that I can think of is: I use an OpenFileDialog and SaveFileDialog a couple times throughout the life of the software, and one of those open/save dialogs access a file within
C:\MyFiles\MyOtherFiles\Resources\SavedFiles\
So it seems to me that when I use the dialogs, I open/save something into the SavedFiles directory and when it creates the new log, the relative file path ..\ goes up to Resources (from SavedFiles), then can't find the directory Log within Resources and throws an exception. However, I can't reproduce the problem using dialogs, and I thought the relative path is relative to the executable? Can the Open/Save File Dialogs alter how the software calculates the relative file path? Anyone have any thoughts? Thanks for your time!
Alng i think that the following link can help you:
http://msdn.microsoft.com/en-us/library/system.windows.forms.filedialog.aspx
Pay attention to the following part:
Important:
If the user of your application changes the folder in the FileDialog, then the current working directory for your application is set to the location specified in the FileDialog. To prevent this, set the RestoreDirectory property to true.
Try to use the Microsoft proposed methodologies for paths as described in the above link.
This can help you also
http://msdn.microsoft.com/en-us/library/system.windows.forms.application.executablepath.aspx
regards
A relative path always works on the current directory of the application. That can easily change, for example when you show a save dialog.
It is always better to create a path relative to your executable.
var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetCallingAssembly();
var path = Path.GetDirectoryName(assembly.Location);
newFileName = Path.Combine(path, "..\\Log\\" + programName + dt.Day + ".log");
I'm making a guess that the application changes the current working directory at some point. As a result, on that basis, I'd use a fully-qualified path for the log file. You could use the assembly's startup path, eg Application.StartupPath, which should not change even if the app changes folders for some reason.
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.
I have an application that is looking through some files for old data. In order to make sure we don't corrupt good projects, I'm copying the files to a temporary location. Some of the directories I'm checking are source-code directories, and they have .svn folders. We use Subversion to manage our code.
Once I've searched through all of the files, I want to delete the temp cache. Sounds easy, right?
For some reason, all of my .svn directories won't delete from the cache. They crash the app.
For reasons (too deep to go into here), I have to use the temp folder, so just "scan the original file" is out of the question for political reasons.
I can go into explorer and delete them. No problem. No warnings. Just deletes. But the code crashes with "Access to {file} is denied." I'm at my wits end with this one, so any help would be appreciated.
While I've simplified the function a LITTLE for sake of your sanity, the code REALLY is about this simple.
List<string> tmpCacheManifest = new List<string>();
string oldRootPath = "C:\\some\\known\\directory\\";
string tempPath = "C:\\temp\\cache\\";
foreach (string file in ListOfFilesToScan)
{
string newFile = file.Replace(oldRootPath, tempPath);
// This works just fine.
File.Copy(file, newFile);
tmpCacheManifest.add(newFile);
}
// ... do some stuff to the cache to verify what I need.
// Okay.. I'm done.. Delete the cache.
foreach (string file in tmpCacheManifest)
{
// CRASH!
File.Delete(file);
}
* Update *: The exception is UnauthorizedAccessException. The text is "Access to the path 'C:\temp\cache\some-sub-dirs\.svn\entries' is denied."
It happens under XP, XP-Pro and Windows 7.
* Update 2 * None of my validation even ATTEMPTS to look at subversion files. I do need them, however. That's part of the political crap. I have to show that EVERY file was copied... wheter it was scanned or not.
And I realize what the usual suspects are for File.Delete. I realize what UnauthorizedAccessException means. I don't have access. That's a no-brainer. But I just copied the file. How can I NOT have access to the file?
* Update 3 *
The answer was in the "read-only" flag. Here's the code I used to fix it:
foreach (string file in ListOfFilesToScan)
{
string newFile = file.Replace(oldRootPath, tempPath);
// This works just fine.
File.Copy(file, newFile);
//// NEW CODE ////
// Clear any "Read-Only" flags
FileInfo fi3 = new FileInfo(fn);
if ((fi3.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
{
fi3.Attributes = (FileAttributes)(Convert.ToInt32(fi3.Attributes) - Convert.ToInt32(FileAttributes.ReadOnly));
}
tmpCacheManifest.add(newFile);
}
// ... do some stuff to the cache to verify what I need.
As far as I recall, Subversion marks the files in its .svn subdirectories as read-only.
Try resetting the read-only attribute before deleting the file. I don't really know any C#, but a quick Google suggests this might do the trick:
File.SetAttributes(file, FileAttributes.Normal);
The only problem I see would be in this part:
// ... do some stuff to the cache to verify what I need.
If you do open the file and forget to close it, you still have exclusive access to it, and thus can't delete it later on.
Sounds like you don't have access to delete the file...
system.io.file.delete
The above link says you get UnauthorizedAccessException when:
The caller does not have the required permission.
-or-
path is a directory.
-or-
path specified a read-only file.
It's one of those.
Sounds like a permissions issue. Tricky one though as you obviously have write access if the File.Copy already works....
Only thing I could think of is the file still has a handle opened somewhere (as others have suggested perhaps in your do some stuff to the cache part).
First of all: "Crash" means an exception, right? Which one? Can you catch it and show it?
Second thing: You are copying subversion repositories, although you don't care about the subversion metadata? That's what svn export is about (no .svn directory in the target).
The answer to the first question is what you really need to provide. Maybe something grabs the .svn and locks some files. TortoiseSVN maybe (to give you nice overlay icons..)?
If a folder contains read only files, Directory.Delete won't delete it and raise the exception you're getting. For future visitors of this page, I've found a simple solution which doesn't require us to recurse through all the files and changing their read-only attribute:
Process.Start("cmd.exe", "/c " + #"rmdir /s/q C:\Test\TestDirectoryContainingReadOnlyFiles");
(Change a bit to not to fire a cmd window momentarily, which is available all over the internet)
Not understanding what you want to do so much, but what about chmoding it to 777 or 775. :-/
Edit:
Noticed your on windows. You'd have to change the permissions. Don't know how windows does that :-/