Optimizer won't stop after end new files found by FSW - c#

I have created a CLI Application that will watch over a directory and optimize any new PDF's that are moved into it. There are no errors as of my last build.
The issue I'm having is when I run it the application will detect a change and optimize the changed files, but it doesn't stop the cycle of optimizing the new files.
How would I set a stopping point in the optimization process once it reaches the end of the new files?
public class Methods
{
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public static void Optimize()
{
Thread.Sleep(1000);
PDFNet.Initialize();
string input_Path = #"C:\Users\user\Desktop\testinpactive\";
string output_Path = #"C:\Users\user\Desktop\output\";
string[] files = Directory.GetFiles(input_Path, "*.pdf", SearchOption.AllDirectories);
foreach (string file in files)
{
string fileName = Path.GetFileName(file);
Console.WriteLine($"Optimizing {fileName}");
string sub = file.Substring(41, 7);
CreateFolder(output_Path + sub);
// CreateFolder(output_Path + );
try
{
using (PDFDoc doc = new PDFDoc(file))
{
//--------------------------------------------------------------------------------
// Example 1) Simple optimization of a pdf with default settings.
doc.InitSecurityHandler();
Optimizer.Optimize(doc);
doc.Save(output_Path + sub + fileName, SDFDoc.SaveOptions.e_linearized);
// File Delete Process
//File.Delete(input_Path + files);
//Console.WriteLine("File Deleted");
Console.WriteLine("Done..\n");
}
}
catch (PDFNetException e)
{
Console.WriteLine(e.Message);
}
}
}
public static void Run()
{
// Create a new FileSystemWatcher and set its properties.
// Params: Path, and filter
using (FileSystemWatcher watcher = new FileSystemWatcher(#"C:\Users\user\Desktop\testinpactive", "*.pdf"))
{
// To watch SubDirectories
watcher.IncludeSubdirectories = true;
FswHandler Handler = new FswHandler();
// Add event handlers.
watcher.Created += Handler.OnCreated;
// Begin watching.
watcher.EnableRaisingEvents = true;
// Wait for the user to quit the program.
Console.WriteLine("Press 'q' to quit the sample.");
while (Console.Read() != 'q');
}
}
public class FswHandler
{
public void OnCreated(Object source, FileSystemEventArgs e)
{
// Write out Path (Testing)
Console.WriteLine($"FILE: {e.FullPath} CHANGE-TYPE: {e.ChangeType}");
// Specify what is done when a file is changed, created, or deleted.
Optimize();
}
}

A big thanks to #BenVoigt for explaining what I was doing wrong.
Here are the changes I made to my code to fix the issue.
public static void Optimize(string filePath, string outputPath, string fileName, string fileNum)
{
PDFNet.Initialize();
try
{
using (PDFDoc doc = new PDFDoc(filePath))
{
//--------------------------------------------------------------------------------
// Example 1) Simple optimization of a pdf with default settings.
doc.InitSecurityHandler();
Optimizer.Optimize(doc);
Directory.CreateDirectory(outputPath + filePath.Substring(41, 7));
doc.Save(filePath, SDFDoc.SaveOptions.e_linearized);
//doc.Save(outputPath + fileNum + fileName, SDFDoc.SaveOptions.e_linearized);
Console.WriteLine("Done..\n");
}
}
catch (PDFNetException e)
{
Console.WriteLine(e.Message);
}
}
public static void Run()
{
// Create a new FileSystemWatcher and set its properties.
// Params: Path, and filter
using (FileSystemWatcher watcher = new FileSystemWatcher(#"C:\Users\user\Desktop\testinpactive", "*.pdf"))
{
// To watch SubDirectories
watcher.IncludeSubdirectories = true;
FswHandler Handler = new FswHandler();
// Add event handlers.
watcher.Created += Handler.OnCreated;
// Begin watching.
watcher.EnableRaisingEvents = true;
// Wait for the user to quit the program.
Console.WriteLine("Press 'q' to quit the sample.");
while (Console.Read() != 'q');
}
}
public class FswHandler
{
public void OnCreated(Object source, FileSystemEventArgs e)
{
string output = #"C:\Users\user\Desktop\output";
// Write out Path (Testing)
Console.WriteLine($"FILE: {e.FullPath} CHANGE-TYPE: {e.ChangeType}");
Thread.Sleep(800);
// Specify what is done when a file is changed, created, or deleted.
Optimize(e.FullPath, output, e.Name.Substring(7), e.FullPath.Substring(40, 8));
}
}

Related

How to fix access denial error when copying an excel file programmatically within a thread in C#

I'm writing a program which copies an excel file to another location and removes the sheets except for the visible sheets and saving the copied file. I have used the BackgroundWorker class in order to achieve this.
First, I initialized the Background Worker methods.
private void InitializeBackgroundWorker()
{
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.WorkerSupportsCancellation = true;
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
}
"BackgroundWorker.DoWork()" method is as follows.
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
GenerateReports(worker);
// Cancel the asynchronous operation.
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
worker.ReportProgress(100);
if(backgroundWorker.IsBusy)
{
this.backgroundWorker.CancelAsync();
}
}
The "GenerateReports()" method contains the "ExtractVisibleSheets()" method which extracts the visible sheets, which then calls the "CopyVisibleSheets()" method.
private void ExtractVisibleSheets(String originalDirectory, String convertedDirectory)
{
//Get the .xlsx files of the original reports and the converted reports
visibleSheetsOriginal = Directory.GetFiles(originalDirectory, "*.xlsx");
visibleSheetsConverted = Directory.GetFiles(convertedDirectory, "*.xlsx");
//Copy the visible sheets to the defined workbooks
//Sample Reports
CopyVisibleSheets(originalDirectory, visibleSheetsOriginal, visibleSheetsBasePath);
//Converted Reports
CopyVisibleSheets(convertedDirectory, visibleSheetsOriginal, visibleSheetsConvertedPath);
}
private void CopyVisibleSheets(String directory, String[] excelFiles, String path)
{
excelApplication = null;
workbook = null;
Excel.Worksheet sheet = null;
String copiedReport = "";
try
{
foreach(String report in excelFiles)
{
copiedReport = path + "\\" + report.Substring(report.LastIndexOf('\\') + 1);
excelApplication = GetExcelApplication();
File.Copy(report, copiedReport);
OpenXmlFileProcessor.RemoveCustomProperty(copiedReport, FileProcessor.BaClientVerParam);
workbook = excelApplication.Workbooks.Open(copiedReport);
EnableDisableAlertsAndEvents(false);
for (int i = workbook.Worksheets.Count; i > 0; i--)
{
sheet = excelApplication.ActiveWorkbook.Worksheets[i];
if(sheet.Visible != XlSheetVisibility.xlSheetVisible)
{
sheet.Visible = XlSheetVisibility.xlSheetVisible;
sheet.Delete();
}
}
workbook.Save();
EnableDisableAlertsAndEvents(true);
workbook.Close();
Marshal.ReleaseComObject(workbook);
}
}
finally
{
QuitAndReleaseExcelApplication(false);
}
}
"BackgroundWorker.RunWorkerCompleted()" method is given below
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
// Next, handle the case where the user cancelled
// the operation.
}
else
{
// Finally, handle the case where the operation
// succeeded.
MessageBox.Show("Directory Generation Successful!");
}
EnableControls();
}
But an error occurs during the line "File.Copy(report, copiedReport)" as follows and is fired up from the "BackgroundWorker.RunWorkerCompleted()" method.
Error
Do let me know if someone knows the reason for this error.
As a rule, the system C: drive requires admin privileges for writing. I'd suggest choosing another drive or folder (application data).
path + "\\" + report.Substring(report.LastIndexOf('\\') + 1);
try to use double qute "" (report.LastIndexOf('\\') + 1);
its a type of strings
try to use path + "//" + report.Substring(report.LastIndexOf("//") + 1);
correct me if im wrong :)

Delete file using file watcher not allowing me to delete second time

My task is to delete file once the processing is completed . I am using FileWatcher to complete this task. It is watching specific folder . Suppose If i copy one file and put that in filewatcher folder it is deleting. Second time when i copy the same file and paste that in the same watching folder. This time it says that Another process is using that file . and exception is throwing . I think i am missing something. Here is my code
private static void Main(string[] args)
{
var fw = new FileSystemWatcher(EmailSetting.DataFolder)
{
IncludeSubdirectories = false
,
EnableRaisingEvents = true
};
fw.Created += (sender, e) =>
{
File.Delete(e.FullPath);
};
Console.ReadLine();
}
You receive the Created event when the file was created (hence the name). But at this point in time the other process that is actually creating it, didn't finish writing content into that file. So the file might be already there, but the other is still working on it (imagine you would copy a 8 GB file).
It would be wiser to simply write the path of the file into a list within the event and let another thread regularly check this concurrent bag (e.g. once a second). First it checks if the file exists and if yes, try to delete it. If succeeded, remove it from the bag, otherwise try again next time.
Code example
private static readonly ConcurrentQueue<FileInfo> _FileCandidates = new ConcurrentQueue<FileInfo>();
private static void Main(string[] args)
{
var watcher = new FileSystemWatcher
{
Path = #"R:\TestFolder",
IncludeSubdirectories = false,
Filter = "*.*",
};
Console.WriteLine("Start watching folder... " + watcher.Path);
watcher.Created += OnFileCreated;
watcher.EnableRaisingEvents = true;
var timer = new Timer
{
AutoReset = true,
Interval = 1000,
};
timer.Elapsed += OnTimerElapsed;
timer.Enabled = true;
Console.ReadKey();
}
static void OnTimerElapsed(object sender, ElapsedEventArgs e)
{
FileInfo file;
var stillInUseFiles = new List<FileInfo>();
Console.WriteLine("Check for file candidates...");
while (_FileCandidates.TryDequeue(out file))
{
try
{
Console.WriteLine("Delete " + file.FullName);
if (file.Exists)
file.Delete();
}
catch (IOException)
{
Console.WriteLine("Could not delete file, try again next time.");
stillInUseFiles.Add(file);
}
}
foreach (var unhappyFile in stillInUseFiles)
{
_FileCandidates.Enqueue(unhappyFile);
}
}
static void OnFileCreated(object sender, FileSystemEventArgs e)
{
Console.WriteLine("Found new file candidate " + e.FullPath);
_FileCandidates.Enqueue(new FileInfo(e.FullPath));
}

Increment ProgressBar within another class

it's my first question I'm asking here, so please be gentle with me ;)
So I've actually got two WinForms in my C# application I'm writing at the moment (I'm quite new to C#).
This window has a button, which saves photos from an usb device you selected before in a list box to another folder.
After clicking on this button my main thread is of course busy with copying, so I decided to create another WinForm which contains my ProgressBar.
Foreach completed copy, I want to increment my ProgressBar accordingly.
So I count the number of copies I have to do and give it the progressbar as maximum. But my problem at the moment is, that I really don't know how to increment the ProgressBar without getting a Thread Unsafe Exception.
Here's my ProgressWindow code:
public partial class ProgressWindow : Form
{
BackgroundWorker updateProgressBarThread = new BackgroundWorker();
private Boolean _isThreadRunning = false;
public Boolean IsThreadRunning
{
get { return _isThreadRunning; }
set { _isThreadRunning = value; }
}
private int _progressbarLength;
public int ProgressbarLength
{
get { return _progressbarLength; }
set { _progressbarLength = value; }
}
private int progress = 1;
public ProgressWindow()
{
Show();
InitializeComponent();
}
private void StartUpdateThread(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
// Reports progress to the ProgressChangedEvent function. (Thread Safe)
}
private void FinishProgressThread(object sender, RunWorkerCompletedEventArgs e)
{
if (!_isThreadRunning)
{
MessageBox.Show("Erfolgreich kopiert");
Close();
}
}
private void ProgressChangedEvent(object sender, ProgressChangedEventArgs e)
{
this.copyProgressbar.Value = e.ProgressPercentage;
this.progressStatus.Text = e.ProgressPercentage.ToString();
}
public void CallUpdateThread()
{
updateProgressBarThread.WorkerReportsProgress = true;
updateProgressBarThread.DoWork += new DoWorkEventHandler(StartUpdateThread);
updateProgressBarThread.ProgressChanged += new ProgressChangedEventHandler(ProgressChangedEvent);
updateProgressBarThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(FinishProgressThread);
updateProgressBarThread.RunWorkerAsync();
}
}
I want to increment my ProgressBar with 1 after each succesful copy.
How do I do this from my main thread?
This is the function which actually handles the copy process
private void SaveFile(System.IO.DirectoryInfo root)
{
try
{
IEnumerable<DirectoryInfo> directoriesNames = root.EnumerateDirectories();
// New instance of thread ProgressWindow.
ProgressWindow progress = new ProgressWindow();
progress.CallUpdateThread();
foreach (DirectoryInfo element in directoriesNames)
{
// Query all subdirectories and count everything with the in the configuration made settings.
if (!element.Attributes.ToString().Contains("System"))
{
// Now we insert the configuration we applied.
String fileExtension = null;
if (Properties.Settings.Default._configPhoto)
{
fileExtension = "*.jpg";
}
if (Properties.Settings.Default._configWordDocument)
{
fileExtension = "*.odt";
}
FileInfo[] jpgList = element.GetFiles(fileExtension, SearchOption.AllDirectories);
// set the size of the progress bar
progress.ProgressbarLength = jpgList.Count();
// Now we go through all our results and save them to our backup folder.
foreach (FileInfo tmp in jpgList)
{
string sourceFilePath = tmp.FullName;
string destFilePath = PATHTOBACKUP + "\\" + tmp.Name;
progress.IsThreadRunning = true;
try
{
System.IO.File.Copy(sourceFilePath, destFilePath, true);
}
catch (IOException ioe)
{
MessageBox.Show(ioe.Message);
}
}
}
}
// progress.IsThreadRunning = false;
}
catch (UnauthorizedAccessException e)
{
MessageBox.Show(e.Message);
}
}
It's pretty obvious that I have to do this after this function
System.IO.File.Copy(sourceFilePath, destFilePath, true);
But how do I report this to my ProgressWindow?
I really hope I explained it well enough, not sure if I'm missing something important.
Thanks in advance guys
Here is a compact example of the key components:
Clicking button starts new thread worker
Progress is done by file lengths, not by number of files
BeginInvoke used to update the progress bar (avoid cross Thread exception)
ProgressBar pb = new ProgressBar() { Minimum = 0, Maximum = 100 };
Button btn = new Button();
btn.Click += delegate {
Thread t = new Thread(() => {
DirectoryInfo dir = new DirectoryInfo("C:\\temp\\");
var files = dir.GetFiles("*.txt");
long totalLength = files.Sum(f => f.Length);
long length = 0;
foreach (var f in files) {
length += f.Length;
int percent = (int) Math.Round(100.0 * length / totalLength);
pb.BeginInvoke((Action) delegate {
pb.Value = percent;
});
File.Copy(f.FullName, "...");
}
});
t.IsBackground = true;
t.Start();
};

Filesystemwatcher double entries

I made a small winforms application to monitor a certain folder for new pdf files, if a new pdf file is created in the particulair folder it will copy it to an other location.
The problem i'm having is that the filesystemwatcher creates double/multiple entries in my listbox, how can i solve this?
namespace Scanmonitor
{
public partial class Form1 : Form
{
FileSystemWatcher watcher = new FileSystemWatcher();
DateTime lastRead = DateTime.MinValue;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
FileWatch();
}
public void FileWatch()
{
watcher.Path = #"C:\Scanner";
watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite;
watcher.Filter = "*.pdf";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
}
public void OnChanged(object source, FileSystemEventArgs e)
{
scannerListBox.Items.Add(e.FullPath);
scannerListBox.SelectedIndex = scannerListBox.Items.Count - 1;
FileMove(scannerListBox.SelectedItem.ToString());
}
public void FileMove(string filePath)
{
try
{
System.IO.File.Copy(filePath, #"\\share\Data\Scans op OCE 600\" + Path.GetFileName(filePath));
}
catch (Exception ex)
{
ToolLabel.Text = ex.Message.ToString();
}
}
}
}
}
Got it working.
public void OnChanged(object source, FileSystemEventArgs e)
{
try
{
watcher.EnableRaisingEvents = false;
FileInfo objFileInfo = new FileInfo(e.FullPath);
if (!objFileInfo.Exists) return;
System.Threading.Thread.Sleep(5000);
FileInfo fileinformatie = new FileInfo(e.FullPath);
string strCreateTime = fileinformatie.CreationTime.ToString();
string strCreateDate = fileinformatie.CreationTime.ToString();
strCreateTime = strCreateTime.Remove(strCreateTime.LastIndexOf(" "));
strCreateDate = strCreateDate.Remove(0,strCreateDate.LastIndexOf(" "));
ProcessAllFiles(e.FullPath, strCreateTime, strCreateDate);
}
catch (Exception ex)
{
ToolLabel.Text = ex.Message.ToString();
}
finally
{
watcher.EnableRaisingEvents = true;
}
}
You need to keep track of files (in a collection or dictionary) that already raised an event by the FileSystemWatcher. According to MSDN
Common file system operations might raise more than one event. For example, when a file is moved from one directory to another, several OnChanged and some OnCreated and OnDeleted events might be raised. Moving a file is a complex operation that consists of multiple simple operations, therefore raising multiple events. Likewise, some applications (for example, antivirus software) might cause additional file system events that are detected by FileSystemWatcher.
public void OnChanged(object source, FileSystemEventArgs e)
{
try
{
watcher.EnableRaisingEvents = false;
FileInfo objFileInfo = new FileInfo(e.FullPath);
if (!objFileInfo.Exists) return;
System.Threading.Thread.Sleep(5000);
FileInfo fileinformatie = new FileInfo(e.FullPath);
string strCreateTime = fileinformatie.CreationTime.ToString();
string strCreateDate = fileinformatie.CreationTime.ToString();
//Ignore this, only for my file information.
strCreateTime = strCreateTime.Remove(strCreateTime.LastIndexOf(" "));
strCreateDate = strCreateDate.Remove(0,strCreateDate.LastIndexOf(" "));
ProcessAllFiles(e.FullPath, strCreateTime, strCreateDate);
}
catch (Exception ex)
{
ToolLabel.Text = ex.Message.ToString();
}
finally
{
watcher.EnableRaisingEvents = true;
}
}

FileSystemWatcher triggers for filestream open

I have a filesystemwatcher that will trigger an event when a file is modified. I want to read from that file once the lock has been removed. At the moment I am just trying to open the file once the event is triggered, when A large file is being copied the file lock stays on for a while after the events have been sent, preventing the file from being opened for read access.
Any suggestions?
This one's actually a bit of a doozie, unless the problem space has changed significantly since I last had to deal with it.
The easiest way is to simply try to open the file, catch the resulting IOException, and if the file is locked, add it to a queue to be checked later. You can't just try to process every file that comes in because there are all kinds of cases where multiple events will be generated for the same file, so setting up a retry loop on every single received event can turn into a disaster, fast. You need to queue them up instead and check the queue at a regular interval.
Here is a basic class template that should help you out with this problem:
public class FileMonitor : IDisposable
{
private const int PollInterval = 5000;
private FileSystemWatcher watcher;
private HashSet<string> filesToProcess = new HashSet<string>();
private Timer fileTimer; // System.Threading.Timer
public FileMonitor(string path)
{
if (path == null)
throw new ArgumentNullException("path");
watcher = new FileSystemWatcher();
watcher.Path = path;
watcher.NotifyFilter = NotifyFilters.FileName;
watcher.Created += new FileSystemEventHandler(FileCreated);
watcher.EnableRaisingEvents = true;
fileTimer = new Timer(new TimerCallback(ProcessFilesTimer),
null, PollInterval, Timeout.Infinite);
}
public void Dispose()
{
fileTimer.Dispose();
watcher.Dispose();
}
private void FileCreated(object source, FileSystemEventArgs e)
{
lock (filesToProcess)
{
filesToProcess.Add(e.FullPath);
}
}
private void ProcessFile(FileStream fs)
{
// Your code here...
}
private void ProcessFilesTimer(object state)
{
string[] currentFiles;
lock (filesToProcess)
{
currentFiles = filesToProcess.ToArray();
}
foreach (string fileName in currentFiles)
{
TryProcessFile(fileName);
}
fileTimer.Change(PollInterval, Timeout.Infinite);
}
private void TryProcessFile(string fileName)
{
FileStream fs = null;
try
{
FileInfo fi = new FileInfo(fileName);
fs = fi.OpenRead();
}
catch (IOException)
{
// Possibly log this error
return;
}
using (fs)
{
ProcessFile(fs);
}
lock (filesToProcess)
{
filesToProcess.Remove(fileName);
}
}
}
(Note - I'm recalling this from memory here so it might not be perfect - let me know if it's buggy.)

Categories

Resources