文件的安全stream更新

我们通过将新logging写入临时文件来执行大文本文件的更新,然后用临时文件replace旧文件。 一个严重的缩写版本:

var tpath = Path.GetTempFileName(); try { using (var sf = new StreamReader(sourcepath)) using (var tf = new StreamWriter(tpath)) { string line; while ((line = sf.ReadLine()) != null) tf.WriteLine(UpdateLine(line)); } File.Delete(sourcepath); File.Move(tpath, sourcepath); } catch { File.Delete(tpath); throw; } 

如果有什么东西抛出exception(文件没有find,没有权限),原始文件保持不变,这是我们想要的。

但是,代码有以下问题:

  1. 是否存在Delete工作但Move失败的真实情况? 这将删除原始和更新的数据。 这会很糟糕。

  2. 最常见的故障是从另一个应用程序打开的源文件,并且Delete失败。 这意味着所有的更新工作都被丢弃。 有没有办法查看源文件在开始时是否可删除,如果不是,则放弃更新?

  3. 我们有用户将Windows资源pipe理器摘要属性(如标题或注释)放在文件上。 当我们删除文件时,这些被放弃。 有没有办法将旧文件的摘要属性复制到一个新的文件? 我们应该这样做吗?

避免“删除然后移动失败问题”的正常方法是:

  • 写入file.new
  • 将file.current移至file.old
  • 将file.new移到file.current
  • 删除file.new

然后当你来读,如果file.current缺少使用file.new,如果你看到它删除file.old。

检查文件是否可用:尝试打开它进行写入,但追加到结尾。 当然,在你移动之前,你需要关闭句柄,而在别人之间可以打开它 – 但至少是一个合理的优化。

不知道复制摘要等,恐怕。

为什么不先尝试检查FileAttributes?

尝试这样的事情:

 //If File is readonly if ( (file.Attribute & System.FileAttributes.ReadOnly) == System.FileAttributes.ReadOnly ) //Don't delete. 

另外尝试使用.OpenWrite()。 如果您可以打开要写入的文件,则不会被访问,也不会被使用。 如果文件当前处于未打开状态,则只能打开文件进行写入。 我不推荐这个,但它可以帮助你。

  FileStream fs = File.OpenWrite(file); fs.Close(); return false; 

您也可以使用FileLock检查方法。 像这样的东西:

 protected virtual bool IsFileLocked(FileInfo file) { try { using (file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { return false; } } catch (IOException) { return true; } 

}

你可能也想检查FileIOPermission.Write。 这允许查看文件是否可写(并且能够删除)。

 fileIOPerm = New FileIOPermission(FileIOPermissionAccess.Write, FileSpec); fileIOPerm.Demand(); 

关于原始帖子中的#3问题…您始终可以使用File.Copy(path1,path2,true)将文件移动到临时文件夹。 您可能需要考虑使用临时文件夹并为文件操作编写更好的逻辑。

如果你决定使用临时文件夹或临时文件/中间文件,那么你也可以修复你的问题#2。 尝试先移动文件。

一些“脏”的把戏。

  1. 首先不要删除原来的文件,先把它移动到另一个位置(temp path),然后如果更新文件的移动成功,则删除旧的文件。 如果更新失败,您可以将原始文件恢复到某个位置。

  2. 我认为这篇文章将帮助你在那里MSDN

  3. 如果用户需要“标题”和“评论”,你应该保留它们。 我从来没有试图从一个文件复制到另一个,所以我不知道如何帮助你。

Windows Vista或更高版本上的事务性NTFS可能对您的方案有用。

正如已经提到的,你真的应该调查ReplaceFile,它旨在帮助你正在做什么。 .NET函数只是Win32函数的一个包装器,在这个函数中可能会有一些希望,即原子性问题已经被敲定。

很多好的建议。 我能够解决以下问题:

 var sInfo = new FileInfo(sourcePath); if (sInfo.IsReadOnly) throw new IOException("File '" + sInfo.FullName + "' is read-only."); var tPath = Path.GetTempFileName(); try { // This throws if sourcePath does not exist, is opened, or is not readable. using (var sf = sInfo.OpenText()) using (var tf = new StreamWriter(tPath)) { string line; while ((line = sf.ReadLine()) != null) tf.WriteLine(UpdateLine(line)); } string backupPath = sInfo.FullName + ".bak"; if (File.Exists(backupPath)) File.Delete(backupPath); File.Move(tPath, backupPath); tPath = backupPath; File.Replace(tPath, sInfo.FullName, null); } catch (Exception ex) { File.Delete(tPath); throw new IOException("File '" + sInfo.FullName + "' could not be overwritten.", ex); } 

如果源文件打开或不可读,则OpenText会抛出,并且更新没有完成。 如果有任何东西抛出,原始文件保持不变。 Replace旧文件的摘要属性Replace为新文件。 即使源文件与临时文件夹位于不同的卷上,也是如此。

我发现把这个模式包装在自己的类中是很有用的。

 class Program { static void Main( string[] args ) { using( var ft = new FileTransaction( @"C:\MyDir\MyFile.txt" ) ) using( var sw = new StreamWriter( ft.TempPath ) ) { sw.WriteLine( "Hello" ); ft.Commit(); } } } public class FileTransaction :IDisposable { public string TempPath { get; private set; } private readonly string filePath; public FileTransaction( string filePath ) { this.filePath = filePath; this.TempPath = Path.GetTempFileName(); } public void Dispose() { if( TempPath != null ) { try { File.Delete( TempPath ); } catch { } } } public void Commit() { try { var oldPath = filePath + ".old"; File.Move( filePath, oldPath ); } catch {} File.Move( TempPath, filePath ); TempPath = null; } } 

这个代码片断展示了一种获取文件独占访问的技巧(在这种情况下阅读):

 // Try to open a file exclusively FileInfo fi = new FileInfo(fullFilePath); int attempts = maxAttempts; do { try { // Try to open for reading with exclusive access... fs = fi.Open(FileMode.Open, FileAccess.Read, FileShare.None); } // Ignore any errors... catch { } if (fs != null) { break; } else { Thread.Sleep(100); } } while (--attempts > 0); // Did we manage to open file exclusively? if (fs != null) { // use open file.... }