Wait until file exists - c#

I've written a set of web services using Web Api 2. They eventually end up calling a CMD program that fires up a OpenEdge Progress client, passes a formatted XML string and then inserts records into a OpenEdge Progress database by calling a .p procedure (WebSpeed is not an option).
The .P file has a set of business logic to run against a Progress application. It subsequently generates an XML file on completion containing an < Error > node. If this node is empty - then it worked. If the file doesn't exist or the node contains text... it failed. I then read this XML file and pass the contents of the < Error > node back to the client in Web Api.
At the moment, there is a static delay of 10 seconds from calling the CMD/Progress applet, to attempting to read the XML file, to give the server time to run the .P file and create said XML file. This isn't great, though, and occasionally an error is returned to the client because it can't find the file, yet, the file was created 1 second after the response was returned because of abnormally high server loads. Alternatively, people are forced to wait 10 seconds when the response could have been handled in 2 seconds.
I need to come up with a way to "check until file exists" until a timeout period has elapsed. I've done some research and can't find anything suitable for a Web Api environment. Does anyone have any suggestions?
Code below - forgive me. I've very much been learning as I've been going along and am very new to this!
Controller
// the request date/time
DateTime requestDate = DateTime.Now;
// list of validation errors
List<string> ohValidation = new List<string>();
...
WebExtensions.callInsertProgram(xml, "JOBLOG");
ohValidation = XmlExtensions.ReadProgressXmlFileWithArray(job.logjob.placeref, requestDate, "joblogging");
CallInsertProgram
public static void callInsertProgram(string xml, string program)
{
try
{
using (Process p = new Process())
{
p.StartInfo.FileName = #"C:\Rubixx\runProgress.exe";
p.StartInfo.WorkingDirectory = #"C:\Rubixx";
// stop windows from appearing on the server
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
// set the arguments for running. The program name and xml are passed in as arguments
// wrapped in escaping "\" to stop spaces from being treated as a separator
p.StartInfo.Arguments = "\"" + program + "," + xml + "\"";
p.Start();
}
}
catch (Exception e)
{
throw new OpenHousingException(e.Message.ToString());
}
}
ReadProgressXMLWithArray
public static List<string> ReadProgressXmlFileWithArray(string reference, DateTime requestDateTime, string folder)
{
// new empty list
List<string> output
= new List<string>();
// wait X seconds before doing anything
// to ensure the XML file has time to be created
Delay_Start(fileDelay);
//
string filename = fullFileName(jobno, folder, requestDateTime);
string filepath = getFullFilepath(filename, folder);
if (checkXmlFileExists(filepath))
{
// if so check for the existence of an error message
output = getXmlErrorArray(filepath);
}
else
{
// if no file is found - the call to Progress hasn't executed. So tell the end user.
throw new OpenHousingException("No OpenHousing file could be found");
}
return output;
}
Delay_Start
private static void Delay_Start(int Seconds)
{
DateTime StartTime;
DateTime EndTime;
StartTime = DateTime.Now;
EndTime = StartTime.AddSeconds(Seconds);
do
{ StartTime = DateTime.Now; } while (StartTime < EndTime);
}
FullFileName (needed because I can't be sure of XML filename until created. File Format is UniqueReference_DateTimeFileCreated.xml (xxxxxxxx_20160401-1100.xml) So, I have to wildcard search a folder with a unique reference).
public static string fullFileName(string jobNo, string folder, DateTime createdDate)
{
string fileName = string.Empty;
string folderPath = fileLocation + folder;
DirectoryInfo dir = new DirectoryInfo(folderPath);
FileInfo[] files = dir.GetFiles(jobNo + "*", SearchOption.TopDirectoryOnly).Where(f => f.CreationTimeUtc > createdDate || f.LastWriteTimeUtc > createdDate).ToArray() ;
foreach (var item in files)
{
fileName = item.Name;
}
if (string.IsNullOrEmpty(fileName))
throw new OpenHousingException("No OpenHousing file could be found");
return fileName;
}
GetFullFilePath (Can probably be consolidated into fullFileName)
private static string getFullFilepath(string filename, string folder)
{
return fileLocation + folder + #"\" + filename;
}
CheckXMLFileExists
private static bool checkXmlFileExists(string filepath)
{
bool fileExists = false;
if (File.Exists(filepath))
{
fileExists = true;
}
return fileExists;
}
GetXMLErrorArray
private static List<string> getXmlErrorArray(string filepath)
{
List<string> output
= new List<string>();
// read the text from XML file
using (TextReader txtReader = new StreamReader(filepath))
{
XmlSerializer xs
= new XmlSerializer(typeof(JobError));
// de-serialise the xml text
// to a strongly typed object
JobError result = (JobError)xs.Deserialize(txtReader);
// if the xml file contains an error - return it to the client
if (!string.IsNullOrEmpty(result.ErrorText))
output.Add(result.ErrorText);
//check for SoR errors that are created under a different node
if (result.LineError != null)
{
List<LineError> lineErrs = result.LineError.ToList();
foreach (LineError le in lineErrs)
{
output.Add(le.SorCode + ":" + le.Error);
}
}
}
return output;
}

OK - so I think I was overcomplicating the problem.
Instead of waiting for the file to exist, I added a line into my CallInsertProgram method as below (as suggested by RB...
public static void callInsertProgram(string xml, string program)
{
try
{
using (Process p = new Process())
{
p.StartInfo.FileName = #"C:\Rubixx\runProgress.exe";
p.StartInfo.WorkingDirectory = #"C:\Rubixx";
// stop windows from appearing on the server
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
// set the arguments for running. The program name and xml are passed in as arguments
// wrapped in escaping "\" to stop spaces from being treated as a separator
p.StartInfo.Arguments = "\"" + program + "," + xml + "\"";
p.Start();
// ADDED
p.WaitForExit(60000);
}
}
catch (Exception e)
{
throw new OpenHousingException(e.Message.ToString());
}
}
This ensures that the Progress cmd applet is completed before moving onto the next line - at which point the XML will have been created (or not if it's failed). Initial testing is working well. Can anyone foresee any problems with this approach?

Related

Every time writes new file in c#

I am working on a c# console application. I am saving some data into a text file. Every time the programs are run it saves the data into that file without overwriting into it. Now I want to save the data into a new file every time I send a request/runs the new program.
var result = XmlDecode(soapResult);
XDocument doc = XDocument.Parse(result);
XmlReader read = doc.CreateReader();
DataSet ds = new DataSet();
ds.ReadXml(read);
read.Close();
if (ds.Tables.Count > 0 && ds.Tables["Reply"] != null && ds.Tables["Reply"].Rows.Count > 0)
{
string refNo = string.Empty;
string uniqueKey = string.Empty;
string meterNo = string.Empty;
List<string> ls = new List<string>();
if (ds.Tables["Reply"].Rows[0][0].ToString().ToUpper() == "OK")
{
if (ds.Tables["Names"] != null && ds.Tables["Names"].Rows.Count > 0)
{
uniqueKey = ds.Tables["Names"].Rows[0]["name"].ToString();
}
if (ds.Tables["NameType"] != null && ds.Tables["NameType"].Rows.Count > 0)
{
refNo = ds.Tables["NameType"].Rows[0]["name"].ToString();
}
if (ds.Tables["Meter"] != null && ds.Tables["Meter"].Rows.Count > 0)
{
if (ds.Tables["Meter"].Columns.Contains("mRID"))
{
meterNo = ds.Tables["Meter"].Rows[0]["mRID"].ToString();
processedRec++;
}
}
}
log = uniqueKey + " | " + refNo + " | " + meterNo + " | " + Environment.NewLine;
ls.Add(log);
}
File.AppendAllText(filePath, log);
How can I create a new file every time?
Any help would be highly appreciated.
Make a unique filePath. Something like this:
var filePath =$"{folderPath}\txtFile_{Guid.NewGuid()}";
This will make the file to be always unique. The guid can be replaced with something more meaningful like Unix Timestamp as well.
Create a custom file name every time and make use of File.WriteAllText (which will Creates a new file, write the contents to the file, and then closes the file. If the target file already exists, it is overwritten.) instead for File.AppendAllText
In your case the filePath should be dynamic which can be construct like this:
string basePath = ""; // this should be path to your directory in which you wanted to create the output files
string extension = ".xml";
string fileName = String.Format("{0}{1}{2}","MyFile",DateTime.Now.ToString("ddMMyy_hhmmss"),extension );
string filePath = Path.Combine(basePath,fileName);
In the above snippet the DateTime.Now.ToString("ddMMyy_hhmmss") will be the current time(at the time of execution of the code) which will be differ in each execution so the file name will differ in each run. And at some later points you can search/group files based on these common pattern.
One more thing:
In your code you have used a variable List<string> ls which is populated with all logs, and you are writing the the contents of log to the file, which contains only the last record. So the statement for writing the content should be:
File.WriteAllText(filePath, String.Join("\n",log));
or even simply
File.WriteAllLines(filePath, log);
simply make your filePath unique, for example by using Ticks
var filePath = $"app-log-{DateTime.Now.Ticks:X}.log";

nAudio FadeInOutSampleProvider not working

I'm writing a program which will take a series of .wav files from a folder created by another application, trims a specified amount of time from the start and end of each sample and copies the result of that action to a temporary folder. From there, I want to take each of the files and apply a small amount of fading to the end of the files as to make the transitions between files smoother (each file will be merged together afterwards), and output the results of that operation to another temporary folder, where the program will merge the files in that folder to create the final output. Unfortunately, I can't get FadeInOutSampleProvider to work.
foreach (string file in files) {
try {
byte[] buffer = new byte[1024];
AudioFileReader afr = new AudioFileReader(tempfile2);
FadeInOutSampleProvider fade = new FadeInOutSampleProvider(afr);
fade.BeginFadeOut(notes[run2].Length - 100);
var stwp = new NAudio.Wave.SampleProviders.SampleToWaveProvider(fade);
WaveFileWriter.CreateWaveFile(tempfile, stwp);
run2++;
}
catch (Exception) { }
}
EDIT:
Here's more code:
public void PlaybackTemp(string tempDir, Sheet playbackSheet) {
string[] files = Directory.GetFiles(tempDir);
string tempdir = "";
// Generate trimmed files ready for splicing
tempdir = GenEditedFiles(files, playbackSheet.notes);
// Show the output in explorer if debug mode is on
if (debug) {
Process p = new Process();
p.StartInfo.FileName = tempdir;
p.Start();
}
// Splice the files
ConcatenateWav(tempdir + "\\render.wav", Directory.GetFiles(tempdir));
// Play back resulting file
new System.Media.SoundPlayer(tempdir + "\\render.wav").Play();
}
private string GenEditedFiles(string[] files, List<Note> notes) {
string tempdir = FluidSys.FluidSys.CreateTempDir();
string tempdir2 = FluidSys.FluidSys.CreateTempDir();
string tempfile = "";
string tempfile2 = "";
int run = 0;
int run2 = 0;
// Trim each note
foreach (string file in files) {
tempfile = tempdir + "\\" + run.ToString() + ".wav";
tempfile2 = tempdir + "\\" + run.ToString() + "0.wav";
WavFileUtils.TrimWavFile(file, tempfile2, TimeSpan.FromMilliseconds(notes[run].VoiceProperties.Start),
TimeSpan.FromMilliseconds(notes[run].VoiceProperties.End));
run++;
}
foreach (string file in files) {
try {
byte[] buffer = new byte[1024];
AudioFileReader afr = new AudioFileReader(tempfile2);
FadeInOutSampleProvider fade = new FadeInOutSampleProvider(afr);
fade.BeginFadeOut(notes[run2].Length - 10);
var stwp = new NAudio.Wave.SampleProviders.SampleToWaveProvider(fade);
WaveFileWriter.CreateWaveFile(tempfile, stwp);
run2++;
}
catch (Exception) { }
}
return tempdir;
}

Expression encoder: change file name after encoding

I'm using Microsoft Expression Encoder and this is my code
using (LiveJob job = new LiveJob())
{
// Creates file source for encoding
LiveFileSource fileSource = job.AddFileSource(DataDirectory);
// Sets playback to loop on reaching the end of the file
fileSource.PlaybackMode = FileSourcePlaybackMode.Jump;
// Sets this source as the current active one
job.ActivateSource(fileSource);
job.ApplyPreset(LivePresets.VC1IISSmoothStreamingLowBandwidthStandard);
PushBroadcastPublishFormat format = new PushBroadcastPublishFormat();
format.PublishingPoint = new Uri(PublishPoint);
job.PublishFormats.Add(format);
// Starts encoding
job.StartEncoding();
}
this code encode a list of files in a directory when he finish one he jump to the next
what I want to do is change the file name when it's encoded before passing to th other one
I have added this Methode I don't know if it work or no
public void liveJob_Status(object sender, EncodeStatusEventArgs e)
{
if (e.Status == EncodeStatus.Jumped)
{ LiveFileSource file = (LiveFileSource)e.LiveSource;
string name = file.Name;
string modified_name = "Encode" + name;
File.Move(DataDirectory + #"\" + name, DataDirectory + #"\" + name.Replace(name, modified_name));
}
}

c# service renaming files!

I have a windows service , that takes files with metadata(FIDEF) and corresponding video file and , translates the XML(FIDEF) using XSLT .
I get the file directory listing for FIDEF's and if a video file of the same name exists it translates it. That works ok , but it is on a timer to search every minute. I am trying to handle situations where the same file name enters the input directory but is already in the output directory. I just have it changing the output name to (copy) thus if another file enters i should get (copy)(copy).mov but the service won't start with filenames of the same directory already in the output , it works once and then does not seem to pick up any new files.
Any Help would be great as I have tried a few things with no good results. I believe its the renaming methods, but I've put most of the code up in case its a clean up issue or something else.
(forgive some of the names just trying different things).
private void getFileList()
{
//Get FILE LIST FROM Directory
try
{
// Process Each String/File In Directory
string result;
//string filename;
filepaths = null;
filepaths = Directory.GetFiles(path, Filetype);
foreach (string s in filepaths)
{
for (int i = 0; i < filepaths.Length; i++)
{
//Result Returns Video Name
result = Path.GetFileNameWithoutExtension(filepaths[i]);
FileInfo f = new FileInfo(filepaths[i]);
PreformTranslation(f, outputPath + result , result);
}
}
}
catch (Exception e)
{
EventLog.WriteEntry("Error " + e);
}
}
private void MoveVideoFiles(String Input, String Output)
{
File.Move(Input, Output);
}
private string GetUniqueName(string name)
{
//Original Filename
String ValidName = name;
//remove FIDEF from filename
String Justname1 = Path.GetFileNameWithoutExtension(name);
//get .mov extension
String Extension2 = Path.GetExtension(Justname1);
//get filename with NO extensions
String Justname = Path.GetFileNameWithoutExtension(Justname1);
//get .Fidef
String Extension = Path.GetExtension(name);
int cnt = 0;
//string[] FileName = Justname.Split('(');
//string Name = FileName[0];
while (File.Exists(ValidName)==true)
{
ValidName = outputPath + Justname + "(Copy)" + Extension2 + Extension;
cnt++;
}
return ValidName;
}
private string getMovFile(string name)
{
String ValidName = name;
String Ext = Path.GetExtension(name);
String JustName = Path.GetFileNameWithoutExtension(name);
while(File.Exists(ValidName))
{
ValidName = outputPath + JustName + "(Copy)" + Ext;
}
return ValidName;
}
//Preforms the translation requires XSL & FIDEF name.
private void PreformTranslation(FileInfo FileName, String OutputFileName , String result)
{
string FidefName = OutputFileName + ".FIDEF";
String CopyName;
String copyVidName = outputPath + result;
XslCompiledTransform myXslTransform;
myXslTransform = new XslCompiledTransform();
try
{
myXslTransform.Load(XSLname);
}
catch
{
EventLog.WriteEntry("Error in loading XSL");
}
try
{ //only process FIDEF's with corresponding Video file
if (AllFidef == "no")
{
//Check if video exists if yes,
if (File.Exists(path + result))
{
//Check for FIDEF File Already Existing in the Output Directory.
if (File.Exists(FidefName))
{
//Get unique name
CopyName = GetUniqueName(FidefName);
copyVidName= getMovFile(copyVidName);
//Translate and create new FIDEF.
//double checking the file is here
if (File.Exists(outputPath + result))
{
myXslTransform.Transform(FileName.ToString(), CopyName);
File.Delete(FileName.ToString());
MoveVideoFiles(path + result, copyVidName);
}
////Move Video file with Corresponding Name.
}
else
{ //If no duplicate file exsists in Directory just move.
myXslTransform.Transform(FileName.ToString(), OutputFileName + ".FIDEF");
MoveVideoFiles(path + result, outputPath + result);
}
}
}
else
{
//Must have FIDEF extension
//Processes All FIDEFS and moves any video files if found.
myXslTransform.Transform(FileName.ToString(), OutputFileName + ".FIDEF");
if (File.Exists(path + result))
{
MoveVideoFiles(path + result, outputPath + result);
}
}
}
catch (Exception e)
{
EventLog.WriteEntry("Error Transforming " + "FILENAME = " + FileName.ToString()
+ " OUTPUT_FILENAME = " + OutputFileName + "\r\n" +"\r\n"+ e);
}
}
There is a lot wrong with your code. getFileList has the unneeded inner for loop for starters. Get rid of it. Your foreach loop has s, which can replace filepaths[i] from your for loop. Also, don't do outputPath + result to make file paths. Use Path.Combine(outputPath, result) instead, since Path.Combine handles directory characters for you. Also, you need to come up with a better name for getFileList, since that is not what the method does at all. Do not make your method names liars.
I would simply get rid of MoveVideoFiles. The compiler just might too.
GetUniqueName only works if your file name is of the form name.mov.fidef, which I'm assuming it is. You really need better variable names though, otherwise it will be a maintenance nightware later on. I would get rid of the == true in the while loop condition, but that is optional. The assignment inside the while is why your files get overwritten. You always generate the same name (something(Copy).mov.fidef), and as far as I can see, if the file exists, I think you blow the stack looping forever. You need to fix that loop to generate a new name (and don't forget Path.Combine). Maybe something like this (note this is untested):
int copyCount = 0;
while (File.Exists(ValidName))
{
const string CopyName = "(Copy)";
string copyString = copyCount == 0 ? CopyName : (CopyName + "(" + copyCount + ")");
string tempName = Justname + copyString + Extension2 + Extension;
ValidName = Path.Combine(outputPath, tempName);
copyCount++;
}
This generates something(Copy).mov.fidef for the first copy, something(Copy)(2).mov.fidef for the second, and so on. Maybe not what you want, but you can make adjustments.
At this point you have a lot to do. getMovFile looks as though it could use work in the same manner as GetUniqueName. You'll figure it out. Good luck.

Deleting files fails as held by and existing process

I'm having issues with a bit of code that I am writing in C#.
I am sending a document using the MailMessage and SMTP components. I copy the files that I wish to send to a temp directory such as c:\temp, loop through the documents and attach them to the email.
The email sends fine, however when I try to delete the files from the temp directory, I get the following error:
The process can not access the file because it is being used by another process
I can't understand why this is happening. Below is the code that processes the documents
public void sendDocument(String email, string barcode, int requestid)
{
string tempDir = #"c:\temp";
//first we get the document information from the database.
Database db = new Database(dbServer, dbName, dbUser, dbPwd);
List<Document> documents = db.getDocumentByID(barcode);
int count = 0;
foreach (Document doc in documents)
{
string tempPath = tempDir + "\\" + doc.getBarcode() + ".pdf";
string sourcePath = doc.getMachineName() + "\\" + doc.getFilePath() + "\\" + doc.getFileName();
//we now copy the file from the source location to the new target location
try
{
//this copies the file to the folder
File.Copy(sourcePath, tempPath, false);
}
catch (IOException ioe)
{
count++;
//the file has failed to copy so we add a number to the file to make it unique and try
//to copy it again.
tempPath = tempDir + "\\" + doc.getBarcode() + "-" + count + ".pdf";
File.Copy(sourcePath, tempPath, false);
}
//we now need to update the filename in the to match the new location
doc.setFileName(doc.getBarcode() + ".pdf");
}
//we now email the document to the user.
this.sendEmail(documents, email, null);
updateSentDocuments(documents, email);
//now we update the request table/
db.updateRequestTable(requestid);
//now we clean up the documents from the temp folder.
foreach (Document doc in documents)
{
string path = #"c:\temp\" + doc.getFileName();
File.Delete(path);
}
}
I would of thought that the this.sendEmail() method would of sent the email before returning to the sendDocument method, as I think it is the smtp object that is causing the deletes to fail.
This is the sendEmail method:
public void sendEmail(List<Document> documents, String email, string division)
{
String SMTPServer = null;
String SMTPUser = null;
String SMTPPwd = null;
String sender = "";
String emailMessage = "";
//first we get all the app setting used to send the email to the users
Database db = new Database(dbServer, dbName, dbUser, dbPwd);
SMTPServer = db.getAppSetting("smtp_server");
SMTPUser = db.getAppSetting("smtp_user");
SMTPPwd = db.getAppSetting("smtp_password");
sender = db.getAppSetting("sender");
emailMessage = db.getAppSetting("bulkmail_message");
DateTime date = DateTime.Now;
MailMessage emailMsg = new MailMessage();
emailMsg.To.Add(email);
if (division == null)
{
emailMsg.Subject = "Document(s) Request - " + date.ToString("dd-MM-yyyy");
}
else
{
emailMsg.Subject = division + " Document Request - " + date.ToString("dd-MM-yyyy");
}
emailMsg.From = new MailAddress(sender);
emailMsg.Body = emailMessage;
bool hasAttachements = false;
foreach (Document doc in documents)
{
String filepath = #"c:\temp\" + doc.getFileName();
Attachment data = new Attachment(filepath);
emailMsg.Attachments.Add(data);
hasAttachements = true;
}
SmtpClient smtp = new SmtpClient(SMTPServer);
//we try and send the email and throw an exception if it all goes tits.
try
{
if (hasAttachements)
{
smtp.Send(emailMsg);
}
}
catch (Exception ex)
{
throw new Exception("EmailFailure");
}
}
How to I get around this problem with a process hogging the file I wish to delete.
I can delete the file(s) once the application finishes.
You're email message isn't being Disposed, try disposing it after you've sent it:
try
{
if (hasAttachements)
{
smtp.Send(emailMsg);
}
}
catch ...
finally
{
emailMsg.Dispose();
}
The first step is to figure out what process is holding onto the file in question. I would grab the SysInternals toolkit and use the handle.exe command to determine what process is holding onto the file.
Without knowing what process has the file open, there is no way to fix this problem.
Sysinternals Link: http://technet.microsoft.com/en-us/sysinternals/default.aspx
Here's a trick I just discoverred, which will hopefully be of use to other folk?
Before adding attachments to a message, create the attachment in a using wrapper. This ensures it gets disposed of correctly, allowing the file to be successfully deleted. I'm not sure if the send also needs to be in this loop; (when I tested, emails didn't get through at first, then half an hour later I got flooded, so decided to leave testing for a time when the network was a little calmer).
using (Attachment attachment = new Attachment(filename))
{
message.Attachments.Add(attachment);
client.SendAsync(message, string.Empty);
}
File.Delete(filename);
Works OK for me anyway :)
Good luck,
JB

Categories