dorisoy_HardwareMonitor/LibreHardwareMonitorLib/Hardware/Network/NetworkGroup.cs
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Text;

namespace LibreHardwareMonitor.Hardware.Network
{
    internal class NetworkGroup : IGroup, IHardwareChanged
    {
        public event HardwareEventHandler HardwareAdded;
        public event HardwareEventHandler HardwareRemoved;

        private readonly Dictionary<string, Network> _networks = new Dictionary<string, Network>();
        private readonly object _scanLock = new object();
        private readonly ISettings _settings;
        private List<Network> _hardware = new List<Network>();

        public NetworkGroup(ISettings settings)
        {
            _settings = settings;
            UpdateNetworkInterfaces(settings);

            NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged;
            NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAddressChanged;
        }

        public IReadOnlyList<IHardware> Hardware => _hardware;

        public string GetReport()
        {
            var report = new StringBuilder();

            foreach (Network network in _hardware)
            {
                report.AppendLine(network.NetworkInterface.Description);
                report.AppendLine(network.NetworkInterface.OperationalStatus.ToString());
                report.AppendLine();

                foreach (ISensor sensor in network.Sensors)
                {
                    report.AppendLine(sensor.Name);
                    report.AppendLine(sensor.Value.ToString());
                    report.AppendLine();
                }
            }

            return report.ToString();
        }

        public void Close()
        {
            NetworkChange.NetworkAddressChanged -= NetworkChange_NetworkAddressChanged;
            NetworkChange.NetworkAvailabilityChanged -= NetworkChange_NetworkAddressChanged;

            foreach (Network network in _hardware)
                network.Close();
        }

        private void UpdateNetworkInterfaces(ISettings settings)
        {
            // When multiple events fire concurrently, we don't want threads interfering
            // with others as they manipulate non-thread safe state.
            lock (_scanLock)
            {
                IOrderedEnumerable<NetworkInterface> networkInterfaces = GetNetworkInterfaces();
                if (networkInterfaces == null)
                    return;


                var foundNetworkInterfaces = networkInterfaces.ToDictionary(x => x.Id, x => x);

                // Remove network interfaces that no longer exist.
                List<string> removeKeys = new List<string>();
                foreach (KeyValuePair<string, Network> networkInterfacePair in _networks)
                {
                    if (foundNetworkInterfaces.ContainsKey(networkInterfacePair.Key))
                        continue;


                    removeKeys.Add(networkInterfacePair.Key);
                }

                foreach (string key in removeKeys)
                {
                    var network = _networks[key];
                    network.Close();
                    _networks.Remove(key);

                    _hardware.Remove(network);
                    HardwareRemoved?.Invoke(network);
                }

                // Add new network interfaces.
                foreach (KeyValuePair<string, NetworkInterface> networkInterfacePair in foundNetworkInterfaces)
                {
                    if (!_networks.ContainsKey(networkInterfacePair.Key))
                    {
                        _networks.Add(networkInterfacePair.Key, new Network(networkInterfacePair.Value, settings));
                        _hardware.Add(_networks[networkInterfacePair.Key]);
                        HardwareAdded?.Invoke(_networks[networkInterfacePair.Key]);
                    }
                }
            }
        }

        private static IOrderedEnumerable<NetworkInterface> GetNetworkInterfaces()
        {
            int retry = 0;

            while (retry++ < 5)
            {
                try
                {
                    return NetworkInterface.GetAllNetworkInterfaces()
                                           .Where(DesiredNetworkType)
                                           .OrderBy(x => x.Name);
                }
                catch (NetworkInformationException)
                {
                    // Disabling IPv4 while running can cause a NetworkInformationException: The pipe is being closed.
                    // This can be retried.
                }
            }

            return null;
        }

        private void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
        {
            UpdateNetworkInterfaces(_settings);
        }

        private static bool DesiredNetworkType(NetworkInterface nic)
        {
            switch (nic.NetworkInterfaceType)
            {
                case NetworkInterfaceType.Loopback:
                case NetworkInterfaceType.Tunnel:
                case NetworkInterfaceType.Unknown:
                    return false;
                default:
                    return true;
            }
        }
    }
}
