add a low level LLSDxml encoder for cases where it makes no sense to use more heavy things like OSD, and use it on displaynames
parent
3aff72e403
commit
266eabcad4
|
@ -0,0 +1,457 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) Contributors, http://opensimulator.org/
|
||||||
|
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of the OpenSimulator Project nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||||
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// a class for low level LLSD encoding into a provided StringBuilder
|
||||||
|
// for cases where we already need to know the low level detail
|
||||||
|
// and so using something like OSD or even protbuf is just a pure waste
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
using OpenMetaverse;
|
||||||
|
|
||||||
|
namespace OpenSim.Framework
|
||||||
|
{
|
||||||
|
public static class LLSDxmlEncode
|
||||||
|
{
|
||||||
|
static readonly DateTime depoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
public static void AddStartHeader(StringBuilder sb, bool addxmlversion = false)
|
||||||
|
{
|
||||||
|
if(addxmlversion)
|
||||||
|
sb.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><llsd>"); // legacy llsd xml name still valid
|
||||||
|
else
|
||||||
|
sb.Append("<llsd>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddEndHeader(StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("</llsd>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// map == a list of key value pairs
|
||||||
|
public static void AddStartMap(StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<map>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddEndMap(StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("</map>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddEmpyMap(StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<map />");
|
||||||
|
}
|
||||||
|
|
||||||
|
// array == a list values
|
||||||
|
public static void AddStartArray(StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<array>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddEndArray(StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("</array>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddEmpyArray(StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<array />");
|
||||||
|
}
|
||||||
|
|
||||||
|
// undefined or null
|
||||||
|
public static void AddUnknownElem(StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<undef />");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddElem(bool e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
if(e)
|
||||||
|
sb.Append("<boolean>1</boolean>");
|
||||||
|
else
|
||||||
|
sb.Append("<boolean />");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddElem(int e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
if(e == 0)
|
||||||
|
sb.Append("<integer />");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("<integer>");
|
||||||
|
sb.Append(e.ToString());
|
||||||
|
sb.Append("</integer>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddElem(float e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
if(e == 0)
|
||||||
|
sb.Append("<real />");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("<real>");
|
||||||
|
sb.Append(e.ToString(CultureInfo.InvariantCulture));
|
||||||
|
sb.Append("</real>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddElem(double e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
if(e == 0)
|
||||||
|
sb.Append("<real />");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("<real>");
|
||||||
|
sb.Append(e.ToString(CultureInfo.InvariantCulture));
|
||||||
|
sb.Append("</real>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddElem(UUID e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
if(e == UUID.Zero)
|
||||||
|
sb.Append("<uuid />");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("<uuid>");
|
||||||
|
EscapeToXML(e.ToString(), sb);
|
||||||
|
sb.Append("</uuid>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddElem(string e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
if(String.IsNullOrEmpty(e))
|
||||||
|
sb.Append("<string />");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("<string>");
|
||||||
|
sb.Append(e.ToString());
|
||||||
|
sb.Append("</string>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddRawElem(string e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
if(String.IsNullOrEmpty(e))
|
||||||
|
sb.Append("<string />");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("<string>");
|
||||||
|
sb.Append(e);
|
||||||
|
sb.Append("</string>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddURIElem(Uri e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
if(e == null)
|
||||||
|
{
|
||||||
|
sb.Append("<uri />");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string s;
|
||||||
|
if (e.IsAbsoluteUri)
|
||||||
|
s = e.AbsoluteUri;
|
||||||
|
else
|
||||||
|
s = e.ToString();
|
||||||
|
|
||||||
|
if(String.IsNullOrEmpty(s))
|
||||||
|
sb.Append("<uri />");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("<uri>");
|
||||||
|
sb.Append(s);
|
||||||
|
sb.Append("</uri>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddElem(DateTime e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
DateTime u = e.ToUniversalTime();
|
||||||
|
if(u == depoch)
|
||||||
|
{
|
||||||
|
sb.Append("<date />");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
string format;
|
||||||
|
if(u.Hour == 0 && u.Minute == 0 && u.Second == 0)
|
||||||
|
format = "yyyy-MM-dd";
|
||||||
|
else if (u.Millisecond > 0)
|
||||||
|
format = "yyyy-MM-ddTHH:mm:ss.ffZ";
|
||||||
|
else
|
||||||
|
format = "yyyy-MM-ddTHH:mm:ssZ";
|
||||||
|
sb.Append("<date>");
|
||||||
|
sb.Append(u.ToString(format,CultureInfo.InvariantCulture));
|
||||||
|
sb.Append("</date>");
|
||||||
|
}
|
||||||
|
|
||||||
|
//************ key value *******************
|
||||||
|
// assumes name is a valid llsd key
|
||||||
|
|
||||||
|
public static void AddStartMap(string name, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<key>");
|
||||||
|
sb.Append(name);
|
||||||
|
sb.Append("</key><map>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddEmpyMap(string name, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<key>");
|
||||||
|
sb.Append(name);
|
||||||
|
sb.Append("</key><map />");
|
||||||
|
}
|
||||||
|
|
||||||
|
// array == a list values
|
||||||
|
public static void AddStartArray(string name, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<key>");
|
||||||
|
sb.Append(name);
|
||||||
|
sb.Append("</key><array>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddEmpyArray(string name, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<key>");
|
||||||
|
sb.Append(name);
|
||||||
|
sb.Append("</key><array />");
|
||||||
|
}
|
||||||
|
|
||||||
|
// undefined or null
|
||||||
|
public static void AddUnknownElem(string name, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<key>");
|
||||||
|
sb.Append(name);
|
||||||
|
sb.Append("</key><undef />");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddElem(string name, bool e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<key>");
|
||||||
|
sb.Append(name);
|
||||||
|
sb.Append("</key>");
|
||||||
|
|
||||||
|
if(e)
|
||||||
|
sb.Append("<boolean>1</boolean>");
|
||||||
|
else
|
||||||
|
sb.Append("<boolean />");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddElem(string name, int e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<key>");
|
||||||
|
sb.Append(name);
|
||||||
|
sb.Append("</key>");
|
||||||
|
|
||||||
|
if(e == 0)
|
||||||
|
sb.Append("<integer />");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("<integer>");
|
||||||
|
sb.Append(e.ToString());
|
||||||
|
sb.Append("</integer>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddElem(string name, float e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<key>");
|
||||||
|
sb.Append(name);
|
||||||
|
sb.Append("</key>");
|
||||||
|
|
||||||
|
if(e == 0)
|
||||||
|
sb.Append("<real />");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("<real>");
|
||||||
|
sb.Append(e.ToString(CultureInfo.InvariantCulture));
|
||||||
|
sb.Append("</real>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddElem(string name, double e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<key>");
|
||||||
|
sb.Append(name);
|
||||||
|
sb.Append("</key>");
|
||||||
|
|
||||||
|
if(e == 0)
|
||||||
|
sb.Append("<real />");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("<real>");
|
||||||
|
sb.Append(e.ToString(CultureInfo.InvariantCulture));
|
||||||
|
sb.Append("</real>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddElem(string name, UUID e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<key>");
|
||||||
|
sb.Append(name);
|
||||||
|
sb.Append("</key>");
|
||||||
|
|
||||||
|
if(e == UUID.Zero)
|
||||||
|
sb.Append("<uuid />");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("<uuid>");
|
||||||
|
EscapeToXML(e.ToString(), sb);
|
||||||
|
sb.Append("</uuid>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddElem(string name, string e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<key>");
|
||||||
|
sb.Append(name);
|
||||||
|
sb.Append("</key>");
|
||||||
|
|
||||||
|
if(String.IsNullOrEmpty(e))
|
||||||
|
sb.Append("<string />");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("<string>");
|
||||||
|
sb.Append(e.ToString());
|
||||||
|
sb.Append("</string>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddRawElem(string name, string e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<key>");
|
||||||
|
sb.Append(name);
|
||||||
|
sb.Append("</key>");
|
||||||
|
|
||||||
|
if(String.IsNullOrEmpty(e))
|
||||||
|
sb.Append("<string />");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("<string>");
|
||||||
|
sb.Append(e);
|
||||||
|
sb.Append("</string>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddURIElem(string name, Uri e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<key>");
|
||||||
|
sb.Append(name);
|
||||||
|
sb.Append("</key>");
|
||||||
|
|
||||||
|
if(e == null)
|
||||||
|
{
|
||||||
|
sb.Append("<uri />");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string s;
|
||||||
|
if (e.IsAbsoluteUri)
|
||||||
|
s = e.AbsoluteUri;
|
||||||
|
else
|
||||||
|
s = e.ToString();
|
||||||
|
|
||||||
|
if(String.IsNullOrEmpty(s))
|
||||||
|
sb.Append("<uri />");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append("<uri>");
|
||||||
|
sb.Append(s);
|
||||||
|
sb.Append("</uri>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddElem(string name, DateTime e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append("<key>");
|
||||||
|
sb.Append(name);
|
||||||
|
sb.Append("</key>");
|
||||||
|
|
||||||
|
DateTime u = e.ToUniversalTime();
|
||||||
|
if(u == depoch)
|
||||||
|
{
|
||||||
|
sb.Append("<date />");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
string format;
|
||||||
|
if(u.Hour == 0 && u.Minute == 0 && u.Second == 0)
|
||||||
|
format = "yyyy-MM-dd";
|
||||||
|
else if (u.Millisecond > 0)
|
||||||
|
format = "yyyy-MM-ddTHH:mm:ss.ffZ";
|
||||||
|
else
|
||||||
|
format = "yyyy-MM-ddTHH:mm:ssZ";
|
||||||
|
sb.Append("<date>");
|
||||||
|
sb.Append(u.ToString(format,CultureInfo.InvariantCulture));
|
||||||
|
sb.Append("</date>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddLLSD(string e, StringBuilder sb)
|
||||||
|
{
|
||||||
|
sb.Append(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EscapeToXML(string s, StringBuilder sb)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
char c;
|
||||||
|
String t;
|
||||||
|
int len = s.Length;
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
c = s[i];
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '<':
|
||||||
|
sb.Append("<");
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
sb.Append(">");
|
||||||
|
break;
|
||||||
|
case '&':
|
||||||
|
sb.Append("&");
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
sb.Append(""");
|
||||||
|
break;
|
||||||
|
case '\\':
|
||||||
|
sb.Append("'");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.Append(c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1824,10 +1824,15 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
|
|
||||||
Dictionary<UUID,string> names = m_UserManager.GetUsersNames(ids);
|
Dictionary<UUID,string> names = m_UserManager.GetUsersNames(ids);
|
||||||
|
|
||||||
OSDMap osdReply = new OSDMap();
|
StringBuilder lsl = new StringBuilder(names.Count * 256 + 256);
|
||||||
OSDArray agents = new OSDArray();
|
LLSDxmlEncode.AddStartHeader(lsl);
|
||||||
|
LLSDxmlEncode.AddStartMap(lsl);
|
||||||
|
if(names.Count == 0)
|
||||||
|
LLSDxmlEncode.AddEmpyArray("agents", lsl);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LLSDxmlEncode.AddStartArray("agents", lsl);
|
||||||
|
|
||||||
osdReply["agents"] = agents;
|
|
||||||
foreach (KeyValuePair<UUID,string> kvp in names)
|
foreach (KeyValuePair<UUID,string> kvp in names)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(kvp.Value))
|
if (string.IsNullOrEmpty(kvp.Value))
|
||||||
|
@ -1836,40 +1841,34 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
string[] parts = kvp.Value.Split(new char[] {' '});
|
string[] parts = kvp.Value.Split(new char[] {' '});
|
||||||
OSDMap osdname = new OSDMap();
|
|
||||||
|
|
||||||
// dont tell about unknown users, we can't send them back on Bad either
|
// dont tell about unknown users, we can't send them back on Bad either
|
||||||
if(parts[0] == "Unknown")
|
if(parts[0] == "Unknown")
|
||||||
continue;
|
continue;
|
||||||
/*
|
|
||||||
if(parts[0] == "Unknown")
|
|
||||||
{
|
|
||||||
osdname["display_name_next_update"] = OSD.FromDate(DateTime.UtcNow.AddHours(1));
|
|
||||||
osdname["display_name_expires"] = OSD.FromDate(DateTime.UtcNow.AddHours(2));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
osdname["display_name_next_update"] = OSD.FromDate(DateTime.UtcNow.AddDays(8));
|
|
||||||
osdname["display_name_expires"] = OSD.FromDate(DateTime.UtcNow.AddMonths(1));
|
|
||||||
}
|
|
||||||
osdname["display_name"] = OSD.FromString(kvp.Value);
|
|
||||||
osdname["legacy_first_name"] = parts[0];
|
|
||||||
osdname["legacy_last_name"] = parts[1];
|
|
||||||
osdname["username"] = OSD.FromString(kvp.Value);
|
|
||||||
osdname["id"] = OSD.FromUUID(kvp.Key);
|
|
||||||
osdname["is_display_name_default"] = OSD.FromBoolean(true);
|
|
||||||
|
|
||||||
agents.Add(osdname);
|
LLSDxmlEncode.AddStartMap(lsl);
|
||||||
|
LLSDxmlEncode.AddElem("display_name_next_update", DateTime.UtcNow.AddDays(8), lsl);
|
||||||
|
LLSDxmlEncode.AddElem("display_name_expires", DateTime.UtcNow.AddMonths(1), lsl);
|
||||||
|
LLSDxmlEncode.AddElem("display_name", kvp.Value, lsl);
|
||||||
|
LLSDxmlEncode.AddElem("legacy_first_name", parts[0], lsl);
|
||||||
|
LLSDxmlEncode.AddElem("legacy_last_name", parts[1], lsl);
|
||||||
|
LLSDxmlEncode.AddElem("username", kvp.Value, lsl);
|
||||||
|
LLSDxmlEncode.AddElem("id", kvp.Key, lsl);
|
||||||
|
LLSDxmlEncode.AddElem("is_display_name_default", true, lsl);
|
||||||
|
LLSDxmlEncode.AddEndMap(lsl);
|
||||||
}
|
}
|
||||||
|
LLSDxmlEncode.AddEndArray(lsl);
|
||||||
|
}
|
||||||
|
|
||||||
|
LLSDxmlEncode.AddEndMap(lsl);
|
||||||
|
LLSDxmlEncode.AddEndHeader(lsl);
|
||||||
|
|
||||||
// Full content request
|
// Full content request
|
||||||
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.OK;
|
httpResponse.StatusCode = (int)System.Net.HttpStatusCode.OK;
|
||||||
//httpResponse.ContentLength = ??;
|
//httpResponse.ContentLength = ??;
|
||||||
httpResponse.ContentType = "application/llsd+xml";
|
httpResponse.ContentType = "application/llsd+xml";
|
||||||
|
|
||||||
string reply = OSDParser.SerializeLLSDXmlString(osdReply);
|
return lsl.ToString();
|
||||||
return reply;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue