Append text in exclusively opened file - c#

I am trying to implement locking scenario where same file is accessed by multiple threads at same time or at different time. While testing it seems code is overwriting the existing line instead of appending new line.
namespace SMPPService
{
public static class LogFile
{
public static void WriteErrorLog(Exception ex)
{
byte[] buf = GetBytes(DateTime.Now.ToString() + ": " + ex.Source.ToString().Trim() + "; " + ex.Message.ToString().Trim());
Lock(HttpRuntime.AppDomainAppPath + "\\Exceptions.txt",
(f) =>
{
try
{
f.Write(buf, 0, buf.Length);
}
catch (IOException ioe)
{
// handle IOException
}
}, buf);
}
public static void WriteErrorLog(string Message)
{
byte[] buf = GetBytes(DateTime.Now.ToString() + ": " + Message);
Lock(HttpRuntime.AppDomainAppPath + "\\LogFile.txt",
(f) =>
{
try
{
f.Write(buf, 0, buf.Length);
}
catch (IOException ioe)
{
// handle IOException
}
}, buf);
System.Threading.Thread.Sleep(60000);
}
public static void Lock(string path, Action<FileStream> action,byte [] lines)
{
var autoResetEvent = new AutoResetEvent(false);
while (true)
{
try
{
using (var file = File.Open(path,
FileMode.OpenOrCreate,
FileAccess.ReadWrite,
FileShare.Write))
{
action(file);
break;
}
}
catch (IOException)
{
var fileSystemWatcher =
new FileSystemWatcher(Path.GetDirectoryName(path))
{
EnableRaisingEvents = true
};
fileSystemWatcher.Changed +=
(o, e) =>
{
if (Path.GetFullPath(e.FullPath) == Path.GetFullPath(path))
{
autoResetEvent.Set();
}
};
autoResetEvent.WaitOne();
}
}
}
static byte[] GetBytes(string str)
{
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
}
}
Usage
LogFile.WriteErrorLog("Requesting SMPP Client from WMAS..." + " " + "Date:" + DateTime.Now + " " + "Source Address:" + msisdn);

FileShare.Write
From MSDN:
Allows subsequent opening of the file for writing.
This is exactly the flag you do not want to use. With files the easiest way to do locking is to let the file system do it for you, use FileShare.Read (so someone can look at the log) and retry if the open fails do to a sharing violation.
Implementing your own locking like that is only going to be re-creating when the file system already does, but badly.
Also, rather than messing handling encoding yourself (getting the edge cases right is not easy):
Open as a text file with a specified encoding (UTF-8 would be my choice).
Open the file for append, so new content will always be written at the end.
There is an overload of StreanWriter's constructor that does this.
EDIT: A quick check of the reference source: this does use FileShare.Read.

You should use File.AppendAllLines instead your lock method. File.AppendAllLines is not Thread safe and you have to lock it.
private object lockObject = new object();
private string fileName = Path.Combine(HttpRuntime.AppDomainAppPath, "LogFile.txt");
public static void WriteErrorLog(string Message)
{
lock(lockObject)
{
File.AppendAllLines(fileName, new string[] { Message + "\n" });
}
}
note that File.AppendAllLines was introduced in .Net 4 and you have to use File.AppendAllText in older framework

Related

How to extract the .img files using c# [duplicate]

I'm trying to extract an ISO to a folder with the same name without .iso on the end.
I'm having a problem with winrar as it will not start the extract when I start up with the seach starting in the folder with the ISO.
UPDATED with answer code
private void ExtractISO(string toExtract, string folderName)
{
// reads the ISO
CDReader Reader = new CDReader(File.Open(toExtract, FileMode.Open), true);
// passes the root directory the folder name and the folder to extract
ExtractDirectory(Reader.Root, folderName /*+ Path.GetFileNameWithoutExtension(toExtract)*/ + "\\", "");
// clears reader and frees memory
Reader.Dispose();
}
private void ExtractDirectory(DiscDirectoryInfo Dinfo, string RootPath, string PathinISO)
{
if (!string.IsNullOrWhiteSpace(PathinISO))
{
PathinISO += "\\" + Dinfo.Name;
}
RootPath += "\\" + Dinfo.Name;
AppendDirectory(RootPath);
foreach (DiscDirectoryInfo dinfo in Dinfo.GetDirectories())
{
ExtractDirectory(dinfo, RootPath, PathinISO);
}
foreach (DiscFileInfo finfo in Dinfo.GetFiles())
{
using (Stream FileStr = finfo.OpenRead())
{
using (FileStream Fs = File.Create(RootPath + "\\" + finfo.Name)) // Here you can Set the BufferSize Also e.g. File.Create(RootPath + "\\" + finfo.Name, 4 * 1024)
{
FileStr.CopyTo(Fs, 4 * 1024); // Buffer Size is 4 * 1024 but you can modify it in your code as per your need
}
}
}
}
static void AppendDirectory(string path)
{
try
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
catch (DirectoryNotFoundException Ex)
{
AppendDirectory(Path.GetDirectoryName(path));
}
catch (PathTooLongException Ex)
{
AppendDirectory(Path.GetDirectoryName(path));
}
}
The user selects the folder to extract (.ISO) toExtract. I then use it in the Process.Start() in the background worker. That just seems to open the mounting software and doesn't extract the ISO to the desired folder name.
Thanks in advance for your help.
Or if anyone could give me a batch to extract the ISO instead and to call it from c# passing toExtract and the folder name that would be helpful too.
Thanks
If external Class Libraries are OK!
Then use SevenZipSharp or .NET DiscUtils to extract ISO's...
These two ClassLibraries can manage ISO and Extract them!
For DiscUtils you can find some codes for ISO Management [CDReader Class] at the Link I provided.
But For SevenZipSharp, Please Explore the ClassLibrary source and find the Code to Extract or Google to find it!
To get the Name of the folder just use Path.GetFileNameWithoutExtension((string)ISOFileName) which will return "ISOFile" for an iso named "ISOFile.iso". And then you can use it with your desired path.
UPDATE
Code To Extract ISO Image with DiscUtils :
using DiscUtils;
using DiscUtils.Iso9660;
void ExtractISO(string ISOName, string ExtractionPath)
{
using (FileStream ISOStream = File.Open(ISOName, FileMode.Open))
{
CDReader Reader = new CDReader(ISOStream, true, true);
ExtractDirectory(Reader.Root, ExtractionPath + Path.GetFileNameWithoutExtension(ISOName) + "\\", "");
Reader.Dispose();
}
}
void ExtractDirectory(DiscDirectoryInfo Dinfo, string RootPath, string PathinISO)
{
if (!string.IsNullOrWhiteSpace(PathinISO))
{
PathinISO += "\\" + Dinfo.Name;
}
RootPath += "\\" + Dinfo.Name;
AppendDirectory(RootPath);
foreach (DiscDirectoryInfo dinfo in Dinfo.GetDirectories())
{
ExtractDirectory(dinfo, RootPath, PathinISO);
}
foreach (DiscFileInfo finfo in Dinfo.GetFiles())
{
using (Stream FileStr = finfo.OpenRead())
{
using (FileStream Fs = File.Create(RootPath + "\\" + finfo.Name)) // Here you can Set the BufferSize Also e.g. File.Create(RootPath + "\\" + finfo.Name, 4 * 1024)
{
FileStr.CopyTo(Fs, 4 * 1024); // Buffer Size is 4 * 1024 but you can modify it in your code as per your need
}
}
}
}
static void AppendDirectory(string path)
{
try
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
catch (DirectoryNotFoundException Ex)
{
AppendDirectory(Path.GetDirectoryName(path));
}
catch (PathTooLongException Exx)
{
AppendDirectory(Path.GetDirectoryName(path));
}
}
Use It with Like This :
ExtractISO(ISOFileName, Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\\");
Working! Tested By Me!
And Of Course You can always add more Optimization to the code...
This Code is Just a Basic One!
For UDF or for making Windows ISO Files after servicing(DISM) with out needs the above accepted answer is not working for me so i tried this working method with DiscUtils
using DiscUtils;
public static void ReadIsoFile(string sIsoFile, string sDestinationRootPath)
{
Stream streamIsoFile = null;
try
{
streamIsoFile = new FileStream(sIsoFile, FileMode.Open);
DiscUtils.FileSystemInfo[] fsia = FileSystemManager.DetectDefaultFileSystems(streamIsoFile);
if (fsia.Length < 1)
{
MessageBox.Show("No valid disc file system detected.");
}
else
{
DiscFileSystem dfs = fsia[0].Open(streamIsoFile);
ReadIsoFolder(dfs, #"", sDestinationRootPath);
return;
}
}
finally
{
if (streamIsoFile != null)
{
streamIsoFile.Close();
}
}
}
public static void ReadIsoFolder(DiscFileSystem cdReader, string sIsoPath, string sDestinationRootPath)
{
try
{
string[] saFiles = cdReader.GetFiles(sIsoPath);
foreach (string sFile in saFiles)
{
DiscFileInfo dfiIso = cdReader.GetFileInfo(sFile);
string sDestinationPath = Path.Combine(sDestinationRootPath, dfiIso.DirectoryName.Substring(0, dfiIso.DirectoryName.Length - 1));
if (!Directory.Exists(sDestinationPath))
{
Directory.CreateDirectory(sDestinationPath);
}
string sDestinationFile = Path.Combine(sDestinationPath, dfiIso.Name);
SparseStream streamIsoFile = cdReader.OpenFile(sFile, FileMode.Open);
FileStream fsDest = new FileStream(sDestinationFile, FileMode.Create);
byte[] baData = new byte[0x4000];
while (true)
{
int nReadCount = streamIsoFile.Read(baData, 0, baData.Length);
if (nReadCount < 1)
{
break;
}
else
{
fsDest.Write(baData, 0, nReadCount);
}
}
streamIsoFile.Close();
fsDest.Close();
}
string[] saDirectories = cdReader.GetDirectories(sIsoPath);
foreach (string sDirectory in saDirectories)
{
ReadIsoFolder(cdReader, sDirectory, sDestinationRootPath);
}
return;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
it has extracted from a application source ISOReader but modified for my requirements
total source is available at http://www.java2s.com/Open-Source/CSharp_Free_CodeDownload/i/isoreader.zip
Try this:
string Desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
Process.Start("Winrar.exe", string.Format("x {0} {1}",
Desktop + "\\test.rar",
Desktop + "\\SomeFolder"));
That would extract the file test.rar to the folder SomeFolder. You can change the .rar extention to .iso, it'll work the same.
As far as I can see in your current code, there is no command given to extract a file, and no path to the file that has to be extracted. Try this example and let me know if it works =]
P.S. If you'd like to hide the extracting screen, you can set the YourProcessInfo.WindowStyle to ProcessWindowStyle.Hidden.
I hace confrunted recently with this kind of .iso extraction issue. After trying several methods, 7zip did the job for me, you just have to make sure that the latest version of 7zip is installed on your system. Maybe it will help
try
{
Process cmd = new Process();
cmd.StartInfo.FileName = "cmd.exe";
cmd.StartInfo.RedirectStandardInput = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.CreateNoWindow = false;
cmd.StartInfo.UseShellExecute = false;
cmd.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
cmd.Start();
cmd.StandardInput.WriteLine("C:");
//Console.WriteLine(cmd.StandardOutput.Read());
cmd.StandardInput.Flush();
cmd.StandardInput.WriteLine("cd C:\\\"Program Files\"\\7-Zip\\");
//Console.WriteLine(cmd.StandardOutput.ReadToEnd());
cmd.StandardInput.Flush();
cmd.StandardInput.WriteLine(string.Format("7z x -y -o{0} {1}", source, copyISOLocation.TempIsoPath));
//Console.WriteLine(cmd.StandardOutput.ReadToEnd());
cmd.StandardInput.Flush();
cmd.StandardInput.Close();
cmd.WaitForExit();
Console.WriteLine(cmd.StandardOutput.ReadToEnd());
}
catch (Exception e)
{
Console.WriteLine(e.Message + "\n" + e.StackTrace);
if (e.InnerException != null)
{
Console.WriteLine(e.InnerException.Message + "\n" + e.InnerException.StackTrace);
}
}

ItextSharp modify takes time

I have a pdf template and I want to add an "option action" to that template, this should be done for all of the Recipients I have, which they're 4000.
This means I should end up with 4000 pdf files for each recipient.
But it takes a very long time, approx 500 files/12 mins.
Note: the template size is 340 KB
I built a windows service to do this job, and this is the code that does the job:
private static byte[] GeneratePdfFromPdfFile(byte[] file, string landingPage, string code)
{
try
{
using (var ms = new MemoryStream())
{
var reader = new PdfReader(file);
var stamper = new PdfStamper(reader, ms);
string _embeddedURL = "http://" + landingPage + "/Default.aspx?code=" + code + "&m=" + eventCode18;
PdfAction act = new PdfAction(_embeddedURL);
stamper.Writer.SetOpenAction(act);
stamper.Close();
reader.Close();
return ms.ToArray();
}
}
catch (Exception ex)
{
return null;
}
}
public static byte[] GenerateAttachment(AttachmentExtenstion type, string Contents, string FileName, string code, string landingPage, bool zipped, byte[] File = null)
{
byte[] finalVal = null;
try
{
switch (type)
{
case AttachmentExtenstion.PDF:
finalVal = GeneratePdfFromPdfFile(File, landingPage, code);
break;
case AttachmentExtenstion.WordX:
case AttachmentExtenstion.Word:
finalVal = GenerateWordFromDocFile(File, code, landingPage);
break;
case AttachmentExtenstion.HTML:
finalVal = GenerateHtmlFile(Contents, code, landingPage);
break;
}
return zipped ? _getZippedFile(finalVal, FileName) : finalVal;
}
catch (Exception ex)
{
return null;
}
finally
{
GC.Collect();
}
}
the GenerateAttachment Method is being called for each Recipient because the action is based on the recipient's id, and I'm using this approach to iterate through the List to speed up the process
Parallel.ForEach(listRecipients.AsEnumerable(),
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount * 2 },
(item) =>
{
File.WriteAllBytes(AppDomain.CurrentDomain.BaseDirectory + #"Attachments\" + group.Code + #"\" + item.Scenario.CampaignCode + #"\" + item.CMPRCode + "." + extension,
GenerateAttachment(_type, value, item.AttachmentName, item.CMPRCode, item.Scenario.LandingDomain, item.Scenario.AttachmentZip.Value));
});

IOException: The process cannot access the file 'file path' because it is being used by another process in Console Application in C#

I am running Console_Application-A in which I am calling another Console_Application-B (in which I am creating log file for Error/Exception).
But when I am running Console_Application-B individually its working properly but when I am running Console_Application-A at that time I am getting an Exception when Application need to write an Error in log file.(Error.txt).
IOException: The process cannot access the file 'Error.txt' because it
is being used by another process
please guide me in this issue.
Code for Writing Error log
public static bool IsFileLocked(FileInfo file)
{
FileStream stream = null;
try
{
stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None);
}
catch (IOException)
{
return true;
}
finally
{
if (stream != null)
stream.Close();
}
return false;
}
catch (Exception e)
{
string filePath =Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\Error.txt";
FileInfo FInfo = new FileInfo(filePath);
var FileState = IsFileLocked(FInfo);
while (FileState){
FileState = IsFileLocked(FInfo);
}
if (!FileState){
using (StreamWriter writer = new StreamWriter(filePath, true))
{
writer.WriteLine("Message :" + e.Message + "<br/>" + Environment.NewLine + "StackTrace :" + e.StackTrace +"" + Environment.NewLine + "Date :" + DateTime.Now.ToString());
writer.WriteLine(Environment.NewLine + "-----------------------------------------------------------------------------" + Environment.NewLine);
writer.Dispose();
}
}
}
There is no need first to check if the file is locked and then access it, as between the check and the access some other process may still get a lock on the file.
using System;
using System.IO;
class DirAppend
{
public static void Main()
{
using (StreamWriter w = File.AppendText("log.txt"))
{
Log("Test1", w);
Log("Test2", w);
}
using (StreamReader r = File.OpenText("log.txt"))
{
DumpLog(r);
}
}
public static void Log(string logMessage, TextWriter w)
{
w.Write("\r\nLog Entry : ");
w.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(),
DateTime.Now.ToLongDateString());
w.WriteLine(" :");
w.WriteLine(" :{0}", logMessage);
w.WriteLine ("-------------------------------");
}
public static void DumpLog(StreamReader r)
{
string line;
while ((line = r.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
Source - https://msdn.microsoft.com/en-us/library/3zc0w663(v=vs.110).aspx

How to write log file in c#?

How would I write a log file in c#?
Currently i have a timer with this statement which ticks every 20 secs:
File.WriteAllText(filePath+"log.txt", log);
For everything that i want logged i do this:
log += "stringToBeLogged";
As you can assume the string log just grows and grows as the program runs. (I don't even know if there is a maximum chars per string?)
I assume that there must be better ways of doing this. i just thought that it would be heavy to write the whole file again and again for every time something is added to the log.
From the performance point of view your solution is not optimal. Every time you add another log entry with +=, the whole string is copied to another place in memory. I would recommend using StringBuilder instead:
StringBuilder sb = new StringBuilder();
...
sb.Append("log something");
...
// flush every 20 seconds as you do it
File.AppendAllText(filePath+"log.txt", sb.ToString());
sb.Clear();
By the way your timer event is probably executed on another thread. So you may want to use a mutex when accessing your sb object.
Another thing to consider is what happens to the log entries that were added within the last 20 seconds of the execution. You probably want to flush your string to the file right before the app exits.
create a class create a object globally and call this
using System.IO;
using System.Reflection;
public class LogWriter
{
private string m_exePath = string.Empty;
public LogWriter(string logMessage)
{
LogWrite(logMessage);
}
public void LogWrite(string logMessage)
{
m_exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
try
{
using (StreamWriter w = File.AppendText(m_exePath + "\\" + "log.txt"))
{
Log(logMessage, w);
}
}
catch (Exception ex)
{
}
}
public void Log(string logMessage, TextWriter txtWriter)
{
try
{
txtWriter.Write("\r\nLog Entry : ");
txtWriter.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(),
DateTime.Now.ToLongDateString());
txtWriter.WriteLine(" :");
txtWriter.WriteLine(" :{0}", logMessage);
txtWriter.WriteLine("-------------------------------");
}
catch (Exception ex)
{
}
}
}
Use File.AppendAllText instead:
File.AppendAllText(filePath + "log.txt", log);
public static void WriteLog(string strLog)
{
StreamWriter log;
FileStream fileStream = null;
DirectoryInfo logDirInfo = null;
FileInfo logFileInfo;
string logFilePath = "C:\\Logs\\";
logFilePath = logFilePath + "Log-" + System.DateTime.Today.ToString("MM-dd-yyyy") + "." + "txt";
logFileInfo = new FileInfo(logFilePath);
logDirInfo = new DirectoryInfo(logFileInfo.DirectoryName);
if (!logDirInfo.Exists) logDirInfo.Create();
if (!logFileInfo.Exists)
{
fileStream = logFileInfo.Create();
}
else
{
fileStream = new FileStream(logFilePath, FileMode.Append);
}
log = new StreamWriter(fileStream);
log.WriteLine(strLog);
log.Close();
}
Refer Link:
blogspot.in
as posted by #randymohan, with using statements instead
public static void WriteLog(string strLog)
{
string logFilePath = #"C:\Logs\Log-" + System.DateTime.Today.ToString("MM-dd-yyyy") + "." + "txt";
FileInfo logFileInfo = new FileInfo(logFilePath);
DirectoryInfo logDirInfo = new DirectoryInfo(logFileInfo.DirectoryName);
if (!logDirInfo.Exists) logDirInfo.Create();
using (FileStream fileStream = new FileStream(logFilePath, FileMode.Append))
{
using (StreamWriter log = new StreamWriter(fileStream))
{
log.WriteLine(strLog);
}
}
}
Very convenient tool for logging is http://logging.apache.org/log4net/
You can also make something of themselves less (more) powerful. You can use http://msdn.microsoft.com/ru-ru/library/system.io.filestream (v = vs.110). Aspx
Add log to file with Static Class
public static class LogWriter
{
private static string m_exePath = string.Empty;
public static void LogWrite(string logMessage)
{
m_exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (!File.Exists(m_exePath + "\\" + "log.txt"))
File.Create(m_exePath + "\\" + "log.txt");
try
{
using (StreamWriter w = File.AppendText(m_exePath + "\\" + "log.txt"))
AppendLog(logMessage, w);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private static void AppendLog(string logMessage, TextWriter txtWriter)
{
try
{
txtWriter.Write("\r\nLog Entry : ");
txtWriter.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(),DateTime.Now.ToLongDateString());
txtWriter.WriteLine(" :");
txtWriter.WriteLine(" :{0}", logMessage);
txtWriter.WriteLine("-------------------------------");
}
catch (Exception ex)
{
}
}
}
if(!File.Exists(filename)) //No File? Create
{
fs = File.Create(filename);
fs.Close();
}
if(File.ReadAllBytes().Length >= 100*1024*1024) // (100mB) File to big? Create new
{
string filenamebase = "myLogFile"; //Insert the base form of the log file, the same as the 1st filename without .log at the end
if(filename.contains("-")) //Check if older log contained -x
{
int lognumber = Int32.Parse(filename.substring(filename.lastIndexOf("-")+1, filename.Length-4); //Get old number, Can cause exception if the last digits aren't numbers
lognumber++; //Increment lognumber by 1
filename = filenamebase + "-" + lognumber + ".log"; //Override filename
}
else
{
filename = filenamebase + "-1.log"; //Override filename
}
fs = File.Create(filename);
fs.Close();
}
Refer link:
http://www.codeproject.com/Questions/163337/How-to-write-in-log-Files-in-C
This is add new string in the file
using (var file = new StreamWriter(filePath + "log.txt", true))
{
file.WriteLine(log);
file.Close();
}
There are 2 easy ways
StreamWriter - http://www.dotnetperls.com/streamwriter
Log4Net like Log4j(Java) - http://www.codeproject.com/Articles/140911/log4net-Tutorial
If your application is multithreaded then in some environments file.appendalltext could give error like file already in use and if you skip that then you could lose important logs .
For that you can use
Lock object technique with file.append.. in that case it will wait for existing process to close and the write your log
This can also save you from adding other libraries in your source
Above code will throw: Process can't access the file because it is being used by another process error because of File.Create(m_exePath + "\" + "log.txt"); if you will comment out this it will work as expected
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace SolutionLogWriter
{
public static class LogWriterClass
{
private static string m_exePath = string.Empty;
public static void LogWrite(string logMessage)
{
m_exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string fullpath = m_exePath + "\\" + "log.txt";
if (File.Exists(fullpath))
{
File.Delete(fullpath);
}
// File.Create(fullpath);
try
{
FileStream fs = new FileStream(fullpath, FileMode.OpenOrCreate);
using (StreamWriter w = new StreamWriter(fs))
AppendLog(logMessage, w);
}
catch (Exception ex)
{
AppendLog(ex.ToString());
}
}
private static void AppendLog(string logMessage, TextWriter txtWriter=null)
{
try
{
txtWriter.Write("\r\nLog Entry : ");
txtWriter.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(), DateTime.Now.ToLongDateString());
txtWriter.WriteLine(" :");
txtWriter.WriteLine(" :{0}", logMessage);
txtWriter.WriteLine("-------------------------------");
}
catch (Exception ex)
{
txtWriter.Write(ex.Message);
}
}
}
}

Combine multiple files into single file

Code:
static void MultipleFilesToSingleFile(string dirPath, string filePattern, string destFile)
{
string[] fileAry = Directory.GetFiles(dirPath, filePattern);
Console.WriteLine("Total File Count : " + fileAry.Length);
using (TextWriter tw = new StreamWriter(destFile, true))
{
foreach (string filePath in fileAry)
{
using (TextReader tr = new StreamReader(filePath))
{
tw.WriteLine(tr.ReadToEnd());
tr.Close();
tr.Dispose();
}
Console.WriteLine("File Processed : " + filePath);
}
tw.Close();
tw.Dispose();
}
}
I need to optimize this as its extremely slow: takes 3 minutes for 45 files of average size 40 — 50 Mb XML file.
Please note: 45 files of an average 45 MB is just one example, it can be n numbers of files of m size, where n is in thousands & m can be of average 128 Kb. In short, it can vary.
Could you please provide any views on optimization?
General answer
Why not just use the Stream.CopyTo(Stream destination) method?
private static void CombineMultipleFilesIntoSingleFile(string inputDirectoryPath, string inputFileNamePattern, string outputFilePath)
{
string[] inputFilePaths = Directory.GetFiles(inputDirectoryPath, inputFileNamePattern);
Console.WriteLine("Number of files: {0}.", inputFilePaths.Length);
using (var outputStream = File.Create(outputFilePath))
{
foreach (var inputFilePath in inputFilePaths)
{
using (var inputStream = File.OpenRead(inputFilePath))
{
// Buffer size can be passed as the second argument.
inputStream.CopyTo(outputStream);
}
Console.WriteLine("The file {0} has been processed.", inputFilePath);
}
}
}
Buffer size adjustment
Please, note that the mentioned method is overloaded.
There are two method overloads:
CopyTo(Stream destination).
CopyTo(Stream destination, int bufferSize).
The second method overload provides the buffer size adjustment through the bufferSize parameter.
One option is to utilize the copy command, and let it do what is does well.
Something like:
static void MultipleFilesToSingleFile(string dirPath, string filePattern, string destFile)
{
var cmd = new ProcessStartInfo("cmd.exe",
String.Format("/c copy {0} {1}", filePattern, destFile));
cmd.WorkingDirectory = dirPath;
cmd.UseShellExecute = false;
Process.Start(cmd);
}
I would use a BlockingCollection to read so you can read and write concurrently.
Clearly should write to a separate physical disk to avoid hardware contention.
This code will preserve order.
Read is going to be faster than write so no need for parallel read.
Again since read is going to be faster limit the size of the collection so read does not get farther ahead of write than it needs to.
A simple task to read the single next in parallel while writing the current has the problem of different file sizes - write a small file is faster than read a big.
I use this pattern to read and parse text on T1 and then insert to SQL on T2.
public void WriteFiles()
{
using (BlockingCollection<string> bc = new BlockingCollection<string>(10))
{
// play with 10 if you have several small files then a big file
// write can get ahead of read if not enough are queued
TextWriter tw = new StreamWriter(#"c:\temp\alltext.text", true);
// clearly you want to write to a different phyical disk
// ideally write to solid state even if you move the files to regular disk when done
// Spin up a Task to populate the BlockingCollection
using (Task t1 = Task.Factory.StartNew(() =>
{
string dir = #"c:\temp\";
string fileText;
int minSize = 100000; // play with this
StringBuilder sb = new StringBuilder(minSize);
string[] fileAry = Directory.GetFiles(dir, #"*.txt");
foreach (string fi in fileAry)
{
Debug.WriteLine("Add " + fi);
fileText = File.ReadAllText(fi);
//bc.Add(fi); for testing just add filepath
if (fileText.Length > minSize)
{
if (sb.Length > 0)
{
bc.Add(sb.ToString());
sb.Clear();
}
bc.Add(fileText); // could be really big so don't hit sb
}
else
{
sb.Append(fileText);
if (sb.Length > minSize)
{
bc.Add(sb.ToString());
sb.Clear();
}
}
}
if (sb.Length > 0)
{
bc.Add(sb.ToString());
sb.Clear();
}
bc.CompleteAdding();
}))
{
// Spin up a Task to consume the BlockingCollection
using (Task t2 = Task.Factory.StartNew(() =>
{
string text;
try
{
while (true)
{
text = bc.Take();
Debug.WriteLine("Take " + text);
tw.WriteLine(text);
}
}
catch (InvalidOperationException)
{
// An InvalidOperationException means that Take() was called on a completed collection
Debug.WriteLine("That's All!");
tw.Close();
tw.Dispose();
}
}))
Task.WaitAll(t1, t2);
}
}
}
BlockingCollection Class
Tried solution posted by sergey-brunov for merging 2GB file. System took around 2 GB of RAM for this work. I have made some changes for more optimization and it now takes 350MB RAM to merge 2GB file.
private static void CombineMultipleFilesIntoSingleFile(string inputDirectoryPath, string inputFileNamePattern, string outputFilePath)
{
string[] inputFilePaths = Directory.GetFiles(inputDirectoryPath, inputFileNamePattern);
Console.WriteLine("Number of files: {0}.", inputFilePaths.Length);
foreach (var inputFilePath in inputFilePaths)
{
using (var outputStream = File.AppendText(outputFilePath))
{
// Buffer size can be passed as the second argument.
outputStream.WriteLine(File.ReadAllText(inputFilePath));
Console.WriteLine("The file {0} has been processed.", inputFilePath);
}
}
}
Several things you can do:
I my experience the default buffer sizes can be increased with noticeable benefit up to about 120K, I suspect setting a large buffer on all streams will be the easiest and most noticeable performance booster:
new System.IO.FileStream("File.txt", System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read, 150000);
Use the Stream class, not the StreamReader class.
Read contents into a large buffer, dump them in output stream at once — this will speed up small files operations.
No need of the redundant close/dispose: you have the using statement.
// Binary File Copy
public static void mergeFiles(string strFileIn1, string strFileIn2, string strFileOut, out string strError)
{
strError = String.Empty;
try
{
using (FileStream streamIn1 = File.OpenRead(strFileIn1))
using (FileStream streamIn2 = File.OpenRead(strFileIn2))
using (FileStream writeStream = File.OpenWrite(strFileOut))
{
BinaryReader reader = new BinaryReader(streamIn1);
BinaryWriter writer = new BinaryWriter(writeStream);
// create a buffer to hold the bytes. Might be bigger.
byte[] buffer = new Byte[1024];
int bytesRead;
// while the read method returns bytes keep writing them to the output stream
while ((bytesRead =
streamIn1.Read(buffer, 0, 1024)) > 0)
{
writeStream.Write(buffer, 0, bytesRead);
}
while ((bytesRead =
streamIn2.Read(buffer, 0, 1024)) > 0)
{
writeStream.Write(buffer, 0, bytesRead);
}
}
}
catch (Exception ex)
{
strError = ex.Message;
}
}

Categories

Resources