using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.Composition; using System.Threading.Tasks; using DTS.Common.Events; using DTS.Common.Utils; using DTS.Common.Classes.Groups; using System.Linq; using System.Text; using System.Windows; using DTS.Common.Enums.Groups; using DTS.Common.Interface.Groups; using GroupImport.Resources; using DTS.Common.Storage; using System.Windows.Media; using Prism.Regions; using DTS.Common.Interactivity; using Unity; using Prism.Events; using Prism.Commands; // ReSharper disable CheckNamespace // ReSharper disable MemberCanBePrivate.Global // ReSharper disable InconsistentNaming namespace GroupImport { /// /// this class handles Group/TestObject import functionality /// [Export(typeof(IGroupImportOptionsView))] [Export(typeof(IGroupImportPreviewView))] [Export(typeof(IGroupImportImportView))] [PartCreationPolicy(CreationPolicy.Shared)] public class GroupImportViewModel : IGroupImportViewModel { #region views /// /// the import view /// public IGroupImportOptionsView ImportOptionsView { get; set; } /// /// the preview view /// public IGroupImportPreviewView ImportPreviewView { get; set; } /// /// the import view /// public IGroupImportImportView ImportView { get; set; } #endregion private IEventAggregator _eventAggregator { get; set; } private IRegionManager _regionManager; private IUnityContainer UnityContainer { get; set; } public InteractionRequest NotificationRequest { get; private set; } public InteractionRequest ConfirmationRequest { get; private set; } /// /// Creates a new instance of the TechnologyDomainEditViewModel. /// /// /// /// The logical placeholder defined within the application's UI (in the shell or within views) into which views are displayed. /// The EventAggregator which allows different components to publish/subscribe to events without being coupled to each other. /// The unityContainer. /// public GroupImportViewModel(IGroupImportOptionsView optionsView, IGroupImportPreviewView previewView, IGroupImportImportView importView, IRegionManager regionManager, IEventAggregator eventAggregator, IUnityContainer unityContainer) { ImportView = importView; ImportView.DataContext = this; ImportOptionsView = optionsView; ImportOptionsView.DataContext = this; ImportPreviewView = previewView; ImportPreviewView.DataContext = this; NotificationRequest = new InteractionRequest(); ConfirmationRequest = new InteractionRequest(); _eventAggregator = eventAggregator; UnityContainer = unityContainer; _regionManager = regionManager; _eventAggregator.GetEvent().Subscribe(OnRaiseNotification); _eventAggregator.GetEvent() .Subscribe(OnBusyIndicatorNotification, ThreadOption.PublisherThread, true); } #region Methods /// /// reads .grp files, parsing out groups and channels /// public void ParseSourceFiles(string userTags) { var groups = new List(); foreach (var file in SourceFiles) { var group = new GroupGRPImportGroup { SourceFile = file }; try { var lines = System.IO.File.ReadAllLines(file); var fi = new System.IO.FileInfo(file); var groupName = fi.Name.Replace(fi.Extension, ""); if (groupName.EndsWith(".")) { groupName = groupName.Substring(0, groupName.Length - 1); } group.GroupName = groupName; group.GroupTags = userTags; group.ImportingUserTags = userTags; if (lines.Any()) { group.GroupErrors = new GroupGRPImportError[0]; var channels = new List(); for (var i = 0; i < lines.Length; i++) { var line = lines[i]; if (string.IsNullOrWhiteSpace(line)) { continue; } var fields = Parse(line); var sensorSerialNumber = fields[0]; var channel = new GroupGRPImportChannel { SensorSerialNumber = sensorSerialNumber, ParentGroup = group }; if (null != CheckSensorExists && !CheckSensorExists.Invoke(sensorSerialNumber) && //FB13753 .grp import should allow empty sensor !string.Empty.Equals(sensorSerialNumber)) { channel.Error = new GroupGRPImportError { ErrorCode = GroupGRPImportError.Errors.SensorNotFound, ExtraInfo = StringResources.Preview_SensorNotFound, File = file, Line = i }; } if (5 == fields.Length) { channel.DisplayName = fields[1]; channel.ISOCode = fields[2]; switch (fields[3].ToLower()) { case "no": channel.Invert = false; break; case "yes": channel.Invert = true; break; default: if (!bool.TryParse(fields[3], out var invert)) { channel.Error = new GroupGRPImportError { ErrorCode = GroupGRPImportError.Errors.InvalidInvertInput, ExtraInfo = StringResources.Preview_InvalidInvert, File = file, Line = i }; continue; } channel.Invert = invert; break; } double capacity; if (!double.TryParse(fields[4], out capacity)) { channel.Error = new GroupGRPImportError { ErrorCode = GroupGRPImportError.Errors.InvalidFullScaleInput, ExtraInfo = StringResources.Preview_InvalidFullScaleCapacity }; continue; } channel.FullScale = capacity; } else { channel.Error = new GroupGRPImportError { ErrorCode = GroupGRPImportError.Errors.InvalidSensorInput, ExtraInfo = StringResources.Preview_InvalidNumberOfFields, File = file, Line = i }; } channels.Add(channel); } group.Channels = channels.ToArray(); if (!channels.Any()) { group.GroupErrors = new[] { new GroupGRPImportError { ErrorCode = GroupGRPImportError.Errors.FileEmpty, ExtraInfo = StringResources.Preview_EmptyFile, File = file, Line = 0 } }; } } else { group.GroupErrors = new[] { new GroupGRPImportError { File = file, ErrorCode = GroupGRPImportError.Errors.FileEmpty, Line = 0, ExtraInfo = StringResources.Preview_EmptyFile } }; } } catch (Exception ex) { group.GroupErrors = new[] { new GroupGRPImportError { File = file, Line = 0, ExtraInfo = ex.Message, ErrorCode = GroupGRPImportError.Errors.FileEmpty } }; LogMessage(ex); } groups.Add(group); } Groups = groups.ToArray(); ProcessChannels(); } /// /// starts the process of creating and commiting groups and channels on a background thread /// public void Import() { System.Threading.ThreadPool.QueueUserWorkItem(ImportFunc); } /// /// the actual thread writing groups and channels /// /// private void ImportFunc(object o) { var channelDefaults = DbOperations.GetChannelSettingDefaults(); ImportProgressValue = 0; ImportProgressBarVisibility = Visibility.Visible; ImportProgressColor = DTS.Common.BrushesAndColors.Brush_ApplicationStatus_Busy.Color; DisableUI?.Invoke(); var done = 0; var allChannels = Groups.Where(g => g.Included).SelectMany(g => g.Channels).Count(); var groups = new List(); foreach (var g in Groups) { if (!g.Included) { continue; } CreateGroup(g.GroupName, g.GroupTags); foreach (var ch in g.Channels) { done++; //ImportProgressText = string.Format(StringResources.Import_Importing, g.GroupName, ch.DisplayName); //ImportProgressValue = Convert.ToInt32(100D * done / allChannels); if (null != ch.Error) { switch (ch.Error.ErrorCode) { case GroupGRPImportError.Errors.FileEmpty: case GroupGRPImportError.Errors.InvalidSensorInput: case GroupGRPImportError.Errors.SensorNotFound: continue;//don't add } } double? capacity = null; if (null == ch.Error || ch.Error.ErrorCode != GroupGRPImportError.Errors.InvalidFullScaleInput) { capacity = ch.FullScale; } bool? invert = null; if (null == ch.Error || ch.Error.ErrorCode != GroupGRPImportError.Errors.InvalidInvertInput) { invert = ch.Invert; } AddChannel(g.GroupName, ch.DisplayName, ch.SensorSerialNumber, capacity, invert, ch.ISOCode, channelDefaults); } groups.Add(g.GroupName); //CommitGroup(g.GroupName); } CommitGroups(groups.ToArray()); //ImportProgressColor = DTS.Common.BrushesAndColors.Brush_ApplicationStatus_Complete.Color; //ImportProgressBarVisibility = Visibility.Collapsed; //EnableUI?.Invoke(); } public void SetStatus(string message, Color color) { ImportProgressColor = color; ImportProgressBarVisibility = Visibility.Collapsed; ImportProgressText = message; EnableUI?.Invoke(); } /// /// re-initializes the group view model /// right now we don't create a new group view model, we appear to reuse the existing one, so it has to be reset /// public void Reset() { SourceFiles = new string[0]; Channels = new GroupGRPImportChannel[0]; Groups = new GroupGRPImportGroup[0]; ImportProgressBarVisibility = Visibility.Collapsed; ImportProgressColor = DTS.Common.BrushesAndColors.Brush_ApplicationStatus_Idle.Color; ImportProgressText = string.Empty; ImportProgressValue = 0; } /// /// parses a single line in a GRP file /// this line may use () to escape values, like "this(,)is all one field" will only return one string /// /// /// private static string[] Parse(string line) { var strings = new List(); var currentString = new StringBuilder(); var currentCharacter = 0; var leftParenCount = 0; while (currentCharacter < line.Length) { var ch = line[currentCharacter]; switch (ch) { case '(': currentString.Append("("); leftParenCount++; break; case ')': currentString.Append(")"); leftParenCount--; break; case ',': if (leftParenCount > 0) { currentString.Append(","); } else { strings.Add(currentString.ToString()); currentString.Clear(); } break; default: currentString.Append(ch); break; } currentCharacter++; } if (currentString.Length > 0) { strings.Add(currentString.ToString()); } return strings.ToArray(); } /// /// checks whether a group name is valid /// a group name is invalid if it exists more than once in the import or if it already exists in the /// application and overwrite is false /// public void CheckGroupName() { var groupNameLookup = new Dictionary>(); foreach (var g in Groups) { if (!groupNameLookup.ContainsKey(g.GroupName)) { groupNameLookup.Add(g.GroupName, new List()); } groupNameLookup[g.GroupName].Add(g); } foreach (var g in Groups) { if (groupNameLookup[g.GroupName].Count > 1) { g.GroupNameHasError = true; } else { if (CheckGroupExists(g.GroupName) && !g.Overwrite) { g.GroupNameHasError = true; } else { g.GroupNameHasError = false; } } } } /// /// log a message /// /// private void LogMessage(params object[] parms) { Logger?.Invoke(parms); } /// /// separates channels out of groups into valid and invalid channels /// popuplates IncompleteChannels and CompleteChannels /// private void ProcessChannels() { var channels = new List(); foreach (var g in Groups) { channels.AddRange(g.Channels); } Channels = channels.ToArray(); } public void Cleanup() { } public Task CleanupAsync() { return Task.CompletedTask; } public void Initialize() { } public void Initialize(object parameter) { } public void Initialize(object parameter, object model) { } public Task InitializeAsync() { return Task.CompletedTask; } public Task InitializeAsync(object parameter) { return Task.CompletedTask; } public void Activated() { } /// /// Private Event handler for RaiseNotification event. /// private void OnBusyIndicatorNotification(bool eventArg) { IsBusy = eventArg; } /// /// Private Event handler for RaiseNotification event. /// private void OnRaiseNotification(NotificationContentEventArgs eventArgsWithTitle) { // The NotificationRequest.Raise triggers the Invoke() method of the PopupWindowAction object to show the NotificationWindow window // Notification object expects a NotificationContentEventArgsWithoutTitle object and a Title string. var eventArgsWithoutTitle = new NotificationContentEventArgs(eventArgsWithTitle.Message, "", eventArgsWithTitle.Image, string.Empty); NotificationRequest.Raise(new Notification { Content = eventArgsWithoutTitle, Title = eventArgsWithTitle.Title }); } #endregion #region Properties public bool IsDirty { get; private set; } private bool _isBusy; public bool IsBusy { get => _isBusy; set { _isBusy = value; OnPropertyChanged("IsBusy"); } } private bool _isMenuIncluded; public bool IsMenuIncluded { get => _isMenuIncluded; set { _isMenuIncluded = value; OnPropertyChanged("IsMenuIncluded"); } } private bool _isNavigationIncluded; public bool IsNavigationIncluded { get => _isNavigationIncluded; set { _isNavigationIncluded = value; OnPropertyChanged("IsNavigationIncluded"); } } /// /// Gets the HeaderInfo. /// public string HeaderInfo => "MainRegion"; #endregion Properties #region Commands /// /// browse to a file to import, should be xml, maybe needs a few other criteria /// private DelegateCommand _importBrowseCommand; public DelegateCommand ImportBrowseCommand => _importBrowseCommand ?? (_importBrowseCommand = new DelegateCommand(ImportBrowseMethod)); private void ImportBrowseMethod() { using (var dlg = new System.Windows.Forms.OpenFileDialog()) { dlg.CheckFileExists = true; dlg.CheckPathExists = true; dlg.Multiselect = true; dlg.Filter = StringResources.ImportFileFilter; dlg.FilterIndex = 0; if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK) return; SourceFiles = dlg.FileNames; BrowseOk = true; SwitchNavSteps?.Invoke(GroupImportEnums.Steps.Preview); } } #endregion #region properties private string _importProgressText; /// /// current status text for import progress bar /// public string ImportProgressText { get => _importProgressText; set { _importProgressText = value; OnPropertyChanged("ImportProgressText"); } } private System.Windows.Media.Color _importProgressColor; /// /// The current color of the import progress bar /// public System.Windows.Media.Color ImportProgressColor { get => _importProgressColor; set { _importProgressColor = value; OnPropertyChanged("ImportProgressColor"); } } public Visibility _importProgressBarVisibility; /// /// whether the progress bar is visible or not /// public Visibility ImportProgressBarVisibility { get => _importProgressBarVisibility; set { _importProgressBarVisibility = value; OnPropertyChanged("ImportProgressBarVisibility"); } } private double _importProgressValue; /// /// percentage complete of import process /// public double ImportProgressValue { get => _importProgressValue; set { _importProgressValue = value; OnPropertyChanged("ImportProgressValue"); } } private string[] _sourceFiles = new string[0]; /// /// .GRP files to parse /// public string[] SourceFiles { get => _sourceFiles; set { _sourceFiles = value; OnPropertyChanged("SourceFiles"); } } public bool BrowseOk { get; set; } = false; private GroupGRPImportGroup[] _groups = new GroupGRPImportGroup[0]; /// /// All groups that have been read in and parsed /// public GroupGRPImportGroup[] Groups { get => _groups; set { _groups = value; OnPropertyChanged("Groups"); } } private GroupGRPImportChannel[] _channels = new GroupGRPImportChannel[0]; /// /// Channels which are invalid but are in groups included in import /// public GroupGRPImportChannel[] Channels { get => _channels; set { _channels = value; InvalidateChannels(); } } public void InvalidateChannels() { OnPropertyChanged("IncompleteChannels"); OnPropertyChanged("CompleteChannels"); } /// /// these are channels which will not be imported /// public GroupGRPImportChannel[] IncompleteChannels { get { var channels = new List(); foreach (var g in Groups) { if (!g.Included) { continue; } foreach (var ch in g.Channels) { if (null == ch.Error) { continue; } else { switch (ch.Error.ErrorCode) { case GroupGRPImportError.Errors.InvalidFullScaleInput: case GroupGRPImportError.Errors.InvalidInvertInput: continue; default: channels.Add(ch); break; } } } } return channels.ToArray(); } } /// /// these are channels which will be imported /// public GroupGRPImportChannel[] CompleteChannels { get { var channels = new List(); foreach (var g in Groups) { if (!g.Included) { continue; } foreach (var ch in g.Channels) { if (null == ch.Error) { channels.Add(ch); } else { switch (ch.Error.ErrorCode) { case GroupGRPImportError.Errors.InvalidFullScaleInput: case GroupGRPImportError.Errors.InvalidInvertInput: channels.Add(ch); break; } } } } return channels.ToArray(); } } /// /// the logging facility to use /// if null no logging is performed /// public FileUtils.LogDelegate Logger { get; set; } = null; /// /// the command for switching current nav step /// public SwitchNavStepsDelegate SwitchNavSteps { get; set; } = null; /// /// the command for checking if a group already exists in the application /// public CheckGroupExistsDelegate CheckGroupExists { get; set; } = null; /// /// the command for checking if a sensor exists in the application /// public CheckSensorExistsDelegate CheckSensorExists { get; set; } = null; /// /// the command for creating a group /// public CreateGroupDelegate CreateGroup { get; set; } = null; /// /// the command for adding a channel to a group /// public AddChannelToGroupDelegate AddChannel { get; set; } = null; /// /// the command for commiting a group to the db /// public CommitGroupsDelegate CommitGroups { get; set; } = null; /// /// the command for disabling the UI /// public Disable_UIDelegate DisableUI { get; set; } /// /// the command for enabling the UI /// public Enable_UIDelegate EnableUI { get; set; } #endregion /// ///Occurs when a property value changes. /// public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }