From 465f0cc1e2e4fc50a0adbef79a4a317eec5eb454 Mon Sep 17 00:00:00 2001 From: LukePulverenti Date: Sat, 23 Feb 2013 12:54:51 -0500 Subject: moved some network code to the networking assembly --- .../Management/NativeMethods.cs | 72 +++ .../Management/NetworkManager.cs | 265 +++++++++ .../Management/NetworkShares.cs | 638 +++++++++++++++++++++ .../MediaBrowser.Networking.csproj | 14 + 4 files changed, 989 insertions(+) create mode 100644 MediaBrowser.Networking/Management/NativeMethods.cs create mode 100644 MediaBrowser.Networking/Management/NetworkManager.cs create mode 100644 MediaBrowser.Networking/Management/NetworkShares.cs (limited to 'MediaBrowser.Networking') diff --git a/MediaBrowser.Networking/Management/NativeMethods.cs b/MediaBrowser.Networking/Management/NativeMethods.cs new file mode 100644 index 0000000000..9a888fab2b --- /dev/null +++ b/MediaBrowser.Networking/Management/NativeMethods.cs @@ -0,0 +1,72 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; + +namespace MediaBrowser.Networking.Management +{ + /// + /// Class NativeMethods + /// + [SuppressUnmanagedCodeSecurity] + public static class NativeMethods + { + //declare the Netapi32 : NetServerEnum method import + /// + /// Nets the server enum. + /// + /// Name of the server. + /// The dw level. + /// The p buf. + /// The dw pref max len. + /// The dw entries read. + /// The dw total entries. + /// Type of the dw server. + /// The domain. + /// The dw resume handle. + /// System.Int32. + [DllImport("Netapi32", CharSet = CharSet.Auto, SetLastError = true), + SuppressUnmanagedCodeSecurityAttribute] + + public static extern int NetServerEnum( + string ServerName, // must be null + int dwLevel, + ref IntPtr pBuf, + int dwPrefMaxLen, + out int dwEntriesRead, + out int dwTotalEntries, + int dwServerType, + string domain, // null for login domain + out int dwResumeHandle + ); + + //declare the Netapi32 : NetApiBufferFree method import + /// + /// Nets the API buffer free. + /// + /// The p buf. + /// System.Int32. + [DllImport("Netapi32", SetLastError = true), + SuppressUnmanagedCodeSecurityAttribute] + + public static extern int NetApiBufferFree( + IntPtr pBuf); + } + + //create a _SERVER_INFO_100 STRUCTURE + /// + /// Struct _SERVER_INFO_100 + /// + [StructLayout(LayoutKind.Sequential)] + public struct _SERVER_INFO_100 + { + /// + /// The sv100_platform_id + /// + internal int sv100_platform_id; + /// + /// The sv100_name + /// + [MarshalAs(UnmanagedType.LPWStr)] + internal string sv100_name; + } +} diff --git a/MediaBrowser.Networking/Management/NetworkManager.cs b/MediaBrowser.Networking/Management/NetworkManager.cs new file mode 100644 index 0000000000..fcead43d3d --- /dev/null +++ b/MediaBrowser.Networking/Management/NetworkManager.cs @@ -0,0 +1,265 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Net; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Management; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace MediaBrowser.Networking.Management +{ + /// + /// Class NetUtils + /// + public class NetworkManager : INetworkManager + { + /// + /// Gets the machine's local ip address + /// + /// IPAddress. + public string GetLocalIpAddress() + { + var host = Dns.GetHostEntry(Dns.GetHostName()); + + var ip = host.AddressList.FirstOrDefault(i => i.AddressFamily == AddressFamily.InterNetwork); + + if (ip == null) + { + return null; + } + + return ip.ToString(); + } + + /// + /// Gets a random port number that is currently available + /// + /// System.Int32. + public int GetRandomUnusedPort() + { + var listener = new TcpListener(IPAddress.Any, 0); + listener.Start(); + var port = ((IPEndPoint)listener.LocalEndpoint).Port; + listener.Stop(); + return port; + } + + /// + /// Creates the netsh URL registration. + /// + public void AuthorizeHttpListening(string url) + { + var startInfo = new ProcessStartInfo + { + FileName = "netsh", + Arguments = string.Format("http add urlacl url={0} user=\"NT AUTHORITY\\Authenticated Users\"", url), + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + Verb = "runas", + ErrorDialog = false + }; + + using (var process = Process.Start(startInfo)) + { + process.WaitForExit(); + } + } + + /// + /// Adds the windows firewall rule. + /// + /// The port. + /// The protocol. + public void AddSystemFirewallRule(int port, NetworkProtocol protocol) + { + // First try to remove it so we don't end up creating duplicates + RemoveSystemFirewallRule(port, protocol); + + var args = string.Format("advfirewall firewall add rule name=\"Port {0}\" dir=in action=allow protocol={1} localport={0}", port, protocol); + + RunNetsh(args); + } + + /// + /// Removes the windows firewall rule. + /// + /// The port. + /// The protocol. + public void RemoveSystemFirewallRule(int port, NetworkProtocol protocol) + { + var args = string.Format("advfirewall firewall delete rule name=\"Port {0}\" protocol={1} localport={0}", port, protocol); + + RunNetsh(args); + } + + /// + /// Runs the netsh. + /// + /// The args. + private void RunNetsh(string args) + { + var startInfo = new ProcessStartInfo + { + FileName = "netsh", + Arguments = args, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + Verb = "runas", + ErrorDialog = false + }; + + using (var process = new Process { StartInfo = startInfo }) + { + process.Start(); + process.WaitForExit(); + } + } + + /// + /// Returns MAC Address from first Network Card in Computer + /// + /// [string] MAC Address + public string GetMacAddress() + { + var mc = new ManagementClass("Win32_NetworkAdapterConfiguration"); + var moc = mc.GetInstances(); + var macAddress = String.Empty; + foreach (ManagementObject mo in moc) + { + if (macAddress == String.Empty) // only return MAC Address from first card + { + try + { + if ((bool)mo["IPEnabled"]) macAddress = mo["MacAddress"].ToString(); + } + catch + { + mo.Dispose(); + return ""; + } + } + mo.Dispose(); + } + + return macAddress.Replace(":", ""); + } + + /// + /// Uses the DllImport : NetServerEnum with all its required parameters + /// (see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/netmgmt/netmgmt/netserverenum.asp + /// for full details or method signature) to retrieve a list of domain SV_TYPE_WORKSTATION + /// and SV_TYPE_SERVER PC's + /// + /// Arraylist that represents all the SV_TYPE_WORKSTATION and SV_TYPE_SERVER + /// PC's in the Domain + public IEnumerable GetNetworkDevices() + { + //local fields + const int MAX_PREFERRED_LENGTH = -1; + var SV_TYPE_WORKSTATION = 1; + var SV_TYPE_SERVER = 2; + var buffer = IntPtr.Zero; + var tmpBuffer = IntPtr.Zero; + var entriesRead = 0; + var totalEntries = 0; + var resHandle = 0; + var sizeofINFO = Marshal.SizeOf(typeof(_SERVER_INFO_100)); + + try + { + //call the DllImport : NetServerEnum with all its required parameters + //see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/netmgmt/netmgmt/netserverenum.asp + //for full details of method signature + var ret = NativeMethods.NetServerEnum(null, 100, ref buffer, MAX_PREFERRED_LENGTH, out entriesRead, out totalEntries, SV_TYPE_WORKSTATION | SV_TYPE_SERVER, null, out resHandle); + + //if the returned with a NERR_Success (C++ term), =0 for C# + if (ret == 0) + { + //loop through all SV_TYPE_WORKSTATION and SV_TYPE_SERVER PC's + for (var i = 0; i < totalEntries; i++) + { + //get pointer to, Pointer to the buffer that received the data from + //the call to NetServerEnum. Must ensure to use correct size of + //STRUCTURE to ensure correct location in memory is pointed to + tmpBuffer = new IntPtr((int)buffer + (i * sizeofINFO)); + //Have now got a pointer to the list of SV_TYPE_WORKSTATION and + //SV_TYPE_SERVER PC's, which is unmanaged memory + //Needs to Marshal data from an unmanaged block of memory to a + //managed object, again using STRUCTURE to ensure the correct data + //is marshalled + var svrInfo = (_SERVER_INFO_100)Marshal.PtrToStructure(tmpBuffer, typeof(_SERVER_INFO_100)); + + //add the PC names to the ArrayList + if (!string.IsNullOrEmpty(svrInfo.sv100_name)) + { + yield return svrInfo.sv100_name; + } + } + } + } + finally + { + //The NetApiBufferFree function frees + //the memory that the NetApiBufferAllocate function allocates + NativeMethods.NetApiBufferFree(buffer); + } + } + + + /// + /// Gets the network shares. + /// + /// The path. + /// IEnumerable{NetworkShare}. + public IEnumerable GetNetworkShares(string path) + { + return new ShareCollection(path).OfType().Select(ToNetworkShare); + } + + /// + /// To the network share. + /// + /// The share. + /// NetworkShare. + private NetworkShare ToNetworkShare(Share share) + { + return new NetworkShare + { + Name = share.NetName, + Path = share.Path, + Remark = share.Remark, + Server = share.Server, + ShareType = ToNetworkShareType(share.ShareType) + }; + } + + /// + /// To the type of the network share. + /// + /// Type of the share. + /// NetworkShareType. + /// Unknown share type + private NetworkShareType ToNetworkShareType(ShareType shareType) + { + switch (shareType) + { + case ShareType.Device: + return NetworkShareType.Device; + case ShareType.Disk : + return NetworkShareType.Disk; + case ShareType.IPC : + return NetworkShareType.Ipc; + case ShareType.Printer : + return NetworkShareType.Printer; + case ShareType.Special: + return NetworkShareType.Special; + default: + throw new ArgumentException("Unknown share type"); + } + } + } + +} diff --git a/MediaBrowser.Networking/Management/NetworkShares.cs b/MediaBrowser.Networking/Management/NetworkShares.cs new file mode 100644 index 0000000000..0239ddead8 --- /dev/null +++ b/MediaBrowser.Networking/Management/NetworkShares.cs @@ -0,0 +1,638 @@ +using System; +using System.IO; +using System.Collections; +using System.Runtime.InteropServices; + +namespace MediaBrowser.Networking.Management +{ + /// + /// Type of share + /// + [Flags] + public enum ShareType + { + /// Disk share + Disk = 0, + /// Printer share + Printer = 1, + /// Device share + Device = 2, + /// IPC share + IPC = 3, + /// Special share + Special = -2147483648, // 0x80000000, + } + + #region Share + + /// + /// Information about a local share + /// + public class Share + { + #region Private data + + private string _server; + private string _netName; + private string _path; + private ShareType _shareType; + private string _remark; + + #endregion + + #region Constructor + + /// + /// Constructor + /// + /// + /// + public Share(string server, string netName, string path, ShareType shareType, string remark) + { + if (ShareType.Special == shareType && "IPC$" == netName) + { + shareType |= ShareType.IPC; + } + + _server = server; + _netName = netName; + _path = path; + _shareType = shareType; + _remark = remark; + } + + #endregion + + #region Properties + + /// + /// The name of the computer that this share belongs to + /// + public string Server + { + get { return _server; } + } + + /// + /// Share name + /// + public string NetName + { + get { return _netName; } + } + + /// + /// Local path + /// + public string Path + { + get { return _path; } + } + + /// + /// Share type + /// + public ShareType ShareType + { + get { return _shareType; } + } + + /// + /// Comment + /// + public string Remark + { + get { return _remark; } + } + + /// + /// Returns true if this is a file system share + /// + public bool IsFileSystem + { + get + { + // Shared device + if (0 != (_shareType & ShareType.Device)) return false; + // IPC share + if (0 != (_shareType & ShareType.IPC)) return false; + // Shared printer + if (0 != (_shareType & ShareType.Printer)) return false; + + // Standard disk share + if (0 == (_shareType & ShareType.Special)) return true; + + // Special disk share (e.g. C$) + return ShareType.Special == _shareType && !string.IsNullOrEmpty(_netName); + } + } + + /// + /// Get the root of a disk-based share + /// + public DirectoryInfo Root + { + get + { + if (IsFileSystem) + { + if (string.IsNullOrEmpty(_server)) + if (string.IsNullOrEmpty(_path)) + return new DirectoryInfo(ToString()); + else + return new DirectoryInfo(_path); + return new DirectoryInfo(ToString()); + } + return null; + } + } + + #endregion + + /// + /// Returns the path to this share + /// + /// + public override string ToString() + { + if (string.IsNullOrEmpty(_server)) + { + return string.Format(@"\\{0}\{1}", Environment.MachineName, _netName); + } + return string.Format(@"\\{0}\{1}", _server, _netName); + } + + /// + /// Returns true if this share matches the local path + /// + /// + /// + public bool MatchesPath(string path) + { + if (!IsFileSystem) return false; + if (string.IsNullOrEmpty(path)) return true; + + return path.ToLower().StartsWith(_path.ToLower()); + } + } + + #endregion + + /// + /// A collection of shares + /// + public class ShareCollection : ReadOnlyCollectionBase + { + #region Platform + + /// + /// Is this an NT platform? + /// + protected static bool IsNT + { + get { return (PlatformID.Win32NT == Environment.OSVersion.Platform); } + } + + /// + /// Returns true if this is Windows 2000 or higher + /// + protected static bool IsW2KUp + { + get + { + OperatingSystem os = Environment.OSVersion; + if (PlatformID.Win32NT == os.Platform && os.Version.Major >= 5) + return true; + else + return false; + } + } + + #endregion + + #region Interop + + #region Constants + + /// Maximum path length + protected const int MAX_PATH = 260; + /// No error + protected const int NO_ERROR = 0; + /// Access denied + protected const int ERROR_ACCESS_DENIED = 5; + /// Access denied + protected const int ERROR_WRONG_LEVEL = 124; + /// More data available + protected const int ERROR_MORE_DATA = 234; + /// Not connected + protected const int ERROR_NOT_CONNECTED = 2250; + /// Level 1 + protected const int UNIVERSAL_NAME_INFO_LEVEL = 1; + /// Max extries (9x) + protected const int MAX_SI50_ENTRIES = 20; + + #endregion + + #region Structures + + /// Unc name + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + protected struct UNIVERSAL_NAME_INFO + { + [MarshalAs(UnmanagedType.LPTStr)] + public string lpUniversalName; + } + + /// Share information, NT, level 2 + /// + /// Requires admin rights to work. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + protected struct SHARE_INFO_2 + { + [MarshalAs(UnmanagedType.LPWStr)] + public string NetName; + public ShareType ShareType; + [MarshalAs(UnmanagedType.LPWStr)] + public string Remark; + public int Permissions; + public int MaxUsers; + public int CurrentUsers; + [MarshalAs(UnmanagedType.LPWStr)] + public string Path; + [MarshalAs(UnmanagedType.LPWStr)] + public string Password; + } + + /// Share information, NT, level 1 + /// + /// Fallback when no admin rights. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + protected struct SHARE_INFO_1 + { + [MarshalAs(UnmanagedType.LPWStr)] + public string NetName; + public ShareType ShareType; + [MarshalAs(UnmanagedType.LPWStr)] + public string Remark; + } + + /// Share information, Win9x + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] + protected struct SHARE_INFO_50 + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)] + public string NetName; + + public byte bShareType; + public ushort Flags; + + [MarshalAs(UnmanagedType.LPTStr)] + public string Remark; + [MarshalAs(UnmanagedType.LPTStr)] + public string Path; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)] + public string PasswordRW; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)] + public string PasswordRO; + + public ShareType ShareType + { + get { return (ShareType)((int)bShareType & 0x7F); } + } + } + + /// Share information level 1, Win9x + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] + protected struct SHARE_INFO_1_9x + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)] + public string NetName; + public byte Padding; + + public ushort bShareType; + + [MarshalAs(UnmanagedType.LPTStr)] + public string Remark; + + public ShareType ShareType + { + get { return (ShareType)((int)bShareType & 0x7FFF); } + } + } + + #endregion + + #region Functions + + /// Get a UNC name + [DllImport("mpr", CharSet = CharSet.Auto)] + protected static extern int WNetGetUniversalName(string lpLocalPath, + int dwInfoLevel, ref UNIVERSAL_NAME_INFO lpBuffer, ref int lpBufferSize); + + /// Get a UNC name + [DllImport("mpr", CharSet = CharSet.Auto)] + protected static extern int WNetGetUniversalName(string lpLocalPath, + int dwInfoLevel, IntPtr lpBuffer, ref int lpBufferSize); + + /// Enumerate shares (NT) + [DllImport("netapi32", CharSet = CharSet.Unicode)] + protected static extern int NetShareEnum(string lpServerName, int dwLevel, + out IntPtr lpBuffer, int dwPrefMaxLen, out int entriesRead, + out int totalEntries, ref int hResume); + + /// Enumerate shares (9x) + [DllImport("svrapi", CharSet = CharSet.Ansi)] + protected static extern int NetShareEnum( + [MarshalAs(UnmanagedType.LPTStr)] string lpServerName, int dwLevel, + IntPtr lpBuffer, ushort cbBuffer, out ushort entriesRead, + out ushort totalEntries); + + /// Free the buffer (NT) + [DllImport("netapi32")] + protected static extern int NetApiBufferFree(IntPtr lpBuffer); + + #endregion + + #region Enumerate shares + + /// + /// Enumerates the shares on Windows NT + /// + /// The server name + /// The ShareCollection + protected static void EnumerateSharesNT(string server, ShareCollection shares) + { + int level = 2; + int entriesRead, totalEntries, nRet, hResume = 0; + IntPtr pBuffer = IntPtr.Zero; + + try + { + nRet = NetShareEnum(server, level, out pBuffer, -1, + out entriesRead, out totalEntries, ref hResume); + + if (ERROR_ACCESS_DENIED == nRet) + { + //Need admin for level 2, drop to level 1 + level = 1; + nRet = NetShareEnum(server, level, out pBuffer, -1, + out entriesRead, out totalEntries, ref hResume); + } + + if (NO_ERROR == nRet && entriesRead > 0) + { + Type t = (2 == level) ? typeof(SHARE_INFO_2) : typeof(SHARE_INFO_1); + int offset = Marshal.SizeOf(t); + + for (int i = 0, lpItem = pBuffer.ToInt32(); i < entriesRead; i++, lpItem += offset) + { + IntPtr pItem = new IntPtr(lpItem); + if (1 == level) + { + SHARE_INFO_1 si = (SHARE_INFO_1)Marshal.PtrToStructure(pItem, t); + shares.Add(si.NetName, string.Empty, si.ShareType, si.Remark); + } + else + { + SHARE_INFO_2 si = (SHARE_INFO_2)Marshal.PtrToStructure(pItem, t); + shares.Add(si.NetName, si.Path, si.ShareType, si.Remark); + } + } + } + + } + finally + { + // Clean up buffer allocated by system + if (IntPtr.Zero != pBuffer) + NetApiBufferFree(pBuffer); + } + } + + /// + /// Enumerates the shares on Windows 9x + /// + /// The server name + /// The ShareCollection + protected static void EnumerateShares9x(string server, ShareCollection shares) + { + int level = 50; + int nRet = 0; + ushort entriesRead, totalEntries; + + Type t = typeof(SHARE_INFO_50); + int size = Marshal.SizeOf(t); + ushort cbBuffer = (ushort)(MAX_SI50_ENTRIES * size); + //On Win9x, must allocate buffer before calling API + IntPtr pBuffer = Marshal.AllocHGlobal(cbBuffer); + + try + { + nRet = NetShareEnum(server, level, pBuffer, cbBuffer, + out entriesRead, out totalEntries); + + if (ERROR_WRONG_LEVEL == nRet) + { + level = 1; + t = typeof(SHARE_INFO_1_9x); + size = Marshal.SizeOf(t); + + nRet = NetShareEnum(server, level, pBuffer, cbBuffer, + out entriesRead, out totalEntries); + } + + if (NO_ERROR == nRet || ERROR_MORE_DATA == nRet) + { + for (int i = 0, lpItem = pBuffer.ToInt32(); i < entriesRead; i++, lpItem += size) + { + IntPtr pItem = new IntPtr(lpItem); + + if (1 == level) + { + SHARE_INFO_1_9x si = (SHARE_INFO_1_9x)Marshal.PtrToStructure(pItem, t); + shares.Add(si.NetName, string.Empty, si.ShareType, si.Remark); + } + else + { + SHARE_INFO_50 si = (SHARE_INFO_50)Marshal.PtrToStructure(pItem, t); + shares.Add(si.NetName, si.Path, si.ShareType, si.Remark); + } + } + } + else + Console.WriteLine(nRet); + + } + finally + { + //Clean up buffer + Marshal.FreeHGlobal(pBuffer); + } + } + + /// + /// Enumerates the shares + /// + /// The server name + /// The ShareCollection + protected static void EnumerateShares(string server, ShareCollection shares) + { + if (null != server && 0 != server.Length && !IsW2KUp) + { + server = server.ToUpper(); + + // On NT4, 9x and Me, server has to start with "\\" + if (!('\\' == server[0] && '\\' == server[1])) + server = @"\\" + server; + } + + if (IsNT) + EnumerateSharesNT(server, shares); + else + EnumerateShares9x(server, shares); + } + + #endregion + + #endregion + + #region Static methods + + /// + /// Returns true if fileName is a valid local file-name of the form: + /// X:\, where X is a drive letter from A-Z + /// + /// The filename to check + /// + public static bool IsValidFilePath(string fileName) + { + if (null == fileName || 0 == fileName.Length) return false; + + char drive = char.ToUpper(fileName[0]); + if ('A' > drive || drive > 'Z') + return false; + + else if (Path.VolumeSeparatorChar != fileName[1]) + return false; + else if (Path.DirectorySeparatorChar != fileName[2]) + return false; + else + return true; + } + + #endregion + + /// The name of the server this collection represents + private string _server; + + #region Constructor + + /// + /// Default constructor - local machine + /// + public ShareCollection() + { + _server = string.Empty; + EnumerateShares(_server, this); + } + + /// + /// Constructor + /// + /// + public ShareCollection(string server) + { + _server = server; + EnumerateShares(_server, this); + } + + #endregion + + #region Add + + protected void Add(Share share) + { + InnerList.Add(share); + } + + protected void Add(string netName, string path, ShareType shareType, string remark) + { + InnerList.Add(new Share(_server, netName, path, shareType, remark)); + } + + #endregion + + #region Properties + + /// + /// Returns the name of the server this collection represents + /// + public string Server + { + get { return _server; } + } + + /// + /// Returns the at the specified index. + /// + public Share this[int index] + { + get { return (Share)InnerList[index]; } + } + + /// + /// Returns the which matches a given local path + /// + /// The path to match + public Share this[string path] + { + get + { + if (null == path || 0 == path.Length) return null; + + path = Path.GetFullPath(path); + if (!IsValidFilePath(path)) return null; + + Share match = null; + + for (int i = 0; i < InnerList.Count; i++) + { + Share s = (Share)InnerList[i]; + + if (s.IsFileSystem && s.MatchesPath(path)) + { + //Store first match + if (null == match) + match = s; + + // If this has a longer path, + // and this is a disk share or match is a special share, + // then this is a better match + else if (match.Path.Length < s.Path.Length) + { + if (ShareType.Disk == s.ShareType || ShareType.Disk != match.ShareType) + match = s; + } + } + } + + return match; + } + } + + #endregion + + /// + /// Copy this collection to an array + /// + /// + /// + public void CopyTo(Share[] array, int index) + { + InnerList.CopyTo(array, index); + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Networking/MediaBrowser.Networking.csproj b/MediaBrowser.Networking/MediaBrowser.Networking.csproj index acd0d01462..cd66fd1844 100644 --- a/MediaBrowser.Networking/MediaBrowser.Networking.csproj +++ b/MediaBrowser.Networking/MediaBrowser.Networking.csproj @@ -32,6 +32,7 @@ + @@ -42,8 +43,21 @@ Properties\SharedVersion.cs + + + + + + {9142eefa-7570-41e1-bfcc-468bb571af2f} + MediaBrowser.Common + + + {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + MediaBrowser.Model + +