add files
parent
2dee27556a
commit
a150ce2898
|
@ -0,0 +1,44 @@
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<Project frameworkVersion="v4_6" name="RegionImages" path="addon-modules/RegionImages/src" type="Exe">
|
||||||
|
<Configuration name="Debug">
|
||||||
|
<Options>
|
||||||
|
<OutputPath>../../bin/</OutputPath>
|
||||||
|
<AllowUnsafe>true</AllowUnsafe>
|
||||||
|
</Options>
|
||||||
|
</Configuration>
|
||||||
|
<Configuration name="Release">
|
||||||
|
<Options>
|
||||||
|
<OutputPath>../../bin/</OutputPath>
|
||||||
|
<AllowUnsafe>true</AllowUnsafe>
|
||||||
|
</Options>
|
||||||
|
</Configuration>
|
||||||
|
|
||||||
|
<ReferencePath>../../bin/</ReferencePath>
|
||||||
|
|
||||||
|
<Reference name="System"/>
|
||||||
|
<Reference name="System.Core"/>
|
||||||
|
<Reference name="System.Xml"/>
|
||||||
|
<Reference name="System.Data"/>
|
||||||
|
<Reference name="System.Drawing"/>
|
||||||
|
<Reference name="System.Runtime.Remoting"/>
|
||||||
|
<Reference name="Nini" path="../../bin/"/>
|
||||||
|
<Reference name="log4net" path="../../bin/"/>
|
||||||
|
<Reference name="XMLRPC" path="../../bin/"/>
|
||||||
|
<Reference name="MySql.Data" path="../../bin/"/>
|
||||||
|
<Reference name="OpenMetaverseTypes" path="../../bin/"/>
|
||||||
|
<Reference name="OpenMetaverse" path="../../bin/"/>
|
||||||
|
<Reference name="OpenSim.Region.Framework"/>
|
||||||
|
<Reference name="OpenSim.Framework"/>
|
||||||
|
<Reference name="OpenSim.Framework.Console"/>
|
||||||
|
<Reference name="OpenSim.Framework.Servers"/>
|
||||||
|
<Reference name="OpenSim.Framework.Servers.HttpServer"/>
|
||||||
|
<Reference name="OpenSim.Services.Interfaces"/>
|
||||||
|
<Reference name="OpenSim.Server.Base"/>
|
||||||
|
|
||||||
|
|
||||||
|
<Files>
|
||||||
|
<Match pattern="*.cs" recurse="true">
|
||||||
|
<Exclude name="Tests" pattern="Tests"/>
|
||||||
|
</Match>
|
||||||
|
</Files>
|
||||||
|
</Project>
|
|
@ -0,0 +1,167 @@
|
||||||
|
using MySql.Data.MySqlClient;
|
||||||
|
using Nini.Config;
|
||||||
|
using OpenMetaverse;
|
||||||
|
using OpenSim.Framework;
|
||||||
|
using OpenSim.Services.Connectors;
|
||||||
|
using OpenSim.Services.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static OpenMetaverse.Primitive;
|
||||||
|
|
||||||
|
namespace GetRegionTextures
|
||||||
|
{
|
||||||
|
class GetRegionTextures
|
||||||
|
{
|
||||||
|
private static MySqlConnection m_mySQLConnection = null;
|
||||||
|
private static String m_assetURL = "http://172.21.0.150:5738/assets/";
|
||||||
|
|
||||||
|
private static List<UUID> m_assets = new List<UUID>();
|
||||||
|
|
||||||
|
private static List<MySQLPrimObject> getAllScenePrims(String regionID)
|
||||||
|
{
|
||||||
|
List<MySQLPrimObject> returnData = new List<MySQLPrimObject>();
|
||||||
|
|
||||||
|
MySqlCommand _mysqlCommand = m_mySQLConnection.CreateCommand();
|
||||||
|
_mysqlCommand.CommandTimeout = int.MaxValue;
|
||||||
|
_mysqlCommand.CommandText = "SELECT prims.Name,prims.UUID,primshapes.Texture FROM prims LEFT JOIN primshapes ON prims.UUID = primshapes.UUID WHERE RegionUUID = ?RegionUUID";
|
||||||
|
_mysqlCommand.Parameters.AddWithValue("RegionUUID", regionID);
|
||||||
|
|
||||||
|
MySqlDataReader _commandReader = _mysqlCommand.ExecuteReader();
|
||||||
|
|
||||||
|
while (_commandReader.Read())
|
||||||
|
returnData.Add(new MySQLPrimObject(_commandReader.GetString(0), _commandReader.GetString(1), (byte[])_commandReader["Texture"]));
|
||||||
|
|
||||||
|
_commandReader.Close();
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> getAllRegionIDsFromStorage()
|
||||||
|
{
|
||||||
|
List<String> returnData = new List<String>();
|
||||||
|
|
||||||
|
MySqlCommand _mysqlCommand = m_mySQLConnection.CreateCommand();
|
||||||
|
_mysqlCommand.CommandTimeout = int.MaxValue;
|
||||||
|
_mysqlCommand.CommandText = "SELECT RegionUUID FROM terrain";
|
||||||
|
|
||||||
|
MySqlDataReader _commandReader = _mysqlCommand.ExecuteReader();
|
||||||
|
|
||||||
|
while (_commandReader.Read())
|
||||||
|
returnData.Add(_commandReader.GetString(0));
|
||||||
|
|
||||||
|
_commandReader.Close();
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
if (!File.Exists("config-include/GridCommon.ini"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
IConfigSource source = new IniConfigSource("config-include/GridCommon.ini");
|
||||||
|
|
||||||
|
if (source.Configs["DatabaseService"] == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (source.Configs["DatabaseService"].GetString("ConnectionString", null) == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_mySQLConnection = new MySqlConnection(source.Configs["DatabaseService"].GetString("ConnectionString", null));
|
||||||
|
m_mySQLConnection.Open();
|
||||||
|
|
||||||
|
if (!Directory.Exists("RegionData"))
|
||||||
|
Directory.CreateDirectory("RegionData");
|
||||||
|
|
||||||
|
List<String> allRegionIDs = getAllRegionIDsFromStorage();
|
||||||
|
|
||||||
|
foreach(String regionID in allRegionIDs)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Start with region '" + regionID + "'");
|
||||||
|
|
||||||
|
if (!Directory.Exists("RegionData/" + regionID))
|
||||||
|
Directory.CreateDirectory("RegionData/" + regionID);
|
||||||
|
|
||||||
|
if (!Directory.Exists("RegionData/assets"))
|
||||||
|
Directory.CreateDirectory("RegionData/assets");
|
||||||
|
|
||||||
|
if (!Directory.Exists("RegionData/" + regionID + "/images"))
|
||||||
|
Directory.CreateDirectory("RegionData/" + regionID + "/images");
|
||||||
|
|
||||||
|
List<MySQLPrimObject> prims = getAllScenePrims(regionID);
|
||||||
|
|
||||||
|
int primCounter = 0;
|
||||||
|
foreach(MySQLPrimObject prim in prims)
|
||||||
|
{
|
||||||
|
Console.WriteLine(" ["+ ++primCounter + " / "+ prims .Count+ "]Start with scene object '" + prim.Name + "'");
|
||||||
|
Primitive.TextureEntry textureEntry = new Primitive.TextureEntry(prim.RawTextueData, 0, prim.RawTextueData.Length);
|
||||||
|
|
||||||
|
foreach (TextureEntryFace faceEntry in textureEntry.FaceTextures)
|
||||||
|
{
|
||||||
|
if (faceEntry != null)
|
||||||
|
{
|
||||||
|
if (faceEntry.TextureID != UUID.Zero)
|
||||||
|
{
|
||||||
|
prim.Texture.Add(faceEntry.TextureID);
|
||||||
|
Console.WriteLine(" Found texture '" + faceEntry.TextureID + "'");
|
||||||
|
|
||||||
|
if (!m_assets.Contains(faceEntry.TextureID))
|
||||||
|
m_assets.Add(faceEntry.TextureID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create CSV
|
||||||
|
List<String> entrys = new List<String>();
|
||||||
|
foreach (MySQLPrimObject prim in prims)
|
||||||
|
{
|
||||||
|
String newEntry = prim.Name + ";" + prim.ID.ToString() + ";";
|
||||||
|
|
||||||
|
foreach (UUID texturID in prim.Texture)
|
||||||
|
newEntry += texturID.ToString() + ";";
|
||||||
|
|
||||||
|
entrys.Add(newEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllLines("RegionData/" + regionID + "/result.csv", entrys.ToArray());
|
||||||
|
|
||||||
|
//Start Asset Download
|
||||||
|
using (WebClient assetHTTPClient = new WebClient())
|
||||||
|
{
|
||||||
|
int downloadCounter = 0;
|
||||||
|
|
||||||
|
foreach(UUID assetID in m_assets)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Console.WriteLine("[" + ++downloadCounter + " / " + m_assets.Count + "] Download asset " + assetID.ToString());
|
||||||
|
if(!File.Exists("RegionData/assets/" + assetID.ToString() + ".jpeg2000"))
|
||||||
|
assetHTTPClient.DownloadFile(m_assetURL + assetID.ToString() + "/data", "RegionData/assets/" + assetID.ToString() + ".jpeg2000");
|
||||||
|
|
||||||
|
if (!File.Exists("RegionData/" + regionID + "/images/" + assetID.ToString() + ".png"))
|
||||||
|
{
|
||||||
|
Process process = new Process();
|
||||||
|
process.StartInfo.FileName = "ffmpeg.exe";
|
||||||
|
process.StartInfo.Arguments = "-i RegionData/assets/" + assetID.ToString() + ".jpeg2000 RegionData/" + regionID + "/images/" + assetID.ToString() + ".png";
|
||||||
|
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||||
|
process.Start();
|
||||||
|
process.WaitForExit();
|
||||||
|
}
|
||||||
|
}catch(Exception error)
|
||||||
|
{
|
||||||
|
Console.WriteLine("[" + downloadCounter + " / " + m_assets.Count + "] Download failed for asset " + assetID.ToString() + ": " + error.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
using OpenMetaverse;
|
||||||
|
using OpenMetaverse.Assets;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GetRegionTextures
|
||||||
|
{
|
||||||
|
class IDTools
|
||||||
|
{
|
||||||
|
public static void addToList(ref List<String> _list, List<String> _input)
|
||||||
|
{
|
||||||
|
foreach (String _entry in _input)
|
||||||
|
{
|
||||||
|
addToList(ref _list, _entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addToList(ref List<String> _list, List<TaskInventoryItemElement> _input)
|
||||||
|
{
|
||||||
|
foreach (TaskInventoryItemElement _entry in _input)
|
||||||
|
{
|
||||||
|
addToList(ref _list, _entry.AssetID.UUID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addToList(ref List<String> _list, String _input)
|
||||||
|
{
|
||||||
|
if (_input == "00000000-0000-0000-0000-000000000000")
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_input == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!_list.Contains(_input))
|
||||||
|
_list.Add(_input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> getUUIDListFromTexturEntry(String _base64)
|
||||||
|
{
|
||||||
|
List<String> _ausgabe = new List<string>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PrimObject obj = new PrimObject();
|
||||||
|
|
||||||
|
byte[] teData = Convert.FromBase64String(_base64);
|
||||||
|
obj.Textures = new Primitive.TextureEntry(teData, 0, teData.Length);
|
||||||
|
|
||||||
|
foreach (Primitive.TextureEntryFace _face in obj.Textures.FaceTextures)
|
||||||
|
{
|
||||||
|
if (_face != null)
|
||||||
|
{
|
||||||
|
_ausgabe.Add(_face.TextureID.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception error)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Cant decode textur data: " + error.Message);
|
||||||
|
}
|
||||||
|
return _ausgabe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> getUUIDListFromString(String _xml)
|
||||||
|
{
|
||||||
|
AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT", TimeSpan.FromSeconds(10));
|
||||||
|
List<String> _ausgabe = new List<string>();
|
||||||
|
String _regex = @"[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Regex _suche = new Regex(_regex);
|
||||||
|
MatchCollection _funde = _suche.Matches(_xml);
|
||||||
|
|
||||||
|
foreach (Match _f in _funde)
|
||||||
|
{
|
||||||
|
String _value = _f.Value.ToUpper().Trim();
|
||||||
|
if (_value.Length < 75 && _value.Length > 10)
|
||||||
|
{
|
||||||
|
if (!_ausgabe.Contains(_value))
|
||||||
|
{
|
||||||
|
_ausgabe.Add(_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception _e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(_e.ToString());
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _ausgabe;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static List<String> getIDListFromSceneObjectGroup(SceneObjectGroup _group)
|
||||||
|
{
|
||||||
|
List<String> _returnList = new List<string>();
|
||||||
|
|
||||||
|
if (_group.RootPart.SceneObjectPart.CollisionSound != null)
|
||||||
|
{
|
||||||
|
if (_group.RootPart.SceneObjectPart.CollisionSound.UUID != null)
|
||||||
|
addToList(ref _returnList, _group.RootPart.SceneObjectPart.CollisionSound.UUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_group.RootPart.SceneObjectPart.SoundID != null)
|
||||||
|
{
|
||||||
|
if (_group.RootPart.SceneObjectPart.SoundID.UUID != null)
|
||||||
|
addToList(ref _returnList, _group.RootPart.SceneObjectPart.SoundID.UUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_group.RootPart.SceneObjectPart.Shape.SculptTexture != null)
|
||||||
|
{
|
||||||
|
if (_group.RootPart.SceneObjectPart.Shape.SculptTexture.UUID != null)
|
||||||
|
addToList(ref _returnList, _group.RootPart.SceneObjectPart.Shape.SculptTexture.UUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_group.RootPart.SceneObjectPart.Shape != null)
|
||||||
|
{
|
||||||
|
if (_group.RootPart.SceneObjectPart.Shape.TextureEntry != null)
|
||||||
|
addToList(ref _returnList, getUUIDListFromTexturEntry(_group.RootPart.SceneObjectPart.Shape.TextureEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_group.RootPart.SceneObjectPart.TaskInventory != null)
|
||||||
|
addToList(ref _returnList, _group.RootPart.SceneObjectPart.TaskInventory);
|
||||||
|
|
||||||
|
if (_group.OtherParts != null)
|
||||||
|
{
|
||||||
|
foreach (PartElement _sope in _group.OtherParts)
|
||||||
|
{
|
||||||
|
if (_sope.SceneObjectPart.CollisionSound != null)
|
||||||
|
{
|
||||||
|
if (_sope.SceneObjectPart.CollisionSound.UUID != null)
|
||||||
|
addToList(ref _returnList, _sope.SceneObjectPart.CollisionSound.UUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sope.SceneObjectPart.SoundID != null)
|
||||||
|
{
|
||||||
|
if (_sope.SceneObjectPart.SoundID.UUID != null)
|
||||||
|
addToList(ref _returnList, _sope.SceneObjectPart.SoundID.UUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sope.SceneObjectPart.Shape.SculptTexture != null)
|
||||||
|
{
|
||||||
|
if (_sope.SceneObjectPart.Shape.SculptTexture.UUID != null)
|
||||||
|
addToList(ref _returnList, _sope.SceneObjectPart.Shape.SculptTexture.UUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sope.SceneObjectPart.Shape != null)
|
||||||
|
{
|
||||||
|
if (_sope.SceneObjectPart.Shape.TextureEntry != null)
|
||||||
|
addToList(ref _returnList, getUUIDListFromTexturEntry(_sope.SceneObjectPart.Shape.TextureEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sope.SceneObjectPart.TaskInventory != null)
|
||||||
|
addToList(ref _returnList, (_sope.SceneObjectPart.TaskInventory));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _returnList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
using OpenMetaverse;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GetRegionTextures
|
||||||
|
{
|
||||||
|
class MySQLPrimObject
|
||||||
|
{
|
||||||
|
public String Name = null;
|
||||||
|
public String ID = null;
|
||||||
|
public byte[] RawTextueData = null;
|
||||||
|
public List<UUID> Texture = new List<UUID>();
|
||||||
|
|
||||||
|
public MySQLPrimObject(String name, String id)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
ID = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MySQLPrimObject(String name, String id, byte[] texturedata)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
ID = id;
|
||||||
|
RawTextueData = texturedata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
|
namespace GetRegionTextures
|
||||||
|
{
|
||||||
|
[Serializable()]
|
||||||
|
[XmlRoot]
|
||||||
|
public class SceneObjectGroup
|
||||||
|
{
|
||||||
|
[XmlElement("RootPart", typeof(RootPartElement))]
|
||||||
|
public RootPartElement RootPart { get; set; }
|
||||||
|
|
||||||
|
[XmlArray("OtherParts")]
|
||||||
|
[XmlArrayItem("Part")]
|
||||||
|
public PartElement[] OtherParts { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot]
|
||||||
|
public class RootPartElement
|
||||||
|
{
|
||||||
|
[XmlElement("SceneObjectPart", typeof(SceneObjectPartElement))]
|
||||||
|
public SceneObjectPartElement SceneObjectPart { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot]
|
||||||
|
public class PartElement
|
||||||
|
{
|
||||||
|
[XmlElement("SceneObjectPart", typeof(SceneObjectPartElement))]
|
||||||
|
public SceneObjectPartElement SceneObjectPart { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot]
|
||||||
|
public class SceneObjectPartElement
|
||||||
|
{
|
||||||
|
[XmlElement("Name", typeof(String))]
|
||||||
|
public String Name { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("Shape", typeof(ShapeElement))]
|
||||||
|
public ShapeElement Shape { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("CollisionSound", typeof(UUIDEntry))]
|
||||||
|
public UUIDEntry CollisionSound { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("SoundID", typeof(UUIDEntry))]
|
||||||
|
public UUIDEntry SoundID { get; set; }
|
||||||
|
|
||||||
|
[XmlArray("TaskInventory")]
|
||||||
|
[XmlArrayItem("TaskInventoryItem")]
|
||||||
|
public List<TaskInventoryItemElement> TaskInventory { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot]
|
||||||
|
public class TaskInventoryItemElement
|
||||||
|
{
|
||||||
|
[XmlElement("Name", typeof(String))]
|
||||||
|
public String Name { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("AssetID", typeof(UUIDEntry))]
|
||||||
|
public UUIDEntry AssetID { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot]
|
||||||
|
public class ShapeElement
|
||||||
|
{
|
||||||
|
[XmlElement("TextureEntry", typeof(String))]
|
||||||
|
public String TextureEntry { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("ExtraParams", typeof(String))]
|
||||||
|
public String ExtraParams { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("SculptTexture", typeof(UUIDEntry))]
|
||||||
|
public UUIDEntry SculptTexture { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot]
|
||||||
|
public class UUIDEntry
|
||||||
|
{
|
||||||
|
[XmlElement("UUID", typeof(String))]
|
||||||
|
public String UUID { get; set; }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue