* More work on getting the database framework to actually work
parent
29c869fd33
commit
5e757d2ad1
|
@ -3,7 +3,7 @@ using MySql.Data.MySqlClient;
|
||||||
|
|
||||||
namespace OpenSim.Framework.Data.MySQL
|
namespace OpenSim.Framework.Data.MySQL
|
||||||
{
|
{
|
||||||
public class MySQLDatabaseMapper : OpenSimDatabaseMapper
|
public class MySQLDatabaseMapper : OpenSimDatabaseConnector
|
||||||
{
|
{
|
||||||
public MySQLDatabaseMapper(string connectionString)
|
public MySQLDatabaseMapper(string connectionString)
|
||||||
: base(connectionString)
|
: base(connectionString)
|
||||||
|
|
|
@ -7,7 +7,7 @@ using TribalMedia.Framework.Data;
|
||||||
|
|
||||||
namespace OpenSim.Framework.Data
|
namespace OpenSim.Framework.Data
|
||||||
{
|
{
|
||||||
public class OpenSimDataReader : DataReader
|
public class OpenSimDataReader : BaseDataReader
|
||||||
{
|
{
|
||||||
public OpenSimDataReader(IDataReader source) : base(source)
|
public OpenSimDataReader(IDataReader source) : base(source)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
using System.Data.Common;
|
using System.Data;
|
||||||
using libsecondlife;
|
using libsecondlife;
|
||||||
using TribalMedia.Framework.Data;
|
using TribalMedia.Framework.Data;
|
||||||
|
|
||||||
namespace OpenSim.Framework.Data
|
namespace OpenSim.Framework.Data
|
||||||
{
|
{
|
||||||
public abstract class OpenSimDatabaseMapper : BaseDatabaseConnector
|
public abstract class OpenSimDatabaseConnector : BaseDatabaseConnector
|
||||||
{
|
{
|
||||||
public OpenSimDatabaseMapper(string connectionString) : base(connectionString)
|
public OpenSimDatabaseConnector(string connectionString) : base(connectionString)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,5 +19,10 @@ namespace OpenSim.Framework.Data
|
||||||
|
|
||||||
return base.ConvertToDbType(value);
|
return base.ConvertToDbType(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override BaseDataReader CreateReader(IDataReader reader)
|
||||||
|
{
|
||||||
|
return new OpenSimDataReader(reader);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -44,7 +44,7 @@ namespace OpenSim.Framework.Data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override object GetValue(DataReader reader)
|
protected override object GetValue(BaseDataReader reader)
|
||||||
{
|
{
|
||||||
object value;
|
object value;
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,5 @@ namespace OpenSim.Framework.Data
|
||||||
public OpenSimTableMapper(BaseDatabaseConnector database, string tableName) : base(database, tableName)
|
public OpenSimTableMapper(BaseDatabaseConnector database, string tableName) : base(database, tableName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DataReader CreateReader(IDataReader reader)
|
|
||||||
{
|
|
||||||
return new OpenSimDataReader(reader);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,11 @@ using libsecondlife;
|
||||||
|
|
||||||
namespace OpenSim.Framework.Data
|
namespace OpenSim.Framework.Data
|
||||||
{
|
{
|
||||||
public class PrimitiveBaseShapeRowMapper : RowMapper<PrimitiveBaseShape>
|
public class PrimitiveBaseShapeRowMapper : BaseRowMapper<PrimitiveBaseShape>
|
||||||
{
|
{
|
||||||
public Guid SceneObjectPartId;
|
public Guid SceneObjectPartId;
|
||||||
|
|
||||||
public PrimitiveBaseShapeRowMapper(Schema schema, PrimitiveBaseShape obj) : base(schema, obj)
|
public PrimitiveBaseShapeRowMapper(BaseSchema schema, PrimitiveBaseShape obj) : base(schema, obj)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace OpenSim.Framework.Data
|
||||||
public PrimitiveBaseShapeTableMapper(BaseDatabaseConnector connection, string tableName)
|
public PrimitiveBaseShapeTableMapper(BaseDatabaseConnector connection, string tableName)
|
||||||
: base(connection, tableName)
|
: base(connection, tableName)
|
||||||
{
|
{
|
||||||
ObjectSchema<PrimitiveBaseShapeRowMapper> rowMapperSchema = new ObjectSchema<PrimitiveBaseShapeRowMapper>(this);
|
BaseSchema<PrimitiveBaseShapeRowMapper> rowMapperSchema = new BaseSchema<PrimitiveBaseShapeRowMapper>(this);
|
||||||
m_schema = rowMapperSchema;
|
m_schema = rowMapperSchema;
|
||||||
|
|
||||||
m_keyFieldMapper = rowMapperSchema.AddMapping<Guid>("SceneObjectPartId",
|
m_keyFieldMapper = rowMapperSchema.AddMapping<Guid>("SceneObjectPartId",
|
||||||
|
@ -111,7 +111,7 @@ namespace OpenSim.Framework.Data
|
||||||
delegate(PrimitiveBaseShapeRowMapper shape, byte[] value) { shape.Object.ExtraParams = value; });
|
delegate(PrimitiveBaseShapeRowMapper shape, byte[] value) { shape.Object.ExtraParams = value; });
|
||||||
}
|
}
|
||||||
|
|
||||||
public override PrimitiveBaseShapeRowMapper FromReader(DataReader reader)
|
public override PrimitiveBaseShapeRowMapper FromReader(BaseDataReader reader)
|
||||||
{
|
{
|
||||||
PrimitiveBaseShape shape = new PrimitiveBaseShape();
|
PrimitiveBaseShape shape = new PrimitiveBaseShape();
|
||||||
|
|
||||||
|
|
|
@ -416,7 +416,7 @@ namespace OpenSim.Region.ClientStack
|
||||||
{
|
{
|
||||||
// this will normally trigger at least one packet (ping response)
|
// this will normally trigger at least one packet (ping response)
|
||||||
SendStartPingCheck(0);
|
SendStartPingCheck(0);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -424,7 +424,7 @@ namespace OpenSim.Region.ClientStack
|
||||||
// Something received in the meantime - we can reset the counters
|
// Something received in the meantime - we can reset the counters
|
||||||
m_probesWithNoIngressPackets = 0;
|
m_probesWithNoIngressPackets = 0;
|
||||||
m_lastPacketsReceived = m_packetsReceived;
|
m_lastPacketsReceived = m_packetsReceived;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2311,7 +2311,7 @@ namespace OpenSim.Region.ClientStack
|
||||||
if (Pack.Header.Reliable) //DIRTY HACK
|
if (Pack.Header.Reliable) //DIRTY HACK
|
||||||
{
|
{
|
||||||
AddAck(Pack); // this adds the need to ack this packet later
|
AddAck(Pack); // this adds the need to ack this packet later
|
||||||
|
|
||||||
|
|
||||||
if (Pack.Type != PacketType.PacketAck && Pack.Type != PacketType.LogoutRequest)
|
if (Pack.Type != PacketType.PacketAck && Pack.Type != PacketType.LogoutRequest)
|
||||||
{
|
{
|
||||||
|
@ -2355,10 +2355,15 @@ namespace OpenSim.Region.ClientStack
|
||||||
{
|
{
|
||||||
lock (m_needAck)
|
lock (m_needAck)
|
||||||
{
|
{
|
||||||
foreach (uint ack in NewPack.Header.AckList)
|
foreach (uint ackedPacketId in NewPack.Header.AckList)
|
||||||
{
|
{
|
||||||
m_unAckedBytes -= m_needAck[ack].ToBytes().Length;
|
Packet ackedPacket;
|
||||||
m_needAck.Remove(ack);
|
|
||||||
|
if (m_needAck.TryGetValue(ackedPacketId, out ackedPacket))
|
||||||
|
{
|
||||||
|
m_unAckedBytes -= ackedPacket.ToBytes().Length;
|
||||||
|
m_needAck.Remove(ackedPacketId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2373,18 +2378,12 @@ namespace OpenSim.Region.ClientStack
|
||||||
{
|
{
|
||||||
foreach (PacketAckPacket.PacketsBlock block in ackPacket.Packets)
|
foreach (PacketAckPacket.PacketsBlock block in ackPacket.Packets)
|
||||||
{
|
{
|
||||||
if (m_needAck.ContainsKey(block.ID))
|
uint ackedPackId = block.ID;
|
||||||
|
Packet ackedPacket;
|
||||||
|
if (m_needAck.TryGetValue(ackedPackId, out ackedPacket))
|
||||||
{
|
{
|
||||||
try
|
m_unAckedBytes -= ackedPacket.ToBytes().Length;
|
||||||
{
|
m_needAck.Remove(ackedPackId);
|
||||||
m_unAckedBytes -= m_needAck[block.ID].ToBytes().Length;
|
|
||||||
m_needAck.Remove(block.ID);
|
|
||||||
}
|
|
||||||
catch (System.Collections.Generic.KeyNotFoundException)
|
|
||||||
{
|
|
||||||
// Did another packet come in with the ack already?
|
|
||||||
// apparently so!
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2500,11 +2499,11 @@ namespace OpenSim.Region.ClientStack
|
||||||
|
|
||||||
protected void AckTimer_Elapsed(object sender, ElapsedEventArgs ea)
|
protected void AckTimer_Elapsed(object sender, ElapsedEventArgs ea)
|
||||||
{
|
{
|
||||||
|
|
||||||
SendAcks();
|
SendAcks();
|
||||||
ResendUnacked();
|
ResendUnacked();
|
||||||
SendPacketStats();
|
SendPacketStats();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SendPacketStats()
|
protected void SendPacketStats()
|
||||||
|
|
|
@ -30,11 +30,11 @@ using System.IO;
|
||||||
|
|
||||||
namespace TribalMedia.Framework.Data
|
namespace TribalMedia.Framework.Data
|
||||||
{
|
{
|
||||||
public class DataReader
|
public class BaseDataReader
|
||||||
{
|
{
|
||||||
private readonly IDataReader m_source;
|
private readonly IDataReader m_source;
|
||||||
|
|
||||||
public DataReader(IDataReader source)
|
public BaseDataReader(IDataReader source)
|
||||||
{
|
{
|
||||||
m_source = source;
|
m_source = source;
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,5 +135,7 @@ namespace TribalMedia.Framework.Data
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract BaseDataReader CreateReader(IDataReader reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -29,10 +29,7 @@ using System.Collections.Generic;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
|
|
||||||
namespace TribalMedia.Framework.Data
|
namespace TribalMedia.Framework.Data
|
||||||
{
|
{
|
||||||
//public delegate TField RowMapperGetAccessor<TRowMapper, TField>(TRowMapper rowMapper);
|
|
||||||
//public delegate void RowMapperSetAccessor<TRowMapper, TField>(TRowMapper rowMapper, TField value);
|
|
||||||
|
|
||||||
public delegate TField ObjectGetAccessor<TObj, TField>(TObj obj);
|
public delegate TField ObjectGetAccessor<TObj, TField>(TObj obj);
|
||||||
public delegate void ObjectSetAccessor<TObj, TField>(TObj obj, TField value);
|
public delegate void ObjectSetAccessor<TObj, TField>(TObj obj, TField value);
|
||||||
|
|
||||||
|
@ -62,7 +59,7 @@ namespace TribalMedia.Framework.Data
|
||||||
m_tableMapper = tableMapper;
|
m_tableMapper = tableMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void SetPropertyFromReader(object mapper, DataReader reader);
|
public abstract void SetPropertyFromReader(object mapper, BaseDataReader reader);
|
||||||
|
|
||||||
public void RawAddParam(DbCommand command, List<string> fieldNames, string fieldName, object value)
|
public void RawAddParam(DbCommand command, List<string> fieldNames, string fieldName, object value)
|
||||||
{
|
{
|
||||||
|
@ -84,7 +81,7 @@ namespace TribalMedia.Framework.Data
|
||||||
RawAddParam(command, fieldNames, fieldName, m_tableMapper.ConvertToDbType(value));
|
RawAddParam(command, fieldNames, fieldName, m_tableMapper.ConvertToDbType(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual object GetValue(DataReader reader)
|
protected virtual object GetValue(BaseDataReader reader)
|
||||||
{
|
{
|
||||||
object value;
|
object value;
|
||||||
|
|
||||||
|
@ -128,43 +125,6 @@ namespace TribalMedia.Framework.Data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//public class RowMapperField<TRowMapper, TField> : FieldMapper
|
|
||||||
// where TRowMapper : RowMapper
|
|
||||||
//{
|
|
||||||
// private readonly RowMapperGetAccessor<TRowMapper, TField> m_fieldGetAccessor;
|
|
||||||
// private readonly RowMapperSetAccessor<TRowMapper, TField> m_fieldSetAccessor;
|
|
||||||
|
|
||||||
// public override object GetParamValue(object obj)
|
|
||||||
// {
|
|
||||||
// return m_fieldGetAccessor((TRowMapper) obj);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public override void SetPropertyFromReader(object mapper, DataReader reader)
|
|
||||||
// {
|
|
||||||
// object value;
|
|
||||||
|
|
||||||
// value = GetValue(reader);
|
|
||||||
|
|
||||||
// if (value == null)
|
|
||||||
// {
|
|
||||||
// m_fieldSetAccessor((TRowMapper) mapper, default(TField));
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// m_fieldSetAccessor((TRowMapper) mapper, (TField) value);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// public RowMapperField(TableMapper tableMapper, string fieldName, RowMapperGetAccessor<TRowMapper, TField> rowMapperGetAccessor,
|
|
||||||
// RowMapperSetAccessor<TRowMapper, TField> rowMapperSetAccessor)
|
|
||||||
// : base(tableMapper, fieldName, typeof(TField))
|
|
||||||
// {
|
|
||||||
// m_fieldGetAccessor = rowMapperGetAccessor;
|
|
||||||
// m_fieldSetAccessor = rowMapperSetAccessor;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
public class ObjectField<TObject, TField> : BaseFieldMapper
|
public class ObjectField<TObject, TField> : BaseFieldMapper
|
||||||
{
|
{
|
||||||
private readonly ObjectGetAccessor<TObject, TField> m_fieldGetAccessor;
|
private readonly ObjectGetAccessor<TObject, TField> m_fieldGetAccessor;
|
||||||
|
@ -175,7 +135,7 @@ namespace TribalMedia.Framework.Data
|
||||||
return m_fieldGetAccessor((TObject)obj);
|
return m_fieldGetAccessor((TObject)obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetPropertyFromReader(object obj, DataReader reader)
|
public override void SetPropertyFromReader(object obj, BaseDataReader reader)
|
||||||
{
|
{
|
||||||
object value;
|
object value;
|
||||||
|
|
||||||
|
|
|
@ -28,14 +28,14 @@ using TribalMedia.Framework.Data;
|
||||||
|
|
||||||
namespace TribalMedia.Framework.Data
|
namespace TribalMedia.Framework.Data
|
||||||
{
|
{
|
||||||
public abstract class RowMapper
|
public abstract class BaseRowMapper
|
||||||
{
|
{
|
||||||
public abstract void FillObject(DataReader reader);
|
public abstract void FillObject(BaseDataReader reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ObjectMapper<TObj> : RowMapper
|
public class BaseRowMapper<TObj> : BaseRowMapper
|
||||||
{
|
{
|
||||||
private readonly Schema m_schema;
|
private readonly BaseSchema m_schema;
|
||||||
private readonly TObj m_obj;
|
private readonly TObj m_obj;
|
||||||
|
|
||||||
public TObj Object
|
public TObj Object
|
||||||
|
@ -43,38 +43,13 @@ namespace TribalMedia.Framework.Data
|
||||||
get { return m_obj; }
|
get { return m_obj; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectMapper(Schema schema, TObj obj)
|
public BaseRowMapper(BaseSchema schema, TObj obj)
|
||||||
{
|
{
|
||||||
m_schema = schema;
|
m_schema = schema;
|
||||||
m_obj = obj;
|
m_obj = obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void FillObject(DataReader reader)
|
public override void FillObject(BaseDataReader reader)
|
||||||
{
|
|
||||||
foreach (BaseFieldMapper fieldMapper in m_schema.Fields.Values)
|
|
||||||
{
|
|
||||||
fieldMapper.SetPropertyFromReader(m_obj, reader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RowMapper<TObj> : RowMapper
|
|
||||||
{
|
|
||||||
private readonly Schema m_schema;
|
|
||||||
private readonly TObj m_obj;
|
|
||||||
|
|
||||||
public TObj Object
|
|
||||||
{
|
|
||||||
get { return m_obj; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public RowMapper(Schema schema, TObj obj)
|
|
||||||
{
|
|
||||||
m_schema = schema;
|
|
||||||
m_obj = obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void FillObject(DataReader reader)
|
|
||||||
{
|
{
|
||||||
foreach (BaseFieldMapper fieldMapper in m_schema.Fields.Values)
|
foreach (BaseFieldMapper fieldMapper in m_schema.Fields.Values)
|
||||||
{
|
{
|
||||||
|
|
|
@ -29,7 +29,7 @@ using TribalMedia.Framework.Data;
|
||||||
|
|
||||||
namespace TribalMedia.Framework.Data
|
namespace TribalMedia.Framework.Data
|
||||||
{
|
{
|
||||||
public class Schema
|
public class BaseSchema
|
||||||
{
|
{
|
||||||
protected BaseTableMapper m_tableMapper;
|
protected BaseTableMapper m_tableMapper;
|
||||||
protected Dictionary<string, BaseFieldMapper> m_mappings;
|
protected Dictionary<string, BaseFieldMapper> m_mappings;
|
||||||
|
@ -39,16 +39,16 @@ namespace TribalMedia.Framework.Data
|
||||||
get { return m_mappings; }
|
get { return m_mappings; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public Schema(BaseTableMapper tableMapper)
|
public BaseSchema(BaseTableMapper tableMapper)
|
||||||
{
|
{
|
||||||
m_mappings = new Dictionary<string, BaseFieldMapper>();
|
m_mappings = new Dictionary<string, BaseFieldMapper>();
|
||||||
m_tableMapper = tableMapper;
|
m_tableMapper = tableMapper;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ObjectSchema<TObj> : Schema
|
public class BaseSchema<TObj> : BaseSchema
|
||||||
{
|
{
|
||||||
public ObjectSchema(BaseTableMapper tableMapper)
|
public BaseSchema(BaseTableMapper tableMapper)
|
||||||
: base(tableMapper)
|
: base(tableMapper)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -65,26 +65,4 @@ namespace TribalMedia.Framework.Data
|
||||||
return rowMapperField;
|
return rowMapperField;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//public class RowMapperSchema<TRowMapper> : Schema
|
|
||||||
// where TRowMapper : RowMapper
|
|
||||||
//{
|
|
||||||
// public RowMapperSchema(TableMapper tableMapper) : base(tableMapper)
|
|
||||||
// {
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public RowMapperField<TRowMapper, TField> AddMapping<TField>(string fieldName,
|
|
||||||
// RowMapperGetAccessor<TRowMapper, TField>
|
|
||||||
// rowMapperGetAccessor,
|
|
||||||
// RowMapperSetAccessor<TRowMapper, TField>
|
|
||||||
// rowMapperSetAccessor)
|
|
||||||
// {
|
|
||||||
// RowMapperField<TRowMapper, TField> rowMapperField =
|
|
||||||
// new RowMapperField<TRowMapper, TField>(m_tableMapper, fieldName, rowMapperGetAccessor, rowMapperSetAccessor);
|
|
||||||
|
|
||||||
// m_mappings.Add(fieldName, rowMapperField);
|
|
||||||
|
|
||||||
// return rowMapperField;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
|
@ -62,8 +62,8 @@ namespace TribalMedia.Framework.Data
|
||||||
get { return m_tableName; }
|
get { return m_tableName; }
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Schema m_schema;
|
protected BaseSchema m_schema;
|
||||||
public Schema Schema
|
public BaseSchema Schema
|
||||||
{
|
{
|
||||||
get { return m_schema; }
|
get { return m_schema; }
|
||||||
}
|
}
|
||||||
|
@ -110,9 +110,9 @@ namespace TribalMedia.Framework.Data
|
||||||
return m_database.ConvertToDbType(value);
|
return m_database.ConvertToDbType(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual DataReader CreateReader(IDataReader reader)
|
protected virtual BaseDataReader CreateReader(IDataReader reader)
|
||||||
{
|
{
|
||||||
return new DataReader(reader);
|
return m_database.CreateReader(reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ namespace TribalMedia.Framework.Data
|
||||||
{
|
{
|
||||||
if (reader.Read())
|
if (reader.Read())
|
||||||
{
|
{
|
||||||
result = FromReader(CreateReader(reader));
|
result = FromReader( CreateReader(reader));
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -240,6 +240,6 @@ namespace TribalMedia.Framework.Data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract TRowMapper FromReader(DataReader reader);
|
public abstract TRowMapper FromReader(BaseDataReader reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue