From 572f409176ecbda14d69b9366209af7d04914e3e Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Fri, 17 Jan 2020 10:01:06 -0800 Subject: [PATCH] Implemented Queues and folder monitors. --- BodyshopUploader/BodyshopUploader.csproj | 10 +- BodyshopUploader/Models/DTO_Base.cs | 33 ++++++ BodyshopUploader/Models/DTO_QueueItem.cs | 23 ++++ .../Properties/Resources.Designer.cs | 18 ++++ BodyshopUploader/Properties/Resources.resx | 6 ++ BodyshopUploader/Utils/CIECAMonitor.cs | 85 +++++++++++++++ BodyshopUploader/Utils/JobProcessingQueue.cs | 101 ++++++++++++++++++ BodyshopUploader/Utils/TrayIcon.cs | 6 +- .../ViewModels/MainViewModel.commands.cs | 32 ++++++ BodyshopUploader/ViewModels/MainViewModel.cs | 64 +++++++---- .../ViewModels/MainViewModel.props.cs | 7 ++ BodyshopUploader/Views/Main.xaml | 38 ++++--- BodyshopUploader/Views/Main.xaml.cs | 6 ++ 13 files changed, 388 insertions(+), 41 deletions(-) create mode 100644 BodyshopUploader/Models/DTO_Base.cs create mode 100644 BodyshopUploader/Models/DTO_QueueItem.cs create mode 100644 BodyshopUploader/Utils/CIECAMonitor.cs create mode 100644 BodyshopUploader/Utils/JobProcessingQueue.cs diff --git a/BodyshopUploader/BodyshopUploader.csproj b/BodyshopUploader/BodyshopUploader.csproj index e1ee7c2..a34dea5 100644 --- a/BodyshopUploader/BodyshopUploader.csproj +++ b/BodyshopUploader/BodyshopUploader.csproj @@ -248,6 +248,8 @@ MSBuild:Compile Designer + + Resources.es-MX.resx True @@ -262,7 +264,11 @@ + + Component + + @@ -334,9 +340,7 @@ - - - + False diff --git a/BodyshopUploader/Models/DTO_Base.cs b/BodyshopUploader/Models/DTO_Base.cs new file mode 100644 index 0000000..06dc769 --- /dev/null +++ b/BodyshopUploader/Models/DTO_Base.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace BodyshopUploader.Models +{ + public abstract class DTO_Base : INotifyPropertyChanged + { + + public event PropertyChangedEventHandler PropertyChanged; + protected bool SetProperty(ref T storage, T value, [CallerMemberName] String propertyName = null) + { + if (object.Equals(storage, value)) return false; + + storage = value; + this.OnPropertyChanged(propertyName); + return true; + } + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + var eventHandler = this.PropertyChanged; + if (eventHandler != null) + { + eventHandler(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} + diff --git a/BodyshopUploader/Models/DTO_QueueItem.cs b/BodyshopUploader/Models/DTO_QueueItem.cs new file mode 100644 index 0000000..abf959e --- /dev/null +++ b/BodyshopUploader/Models/DTO_QueueItem.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BodyshopUploader.Models +{ + public class DTO_QueueItem : DTO_Base + { + public DTO_QueueItem() + { + + } + + private string _filePath; + public string FilePath + { + get { return _filePath; } + set { SetProperty(ref _filePath, value); } + } + } +} diff --git a/BodyshopUploader/Properties/Resources.Designer.cs b/BodyshopUploader/Properties/Resources.Designer.cs index 8b7d30a..ab23683 100644 --- a/BodyshopUploader/Properties/Resources.Designer.cs +++ b/BodyshopUploader/Properties/Resources.Designer.cs @@ -114,6 +114,24 @@ namespace BodyshopUploader.Properties { } } + /// + /// Looks up a localized string similar to Start All Monitors. + /// + public static string Label_StartAllMonitors { + get { + return ResourceManager.GetString("Label_StartAllMonitors", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Stop All Monitors. + /// + public static string Label_StopAllMonitors { + get { + return ResourceManager.GetString("Label_StopAllMonitors", resourceCulture); + } + } + /// /// Looks up a localized string similar to Login. /// diff --git a/BodyshopUploader/Properties/Resources.resx b/BodyshopUploader/Properties/Resources.resx index 993bb3e..13594ef 100644 --- a/BodyshopUploader/Properties/Resources.resx +++ b/BodyshopUploader/Properties/Resources.resx @@ -135,6 +135,12 @@ Add Path + + Start All Monitors + + + Stop All Monitors + Login diff --git a/BodyshopUploader/Utils/CIECAMonitor.cs b/BodyshopUploader/Utils/CIECAMonitor.cs new file mode 100644 index 0000000..1940f35 --- /dev/null +++ b/BodyshopUploader/Utils/CIECAMonitor.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BodyshopUploader.Models; + +namespace BodyshopUploader.Utils +{ + public class CIECAMonitor : FileSystemWatcher + { + private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + + public CIECAMonitor() + { + Init(); + } + + public CIECAMonitor(String inDirectoryPath) + : base(inDirectoryPath) + { + Init(); + } + + public CIECAMonitor(String inDirectoryPath, string inFilter) + : base(inDirectoryPath, inFilter) + { + Init(); + } + + + private void Init() + { + IncludeSubdirectories = false; + // Eliminate duplicates when timestamp doesn't change + Filter = "*.env"; + NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite; + //NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size; // The default also has NotifyFilters.LastWrite + EnableRaisingEvents = true; + Created += Watcher_Created; + Changed += Watcher_Changed; + //Deleted += Watcher_Deleted; + //Renamed += Watcher_Renamed; + Error += Wathcer_Error; + logger.Debug("CIECA Folder watcher started for path: {0}", Path); + } + + private void Wathcer_Error(object sender, ErrorEventArgs e) + { + logger.Error("CIECA monitor encountered an error. Trying to restart. {0}", e.ToString()); + ((FileSystemWatcher)sender).EnableRaisingEvents = true; + } + + public void Watcher_Created(object source, FileSystemEventArgs inArgs) + { + logger.Trace("New CIECA fileset detected | {0}", inArgs.FullPath, inArgs.ChangeType); + JobProcessingQueue.Enqueue(new DTO_QueueItem() { FilePath = inArgs.FullPath }); + } + + public void Watcher_Changed(object sender, FileSystemEventArgs inArgs) + { + logger.Trace("Updated CIECA fileset detected | {0}", inArgs.FullPath); + JobProcessingQueue.Enqueue(new DTO_QueueItem() { FilePath = inArgs.FullPath }); + } + public void Watcher_Deleted(object sender, FileSystemEventArgs inArgs) + { + } + + public void Watcher_Renamed(object sender, RenamedEventArgs inArgs) + { + } + + private void InitializeComponent() + { + ((System.ComponentModel.ISupportInitialize)(this)).BeginInit(); + // + // FolderMonitor + // + this.IncludeSubdirectories = false; + ((System.ComponentModel.ISupportInitialize)(this)).EndInit(); + + } + } +} diff --git a/BodyshopUploader/Utils/JobProcessingQueue.cs b/BodyshopUploader/Utils/JobProcessingQueue.cs new file mode 100644 index 0000000..14cb41f --- /dev/null +++ b/BodyshopUploader/Utils/JobProcessingQueue.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using BodyshopUploader.Utils; +using BodyshopUploader.Models; + +namespace BodyshopUploader.Utils +{ + public static class JobProcessingQueue + { + private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + private static Queue _jobs = new Queue(); + private static bool _delegateQueuedOrRunning = false; + + public static void Enqueue(DTO_QueueItem item) + { + lock (_jobs) + { + //See if the job is already in the queue based on the file name. If it is, ignore it. + if (!(_jobs.Any(_ => _.FilePath == item.FilePath))) + { + logger.Debug("Adding job to the queue. | {0}", item.FilePath); + _jobs.Enqueue(item); + if (!_delegateQueuedOrRunning) + { + _delegateQueuedOrRunning = true; + ThreadPool.UnsafeQueueUserWorkItem(ProcessQueuedItems, null); + } + } + else + { + logger.Debug("Ignoring job {0}, it's already in the queue.", item.FilePath); + } + } + } + + private static void ProcessQueuedItems(object ignored) + { + while (true) + { + DTO_QueueItem item; + lock (_jobs) + { + if (_jobs.Count == 0) + { + _delegateQueuedOrRunning = false; + break; + } + //Only peek at the first item in the queue so that it does not get added again while it is still getting processed. + item = _jobs.Peek(); + } + + try + { + Thread.Sleep(1000);//Allow a small amount of time to pass before processing the queue item so that any writes can finish. + + DecodeQueueItemJob(item); + + UpsertQueueItem(item); + } + catch + { + ThreadPool.UnsafeQueueUserWorkItem(ProcessQueuedItems, null); + throw; + } + } + } + + private static void DecodeQueueItemJob(DTO_QueueItem item) + { + //Process the job. + logger.Info("Should process the job here. {0}", item.FilePath); + } + + private static void UpsertQueueItem(DTO_QueueItem item) + { + //Save the job to the DB. + logger.Info("Should upsert the job graphqlly here. {0}", item.FilePath); + + _jobs.Dequeue(); + } + + private static void MoveFile(string FullPath) + { + try + { + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(FullPath) + @"\Processed\"); + File.Move(FullPath, System.IO.Path.GetDirectoryName(FullPath) + @"\Processed\" + System.IO.Path.GetFileNameWithoutExtension(FullPath) + DateTime.Now.Ticks.ToString() + ".bak"); + + } + catch (Exception Ex) + { + logger.Error(Ex, "Can't move file {0} - it's gone!", FullPath); + } + } + } +} diff --git a/BodyshopUploader/Utils/TrayIcon.cs b/BodyshopUploader/Utils/TrayIcon.cs index c2a8964..9f3c8de 100644 --- a/BodyshopUploader/Utils/TrayIcon.cs +++ b/BodyshopUploader/Utils/TrayIcon.cs @@ -11,8 +11,10 @@ namespace BodyshopUploader.Utils { public void Execute(object parameter) { - new Views.Main().Show(); - } + var m = App.Current.Windows.OfType().FirstOrDefault(); + m?.Show(); + m?.Focus(); + } public bool CanExecute(object parameter) { diff --git a/BodyshopUploader/ViewModels/MainViewModel.commands.cs b/BodyshopUploader/ViewModels/MainViewModel.commands.cs index 01fa1e7..9598ebe 100644 --- a/BodyshopUploader/ViewModels/MainViewModel.commands.cs +++ b/BodyshopUploader/ViewModels/MainViewModel.commands.cs @@ -70,5 +70,37 @@ namespace BodyshopUploader.ViewModels return _removeMonitoringPathCommand; } } + + private ICommand _startFolderMonitorsCommand; + public ICommand StartFolderMonitorsCommand + { + get + { + if (_startFolderMonitorsCommand == null) + { + _startFolderMonitorsCommand = new RelayCommand( + p => MonitoringPaths.Count > 0, + p => StartAllFolderMonitors() + ); + } + return _startFolderMonitorsCommand; + } + } + + private ICommand _stopFolderMonitorsCommand; + public ICommand StopFolderMonitorsCommand + { + get + { + if (_stopFolderMonitorsCommand == null) + { + _stopFolderMonitorsCommand = new RelayCommand( + p => FolderMonitors.Count > 0, + p => StopAllFolderMonitors() + ); + } + return _stopFolderMonitorsCommand; + } + } } } diff --git a/BodyshopUploader/ViewModels/MainViewModel.cs b/BodyshopUploader/ViewModels/MainViewModel.cs index a7b1e89..88b87c7 100644 --- a/BodyshopUploader/ViewModels/MainViewModel.cs +++ b/BodyshopUploader/ViewModels/MainViewModel.cs @@ -48,31 +48,51 @@ namespace BodyshopUploader.ViewModels Properties.Settings.Default.Save(); } + public void StartAllFolderMonitors() + { + if (FolderMonitors.Count > 0) + foreach (var m in FolderMonitors) + { + m.Dispose(); + } + + foreach (var p in MonitoringPaths) + { + //Ensure the directory exists, then start monitoring for CIECA files. + System.IO.Directory.CreateDirectory(p); + FolderMonitors.Add(new Utils.CIECAMonitor(p)); + } + } + + public void StopAllFolderMonitors() + { + if (FolderMonitors.Count > 0) + foreach (var m in FolderMonitors) + { + m.Dispose(); + } + } + public async Task TestGql() { - if (MonitoringPaths == null) MonitoringPaths = new ObservableCollection(); - MonitoringPaths.Add(@"C:\IMEX"); - Properties.Settings.Default.MonitoringPaths = MonitoringPaths; - Properties.Settings.Default.Save(); + var r = new GraphQLRequest + { + Query = @" + query { + jobs { + id + est_number + ro_number + } + } + " + }; - //var r = new GraphQLRequest - //{ - // Query = @" - // query { - // jobs { - // id - // est_number - // ro_number - // } - // } - // " - //}; - - //using (var g = Utils.GraphQL.CreateGQLClient()) - //{ - // var graphQLResponse = await g.PostAsync(r); - // logger.Info(graphQLResponse.Data.jobs); - //} + using (var g = Utils.GraphQL.CreateGQLClient()) + { + var graphQLResponse = await g.PostAsync(r); + logger.Info(graphQLResponse.Data.jobs); + } } } } diff --git a/BodyshopUploader/ViewModels/MainViewModel.props.cs b/BodyshopUploader/ViewModels/MainViewModel.props.cs index a915376..363a33d 100644 --- a/BodyshopUploader/ViewModels/MainViewModel.props.cs +++ b/BodyshopUploader/ViewModels/MainViewModel.props.cs @@ -16,6 +16,13 @@ namespace BodyshopUploader.ViewModels set { SetProperty(ref _progress, value); } } + private ObservableCollection _folderMonitors = new ObservableCollection() ; + public ObservableCollection FolderMonitors + { + get { return _folderMonitors; } + set { SetProperty(ref _folderMonitors, value); } + } + private ObservableCollection _monitoringPaths = Properties.Settings.Default.MonitoringPaths ; public ObservableCollection MonitoringPaths { diff --git a/BodyshopUploader/Views/Main.xaml b/BodyshopUploader/Views/Main.xaml index 15dcfd5..9dcd139 100644 --- a/BodyshopUploader/Views/Main.xaml +++ b/BodyshopUploader/Views/Main.xaml @@ -20,15 +20,17 @@ Background="{DynamicResource MaterialDesignPaper}" FontFamily="{DynamicResource MaterialDesignFont}" Loaded="Window_Loaded" - xmlns:util="clr-namespace:BodyshopUploader.Utils"> + xmlns:util="clr-namespace:BodyshopUploader.Utils" + Closing="Window_Closing"> - + - - + - + - + + +