source: 2014/tet/FileWatcher/FileWatcher/Program.cs @ 5795

Revision 5795, 23.9 KB checked in by mikrkana, 8 years ago (diff)

Korjattu bugeja.

Line 
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Text;
5using System.Security.Permissions;
6using System.IO;
7using System.Diagnostics;
8using System.Threading;
9using System.ComponentModel;
10using System.Runtime.Serialization;
11using System.Runtime.Serialization.Formatters.Binary;
12using System.Windows.Threading;
13
14
15namespace FileWatcher
16{
17    public static class Program
18    {
19        /// <summary>
20        /// Kansio, jota tarkkaillaan.
21        /// </summary>
22        public static String SelectedFolder { get; set; }
23
24        /// <summary>
25        /// Tiedostotyypit, joita tarkkaillaan.
26        /// </summary>
27        public static String[] SelectedFileExtensions { get; set; }
28
29        /// <summary>
30        /// Ohjelmat, jotka ajetaan tiedoston löytyessä.
31        /// </summary>
32        public static List<Executable> ExecutablePrograms;
33
34        private static List<String> jobs = new List<String>();
35        private static List<FileSystemWatcher> watchers;
36
37        public static bool WatchCreation { get; set; }
38        public static bool WatchDeletion { get; set; }
39        public static bool WatchChanges { get; set; }
40        public static bool WatchRenaming { get; set; }
41
42        /// <summary>
43        /// Tarkkaillaanko alikansioita.
44        /// </summary>
45        public static bool WatchSubdirectories { get; set; }
46
47        public static readonly String WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory;
48        public static readonly String OptionsFilePath = WorkingDirectory + "\\options.dat";
49        public static readonly String LogFilePath = WorkingDirectory + "\\log.txt";
50        public static StreamWriter LogWriter;
51
52        /// <summary>
53        /// Ladataanko edelliset asetukset automaattisesti ohjelman käynnistyessä.
54        /// </summary>
55        public static bool LoadSettingsAutomatically { get; set; }
56
57        /// <summary>
58        /// Tallenetaanko asetukset ohjelman sulkeutuessa.
59        /// </summary>
60        public static bool SaveSettingsAutomatically { get; set; }
61
62        /// <summary>
63        /// Aloitetaanko tarkkailu automaattisesti edellisillä asetuksilla.
64        /// </summary>
65        public static bool StartWatchingAutomatically { get; set; }
66
67        /// <summary>
68        /// Pidetäänkö lokia ohjelman tekemisistä.
69        /// </summary>
70        public static bool KeepLog { get; set; }
71
72        /// <summary>
73        /// Oletusasetukset.
74        /// </summary>
75        public static readonly OptionsFile DefaultSettings = new OptionsFile("", null, null, true, false, false, false, false, false, false, true);
76
77        public static void Initialize()
78        {
79            AddMessage("Initializing...");
80            LogWriter = new StreamWriter(LogFilePath, true);
81            OptionsFile ops = LoadSettings();
82            if (ops.LoadSettingsAutomatically)
83            {
84                UseSettings(ops);
85                AddMessage("Loaded previous settings.");
86            }
87            else UseSettings(ops, true);
88            if (ops.KeepLog)
89            {
90                AddMessage("Logging ON, keeping log in file: log.txt.");
91            }
92            else AddMessage("Logging OFF.");
93            if (StartWatchingAutomatically)
94            {
95                AddMessage("Starting to watch automatically...");
96                Start();
97                MainWindow.StartButton.IsEnabled = false;
98                MainWindow.StopButton.IsEnabled = true;
99                MainWindow.ResetButton.IsEnabled = false;
100                MainWindow.Directory.IsEnabled = false;
101                MainWindow.Extensions.IsEnabled = false;
102                MainWindow.SubfolderBox.IsEnabled = false;
103                MainWindow.CreationBox.IsEnabled = false;
104                MainWindow.DeletionBox.IsEnabled = false;
105                MainWindow.ChangeBox.IsEnabled = false;
106                MainWindow.RenameBox.IsEnabled = false;
107                MainWindow.LoadButton.IsEnabled = false;
108                MainWindow.DirectoryBrowseButton.IsEnabled = false;
109            }
110        }
111
112        /// <summary>
113        /// Aloitetaan tarkkailuprosessi.
114        /// </summary>
115        public static bool Start()
116        {
117            AddMessage("Verifying inputs...");
118
119            if (SelectedFolder == "" || SelectedFolder == MainWindow.DIRECTORY_DEFAULT_TEXT)
120            {
121                AddMessage("The folder path is empty!");
122                return false;
123            }
124
125            try
126            {
127                FileInfo f = new FileInfo(SelectedFolder); // tarkistetaan kansion polun oikeellisuus
128            }
129            catch (ArgumentException)
130            {
131                AddMessage("The folder path is invalid!");
132                return false;
133            }
134
135            if (SelectedFileExtensions == null)
136            {
137                AddMessage("No file extensions specified!");
138                return false;
139            }
140
141            if (SelectedFileExtensions.Length == 0)
142            {
143                AddMessage("No file extensions specified!");
144                return false;
145            }
146
147            if (ExecutablePrograms == null)
148            {
149                AddMessage("No executable programs selected!");
150                return false;
151            }
152
153            WatchCreation = (bool)MainWindow.CreationBox.IsChecked;
154            WatchDeletion = (bool)MainWindow.DeletionBox.IsChecked;
155            WatchChanges = (bool)MainWindow.ChangeBox.IsChecked;
156            WatchRenaming = (bool)MainWindow.RenameBox.IsChecked;
157
158            if (WatchChanges == false && WatchCreation == false && WatchDeletion == false && WatchRenaming == false)
159            {
160                AddMessage("No events selected!");
161                return false;
162            }
163
164            AddMessage("Inputs valid. Starting to watch.");
165
166            StartWatching();
167            return true;
168        }
169
170        /// <summary>
171        /// Aloitetaan tiedosten muutosten tarkkailu valitussa kansiossa.
172        /// </summary>
173        [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
174        public static void StartWatching()
175        {
176            watchers = new List<FileSystemWatcher>();
177
178            foreach (String extension in SelectedFileExtensions)
179            {
180                FileSystemWatcher watcher = new FileSystemWatcher();
181                var searchPath = SelectedFolder;
182                watcher.Path = searchPath;
183                watcher.IncludeSubdirectories = WatchSubdirectories;
184                watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
185                   | NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.Attributes | NotifyFilters.Size | NotifyFilters.Security | NotifyFilters.CreationTime;
186                watcher.Filter = extension;
187
188                if (WatchCreation) watcher.Created += delegate(object source, FileSystemEventArgs e) { OnChanged(source, e); };
189                if (WatchDeletion) watcher.Deleted += delegate(object source, FileSystemEventArgs e) { OnChanged(source, e); };
190                if (WatchChanges) watcher.Changed += delegate(object source, FileSystemEventArgs e) { OnChanged(source, e); };
191                if (WatchRenaming) watcher.Renamed += delegate(object source, RenamedEventArgs e) { OnChanged(source, e); };
192
193                watcher.EnableRaisingEvents = true;
194                watchers.Add(watcher);
195            }
196            if (WatchSubdirectories) AddMessage("Watching folder " + SelectedFolder + " and its subfolders for changes.");
197            else AddMessage("Watching folder " + SelectedFolder + " for changes.");
198
199            AddMessage("Watched file types: ", false);
200            foreach (String ext in SelectedFileExtensions)
201            {
202                AddMessage(ext, false);
203            }
204
205            AddMessage("\nWatched events: ");
206            if (WatchCreation) AddMessage("File creation");
207            if (WatchDeletion) AddMessage("File deletion");
208            if (WatchChanges) AddMessage("Changes in files");
209            if (WatchRenaming) AddMessage("Renaming files");
210        }
211
212        /// <summary>
213        /// Lopetetaan muutosten tarkkailu.
214        /// </summary>
215        public static void StopWatching()
216        {
217            if (watchers != null)
218            {
219                for (int i = 0; i < watchers.Count; i++)
220                {
221                    watchers[i].EnableRaisingEvents = false;
222                    watchers[i].Dispose();
223                }
224                watchers.Clear();
225                jobs.Clear();
226                AddMessage("Stopped watching.");
227            }
228            AddMessage("-----------------------------------");
229            AddMessage();
230        }
231
232        /// <summary>
233        /// Näytetään tekstiä viesti-ikkunassa.
234        /// </summary>
235        /// <param name="message">Näytettävä viesti.</param>
236        public static void AddMessage(String message, bool addNewLine = true)
237        {
238            if (addNewLine) MainWindow.MessageWindow.Content += message + "\n";
239            else MainWindow.MessageWindow.Content += message;
240            MainWindow.MessageWindow.ScrollToBottom();
241            if (KeepLog) WriteToLog(message);
242        }
243
244        /// <summary>
245        /// Lisätään rivinvaihto viesti-ikkunaan.
246        /// </summary>
247        public static void AddMessage()
248        {
249            MainWindow.MessageWindow.Content += "\n";
250            MainWindow.MessageWindow.ScrollToBottom();
251            if (KeepLog) WriteToLog();
252        }
253
254        /// <summary>
255        /// Lisätään viesti-ikkunaan viesti toisesta säikeestä.
256        /// </summary>
257        /// <param name="message">Viesti</param>
258        public static void AddMessageFromAnotherThread(String message, bool addNewLine = true)
259        {
260            MainWindow.UIDispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
261            {
262                if (addNewLine) MainWindow.MessageWindow.Content += message + "\n";
263                else MainWindow.MessageWindow.Content += message;
264                MainWindow.MessageWindow.ScrollToBottom();
265                if (KeepLog) WriteToLog(message);
266            }));
267        }
268
269        /// <summary>
270        /// Tyhjennetään viesti-ikkuna.
271        /// </summary>
272        public static void ClearMessages()
273        {
274            MainWindow.MessageWindow.Content = "";
275        }
276
277        /// <summary>
278        /// Ajetaan ohjelmat tiedoston muututtua.
279        /// </summary>
280        /// <param name="source">Jaa?</param>
281        /// <param name="e">Tiedoston polku.</param>
282        private static void OnChanged(object source, FileSystemEventArgs e)
283        {
284            String changedFile = e.FullPath;
285            FileInfo file = new FileInfo(changedFile);
286
287            if (jobs.Contains(changedFile))
288                return;
289
290            AddMessageFromAnotherThread("File \"" + GetName(changedFile) + "\" changed (" + e.ChangeType.ToString() + ")");
291            jobs.Add(changedFile);
292
293            /*// wait until file is not locked anymore
294            while (IsFileLocked(file))
295            {
296                AddMessageFromAnotherThread("File locked, waiting for it to be released before continuing.");
297                Thread.Sleep(5000);
298            }*/
299
300            int waitedTime = 0;
301            const int MAXIMUM_WAIT_TIME = 60000; // minuutti
302            // wait until file is not locked anymore
303            while (true)
304            {
305                bool? fileState = IsFileLocked(file);
306                if (fileState == false) break;
307                if (fileState == null)
308                {
309                    AddMessageFromAnotherThread("Aborting execution, file is missing."); // ehkä ajo ilman tiedostoa?
310                    return;
311                }
312                // jäljellä true, joten odotellaan tiedoston vapautumista
313
314                if (waitedTime > MAXIMUM_WAIT_TIME)
315                {
316                    AddMessageFromAnotherThread("Aborting execution, file has been locked for too long or something else has gone wrong.");
317                    return;
318                }
319
320                AddMessageFromAnotherThread("File locked, waiting for it to be released before continuing.");
321                Thread.Sleep(5000);
322                waitedTime += 5000;
323            }
324
325
326            for (int i = 0; i < ExecutablePrograms.Count; i++)
327            {
328                ProcessStartInfo psi = new ProcessStartInfo();
329                psi.FileName = Environment.ExpandEnvironmentVariables(ExecutablePrograms[i].Path);
330                psi.Arguments = Environment.ExpandEnvironmentVariables(GenerateArguments(ExecutablePrograms[i], changedFile));
331
332                try
333                {
334                    AddMessageFromAnotherThread("Executing program \"" + ExecutablePrograms[i].GetName() + "\".");
335                    Process process = Process.Start(psi);
336                    process.EnableRaisingEvents = true; // muuten ei tule Exitediä
337                    process.Exited += delegate {
338                        jobs.Remove(changedFile);
339                        /*if (ExecutablePrograms[i].SubPrograms != null && ExecutablePrograms[i].SubPrograms.Count != 0)
340                        {
341                            RunSubProgram(ExecutablePrograms[i], changedFile);
342                        }*/
343                    };
344                }
345                catch (Win32Exception ex)
346                {
347                    AddMessageFromAnotherThread("Execution failed, Win32 error code: " + (ex.ErrorCode + Math.Pow(2, 31)));
348                    jobs.Remove(changedFile);
349                }
350            }
351        }
352
353        /// <summary>
354        /// Ajetaan kaikki ohjelman aliohjelmat siten, että edellisen päätyttyä seuraava käynnistyy.
355        /// </summary>
356        /// <param name="parent">Ohjelma, jonka aliohjelmat ajetaan.</param>
357        /// <param name="file">Päivittynyt tiedosto.</param>
358        /*private static void RunSubProgram(Executable parent, string file)
359        {
360            if (parent.CurrentSubProgram >= parent.SubPrograms.Count) return;
361
362            ProcessStartInfo psi = new ProcessStartInfo();
363            psi.FileName = Environment.ExpandEnvironmentVariables(parent.SubPrograms[parent.CurrentSubProgram].Path);
364            psi.Arguments = Environment.ExpandEnvironmentVariables(GenerateArguments(parent.SubPrograms[parent.CurrentSubProgram], file));
365
366            try
367            {
368                AddMessageFromAnotherThread("Executing subprogram \"" + parent.SubPrograms[parent.CurrentSubProgram].GetName() + "\".");
369                Process process = Process.Start(psi);
370                process.EnableRaisingEvents = true; // muuten ei tule Exitediä
371                process.Exited += delegate
372                {
373                    parent.CurrentSubProgram++;
374                    RunSubProgram(parent, file);
375                };
376            }
377            catch (Win32Exception ex)
378            {
379                AddMessageFromAnotherThread("Execution failed, Win32 error code: " + ex.ErrorCode);
380            }
381        }*/
382
383        /// <summary>
384        /// Muutetaan parametrit sopivaan muotoon asetusten mukaan.
385        /// </summary>
386        /// <param name="ex">Parametrit ja niiden asetukset.</param>
387        /// <param name="file">Tiedosto.</param>
388        /// <returns>Parametrit.</returns>
389        public static String GenerateArguments(Executable ex, string file)
390        {
391            String args = "";
392
393            if (ex.HasFileAsFirstParameter)
394            {
395                args = file + " " + ex.Arguments;
396            }
397            else
398            {
399                int filePos = ex.Arguments.IndexOf("%1");
400                args = ex.Arguments.Substring(0, filePos); // ennen %1
401                args += file; // tiedostopolku
402                args += ex.Arguments.Substring(filePos + 2); // %1 jälkeen
403            }
404
405            return args;
406        }
407
408        /// <summary>
409        /// Palauttaa ohjelman nimen ilman hakemistopolkua.
410        /// </summary>
411        /// <param name="path">Koko hakemistopolku.</param>
412        /// <returns>Ohjelman nimi.</returns>
413        public static String GetName(string path)
414        {
415            int kauttaviivanPaikka = 0;
416            bool loytyiko = false;
417            String nimi = "";
418            for (int i = path.Length - 1; i >= 0; i--)
419            {
420                if (path[i] == '\\')
421                {
422                    kauttaviivanPaikka = i;
423                    loytyiko = true;
424                    break;
425                }
426            }
427            if (loytyiko)
428                nimi = path.Substring(kauttaviivanPaikka + 1);
429            else
430                nimi = path;
431            return nimi;
432        }
433
434
435        /// <summary>
436        /// Checks if the file is locked because of
437        /// still being written to
438        /// or being processed by another thread
439        /// or does not exist (has already been processed)
440        /// </summary>
441        /// <param name="file">File</param>
442        /// <returns>Is the file locked</returns>
443        private static bool? IsFileLocked(FileInfo file)
444        {
445            FileStream stream = null;
446
447            try
448            {
449                stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
450                //stream = file.Open(FileMode.Open, FileAccess.Read);
451            }
452            catch (FileNotFoundException e)
453            {
454                return null;
455            }
456            catch (IOException e)
457            {
458                //the file is unavailable because it is:
459                // * still being written to or
460                // * being processed by another thread or
461                // * does not exist (has already been processed)
462                return true;
463            }
464            finally
465            {
466                if (stream != null)
467                    stream.Close();
468            }
469
470            //file is not locked
471            return false;
472        }
473
474        public static void UpdateExecutableList()
475        {
476            MainWindow.ProgramWindow.Items.Clear();
477            for (int i = 0; i < ExecutablePrograms.Count; i++)
478            {
479                if (ExecutablePrograms[i].HasFileAsFirstParameter)
480                    MainWindow.ProgramWindow.Items.Add(ExecutablePrograms[i].GetName() + " [file] " + ExecutablePrograms[i].Arguments);
481                else
482                    MainWindow.ProgramWindow.Items.Add(ExecutablePrograms[i].GetName() + " " + ExecutablePrograms[i].Arguments.Replace("%1", "[file]"));
483
484                if (ExecutablePrograms[i].SubPrograms == null) continue;
485                for (int j = 0; j < ExecutablePrograms[i].SubPrograms.Count; j++)
486                {
487                    if (ExecutablePrograms[i].SubPrograms[j].HasFileAsFirstParameter)
488                        MainWindow.ProgramWindow.Items.Add("  " + (j + 1) + ". " + ExecutablePrograms[i].SubPrograms[j].GetName() + " [file] " + ExecutablePrograms[i].SubPrograms[j].Arguments);
489                    else
490                        MainWindow.ProgramWindow.Items.Add("  " + (j + 1) + ". " + ExecutablePrograms[i].SubPrograms[j].GetName() + " " + ExecutablePrograms[i].SubPrograms[j].Arguments.Replace("%1", "[file]"));
491                }
492            }
493        }
494
495        public static void UpdateEvents()
496        {
497            WatchCreation = (bool)MainWindow.CreationBox.IsChecked;
498            WatchDeletion = (bool)MainWindow.DeletionBox.IsChecked;
499            WatchChanges = (bool)MainWindow.ChangeBox.IsChecked;
500            WatchRenaming = (bool)MainWindow.RenameBox.IsChecked;
501        }
502
503        /// <summary>
504        /// Tallennetaan käyttäjän tämänhetkiset asetukset.
505        /// </summary>
506        public static void SaveSettings()
507        {
508            UpdateEvents();
509            OptionsFile ops = new OptionsFile(SelectedFolder, SelectedFileExtensions, ExecutablePrograms, WatchCreation, WatchDeletion, WatchChanges, WatchRenaming, LoadSettingsAutomatically, SaveSettingsAutomatically, StartWatchingAutomatically, KeepLog);
510
511            FileStream stream = new FileStream(OptionsFilePath, FileMode.Create);
512
513            BinaryFormatter formatter = new BinaryFormatter();
514            try
515            {
516                formatter.Serialize(stream, ops);
517            }
518            catch (SerializationException se)
519            {
520                AddMessage("Saving settings failed: " + se.Message);
521            }
522            finally
523            {
524                stream.Close();
525            }
526        }
527
528        /// <summary>
529        /// Tallennetaan käyttäjän tämänhetkiset asetukset.
530        /// </summary>
531        public static void SaveSettings(OptionsFile options)
532        {
533            if (options == null) return;
534
535            UpdateEvents();
536            FileStream stream = new FileStream(OptionsFilePath, FileMode.Create);
537
538            BinaryFormatter formatter = new BinaryFormatter();
539            try
540            {
541                formatter.Serialize(stream, options);
542            }
543            catch (SerializationException se)
544            {
545                AddMessage("Saving settings failed: " + se.Message);
546            }
547            finally
548            {
549                stream.Close();
550            }
551        }
552
553
554        /// <summary>
555        /// Ladataan asetukset tiedostosta.
556        /// </summary>
557        public static OptionsFile LoadSettings()
558        {
559            OptionsFile ops = new OptionsFile();
560            FileStream stream;
561            BinaryFormatter formatter = new BinaryFormatter();
562            try
563            {
564                using (stream = new FileStream(OptionsFilePath, FileMode.Open))
565                {
566                    ops = (OptionsFile)formatter.Deserialize(stream);
567                }
568            }
569            catch (SerializationException se)
570            {
571                AddMessage("Loading settings failed: " + se.Message);
572            }
573            catch (FileNotFoundException)
574            {
575                AddMessage("Options file missing, reverting to defaults.");
576                SaveSettings(DefaultSettings);
577                return DefaultSettings;
578            }
579            return ops;
580        }
581
582        public static void UseSettings(OptionsFile settings, bool useOnlyOptionsScreenSettings = false)
583        {
584            if (!useOnlyOptionsScreenSettings)
585            {
586                SelectedFolder = settings.SelectedFolder;
587                SelectedFileExtensions = settings.SelectedFileExtensions;
588                ExecutablePrograms = settings.ExecutablePrograms;
589                WatchSubdirectories = settings.WatchSubdirectories;
590
591                WatchCreation = settings.WatchCreation;
592                WatchDeletion = settings.WatchDeletion;
593                WatchChanges = settings.WatchChanges;
594                WatchRenaming = settings.WatchRenaming;
595            }
596
597            LoadSettingsAutomatically = settings.LoadSettingsAutomatically;
598            SaveSettingsAutomatically = settings.SaveSettingsAutomatically;
599            StartWatchingAutomatically = settings.StartWatchingAutomatically;
600
601            KeepLog = settings.KeepLog;
602
603            if (!useOnlyOptionsScreenSettings)
604                MainWindow.UpdateVisuals();
605        }
606
607        /// <summary>
608        /// Kirjoitetaan tekstiä lokitiedostoon.
609        /// </summary>
610        /// <param name="message">Kirjoitettava teksti.</param>
611        public static void WriteToLog(string message)
612        {
613            LogWriter.WriteLine(DateTime.Now + " " + message);
614            LogWriter.Flush();
615        }
616
617        /// <summary>
618        /// Lisätään rivinvaihto lokiin.
619        /// </summary>
620        public static void WriteToLog()
621        {
622            LogWriter.WriteLine();
623            LogWriter.Flush();
624        }
625    }
626}
Note: See TracBrowser for help on using the repository browser.