2018-11-25 21:00:36 -05:00
using Microsoft.EntityFrameworkCore ;
2018-04-08 02:44:42 -04:00
using SharedLibraryCore.Database ;
using SharedLibraryCore.Database.Models ;
using SharedLibraryCore.Objects ;
2018-11-25 21:00:36 -05:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Threading.Tasks ;
2018-11-05 22:01:29 -05:00
using static SharedLibraryCore . Database . Models . EFClient ;
2017-11-25 20:29:58 -05:00
2018-04-08 02:44:42 -04:00
namespace SharedLibraryCore.Services
2017-11-25 20:29:58 -05:00
{
2017-11-29 19:35:50 -05:00
2017-11-25 20:29:58 -05:00
public class ClientService : Interfaces . IEntityService < EFClient >
{
public async Task < EFClient > Create ( EFClient entity )
{
2017-11-29 19:35:50 -05:00
using ( var context = new DatabaseContext ( ) )
2017-11-25 20:29:58 -05:00
{
2019-04-05 14:34:03 -04:00
int? linkId = null ;
int? aliasId = null ;
if ( entity . IPAddress ! = null )
{
var existingAlias = await context . Aliases
. Select ( _alias = > new { _alias . AliasId , _alias . LinkId , _alias . IPAddress , _alias . Name } )
. FirstOrDefaultAsync ( _alias = > _alias . IPAddress = = entity . IPAddress ) ;
if ( existingAlias ! = null )
{
linkId = existingAlias . LinkId ;
if ( existingAlias . Name = = entity . Name )
{
aliasId = existingAlias . AliasId ;
}
}
}
2018-11-25 21:00:36 -05:00
var client = new EFClient ( )
{
Level = Permission . User ,
FirstConnection = DateTime . UtcNow ,
LastConnection = DateTime . UtcNow ,
2019-04-05 14:34:03 -04:00
NetworkId = entity . NetworkId
2018-11-25 21:00:36 -05:00
} ;
2019-04-05 22:15:17 -04:00
// they're just using a new GUID
if ( aliasId . HasValue )
2018-11-25 21:00:36 -05:00
{
2019-04-05 22:15:17 -04:00
client . CurrentAliasId = aliasId . Value ;
2019-04-05 14:34:03 -04:00
client . AliasLinkId = linkId . Value ;
}
2019-04-05 22:15:17 -04:00
// link was found but they don't have an exact alias
else if ( ! aliasId . HasValue & & linkId . HasValue )
2019-04-05 14:34:03 -04:00
{
2019-04-05 22:15:17 -04:00
client . AliasLinkId = linkId . Value ;
client . CurrentAlias = new Alias ( )
{
Name = entity . Name ,
DateAdded = DateTime . UtcNow ,
IPAddress = entity . IPAddress ,
LinkId = linkId . Value
} ;
2019-04-05 14:34:03 -04:00
}
2019-04-05 22:15:17 -04:00
// brand new players (supposedly)
2019-04-05 14:34:03 -04:00
else
{
2019-04-05 22:15:17 -04:00
client . AliasLink = new EFAliasLink ( ) ;
2019-04-05 14:34:03 -04:00
client . CurrentAlias = new Alias ( )
{
Name = entity . Name ,
DateAdded = DateTime . UtcNow ,
IPAddress = entity . IPAddress ,
Link = client . AliasLink ,
} ;
}
2018-11-25 21:00:36 -05:00
context . Clients . Add ( client ) ;
await context . SaveChangesAsync ( ) ;
return client ;
}
}
2019-01-02 19:32:39 -05:00
private async Task UpdateAlias ( string name , int? ip , EFClient entity , DatabaseContext context )
2018-11-25 21:00:36 -05:00
{
2019-01-02 19:32:39 -05:00
// entity is the tracked db context item
// get all aliases by IP address and LinkId
var iqAliases = context . Aliases
. Include ( a = > a . Link )
2019-04-02 21:20:37 -04:00
// we only want alias that have the same IP address or share a link
2019-04-05 14:34:03 -04:00
. Where ( _alias = > _alias . IPAddress = = ip | | ( _alias . LinkId = = entity . AliasLinkId ) ) ;
2018-12-03 20:21:13 -05:00
#if DEBUG = = true
2019-01-02 19:32:39 -05:00
var aliasSql = iqAliases . ToSql ( ) ;
2018-12-03 20:21:13 -05:00
#endif
2019-01-02 19:32:39 -05:00
var aliases = await iqAliases . ToListAsync ( ) ;
2018-12-31 21:52:19 -05:00
2019-04-05 14:34:03 -04:00
// update each of the aliases where this is no IP but the name is identical
foreach ( var alias in aliases . Where ( _alias = > ( _alias . IPAddress = = null | | _alias . IPAddress = = 0 ) ) )
{
alias . IPAddress = ip ;
}
await context . SaveChangesAsync ( ) ;
2019-01-02 19:32:39 -05:00
// see if they have a matching IP + Name but new NetworkId
var existingExactAlias = aliases . FirstOrDefault ( a = > a . Name = = name & & a . IPAddress = = ip ) ;
2019-04-02 21:20:37 -04:00
bool hasExactAliasMatch = existingExactAlias ! = null ;
2018-11-25 21:00:36 -05:00
2019-01-02 19:32:39 -05:00
// if existing alias matches link them
2019-04-02 21:20:37 -04:00
var newAliasLink = existingExactAlias ? . Link ;
// if no exact matches find the first IP or LinkId that matches
newAliasLink = newAliasLink ? ? aliases . FirstOrDefault ( ) ? . Link ;
// if no matches are found, use our current one ( it will become permanent )
newAliasLink = newAliasLink ? ? entity . AliasLink ;
2019-01-02 19:32:39 -05:00
bool hasExistingAlias = aliases . Count > 0 ;
2019-04-02 21:20:37 -04:00
bool isAliasLinkUpdated = newAliasLink . AliasLinkId ! = entity . AliasLink . AliasLinkId ;
2019-01-02 19:32:39 -05:00
2019-04-02 21:20:37 -04:00
// this happens when the link we found is different than the one we create before adding an IP
if ( isAliasLinkUpdated )
2019-01-02 19:32:39 -05:00
{
2019-04-02 21:20:37 -04:00
entity . CurrentServer . Logger . WriteDebug ( $"found a link for {entity} so we are updating link from {entity.AliasLink.AliasLinkId} to {newAliasLink.AliasLinkId}" ) ;
2019-01-02 19:32:39 -05:00
2019-04-02 21:20:37 -04:00
var oldAliasLink = entity . AliasLink ;
2019-01-02 19:32:39 -05:00
2019-04-05 22:15:17 -04:00
// update all the clients that have the old alias link
await context . Clients
. Where ( _client = > _client . AliasLinkId = = oldAliasLink . AliasLinkId )
. ForEachAsync ( _client = > _client . AliasLinkId = newAliasLink . AliasLinkId ) ;
2019-04-02 21:20:37 -04:00
entity . AliasLink = newAliasLink ;
entity . AliasLinkId = newAliasLink . AliasLinkId ;
2018-12-31 21:52:19 -05:00
2019-04-02 21:20:37 -04:00
// update all previous aliases
await context . Aliases
. Where ( _alias = > _alias . LinkId = = oldAliasLink . AliasLinkId )
2019-04-05 14:34:03 -04:00
. ForEachAsync ( _alias = > _alias . LinkId = newAliasLink . AliasLinkId ) ;
2018-12-31 21:52:19 -05:00
2019-04-02 21:20:37 -04:00
await context . SaveChangesAsync ( ) ;
// we want to delete the now inactive alias
context . AliasLinks . Remove ( oldAliasLink ) ;
await context . SaveChangesAsync ( ) ;
2019-01-02 19:32:39 -05:00
}
2018-12-31 21:52:19 -05:00
2019-01-02 19:32:39 -05:00
// the existing alias matches ip and name, so we can just ignore the temporary one
2019-04-02 21:20:37 -04:00
if ( hasExactAliasMatch )
2019-01-02 19:32:39 -05:00
{
entity . CurrentServer . Logger . WriteDebug ( $"{entity} has exact alias match" ) ;
2019-04-02 21:20:37 -04:00
var oldAlias = entity . CurrentAlias ;
2019-01-02 19:32:39 -05:00
entity . CurrentAliasId = existingExactAlias . AliasId ;
entity . CurrentAlias = existingExactAlias ;
await context . SaveChangesAsync ( ) ;
2019-04-02 21:20:37 -04:00
// the alias is the same so we can just remove it
if ( oldAlias . AliasId ! = existingExactAlias . AliasId )
{
context . Aliases . Remove ( oldAlias ) ;
await context . SaveChangesAsync ( ) ;
}
2019-01-02 19:32:39 -05:00
}
2018-11-25 21:00:36 -05:00
2019-01-02 19:32:39 -05:00
// theres no exact match, but they've played before with the GUID or IP
2019-04-05 14:34:03 -04:00
else
2019-01-02 19:32:39 -05:00
{
2019-04-02 21:20:37 -04:00
entity . CurrentServer . Logger . WriteDebug ( $"Connecting player is using a new alias {entity}" ) ;
2018-12-31 21:52:19 -05:00
2019-04-05 14:34:03 -04:00
var newAlias = new EFAlias ( )
2019-01-02 19:32:39 -05:00
{
2019-04-05 14:34:03 -04:00
DateAdded = DateTime . UtcNow ,
IPAddress = ip ,
LinkId = newAliasLink . AliasLinkId ,
Name = name ,
Active = true ,
} ;
2018-12-31 21:52:19 -05:00
2019-04-05 14:34:03 -04:00
entity . CurrentAlias = newAlias ;
entity . CurrentAliasId = 0 ;
2019-01-02 19:32:39 -05:00
await context . SaveChangesAsync ( ) ;
}
2019-04-02 21:20:37 -04:00
}
2018-11-25 21:00:36 -05:00
2019-04-02 21:20:37 -04:00
/// <summary>
/// updates the permission level of the given target to the given permission level
/// </summary>
/// <param name="newPermission"></param>
/// <param name="temporalClient"></param>
/// <param name="origin"></param>
/// <param name="ctx"></param>
/// <returns></returns>
public async Task UpdateLevel ( Permission newPermission , EFClient temporalClient , EFClient origin )
{
using ( var ctx = new DatabaseContext ( ) )
2019-01-02 19:32:39 -05:00
{
2019-04-02 21:20:37 -04:00
var entity = await ctx . Clients
. Where ( _client = > _client . AliasLinkId = = temporalClient . AliasLinkId )
. FirstAsync ( ) ;
2019-04-05 14:34:03 -04:00
var oldPermission = entity . Level ;
entity . Level = newPermission ;
await ctx . SaveChangesAsync ( ) ;
#if DEBUG = = true
temporalClient . CurrentServer . Logger . WriteDebug ( $"Updated {temporalClient.ClientId} to {newPermission}" ) ;
#endif
2019-04-02 21:20:37 -04:00
// if their permission level has been changed to level that needs to be updated on all accounts
2019-04-05 14:34:03 -04:00
if ( ( oldPermission ! = newPermission ) & &
2019-04-02 21:20:37 -04:00
( newPermission = = Permission . Banned | |
newPermission = = Permission . Flagged | |
newPermission = = Permission . User ) )
2018-12-03 20:21:13 -05:00
{
2019-04-02 21:20:37 -04:00
var changeSvc = new ChangeHistoryService ( ) ;
2019-04-05 14:34:03 -04:00
//get all clients that have the same linkId
2019-04-02 21:20:37 -04:00
var iqMatchingClients = ctx . Clients
2019-04-05 14:34:03 -04:00
. Where ( _client = > _client . AliasLinkId = = entity . AliasLinkId ) ;
// make sure we don't select ourselves twice
//.Where(_client => _client.ClientId != temporalClient.ClientId);
2019-04-02 21:20:37 -04:00
// this updates the level for all the clients with the same LinkId
// only if their new level is flagged or banned
2019-04-05 14:34:03 -04:00
await iqMatchingClients . ForEachAsync ( async ( _client ) = >
2019-04-02 21:20:37 -04:00
{
2019-04-05 14:34:03 -04:00
_client . Level = newPermission ;
2019-04-02 21:20:37 -04:00
// hack this saves our change to the change history log
await changeSvc . Add ( new GameEvent ( )
{
Type = GameEvent . EventType . ChangePermission ,
Extra = newPermission ,
Origin = origin ,
2019-04-05 14:34:03 -04:00
Target = _client
2019-04-02 21:20:37 -04:00
} , ctx ) ;
2019-04-05 14:34:03 -04:00
#if DEBUG = = true
temporalClient . CurrentServer . Logger . WriteDebug ( $"Updated linked {_client.ClientId} to {newPermission}" ) ;
#endif
} ) ;
2019-04-02 21:20:37 -04:00
2019-04-05 14:34:03 -04:00
await ctx . SaveChangesAsync ( ) ;
}
2017-11-25 20:29:58 -05:00
}
2019-04-02 21:20:37 -04:00
temporalClient . Level = newPermission ;
2017-11-25 20:29:58 -05:00
}
public async Task < EFClient > Delete ( EFClient entity )
{
2017-11-29 19:35:50 -05:00
using ( var context = new DatabaseContext ( ) )
2017-11-25 20:29:58 -05:00
{
2017-11-29 19:35:50 -05:00
var client = context . Clients
. Single ( e = > e . ClientId = = entity . ClientId ) ;
2017-11-25 20:29:58 -05:00
entity . Active = false ;
context . Entry ( entity ) . State = EntityState . Modified ;
await context . SaveChangesAsync ( ) ;
return entity ;
}
}
public async Task < IList < EFClient > > Find ( Func < EFClient , bool > e )
{
2018-02-07 00:19:06 -05:00
return await Task . Run ( ( ) = >
{
2018-09-13 15:34:42 -04:00
using ( var context = new DatabaseContext ( true ) )
2018-02-11 20:17:20 -05:00
{
2018-02-07 00:19:06 -05:00
return context . Clients
. Include ( c = > c . CurrentAlias )
. Include ( c = > c . AliasLink . Children )
. Where ( e ) . ToList ( ) ;
2018-02-11 20:17:20 -05:00
}
2018-02-07 00:19:06 -05:00
} ) ;
2017-11-25 20:29:58 -05:00
}
public async Task < EFClient > Get ( int entityID )
{
2018-09-13 15:34:42 -04:00
using ( var context = new DatabaseContext ( true ) )
2018-02-10 23:33:42 -05:00
{
2018-05-14 13:55:10 -04:00
var iqClient = from client in context . Clients
. Include ( c = > c . CurrentAlias )
. Include ( c = > c . AliasLink . Children )
2018-06-02 00:48:10 -04:00
. Include ( c = > c . Meta )
2018-05-14 13:55:10 -04:00
where client . ClientId = = entityID
select new
{
Client = client ,
LinkedAccounts = ( from linkedClient in context . Clients
2018-06-07 22:19:12 -04:00
where client . AliasLinkId = = linkedClient . AliasLinkId
select new
{
linkedClient . ClientId ,
linkedClient . NetworkId
} )
2018-05-14 13:55:10 -04:00
} ;
2018-09-13 15:34:42 -04:00
#if DEBUG = = true
var clientSql = iqClient . ToSql ( ) ;
#endif
2018-05-14 13:55:10 -04:00
var foundClient = await iqClient . FirstOrDefaultAsync ( ) ;
2018-05-24 15:48:57 -04:00
if ( foundClient = = null )
2018-11-25 21:00:36 -05:00
{
2018-05-24 15:48:57 -04:00
return null ;
2018-11-25 21:00:36 -05:00
}
2018-05-24 15:48:57 -04:00
2018-05-14 13:55:10 -04:00
foundClient . Client . LinkedAccounts = new Dictionary < int , long > ( ) ;
// todo: find out the best way to do this
// I'm doing this here because I don't know the best way to have multiple awaits in the query
foreach ( var linked in foundClient . LinkedAccounts )
2018-11-25 21:00:36 -05:00
{
2018-05-14 13:55:10 -04:00
foundClient . Client . LinkedAccounts . Add ( linked . ClientId , linked . NetworkId ) ;
2018-11-25 21:00:36 -05:00
}
2018-05-14 13:55:10 -04:00
return foundClient . Client ;
2018-02-10 23:33:42 -05:00
}
2017-11-25 20:29:58 -05:00
}
2018-09-13 15:34:42 -04:00
private static readonly Func < DatabaseContext , long , Task < EFClient > > _getUniqueQuery =
EF . CompileAsyncQuery ( ( DatabaseContext context , long networkId ) = >
context . Clients
. Include ( c = > c . CurrentAlias )
. Include ( c = > c . AliasLink . Children )
2018-12-29 13:43:40 -05:00
. Include ( c = > c . ReceivedPenalties )
2018-09-13 15:34:42 -04:00
. FirstOrDefault ( c = > c . NetworkId = = networkId )
) ;
2018-02-10 23:33:42 -05:00
public async Task < EFClient > GetUnique ( long entityAttribute )
2017-11-25 20:29:58 -05:00
{
2018-09-13 15:34:42 -04:00
using ( var context = new DatabaseContext ( true ) )
2017-11-25 20:29:58 -05:00
{
2018-09-13 15:34:42 -04:00
return await _getUniqueQuery ( context , entityAttribute ) ;
2017-11-25 20:29:58 -05:00
}
}
2019-04-02 21:20:37 -04:00
public async Task UpdateAlias ( EFClient temporalClient )
2017-11-25 20:29:58 -05:00
{
2017-11-29 19:35:50 -05:00
using ( var context = new DatabaseContext ( ) )
2017-11-25 20:29:58 -05:00
{
2019-04-02 21:20:37 -04:00
var entity = context . Clients
2017-11-29 19:35:50 -05:00
. Include ( c = > c . AliasLink )
2018-02-07 00:19:06 -05:00
. Include ( c = > c . CurrentAlias )
2019-04-02 21:20:37 -04:00
. First ( e = > e . ClientId = = temporalClient . ClientId ) ;
2018-09-11 15:28:37 -04:00
2019-04-02 21:20:37 -04:00
entity . CurrentServer = temporalClient . CurrentServer ;
2019-01-02 19:32:39 -05:00
2019-04-02 21:20:37 -04:00
await UpdateAlias ( temporalClient . Name , temporalClient . IPAddress , entity , context ) ;
2019-01-02 19:32:39 -05:00
2019-04-02 21:20:37 -04:00
temporalClient . CurrentAlias = entity . CurrentAlias ;
temporalClient . CurrentAliasId = entity . CurrentAliasId ;
temporalClient . AliasLink = entity . AliasLink ;
temporalClient . AliasLinkId = entity . AliasLinkId ;
2019-01-02 19:32:39 -05:00
}
}
2019-04-02 21:20:37 -04:00
public async Task < EFClient > Update ( EFClient temporalClient )
2019-01-02 19:32:39 -05:00
{
using ( var context = new DatabaseContext ( ) )
{
// grab the context version of the entity
2019-04-02 21:20:37 -04:00
var entity = context . Clients
. First ( client = > client . ClientId = = temporalClient . ClientId ) ;
2019-01-02 19:32:39 -05:00
2019-04-02 21:20:37 -04:00
entity . LastConnection = temporalClient . LastConnection ;
entity . Connections = temporalClient . Connections ;
entity . FirstConnection = temporalClient . FirstConnection ;
entity . Masked = temporalClient . Masked ;
entity . TotalConnectionTime = temporalClient . TotalConnectionTime ;
entity . Password = temporalClient . Password ;
entity . PasswordSalt = temporalClient . PasswordSalt ;
2017-11-29 19:35:50 -05:00
// update in database
2017-11-25 20:29:58 -05:00
await context . SaveChangesAsync ( ) ;
2019-04-02 21:20:37 -04:00
return entity ;
2017-11-25 20:29:58 -05:00
}
}
2018-09-16 16:34:16 -04:00
#region ServiceSpecific
2017-11-25 20:29:58 -05:00
public async Task < IList < EFClient > > GetOwners ( )
{
2017-11-29 19:35:50 -05:00
using ( var context = new DatabaseContext ( ) )
2018-11-25 21:00:36 -05:00
{
2017-11-29 19:35:50 -05:00
return await context . Clients
2018-11-05 22:01:29 -05:00
. Where ( c = > c . Level = = Permission . Owner )
2017-11-29 19:35:50 -05:00
. ToListAsync ( ) ;
2018-11-25 21:00:36 -05:00
}
2017-11-25 20:29:58 -05:00
}
2019-03-24 22:34:20 -04:00
/// <summary>
/// retrieves the number of owners
/// (client level is owner)
/// </summary>
/// <returns></returns>
public async Task < int > GetOwnerCount ( )
{
using ( var ctx = new DatabaseContext ( true ) )
{
return await ctx . Clients . AsNoTracking ( )
. CountAsync ( _client = > _client . Level = = Permission . Owner ) ;
}
}
2018-11-25 21:00:36 -05:00
public async Task < List < EFClient > > GetPrivilegedClients ( )
2018-02-16 23:24:03 -05:00
{
2018-09-13 15:34:42 -04:00
using ( var context = new DatabaseContext ( disableTracking : true ) )
2018-02-16 23:24:03 -05:00
{
2019-03-24 22:34:20 -04:00
var iqClients = from client in context . Clients . AsNoTracking ( )
2018-11-05 22:01:29 -05:00
where client . Level > = Permission . Trusted
2018-09-13 15:34:42 -04:00
where client . Active
2018-11-25 21:00:36 -05:00
select new EFClient ( )
2018-09-13 15:34:42 -04:00
{
2018-11-25 22:11:55 -05:00
AliasLinkId = client . AliasLinkId ,
2018-11-25 21:00:36 -05:00
CurrentAlias = client . CurrentAlias ,
2018-09-13 15:34:42 -04:00
ClientId = client . ClientId ,
2018-11-25 21:00:36 -05:00
Level = client . Level ,
Password = client . Password ,
2019-01-03 15:39:22 -05:00
PasswordSalt = client . PasswordSalt ,
2019-03-25 22:12:16 -04:00
NetworkId = client . NetworkId ,
LastConnection = client . LastConnection
2018-09-13 15:34:42 -04:00
} ;
#if DEBUG = = true
var clientsSql = iqClients . ToSql ( ) ;
#endif
2018-05-14 13:55:10 -04:00
return await iqClients . ToListAsync ( ) ;
2018-02-11 20:17:20 -05:00
}
2017-11-25 20:29:58 -05:00
}
2018-10-15 20:51:04 -04:00
public async Task < IList < EFClient > > FindClientsByIdentifier ( string identifier )
2018-02-11 20:17:20 -05:00
{
2018-10-15 20:51:04 -04:00
if ( identifier . Length < 3 )
{
2018-04-21 18:18:20 -04:00
return new List < EFClient > ( ) ;
2018-10-15 20:51:04 -04:00
}
2018-04-21 18:18:20 -04:00
2018-10-15 20:51:04 -04:00
identifier = identifier . ToLower ( ) ;
2018-06-07 22:19:12 -04:00
2018-09-16 16:34:16 -04:00
using ( var context = new DatabaseContext ( disableTracking : true ) )
2018-09-11 15:28:37 -04:00
{
2018-10-15 20:51:04 -04:00
long networkId = identifier . ConvertLong ( ) ;
2018-12-16 22:16:56 -05:00
int? ipAddress = identifier . ConvertToIP ( ) ;
2018-06-07 22:19:12 -04:00
2018-09-11 15:28:37 -04:00
var iqLinkIds = ( from alias in context . Aliases
2018-12-19 20:24:31 -05:00
where ( alias . IPAddress ! = null & & alias . IPAddress = = ipAddress ) | |
2018-10-15 20:51:04 -04:00
alias . Name . ToLower ( ) . Contains ( identifier )
select alias . LinkId ) . Distinct ( ) ;
2018-09-11 15:28:37 -04:00
var linkIds = iqLinkIds . ToList ( ) ;
var iqClients = context . Clients
2018-10-15 20:51:04 -04:00
. Where ( c = > linkIds . Contains ( c . AliasLinkId ) | |
networkId = = c . NetworkId )
2018-09-11 15:28:37 -04:00
. Include ( c = > c . CurrentAlias )
. Include ( c = > c . AliasLink . Children ) ;
#if DEBUG = = true
var iqClientsSql = iqClients . ToSql ( ) ;
#endif
2018-02-23 02:06:13 -05:00
return await iqClients . ToListAsync ( ) ;
}
}
2017-11-25 20:29:58 -05:00
public async Task < int > GetTotalClientsAsync ( )
{
2018-09-16 16:34:16 -04:00
using ( var context = new DatabaseContext ( true ) )
2018-11-25 21:00:36 -05:00
{
2017-11-29 19:35:50 -05:00
return await context . Clients
. CountAsync ( ) ;
2018-11-25 21:00:36 -05:00
}
2017-11-25 20:29:58 -05:00
}
public Task < EFClient > CreateProxy ( )
{
throw new NotImplementedException ( ) ;
}
2018-09-16 16:34:16 -04:00
#endregion
2017-11-25 20:29:58 -05:00
}
}