<commit_before>﻿/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the Apache License, Version 2.0, please send an email to 
 * <EMAIL>. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Apache License, Version 2.0.
 *
 * You must not remove this notice, or any other, from this software.
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using Microsoft.NodejsTools.Npm;

namespace Microsoft.NodejsTools.NpmUI {
    class NpmOutputControlViewModel : INotifyPropertyChanged, IDisposable {
        private INpmController _npmController;
        private readonly Queue<QueuedNpmCommandInfo> _commandQueue = new Queue<QueuedNpmCommandInfo>();
        private readonly object _lock = new object();
        private bool _isDisposed;
        private string _statusText = Resources.NpmStatusReady;
        private bool _isExecutingCommand;
        private bool _withErrors;
        private readonly FlowDocument _output = new FlowDocument();
        private readonly Thread _worker;
        private QueuedNpmCommandInfo _currentCommand;
        private INpmCommander _commander;
        
        public NpmOutputControlViewModel() {
            var style = new Style(typeof(Paragraph));
            style.Setters.Add(new Setter(Block.MarginProperty, new Thickness(0)));
            _output.Resources.Add(typeof(Paragraph), style);

            _worker = new Thread(Run);
            _worker.Name = "npm UI Execution";
            _worker.IsBackground = true;
            _worker.Start();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }

        private void Pulse() {
            lock (_lock) {
                Monitor.PulseAll(_lock);
            }
        }

        public INpmController NpmController {
            get { return _npmController; }
            set {
                _npmController = value;   
                OnPropertyChanged();
                Pulse();
            }
        }

        public string StatusText {
            get { return _statusText; }
            set {
                _statusText = value;
                OnPropertyChanged();
            }
        }

        public bool IsExecutingCommand {
            get {
                lock (_lock) {
                    return _isExecutingCommand;
                }
            }
            set {
                lock (_lock) {
                    _isExecutingCommand = value;
                    Pulse();
                }
                OnPropertyChanged();
                OnPropertyChanged("ExecutionProgressVisibility");
                OnPropertyChanged("IsCancellable");
            }
        }

        public Visibility ExecutionProgressVisibility {
            get { return IsExecutingCommand ? Visibility.Visible : Visibility.Hidden; }
        }

        public bool IsCancellable {
            get {
                lock (_lock) {
                    return _commandQueue.Count > 0 || IsExecutingCommand;
                }
            }
        }

        public void Cancel() {
            lock (_lock) {
                _commandQueue.Clear();
                if (null != _commander) {
                    _commander.CancelCurrentCommand();
                }
            }

            UpdateStatusMessage();
            OnPropertyChanged("IsCancellable");
        }

        private void QueueCommand(QueuedNpmCommandInfo info) {
            lock (_lock) {
                _commandQueue.Enqueue(info);
                Monitor.PulseAll(_lock);
            }

            UpdateStatusMessageSafe();
            OnPropertyChanged("IsCancellable");
        }

        public void QueueCommand(string arguments) {
            QueueCommand(new QueuedNpmCommandInfo(arguments));
        }

        public void QueueCommand(string command, string arguments) {
            QueueCommand(string.Format("{0} {1}", command, arguments));
        }

        public void QueueInstallPackage(
            string name,
            string version,
            DependencyType type) {
            QueueCommand(new QueuedNpmCommandInfo(name, version, type));
        }

        public void QueueInstallGlobalPackage(
            string name,
            string version) {
            QueueCommand(new QueuedNpmCommandInfo(name, version));
        }

        private async void Execute(QueuedNpmCommandInfo info) {
            IsExecutingCommand = true;
            try {
                lock (_lock) {
                    _commander = _npmController.CreateNpmCommander();
                    _commander.OutputLogged += commander_OutputLogged;
                    _commander.ErrorLogged += commander_ErrorLogged;
                    _commander.ExceptionLogged += commander_ExceptionLogged;
                    _commander.CommandCompleted += commander_CommandCompleted;
                }

                if (info.IsFreeformArgumentCommand) {
                    await _commander.ExecuteNpmCommandAsync(info.Arguments);
                } else if (info.IsGlobalInstall) {
                    await _commander.InstallGlobalPackageByVersionAsync(
                            info.Name,
                            info.Version);
                } else {
                    await _commander.InstallPackageByVersionAsync(
                                info.Name,
                                info.Version,
                                info.DependencyType,
                                true);
                }
            } finally {
                _commander.OutputLogged -= commander_OutputLogged;
                _commander.ErrorLogged -= commander_ErrorLogged;
                _commander.ExceptionLogged -= commander_ExceptionLogged;
                _commander.CommandCompleted -= commander_CommandCompleted;
                lock (_lock) {
                    _commander = null;
                }
            }
        }

        private void HandleCompletionSafe() {
            UpdateStatusMessage();
            OnPropertyChanged("IsCancellable");
        }

        private void commander_CommandCompleted(object sender, NpmCommandCompletedEventArgs e) {
            IsExecutingCommand = false;
            Application.Current.Dispatcher.BeginInvoke(
                new Action(HandleCompletionSafe));
        }

        public FlowDocument Output {
            get { return _output; }
        }

        public event EventHandler OutputWritten;

        private void OnOutputWritten() {
            var handlers = OutputWritten;
            if (null != handlers) {
                handlers(this, EventArgs.Empty);
            }
        }

        private string Preprocess(string source) {
            return source.EndsWith(Environment.NewLine) ? source.Substring(0, source.Length - Environment.NewLine.Length) : source;<commit_msg>Fixed rare NullReferenceException when unregistering event handlers.<commit_after>﻿/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the Apache License, Version 2.0, please send an email to 
 * <EMAIL>.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Apache License, Version 2.0.
 *
 * You must not remove this notice, or any other, from this software.
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using Microsoft.NodejsTools.Npm;

namespace Microsoft.NodejsTools.NpmUI {
    class NpmOutputControlViewModel : INotifyPropertyChanged, IDisposable {
        private INpmController _npmController;
        private readonly Queue<QueuedNpmCommandInfo> _commandQueue = new Queue<QueuedNpmCommandInfo>();
        private readonly object _lock = new object();
        private bool _isDisposed;
        private string _statusText = Resources.NpmStatusReady;
        private bool _isExecutingCommand;
        private bool _withErrors;
        private readonly FlowDocument _output = new FlowDocument();
        private readonly Thread _worker;
        private QueuedNpmCommandInfo _currentCommand;
        private INpmCommander _commander;
        
        public NpmOutputControlViewModel() {
            var style = new Style(typeof(Paragraph));
            style.Setters.Add(new Setter(Block.MarginProperty, new Thickness(0)));
            _output.Resources.Add(typeof(Paragraph), style);

            _worker = new Thread(Run);
            _worker.Name = "npm UI Execution";
            _worker.IsBackground = true;
            _worker.Start();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }

        private void Pulse() {
            lock (_lock) {
                Monitor.PulseAll(_lock);
            }
        }

        public INpmController NpmController {
            get { return _npmController; }
            set {
                _npmController = value;   
                OnPropertyChanged();
                Pulse();
            }
        }

        public string StatusText {
            get { return _statusText; }
            set {
                _statusText = value;
                OnPropertyChanged();
            }
        }

        public bool IsExecutingCommand {
            get {
                lock (_lock) {
                    return _isExecutingCommand;
                }
            }
            set {
                lock (_lock) {
                    _isExecutingCommand = value;
                    Pulse();
                }
                OnPropertyChanged();
                OnPropertyChanged("ExecutionProgressVisibility");
                OnPropertyChanged("IsCancellable");
            }
        }

        public Visibility ExecutionProgressVisibility {
            get { return IsExecutingCommand ? Visibility.Visible : Visibility.Hidden; }
        }

        public bool IsCancellable {
            get {
                lock (_lock) {
                    return _commandQueue.Count > 0 || IsExecutingCommand;
                }
            }
        }

        public void Cancel() {
            lock (_lock) {
                _commandQueue.Clear();
                if (null != _commander) {
                    _commander.CancelCurrentCommand();
                }
            }

            UpdateStatusMessage();
            OnPropertyChanged("IsCancellable");
        }

        private void QueueCommand(QueuedNpmCommandInfo info) {
            lock (_lock) {
                _commandQueue.Enqueue(info);
                Monitor.PulseAll(_lock);
            }

            UpdateStatusMessageSafe();
            OnPropertyChanged("IsCancellable");
        }

        public void QueueCommand(string arguments) {
            QueueCommand(new QueuedNpmCommandInfo(arguments));
        }

        public void QueueCommand(string command, string arguments) {
            QueueCommand(string.Format("{0} {1}", command, arguments));
        }

        public void QueueInstallPackage(
            string name,
            string version,
            DependencyType type) {
            QueueCommand(new QueuedNpmCommandInfo(name, version, type));
        }

        public void QueueInstallGlobalPackage(
            string name,
            string version) {
            QueueCommand(new QueuedNpmCommandInfo(name, version));
        }

        private async void Execute(QueuedNpmCommandInfo info) {
            IsExecutingCommand = true;
            try {
                lock (_lock) {
                    _commander = _npmController.CreateNpmCommander();
                    _commander.OutputLogged += commander_OutputLogged;
                    _commander.ErrorLogged += commander_ErrorLogged;
                    _commander.ExceptionLogged += commander_ExceptionLogged;
                    _commander.CommandCompleted += commander_CommandCompleted;
                }

                if (info.IsFreeformArgumentCommand) {
                    await _commander.ExecuteNpmCommandAsync(info.Arguments);
                } else if (info.IsGlobalInstall) {
                    await _commander.InstallGlobalPackageByVersionAsync(
                            info.Name,
                            info.Version);
                } else {
                    await _commander.InstallPackageByVersionAsync(
                                info.Name,
                                info.Version,
                                info.DependencyType,
                                true);
                }
            } finally {
                var cmdr = _commander;
                if (null != cmdr) {
                    cmdr.OutputLogged -= commander_OutputLogged;
                    cmdr.ErrorLogged -= commander_ErrorLogged;
                    cmdr.ExceptionLogged -= commander_ExceptionLogged;
                    cmdr.CommandCompleted -= commander_CommandCompleted;
                    lock (_lock) {
                        _commander = null;
                    }
                }
            }
        }

        private void HandleCompletionSafe() {
            UpdateStatusMessage();
            OnPropertyChanged("IsCancellable");
        }

        private void commander_CommandCompleted(object sender, NpmCommandCompletedEventArgs e) {
            IsExecutingCommand = false;
            Application.Current.Dispatcher.BeginInvoke(
                new Action(HandleCompletionSafe));
        }

        public FlowDocument Output {
            get { return _output; }
        }

        public event EventHandler OutputWritten;

        private void OnOutputWritten() {
            var handlers = OutputWritten;
            if (null != handlers) {
                handlers(this, EventArgs.Empty);
            }
        }

        private string Preprocess(string source) {
            return source.EndsWith(Environment.NewLine) ? source.Substring(0, source.Length - Environment.NewLine.Length) : source;
{'fraction_non_alphanumeric': 0.0877922077922078, 'fraction_numerical': 0.0014285714285714286, 'mean_word_length': 1.7301896826803758, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6033638', 'n_tokens_mistral': 3640, 'n_tokens_neox': 3420, 'n_words': 855}