mirror of
https://github.com/TechplexEngineer/plexview.git
synced 2026-08-14 00:57:58 +00:00
Initial commit
I wish I could figure out how to have PlexVie be out of the LibOMV tree but its just not working die to a DLL not found exception about libopenjpeg. (Yes the dll is in the bin folder) This will do for now
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
||||
compile.bat
|
||||
*.csproj
|
||||
*.user
|
||||
*.userprefs
|
||||
*.sln
|
||||
*.suo
|
||||
*.cache
|
||||
|
||||
[Oo]bj/
|
||||
[Bb]in/
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
|
||||
Copyright (c) 2006-2014, openmetaverse.org
|
||||
All rights reserved.
|
||||
|
||||
- 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.
|
||||
- Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
@@ -0,0 +1,60 @@
|
||||
#region Header
|
||||
/*
|
||||
* IJsonWrapper.cs
|
||||
* Interface that represents a type capable of handling all kinds of JSON
|
||||
* data. This is mainly used when mapping objects through JsonMapper, and
|
||||
* it's implemented by JsonData.
|
||||
*
|
||||
* The authors disclaim copyright to this source code. For more details, see
|
||||
* the COPYING file included with this distribution.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
|
||||
namespace LitJson
|
||||
{
|
||||
public enum JsonType
|
||||
{
|
||||
None,
|
||||
|
||||
Object,
|
||||
Array,
|
||||
String,
|
||||
Int,
|
||||
Long,
|
||||
Double,
|
||||
Boolean
|
||||
}
|
||||
|
||||
public interface IJsonWrapper : IList, IOrderedDictionary
|
||||
{
|
||||
bool IsArray { get; }
|
||||
bool IsBoolean { get; }
|
||||
bool IsDouble { get; }
|
||||
bool IsInt { get; }
|
||||
bool IsLong { get; }
|
||||
bool IsObject { get; }
|
||||
bool IsString { get; }
|
||||
|
||||
bool GetBoolean ();
|
||||
double GetDouble ();
|
||||
int GetInt ();
|
||||
JsonType GetJsonType ();
|
||||
long GetLong ();
|
||||
string GetString ();
|
||||
|
||||
void SetBoolean (bool val);
|
||||
void SetDouble (double val);
|
||||
void SetInt (int val);
|
||||
void SetJsonType (JsonType type);
|
||||
void SetLong (long val);
|
||||
void SetString (string val);
|
||||
|
||||
string ToJson ();
|
||||
void ToJson (JsonWriter writer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,993 @@
|
||||
#region Header
|
||||
/*
|
||||
* JsonData.cs
|
||||
* Generic type to hold JSON data (objects, arrays, and so on). This is
|
||||
* the default type returned by JsonMapper.ToObject().
|
||||
*
|
||||
* The authors disclaim copyright to this source code. For more details, see
|
||||
* the COPYING file included with this distribution.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
|
||||
|
||||
namespace LitJson
|
||||
{
|
||||
public class JsonData : IJsonWrapper, IEquatable<JsonData>
|
||||
{
|
||||
#region Fields
|
||||
private IList<JsonData> inst_array;
|
||||
private bool inst_boolean;
|
||||
private double inst_double;
|
||||
private int inst_int;
|
||||
private long inst_long;
|
||||
private IDictionary<string, JsonData> inst_object;
|
||||
private string inst_string;
|
||||
private string json;
|
||||
private JsonType type;
|
||||
|
||||
// Used to implement the IOrderedDictionary interface
|
||||
private IList<KeyValuePair<string, JsonData>> object_list;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
public int Count {
|
||||
get { return EnsureCollection ().Count; }
|
||||
}
|
||||
|
||||
public bool IsArray {
|
||||
get { return type == JsonType.Array; }
|
||||
}
|
||||
|
||||
public bool IsBoolean {
|
||||
get { return type == JsonType.Boolean; }
|
||||
}
|
||||
|
||||
public bool IsDouble {
|
||||
get { return type == JsonType.Double; }
|
||||
}
|
||||
|
||||
public bool IsInt {
|
||||
get { return type == JsonType.Int; }
|
||||
}
|
||||
|
||||
public bool IsLong {
|
||||
get { return type == JsonType.Long; }
|
||||
}
|
||||
|
||||
public bool IsObject {
|
||||
get { return type == JsonType.Object; }
|
||||
}
|
||||
|
||||
public bool IsString {
|
||||
get { return type == JsonType.String; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region ICollection Properties
|
||||
int ICollection.Count {
|
||||
get {
|
||||
return Count;
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection.IsSynchronized {
|
||||
get {
|
||||
return EnsureCollection ().IsSynchronized;
|
||||
}
|
||||
}
|
||||
|
||||
object ICollection.SyncRoot {
|
||||
get {
|
||||
return EnsureCollection ().SyncRoot;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region IDictionary Properties
|
||||
bool IDictionary.IsFixedSize {
|
||||
get {
|
||||
return EnsureDictionary ().IsFixedSize;
|
||||
}
|
||||
}
|
||||
|
||||
bool IDictionary.IsReadOnly {
|
||||
get {
|
||||
return EnsureDictionary ().IsReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
ICollection IDictionary.Keys {
|
||||
get {
|
||||
EnsureDictionary ();
|
||||
IList<string> keys = new List<string> ();
|
||||
|
||||
foreach (KeyValuePair<string, JsonData> entry in
|
||||
object_list) {
|
||||
keys.Add (entry.Key);
|
||||
}
|
||||
|
||||
return (ICollection) keys;
|
||||
}
|
||||
}
|
||||
|
||||
ICollection IDictionary.Values {
|
||||
get {
|
||||
EnsureDictionary ();
|
||||
IList<JsonData> values = new List<JsonData> ();
|
||||
|
||||
foreach (KeyValuePair<string, JsonData> entry in
|
||||
object_list) {
|
||||
values.Add (entry.Value);
|
||||
}
|
||||
|
||||
return (ICollection) values;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region IJsonWrapper Properties
|
||||
bool IJsonWrapper.IsArray {
|
||||
get { return IsArray; }
|
||||
}
|
||||
|
||||
bool IJsonWrapper.IsBoolean {
|
||||
get { return IsBoolean; }
|
||||
}
|
||||
|
||||
bool IJsonWrapper.IsDouble {
|
||||
get { return IsDouble; }
|
||||
}
|
||||
|
||||
bool IJsonWrapper.IsInt {
|
||||
get { return IsInt; }
|
||||
}
|
||||
|
||||
bool IJsonWrapper.IsLong {
|
||||
get { return IsLong; }
|
||||
}
|
||||
|
||||
bool IJsonWrapper.IsObject {
|
||||
get { return IsObject; }
|
||||
}
|
||||
|
||||
bool IJsonWrapper.IsString {
|
||||
get { return IsString; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region IList Properties
|
||||
bool IList.IsFixedSize {
|
||||
get {
|
||||
return EnsureList ().IsFixedSize;
|
||||
}
|
||||
}
|
||||
|
||||
bool IList.IsReadOnly {
|
||||
get {
|
||||
return EnsureList ().IsReadOnly;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region IDictionary Indexer
|
||||
object IDictionary.this[object key] {
|
||||
get {
|
||||
return EnsureDictionary ()[key];
|
||||
}
|
||||
|
||||
set {
|
||||
if (! (key is String))
|
||||
throw new ArgumentException (
|
||||
"The key has to be a string");
|
||||
|
||||
JsonData data = ToJsonData (value);
|
||||
|
||||
this[(string) key] = data;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region IOrderedDictionary Indexer
|
||||
object IOrderedDictionary.this[int idx] {
|
||||
get {
|
||||
EnsureDictionary ();
|
||||
return object_list[idx].Value;
|
||||
}
|
||||
|
||||
set {
|
||||
EnsureDictionary ();
|
||||
JsonData data = ToJsonData (value);
|
||||
|
||||
KeyValuePair<string, JsonData> old_entry = object_list[idx];
|
||||
|
||||
inst_object[old_entry.Key] = data;
|
||||
|
||||
KeyValuePair<string, JsonData> entry =
|
||||
new KeyValuePair<string, JsonData> (old_entry.Key, data);
|
||||
|
||||
object_list[idx] = entry;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region IList Indexer
|
||||
object IList.this[int index] {
|
||||
get {
|
||||
return EnsureList ()[index];
|
||||
}
|
||||
|
||||
set {
|
||||
EnsureList ();
|
||||
JsonData data = ToJsonData (value);
|
||||
|
||||
this[index] = data;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Public Indexers
|
||||
public JsonData this[string prop_name] {
|
||||
get {
|
||||
EnsureDictionary ();
|
||||
return inst_object[prop_name];
|
||||
}
|
||||
|
||||
set {
|
||||
EnsureDictionary ();
|
||||
|
||||
KeyValuePair<string, JsonData> entry =
|
||||
new KeyValuePair<string, JsonData> (prop_name, value);
|
||||
|
||||
if (inst_object.ContainsKey (prop_name)) {
|
||||
for (int i = 0; i < object_list.Count; i++) {
|
||||
if (object_list[i].Key == prop_name) {
|
||||
object_list[i] = entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else
|
||||
object_list.Add (entry);
|
||||
|
||||
inst_object[prop_name] = value;
|
||||
|
||||
json = null;
|
||||
}
|
||||
}
|
||||
|
||||
public JsonData this[int index] {
|
||||
get {
|
||||
EnsureCollection ();
|
||||
|
||||
if (type == JsonType.Array)
|
||||
return inst_array[index];
|
||||
|
||||
return object_list[index].Value;
|
||||
}
|
||||
|
||||
set {
|
||||
EnsureCollection ();
|
||||
|
||||
if (type == JsonType.Array)
|
||||
inst_array[index] = value;
|
||||
else {
|
||||
KeyValuePair<string, JsonData> entry = object_list[index];
|
||||
KeyValuePair<string, JsonData> new_entry =
|
||||
new KeyValuePair<string, JsonData> (entry.Key, value);
|
||||
|
||||
object_list[index] = new_entry;
|
||||
inst_object[entry.Key] = value;
|
||||
}
|
||||
|
||||
json = null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public JsonData ()
|
||||
{
|
||||
}
|
||||
|
||||
public JsonData (bool boolean)
|
||||
{
|
||||
type = JsonType.Boolean;
|
||||
inst_boolean = boolean;
|
||||
}
|
||||
|
||||
public JsonData (double number)
|
||||
{
|
||||
type = JsonType.Double;
|
||||
inst_double = number;
|
||||
}
|
||||
|
||||
public JsonData (int number)
|
||||
{
|
||||
type = JsonType.Int;
|
||||
inst_int = number;
|
||||
}
|
||||
|
||||
public JsonData (long number)
|
||||
{
|
||||
type = JsonType.Long;
|
||||
inst_long = number;
|
||||
}
|
||||
|
||||
public JsonData (object obj)
|
||||
{
|
||||
if (obj is Boolean) {
|
||||
type = JsonType.Boolean;
|
||||
inst_boolean = (bool) obj;
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is Double) {
|
||||
type = JsonType.Double;
|
||||
inst_double = (double) obj;
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is Int32) {
|
||||
type = JsonType.Int;
|
||||
inst_int = (int) obj;
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is Int64) {
|
||||
type = JsonType.Long;
|
||||
inst_long = (long) obj;
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is String) {
|
||||
type = JsonType.String;
|
||||
inst_string = (string) obj;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ArgumentException (
|
||||
"Unable to wrap the given object with JsonData");
|
||||
}
|
||||
|
||||
public JsonData (string str)
|
||||
{
|
||||
type = JsonType.String;
|
||||
inst_string = str;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Implicit Conversions
|
||||
public static implicit operator JsonData (Boolean data)
|
||||
{
|
||||
return new JsonData (data);
|
||||
}
|
||||
|
||||
public static implicit operator JsonData (Double data)
|
||||
{
|
||||
return new JsonData (data);
|
||||
}
|
||||
|
||||
public static implicit operator JsonData (Int32 data)
|
||||
{
|
||||
return new JsonData (data);
|
||||
}
|
||||
|
||||
public static implicit operator JsonData (Int64 data)
|
||||
{
|
||||
return new JsonData (data);
|
||||
}
|
||||
|
||||
public static implicit operator JsonData (String data)
|
||||
{
|
||||
return new JsonData (data);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Explicit Conversions
|
||||
public static explicit operator Boolean (JsonData data)
|
||||
{
|
||||
if (data.type != JsonType.Boolean)
|
||||
throw new InvalidCastException (
|
||||
"Instance of JsonData doesn't hold a double");
|
||||
|
||||
return data.inst_boolean;
|
||||
}
|
||||
|
||||
public static explicit operator Double (JsonData data)
|
||||
{
|
||||
if (data.type != JsonType.Double)
|
||||
throw new InvalidCastException (
|
||||
"Instance of JsonData doesn't hold a double");
|
||||
|
||||
return data.inst_double;
|
||||
}
|
||||
|
||||
public static explicit operator Int32 (JsonData data)
|
||||
{
|
||||
if (data.type != JsonType.Int)
|
||||
throw new InvalidCastException (
|
||||
"Instance of JsonData doesn't hold an int");
|
||||
|
||||
return data.inst_int;
|
||||
}
|
||||
|
||||
public static explicit operator Int64 (JsonData data)
|
||||
{
|
||||
if (data.type != JsonType.Long)
|
||||
throw new InvalidCastException (
|
||||
"Instance of JsonData doesn't hold an int");
|
||||
|
||||
return data.inst_long;
|
||||
}
|
||||
|
||||
public static explicit operator String (JsonData data)
|
||||
{
|
||||
if (data.type != JsonType.String)
|
||||
throw new InvalidCastException (
|
||||
"Instance of JsonData doesn't hold a string");
|
||||
|
||||
return data.inst_string;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region ICollection Methods
|
||||
void ICollection.CopyTo (Array array, int index)
|
||||
{
|
||||
EnsureCollection ().CopyTo (array, index);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region IDictionary Methods
|
||||
void IDictionary.Add (object key, object value)
|
||||
{
|
||||
JsonData data = ToJsonData (value);
|
||||
|
||||
EnsureDictionary ().Add (key, data);
|
||||
|
||||
KeyValuePair<string, JsonData> entry =
|
||||
new KeyValuePair<string, JsonData> ((string) key, data);
|
||||
object_list.Add (entry);
|
||||
|
||||
json = null;
|
||||
}
|
||||
|
||||
void IDictionary.Clear ()
|
||||
{
|
||||
EnsureDictionary ().Clear ();
|
||||
object_list.Clear ();
|
||||
json = null;
|
||||
}
|
||||
|
||||
bool IDictionary.Contains (object key)
|
||||
{
|
||||
return EnsureDictionary ().Contains (key);
|
||||
}
|
||||
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator ()
|
||||
{
|
||||
return ((IOrderedDictionary) this).GetEnumerator ();
|
||||
}
|
||||
|
||||
void IDictionary.Remove (object key)
|
||||
{
|
||||
EnsureDictionary ().Remove (key);
|
||||
|
||||
for (int i = 0; i < object_list.Count; i++) {
|
||||
if (object_list[i].Key == (string) key) {
|
||||
object_list.RemoveAt (i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
json = null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region IEnumerable Methods
|
||||
IEnumerator IEnumerable.GetEnumerator ()
|
||||
{
|
||||
return EnsureCollection ().GetEnumerator ();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region IJsonWrapper Methods
|
||||
bool IJsonWrapper.GetBoolean ()
|
||||
{
|
||||
if (type != JsonType.Boolean)
|
||||
throw new InvalidOperationException (
|
||||
"JsonData instance doesn't hold a boolean");
|
||||
|
||||
return inst_boolean;
|
||||
}
|
||||
|
||||
double IJsonWrapper.GetDouble ()
|
||||
{
|
||||
if (type != JsonType.Double)
|
||||
throw new InvalidOperationException (
|
||||
"JsonData instance doesn't hold a double");
|
||||
|
||||
return inst_double;
|
||||
}
|
||||
|
||||
int IJsonWrapper.GetInt ()
|
||||
{
|
||||
if (type != JsonType.Int)
|
||||
throw new InvalidOperationException (
|
||||
"JsonData instance doesn't hold an int");
|
||||
|
||||
return inst_int;
|
||||
}
|
||||
|
||||
long IJsonWrapper.GetLong ()
|
||||
{
|
||||
if (type != JsonType.Long)
|
||||
throw new InvalidOperationException (
|
||||
"JsonData instance doesn't hold a long");
|
||||
|
||||
return inst_long;
|
||||
}
|
||||
|
||||
string IJsonWrapper.GetString ()
|
||||
{
|
||||
if (type != JsonType.String)
|
||||
throw new InvalidOperationException (
|
||||
"JsonData instance doesn't hold a string");
|
||||
|
||||
return inst_string;
|
||||
}
|
||||
|
||||
void IJsonWrapper.SetBoolean (bool val)
|
||||
{
|
||||
type = JsonType.Boolean;
|
||||
inst_boolean = val;
|
||||
json = null;
|
||||
}
|
||||
|
||||
void IJsonWrapper.SetDouble (double val)
|
||||
{
|
||||
type = JsonType.Double;
|
||||
inst_double = val;
|
||||
json = null;
|
||||
}
|
||||
|
||||
void IJsonWrapper.SetInt (int val)
|
||||
{
|
||||
type = JsonType.Int;
|
||||
inst_int = val;
|
||||
json = null;
|
||||
}
|
||||
|
||||
void IJsonWrapper.SetLong (long val)
|
||||
{
|
||||
type = JsonType.Long;
|
||||
inst_long = val;
|
||||
json = null;
|
||||
}
|
||||
|
||||
void IJsonWrapper.SetString (string val)
|
||||
{
|
||||
type = JsonType.String;
|
||||
inst_string = val;
|
||||
json = null;
|
||||
}
|
||||
|
||||
string IJsonWrapper.ToJson ()
|
||||
{
|
||||
return ToJson ();
|
||||
}
|
||||
|
||||
void IJsonWrapper.ToJson (JsonWriter writer)
|
||||
{
|
||||
ToJson (writer);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region IList Methods
|
||||
int IList.Add (object value)
|
||||
{
|
||||
return Add (value);
|
||||
}
|
||||
|
||||
void IList.Clear ()
|
||||
{
|
||||
EnsureList ().Clear ();
|
||||
json = null;
|
||||
}
|
||||
|
||||
bool IList.Contains (object value)
|
||||
{
|
||||
return EnsureList ().Contains (value);
|
||||
}
|
||||
|
||||
int IList.IndexOf (object value)
|
||||
{
|
||||
return EnsureList ().IndexOf (value);
|
||||
}
|
||||
|
||||
void IList.Insert (int index, object value)
|
||||
{
|
||||
EnsureList ().Insert (index, value);
|
||||
json = null;
|
||||
}
|
||||
|
||||
void IList.Remove (object value)
|
||||
{
|
||||
EnsureList ().Remove (value);
|
||||
json = null;
|
||||
}
|
||||
|
||||
void IList.RemoveAt (int index)
|
||||
{
|
||||
EnsureList ().RemoveAt (index);
|
||||
json = null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region IOrderedDictionary Methods
|
||||
IDictionaryEnumerator IOrderedDictionary.GetEnumerator ()
|
||||
{
|
||||
EnsureDictionary ();
|
||||
|
||||
return new OrderedDictionaryEnumerator (
|
||||
object_list.GetEnumerator ());
|
||||
}
|
||||
|
||||
void IOrderedDictionary.Insert (int idx, object key, object value)
|
||||
{
|
||||
string property = (string) key;
|
||||
JsonData data = ToJsonData (value);
|
||||
|
||||
this[property] = data;
|
||||
|
||||
KeyValuePair<string, JsonData> entry =
|
||||
new KeyValuePair<string, JsonData> (property, data);
|
||||
|
||||
object_list.Insert (idx, entry);
|
||||
}
|
||||
|
||||
void IOrderedDictionary.RemoveAt (int idx)
|
||||
{
|
||||
EnsureDictionary ();
|
||||
|
||||
inst_object.Remove (object_list[idx].Key);
|
||||
object_list.RemoveAt (idx);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Private Methods
|
||||
private ICollection EnsureCollection ()
|
||||
{
|
||||
if (type == JsonType.Array)
|
||||
return (ICollection) inst_array;
|
||||
|
||||
if (type == JsonType.Object)
|
||||
return (ICollection) inst_object;
|
||||
|
||||
throw new InvalidOperationException (
|
||||
"The JsonData instance has to be initialized first");
|
||||
}
|
||||
|
||||
private IDictionary EnsureDictionary ()
|
||||
{
|
||||
if (type == JsonType.Object)
|
||||
return (IDictionary) inst_object;
|
||||
|
||||
if (type != JsonType.None)
|
||||
throw new InvalidOperationException (
|
||||
"Instance of JsonData is not a dictionary");
|
||||
|
||||
type = JsonType.Object;
|
||||
inst_object = new Dictionary<string, JsonData> ();
|
||||
object_list = new List<KeyValuePair<string, JsonData>> ();
|
||||
|
||||
return (IDictionary) inst_object;
|
||||
}
|
||||
|
||||
private IList EnsureList ()
|
||||
{
|
||||
if (type == JsonType.Array)
|
||||
return (IList) inst_array;
|
||||
|
||||
if (type != JsonType.None)
|
||||
throw new InvalidOperationException (
|
||||
"Instance of JsonData is not a list");
|
||||
|
||||
type = JsonType.Array;
|
||||
inst_array = new List<JsonData> ();
|
||||
|
||||
return (IList) inst_array;
|
||||
}
|
||||
|
||||
private JsonData ToJsonData (object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return null;
|
||||
|
||||
if (obj is JsonData)
|
||||
return (JsonData) obj;
|
||||
|
||||
return new JsonData (obj);
|
||||
}
|
||||
|
||||
private static void WriteJson (IJsonWrapper obj, JsonWriter writer)
|
||||
{
|
||||
if (obj.IsString) {
|
||||
writer.Write (obj.GetString ());
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj.IsBoolean) {
|
||||
writer.Write (obj.GetBoolean ());
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj.IsDouble) {
|
||||
writer.Write (obj.GetDouble ());
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj.IsInt) {
|
||||
writer.Write (obj.GetInt ());
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj.IsLong) {
|
||||
writer.Write (obj.GetLong ());
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj.IsArray) {
|
||||
writer.WriteArrayStart ();
|
||||
foreach (object elem in (IList) obj)
|
||||
WriteJson ((JsonData) elem, writer);
|
||||
writer.WriteArrayEnd ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj.IsObject) {
|
||||
writer.WriteObjectStart ();
|
||||
|
||||
foreach (DictionaryEntry entry in ((IDictionary) obj)) {
|
||||
writer.WritePropertyName ((string) entry.Key);
|
||||
WriteJson ((JsonData) entry.Value, writer);
|
||||
}
|
||||
writer.WriteObjectEnd ();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
public int Add (object value)
|
||||
{
|
||||
JsonData data = ToJsonData (value);
|
||||
|
||||
json = null;
|
||||
|
||||
return EnsureList ().Add (data);
|
||||
}
|
||||
|
||||
public void Clear ()
|
||||
{
|
||||
if (IsObject) {
|
||||
((IDictionary) this).Clear ();
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsArray) {
|
||||
((IList) this).Clear ();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals (JsonData x)
|
||||
{
|
||||
if (x == null)
|
||||
return false;
|
||||
|
||||
if (x.type != this.type)
|
||||
return false;
|
||||
|
||||
switch (this.type) {
|
||||
case JsonType.None:
|
||||
return true;
|
||||
|
||||
case JsonType.Object:
|
||||
return this.inst_object.Equals (x.inst_object);
|
||||
|
||||
case JsonType.Array:
|
||||
return this.inst_array.Equals (x.inst_array);
|
||||
|
||||
case JsonType.String:
|
||||
return this.inst_string.Equals (x.inst_string);
|
||||
|
||||
case JsonType.Int:
|
||||
return this.inst_int.Equals (x.inst_int);
|
||||
|
||||
case JsonType.Long:
|
||||
return this.inst_long.Equals (x.inst_long);
|
||||
|
||||
case JsonType.Double:
|
||||
return this.inst_double.Equals (x.inst_double);
|
||||
|
||||
case JsonType.Boolean:
|
||||
return this.inst_boolean.Equals (x.inst_boolean);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public JsonType GetJsonType ()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
public void SetJsonType (JsonType type)
|
||||
{
|
||||
if (this.type == type)
|
||||
return;
|
||||
|
||||
switch (type) {
|
||||
case JsonType.None:
|
||||
break;
|
||||
|
||||
case JsonType.Object:
|
||||
inst_object = new Dictionary<string, JsonData> ();
|
||||
object_list = new List<KeyValuePair<string, JsonData>> ();
|
||||
break;
|
||||
|
||||
case JsonType.Array:
|
||||
inst_array = new List<JsonData> ();
|
||||
break;
|
||||
|
||||
case JsonType.String:
|
||||
inst_string = default (String);
|
||||
break;
|
||||
|
||||
case JsonType.Int:
|
||||
inst_int = default (Int32);
|
||||
break;
|
||||
|
||||
case JsonType.Long:
|
||||
inst_long = default (Int64);
|
||||
break;
|
||||
|
||||
case JsonType.Double:
|
||||
inst_double = default (Double);
|
||||
break;
|
||||
|
||||
case JsonType.Boolean:
|
||||
inst_boolean = default (Boolean);
|
||||
break;
|
||||
}
|
||||
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public string ToJson ()
|
||||
{
|
||||
if (json != null)
|
||||
return json;
|
||||
|
||||
StringWriter sw = new StringWriter ();
|
||||
JsonWriter writer = new JsonWriter (sw);
|
||||
writer.Validate = false;
|
||||
|
||||
WriteJson (this, writer);
|
||||
json = sw.ToString ();
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
public void ToJson (JsonWriter writer)
|
||||
{
|
||||
bool old_validate = writer.Validate;
|
||||
|
||||
writer.Validate = false;
|
||||
|
||||
WriteJson (this, writer);
|
||||
|
||||
writer.Validate = old_validate;
|
||||
}
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
switch (type) {
|
||||
case JsonType.Array:
|
||||
return "JsonData array";
|
||||
|
||||
case JsonType.Boolean:
|
||||
return inst_boolean.ToString ();
|
||||
|
||||
case JsonType.Double:
|
||||
return inst_double.ToString ();
|
||||
|
||||
case JsonType.Int:
|
||||
return inst_int.ToString ();
|
||||
|
||||
case JsonType.Long:
|
||||
return inst_long.ToString ();
|
||||
|
||||
case JsonType.Object:
|
||||
return "JsonData object";
|
||||
|
||||
case JsonType.String:
|
||||
return inst_string;
|
||||
}
|
||||
|
||||
return "Uninitialized JsonData";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class OrderedDictionaryEnumerator : IDictionaryEnumerator
|
||||
{
|
||||
IEnumerator<KeyValuePair<string, JsonData>> list_enumerator;
|
||||
|
||||
|
||||
public object Current {
|
||||
get { return Entry; }
|
||||
}
|
||||
|
||||
public DictionaryEntry Entry {
|
||||
get {
|
||||
KeyValuePair<string, JsonData> curr = list_enumerator.Current;
|
||||
return new DictionaryEntry (curr.Key, curr.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public object Key {
|
||||
get { return list_enumerator.Current.Key; }
|
||||
}
|
||||
|
||||
public object Value {
|
||||
get { return list_enumerator.Current.Value; }
|
||||
}
|
||||
|
||||
|
||||
public OrderedDictionaryEnumerator (
|
||||
IEnumerator<KeyValuePair<string, JsonData>> enumerator)
|
||||
{
|
||||
list_enumerator = enumerator;
|
||||
}
|
||||
|
||||
|
||||
public bool MoveNext ()
|
||||
{
|
||||
return list_enumerator.MoveNext ();
|
||||
}
|
||||
|
||||
public void Reset ()
|
||||
{
|
||||
list_enumerator.Reset ();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#region Header
|
||||
/*
|
||||
* JsonException.cs
|
||||
* Base class throwed by LitJSON when a parsing error occurs.
|
||||
*
|
||||
* The authors disclaim copyright to this source code. For more details, see
|
||||
* the COPYING file included with this distribution.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
|
||||
using System;
|
||||
|
||||
|
||||
namespace LitJson
|
||||
{
|
||||
public class JsonException : ApplicationException
|
||||
{
|
||||
public JsonException () : base ()
|
||||
{
|
||||
}
|
||||
|
||||
internal JsonException (ParserToken token) :
|
||||
base (String.Format (
|
||||
"Invalid token '{0}' in input string", token))
|
||||
{
|
||||
}
|
||||
|
||||
internal JsonException (ParserToken token,
|
||||
Exception inner_exception) :
|
||||
base (String.Format (
|
||||
"Invalid token '{0}' in input string", token),
|
||||
inner_exception)
|
||||
{
|
||||
}
|
||||
|
||||
internal JsonException (int c) :
|
||||
base (String.Format (
|
||||
"Invalid character '{0}' in input string", (char) c))
|
||||
{
|
||||
}
|
||||
|
||||
internal JsonException (int c, Exception inner_exception) :
|
||||
base (String.Format (
|
||||
"Invalid character '{0}' in input string", (char) c),
|
||||
inner_exception)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public JsonException (string message) : base (message)
|
||||
{
|
||||
}
|
||||
|
||||
public JsonException (string message, Exception inner_exception) :
|
||||
base (message, inner_exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,901 @@
|
||||
#region Header
|
||||
/*
|
||||
* JsonMapper.cs
|
||||
* JSON to .Net object and object to JSON conversions.
|
||||
*
|
||||
* The authors disclaim copyright to this source code. For more details, see
|
||||
* the COPYING file included with this distribution.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
|
||||
namespace LitJson
|
||||
{
|
||||
internal struct PropertyMetadata
|
||||
{
|
||||
public MemberInfo Info;
|
||||
public bool IsField;
|
||||
public Type Type;
|
||||
}
|
||||
|
||||
|
||||
internal struct ArrayMetadata
|
||||
{
|
||||
private Type element_type;
|
||||
private bool is_array;
|
||||
private bool is_list;
|
||||
|
||||
|
||||
public Type ElementType {
|
||||
get {
|
||||
if (element_type == null)
|
||||
return typeof (JsonData);
|
||||
|
||||
return element_type;
|
||||
}
|
||||
|
||||
set { element_type = value; }
|
||||
}
|
||||
|
||||
public bool IsArray {
|
||||
get { return is_array; }
|
||||
set { is_array = value; }
|
||||
}
|
||||
|
||||
public bool IsList {
|
||||
get { return is_list; }
|
||||
set { is_list = value; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal struct ObjectMetadata
|
||||
{
|
||||
private Type element_type;
|
||||
private bool is_dictionary;
|
||||
|
||||
private IDictionary<string, PropertyMetadata> properties;
|
||||
|
||||
|
||||
public Type ElementType {
|
||||
get {
|
||||
if (element_type == null)
|
||||
return typeof (JsonData);
|
||||
|
||||
return element_type;
|
||||
}
|
||||
|
||||
set { element_type = value; }
|
||||
}
|
||||
|
||||
public bool IsDictionary {
|
||||
get { return is_dictionary; }
|
||||
set { is_dictionary = value; }
|
||||
}
|
||||
|
||||
public IDictionary<string, PropertyMetadata> Properties {
|
||||
get { return properties; }
|
||||
set { properties = value; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal delegate void ExporterFunc (object obj, JsonWriter writer);
|
||||
public delegate void ExporterFunc<T> (T obj, JsonWriter writer);
|
||||
|
||||
internal delegate object ImporterFunc (object input);
|
||||
public delegate TValue ImporterFunc<TJson, TValue> (TJson input);
|
||||
|
||||
public delegate IJsonWrapper WrapperFactory ();
|
||||
|
||||
|
||||
public class JsonMapper
|
||||
{
|
||||
#region Fields
|
||||
private static int max_nesting_depth;
|
||||
|
||||
private static IFormatProvider datetime_format;
|
||||
|
||||
private static IDictionary<Type, ExporterFunc> base_exporters_table;
|
||||
private static IDictionary<Type, ExporterFunc> custom_exporters_table;
|
||||
|
||||
private static IDictionary<Type,
|
||||
IDictionary<Type, ImporterFunc>> base_importers_table;
|
||||
private static IDictionary<Type,
|
||||
IDictionary<Type, ImporterFunc>> custom_importers_table;
|
||||
|
||||
private static IDictionary<Type, ArrayMetadata> array_metadata;
|
||||
private static readonly object array_metadata_lock = new Object ();
|
||||
|
||||
private static IDictionary<Type,
|
||||
IDictionary<Type, MethodInfo>> conv_ops;
|
||||
private static readonly object conv_ops_lock = new Object ();
|
||||
|
||||
private static IDictionary<Type, ObjectMetadata> object_metadata;
|
||||
private static readonly object object_metadata_lock = new Object ();
|
||||
|
||||
private static IDictionary<Type,
|
||||
IList<PropertyMetadata>> type_properties;
|
||||
private static readonly object type_properties_lock = new Object ();
|
||||
|
||||
private static JsonWriter static_writer;
|
||||
private static readonly object static_writer_lock = new Object ();
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
static JsonMapper ()
|
||||
{
|
||||
max_nesting_depth = 100;
|
||||
|
||||
array_metadata = new Dictionary<Type, ArrayMetadata> ();
|
||||
conv_ops = new Dictionary<Type, IDictionary<Type, MethodInfo>> ();
|
||||
object_metadata = new Dictionary<Type, ObjectMetadata> ();
|
||||
type_properties = new Dictionary<Type,
|
||||
IList<PropertyMetadata>> ();
|
||||
|
||||
static_writer = new JsonWriter ();
|
||||
|
||||
datetime_format = DateTimeFormatInfo.InvariantInfo;
|
||||
|
||||
base_exporters_table = new Dictionary<Type, ExporterFunc> ();
|
||||
custom_exporters_table = new Dictionary<Type, ExporterFunc> ();
|
||||
|
||||
base_importers_table = new Dictionary<Type,
|
||||
IDictionary<Type, ImporterFunc>> ();
|
||||
custom_importers_table = new Dictionary<Type,
|
||||
IDictionary<Type, ImporterFunc>> ();
|
||||
|
||||
RegisterBaseExporters ();
|
||||
RegisterBaseImporters ();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Private Methods
|
||||
private static void AddArrayMetadata (Type type)
|
||||
{
|
||||
if (array_metadata.ContainsKey (type))
|
||||
return;
|
||||
|
||||
ArrayMetadata data = new ArrayMetadata ();
|
||||
|
||||
data.IsArray = type.IsArray;
|
||||
|
||||
if (type.GetInterface ("System.Collections.IList") != null)
|
||||
data.IsList = true;
|
||||
|
||||
foreach (PropertyInfo p_info in type.GetProperties ()) {
|
||||
if (p_info.Name != "Item")
|
||||
continue;
|
||||
|
||||
ParameterInfo[] parameters = p_info.GetIndexParameters ();
|
||||
|
||||
if (parameters.Length != 1)
|
||||
continue;
|
||||
|
||||
if (parameters[0].ParameterType == typeof (int))
|
||||
data.ElementType = p_info.PropertyType;
|
||||
}
|
||||
|
||||
lock (array_metadata_lock) {
|
||||
try {
|
||||
array_metadata.Add (type, data);
|
||||
} catch (ArgumentException) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddObjectMetadata (Type type)
|
||||
{
|
||||
if (object_metadata.ContainsKey (type))
|
||||
return;
|
||||
|
||||
ObjectMetadata data = new ObjectMetadata ();
|
||||
|
||||
if (type.GetInterface ("System.Collections.IDictionary") != null)
|
||||
data.IsDictionary = true;
|
||||
|
||||
data.Properties = new Dictionary<string, PropertyMetadata> ();
|
||||
|
||||
foreach (PropertyInfo p_info in type.GetProperties ()) {
|
||||
if (p_info.Name == "Item") {
|
||||
ParameterInfo[] parameters = p_info.GetIndexParameters ();
|
||||
|
||||
if (parameters.Length != 1)
|
||||
continue;
|
||||
|
||||
if (parameters[0].ParameterType == typeof (string))
|
||||
data.ElementType = p_info.PropertyType;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
PropertyMetadata p_data = new PropertyMetadata ();
|
||||
p_data.Info = p_info;
|
||||
p_data.Type = p_info.PropertyType;
|
||||
|
||||
data.Properties.Add (p_info.Name, p_data);
|
||||
}
|
||||
|
||||
foreach (FieldInfo f_info in type.GetFields ()) {
|
||||
PropertyMetadata p_data = new PropertyMetadata ();
|
||||
p_data.Info = f_info;
|
||||
p_data.IsField = true;
|
||||
p_data.Type = f_info.FieldType;
|
||||
|
||||
data.Properties.Add (f_info.Name, p_data);
|
||||
}
|
||||
|
||||
lock (object_metadata_lock) {
|
||||
try {
|
||||
object_metadata.Add (type, data);
|
||||
} catch (ArgumentException) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddTypeProperties (Type type)
|
||||
{
|
||||
if (type_properties.ContainsKey (type))
|
||||
return;
|
||||
|
||||
IList<PropertyMetadata> props = new List<PropertyMetadata> ();
|
||||
|
||||
foreach (PropertyInfo p_info in type.GetProperties ()) {
|
||||
if (p_info.Name == "Item")
|
||||
continue;
|
||||
|
||||
PropertyMetadata p_data = new PropertyMetadata ();
|
||||
p_data.Info = p_info;
|
||||
p_data.IsField = false;
|
||||
props.Add (p_data);
|
||||
}
|
||||
|
||||
foreach (FieldInfo f_info in type.GetFields ()) {
|
||||
PropertyMetadata p_data = new PropertyMetadata ();
|
||||
p_data.Info = f_info;
|
||||
p_data.IsField = true;
|
||||
|
||||
props.Add (p_data);
|
||||
}
|
||||
|
||||
lock (type_properties_lock) {
|
||||
try {
|
||||
type_properties.Add (type, props);
|
||||
} catch (ArgumentException) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodInfo GetConvOp (Type t1, Type t2)
|
||||
{
|
||||
lock (conv_ops_lock) {
|
||||
if (! conv_ops.ContainsKey (t1))
|
||||
conv_ops.Add (t1, new Dictionary<Type, MethodInfo> ());
|
||||
}
|
||||
|
||||
if (conv_ops[t1].ContainsKey (t2))
|
||||
return conv_ops[t1][t2];
|
||||
|
||||
MethodInfo op = t1.GetMethod (
|
||||
"op_Implicit", new Type[] { t2 });
|
||||
|
||||
lock (conv_ops_lock) {
|
||||
try {
|
||||
conv_ops[t1].Add (t2, op);
|
||||
} catch (ArgumentException) {
|
||||
return conv_ops[t1][t2];
|
||||
}
|
||||
}
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
private static object ReadValue (Type inst_type, JsonReader reader)
|
||||
{
|
||||
reader.Read ();
|
||||
|
||||
if (reader.Token == JsonToken.ArrayEnd)
|
||||
return null;
|
||||
|
||||
if (reader.Token == JsonToken.Null) {
|
||||
|
||||
if (! inst_type.IsClass)
|
||||
throw new JsonException (String.Format (
|
||||
"Can't assign null to an instance of type {0}",
|
||||
inst_type));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (reader.Token == JsonToken.Double ||
|
||||
reader.Token == JsonToken.Int ||
|
||||
reader.Token == JsonToken.Long ||
|
||||
reader.Token == JsonToken.String ||
|
||||
reader.Token == JsonToken.Boolean) {
|
||||
|
||||
Type json_type = reader.Value.GetType ();
|
||||
|
||||
if (inst_type.IsAssignableFrom (json_type))
|
||||
return reader.Value;
|
||||
|
||||
// If there's a custom importer that fits, use it
|
||||
if (custom_importers_table.ContainsKey (json_type) &&
|
||||
custom_importers_table[json_type].ContainsKey (
|
||||
inst_type)) {
|
||||
|
||||
ImporterFunc importer =
|
||||
custom_importers_table[json_type][inst_type];
|
||||
|
||||
return importer (reader.Value);
|
||||
}
|
||||
|
||||
// Maybe there's a base importer that works
|
||||
if (base_importers_table.ContainsKey (json_type) &&
|
||||
base_importers_table[json_type].ContainsKey (
|
||||
inst_type)) {
|
||||
|
||||
ImporterFunc importer =
|
||||
base_importers_table[json_type][inst_type];
|
||||
|
||||
return importer (reader.Value);
|
||||
}
|
||||
|
||||
// Maybe it's an enum
|
||||
if (inst_type.IsEnum)
|
||||
return Enum.ToObject (inst_type, reader.Value);
|
||||
|
||||
// Try using an implicit conversion operator
|
||||
MethodInfo conv_op = GetConvOp (inst_type, json_type);
|
||||
|
||||
if (conv_op != null)
|
||||
return conv_op.Invoke (null,
|
||||
new object[] { reader.Value });
|
||||
|
||||
// No luck
|
||||
throw new JsonException (String.Format (
|
||||
"Can't assign value '{0}' (type {1}) to type {2}",
|
||||
reader.Value, json_type, inst_type));
|
||||
}
|
||||
|
||||
object instance = null;
|
||||
|
||||
if (reader.Token == JsonToken.ArrayStart) {
|
||||
|
||||
AddArrayMetadata (inst_type);
|
||||
ArrayMetadata t_data = array_metadata[inst_type];
|
||||
|
||||
if (! t_data.IsArray && ! t_data.IsList)
|
||||
throw new JsonException (String.Format (
|
||||
"Type {0} can't act as an array",
|
||||
inst_type));
|
||||
|
||||
IList list;
|
||||
Type elem_type;
|
||||
|
||||
if (! t_data.IsArray) {
|
||||
list = (IList) Activator.CreateInstance (inst_type);
|
||||
elem_type = t_data.ElementType;
|
||||
} else {
|
||||
list = new ArrayList ();
|
||||
elem_type = inst_type.GetElementType ();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
object item = ReadValue (elem_type, reader);
|
||||
if (reader.Token == JsonToken.ArrayEnd)
|
||||
break;
|
||||
|
||||
list.Add (item);
|
||||
}
|
||||
|
||||
if (t_data.IsArray) {
|
||||
int n = list.Count;
|
||||
instance = Array.CreateInstance (elem_type, n);
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
((Array) instance).SetValue (list[i], i);
|
||||
} else
|
||||
instance = list;
|
||||
|
||||
} else if (reader.Token == JsonToken.ObjectStart) {
|
||||
|
||||
AddObjectMetadata (inst_type);
|
||||
ObjectMetadata t_data = object_metadata[inst_type];
|
||||
|
||||
instance = Activator.CreateInstance (inst_type);
|
||||
|
||||
while (true) {
|
||||
reader.Read ();
|
||||
|
||||
if (reader.Token == JsonToken.ObjectEnd)
|
||||
break;
|
||||
|
||||
string property = (string) reader.Value;
|
||||
|
||||
if (t_data.Properties.ContainsKey (property)) {
|
||||
PropertyMetadata prop_data =
|
||||
t_data.Properties[property];
|
||||
|
||||
if (prop_data.IsField) {
|
||||
((FieldInfo) prop_data.Info).SetValue (
|
||||
instance, ReadValue (prop_data.Type, reader));
|
||||
} else {
|
||||
PropertyInfo p_info =
|
||||
(PropertyInfo) prop_data.Info;
|
||||
|
||||
if (p_info.CanWrite)
|
||||
p_info.SetValue (
|
||||
instance,
|
||||
ReadValue (prop_data.Type, reader),
|
||||
null);
|
||||
else
|
||||
ReadValue (prop_data.Type, reader);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (! t_data.IsDictionary)
|
||||
throw new JsonException (String.Format (
|
||||
"The type {0} doesn't have the " +
|
||||
"property '{1}'", inst_type, property));
|
||||
|
||||
((IDictionary) instance).Add (
|
||||
property, ReadValue (
|
||||
t_data.ElementType, reader));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static IJsonWrapper ReadValue (WrapperFactory factory,
|
||||
JsonReader reader)
|
||||
{
|
||||
reader.Read ();
|
||||
|
||||
if (reader.Token == JsonToken.ArrayEnd ||
|
||||
reader.Token == JsonToken.Null)
|
||||
return null;
|
||||
|
||||
IJsonWrapper instance = factory ();
|
||||
|
||||
switch (reader.Token)
|
||||
{
|
||||
case JsonToken.String:
|
||||
instance.SetString ((string) reader.Value);
|
||||
break;
|
||||
case JsonToken.Double:
|
||||
instance.SetDouble ((double) reader.Value);
|
||||
break;
|
||||
case JsonToken.Int:
|
||||
instance.SetInt ((int) reader.Value);
|
||||
break;
|
||||
case JsonToken.Long:
|
||||
instance.SetLong ((long) reader.Value);
|
||||
break;
|
||||
case JsonToken.Boolean:
|
||||
instance.SetBoolean ((bool) reader.Value);
|
||||
break;
|
||||
case JsonToken.ArrayStart:
|
||||
instance.SetJsonType (JsonType.Array);
|
||||
|
||||
while (true) {
|
||||
IJsonWrapper item = ReadValue (factory, reader);
|
||||
if (item == null && reader.Token == JsonToken.ArrayEnd)
|
||||
break;
|
||||
|
||||
((IList) instance).Add (item);
|
||||
}
|
||||
break;
|
||||
case JsonToken.ObjectStart:
|
||||
instance.SetJsonType (JsonType.Object);
|
||||
|
||||
while (true) {
|
||||
reader.Read ();
|
||||
if (reader.Token == JsonToken.ObjectEnd)
|
||||
break;
|
||||
|
||||
string property = (string) reader.Value;
|
||||
((IDictionary) instance)[property] = ReadValue (
|
||||
factory, reader);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static void RegisterBaseExporters ()
|
||||
{
|
||||
base_exporters_table[typeof (byte)] =
|
||||
delegate (object obj, JsonWriter writer) {
|
||||
writer.Write (Convert.ToInt32 ((byte) obj));
|
||||
};
|
||||
|
||||
base_exporters_table[typeof (char)] =
|
||||
delegate (object obj, JsonWriter writer) {
|
||||
writer.Write (Convert.ToString ((char) obj));
|
||||
};
|
||||
|
||||
base_exporters_table[typeof (DateTime)] =
|
||||
delegate (object obj, JsonWriter writer) {
|
||||
writer.Write (Convert.ToString ((DateTime) obj,
|
||||
datetime_format));
|
||||
};
|
||||
|
||||
base_exporters_table[typeof (decimal)] =
|
||||
delegate (object obj, JsonWriter writer) {
|
||||
writer.Write ((decimal) obj);
|
||||
};
|
||||
|
||||
base_exporters_table[typeof (sbyte)] =
|
||||
delegate (object obj, JsonWriter writer) {
|
||||
writer.Write (Convert.ToInt32 ((sbyte) obj));
|
||||
};
|
||||
|
||||
base_exporters_table[typeof (short)] =
|
||||
delegate (object obj, JsonWriter writer) {
|
||||
writer.Write (Convert.ToInt32 ((short) obj));
|
||||
};
|
||||
|
||||
base_exporters_table[typeof (ushort)] =
|
||||
delegate (object obj, JsonWriter writer) {
|
||||
writer.Write (Convert.ToInt32 ((ushort) obj));
|
||||
};
|
||||
|
||||
base_exporters_table[typeof (uint)] =
|
||||
delegate (object obj, JsonWriter writer) {
|
||||
writer.Write (Convert.ToUInt64 ((uint) obj));
|
||||
};
|
||||
|
||||
base_exporters_table[typeof (ulong)] =
|
||||
delegate (object obj, JsonWriter writer) {
|
||||
writer.Write ((ulong) obj);
|
||||
};
|
||||
}
|
||||
|
||||
private static void RegisterBaseImporters ()
|
||||
{
|
||||
ImporterFunc importer;
|
||||
|
||||
importer = delegate (object input) {
|
||||
return Convert.ToByte ((int) input);
|
||||
};
|
||||
RegisterImporter (base_importers_table, typeof (int),
|
||||
typeof (byte), importer);
|
||||
|
||||
importer = delegate (object input) {
|
||||
return Convert.ToUInt64 ((int) input);
|
||||
};
|
||||
RegisterImporter (base_importers_table, typeof (int),
|
||||
typeof (ulong), importer);
|
||||
|
||||
importer = delegate (object input) {
|
||||
return Convert.ToSByte ((int) input);
|
||||
};
|
||||
RegisterImporter (base_importers_table, typeof (int),
|
||||
typeof (sbyte), importer);
|
||||
|
||||
importer = delegate (object input) {
|
||||
return Convert.ToInt16 ((int) input);
|
||||
};
|
||||
RegisterImporter (base_importers_table, typeof (int),
|
||||
typeof (short), importer);
|
||||
|
||||
importer = delegate (object input) {
|
||||
return Convert.ToUInt16 ((int) input);
|
||||
};
|
||||
RegisterImporter (base_importers_table, typeof (int),
|
||||
typeof (ushort), importer);
|
||||
|
||||
importer = delegate (object input) {
|
||||
return Convert.ToUInt32 ((int) input);
|
||||
};
|
||||
RegisterImporter (base_importers_table, typeof (int),
|
||||
typeof (uint), importer);
|
||||
|
||||
importer = delegate (object input) {
|
||||
return Convert.ToSingle ((int) input);
|
||||
};
|
||||
RegisterImporter (base_importers_table, typeof (int),
|
||||
typeof (float), importer);
|
||||
|
||||
importer = delegate (object input) {
|
||||
return Convert.ToDouble ((int) input);
|
||||
};
|
||||
RegisterImporter (base_importers_table, typeof (int),
|
||||
typeof (double), importer);
|
||||
|
||||
importer = delegate (object input) {
|
||||
return Convert.ToDecimal ((double) input);
|
||||
};
|
||||
RegisterImporter (base_importers_table, typeof (double),
|
||||
typeof (decimal), importer);
|
||||
|
||||
|
||||
importer = delegate (object input) {
|
||||
return Convert.ToUInt32 ((long) input);
|
||||
};
|
||||
RegisterImporter (base_importers_table, typeof (long),
|
||||
typeof (uint), importer);
|
||||
|
||||
importer = delegate (object input) {
|
||||
return Convert.ToChar ((string) input);
|
||||
};
|
||||
RegisterImporter (base_importers_table, typeof (string),
|
||||
typeof (char), importer);
|
||||
|
||||
importer = delegate (object input) {
|
||||
return Convert.ToDateTime ((string) input, datetime_format);
|
||||
};
|
||||
RegisterImporter (base_importers_table, typeof (string),
|
||||
typeof (DateTime), importer);
|
||||
}
|
||||
|
||||
private static void RegisterImporter (
|
||||
IDictionary<Type, IDictionary<Type, ImporterFunc>> table,
|
||||
Type json_type, Type value_type, ImporterFunc importer)
|
||||
{
|
||||
if (! table.ContainsKey (json_type))
|
||||
table.Add (json_type, new Dictionary<Type, ImporterFunc> ());
|
||||
|
||||
table[json_type][value_type] = importer;
|
||||
}
|
||||
|
||||
private static void WriteValue (object obj, JsonWriter writer,
|
||||
bool writer_is_private,
|
||||
int depth)
|
||||
{
|
||||
if (depth > max_nesting_depth)
|
||||
throw new JsonException (
|
||||
String.Format ("Max allowed object depth reached while " +
|
||||
"trying to export from type {0}",
|
||||
obj.GetType ()));
|
||||
|
||||
if (obj == null) {
|
||||
writer.Write (null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is IJsonWrapper) {
|
||||
if (writer_is_private)
|
||||
writer.TextWriter.Write (((IJsonWrapper) obj).ToJson ());
|
||||
else
|
||||
((IJsonWrapper) obj).ToJson (writer);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is String) {
|
||||
writer.Write ((string) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is Double) {
|
||||
writer.Write ((double) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is Int32) {
|
||||
writer.Write ((int) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is Boolean) {
|
||||
writer.Write ((bool) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is Int64) {
|
||||
writer.Write ((long) obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is Array) {
|
||||
writer.WriteArrayStart ();
|
||||
|
||||
foreach (object elem in (Array) obj)
|
||||
WriteValue (elem, writer, writer_is_private, depth + 1);
|
||||
|
||||
writer.WriteArrayEnd ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is IList) {
|
||||
writer.WriteArrayStart ();
|
||||
foreach (object elem in (IList) obj)
|
||||
WriteValue (elem, writer, writer_is_private, depth + 1);
|
||||
writer.WriteArrayEnd ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj is IDictionary) {
|
||||
writer.WriteObjectStart ();
|
||||
foreach (DictionaryEntry entry in (IDictionary) obj) {
|
||||
writer.WritePropertyName ((string) entry.Key);
|
||||
WriteValue (entry.Value, writer, writer_is_private,
|
||||
depth + 1);
|
||||
}
|
||||
writer.WriteObjectEnd ();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Type obj_type = obj.GetType ();
|
||||
|
||||
// See if there's a custom exporter for the object
|
||||
if (custom_exporters_table.ContainsKey (obj_type)) {
|
||||
ExporterFunc exporter = custom_exporters_table[obj_type];
|
||||
exporter (obj, writer);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If not, maybe there's a base exporter
|
||||
if (base_exporters_table.ContainsKey (obj_type)) {
|
||||
ExporterFunc exporter = base_exporters_table[obj_type];
|
||||
exporter (obj, writer);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Last option, let's see if it's an enum
|
||||
if (obj is Enum) {
|
||||
Type e_type = Enum.GetUnderlyingType (obj_type);
|
||||
|
||||
if (e_type == typeof (long)
|
||||
|| e_type == typeof (uint)
|
||||
|| e_type == typeof (ulong))
|
||||
writer.Write ((ulong) obj);
|
||||
else
|
||||
writer.Write ((int) obj);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Okay, so it looks like the input should be exported as an
|
||||
// object
|
||||
AddTypeProperties (obj_type);
|
||||
IList<PropertyMetadata> props = type_properties[obj_type];
|
||||
|
||||
writer.WriteObjectStart ();
|
||||
foreach (PropertyMetadata p_data in props) {
|
||||
if (p_data.IsField) {
|
||||
writer.WritePropertyName (p_data.Info.Name);
|
||||
WriteValue (((FieldInfo) p_data.Info).GetValue (obj),
|
||||
writer, writer_is_private, depth + 1);
|
||||
}
|
||||
else {
|
||||
PropertyInfo p_info = (PropertyInfo) p_data.Info;
|
||||
|
||||
if (p_info.CanRead) {
|
||||
writer.WritePropertyName (p_data.Info.Name);
|
||||
WriteValue (p_info.GetValue (obj, null),
|
||||
writer, writer_is_private, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
writer.WriteObjectEnd ();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
public static string ToJson (object obj)
|
||||
{
|
||||
lock (static_writer_lock) {
|
||||
static_writer.Reset ();
|
||||
|
||||
WriteValue (obj, static_writer, true, 0);
|
||||
|
||||
return static_writer.ToString ();
|
||||
}
|
||||
}
|
||||
|
||||
public static void ToJson (object obj, JsonWriter writer)
|
||||
{
|
||||
WriteValue (obj, writer, false, 0);
|
||||
}
|
||||
|
||||
public static JsonData ToObject (JsonReader reader)
|
||||
{
|
||||
return (JsonData) ToWrapper (
|
||||
delegate { return new JsonData (); }, reader);
|
||||
}
|
||||
|
||||
public static JsonData ToObject (TextReader reader)
|
||||
{
|
||||
JsonReader json_reader = new JsonReader (reader);
|
||||
|
||||
return (JsonData) ToWrapper (
|
||||
delegate { return new JsonData (); }, json_reader);
|
||||
}
|
||||
|
||||
public static JsonData ToObject (string json)
|
||||
{
|
||||
return (JsonData) ToWrapper (
|
||||
delegate { return new JsonData (); }, json);
|
||||
}
|
||||
|
||||
public static T ToObject<T> (JsonReader reader)
|
||||
{
|
||||
return (T) ReadValue (typeof (T), reader);
|
||||
}
|
||||
|
||||
public static T ToObject<T> (TextReader reader)
|
||||
{
|
||||
JsonReader json_reader = new JsonReader (reader);
|
||||
|
||||
return (T) ReadValue (typeof (T), json_reader);
|
||||
}
|
||||
|
||||
public static T ToObject<T> (string json)
|
||||
{
|
||||
JsonReader reader = new JsonReader (json);
|
||||
|
||||
return (T) ReadValue (typeof (T), reader);
|
||||
}
|
||||
|
||||
public static IJsonWrapper ToWrapper (WrapperFactory factory,
|
||||
JsonReader reader)
|
||||
{
|
||||
return ReadValue (factory, reader);
|
||||
}
|
||||
|
||||
public static IJsonWrapper ToWrapper (WrapperFactory factory,
|
||||
string json)
|
||||
{
|
||||
JsonReader reader = new JsonReader (json);
|
||||
|
||||
return ReadValue (factory, reader);
|
||||
}
|
||||
|
||||
public static void RegisterExporter<T> (ExporterFunc<T> exporter)
|
||||
{
|
||||
ExporterFunc exporter_wrapper =
|
||||
delegate (object obj, JsonWriter writer) {
|
||||
exporter ((T) obj, writer);
|
||||
};
|
||||
|
||||
custom_exporters_table[typeof (T)] = exporter_wrapper;
|
||||
}
|
||||
|
||||
public static void RegisterImporter<TJson, TValue> (
|
||||
ImporterFunc<TJson, TValue> importer)
|
||||
{
|
||||
ImporterFunc importer_wrapper =
|
||||
delegate (object input) {
|
||||
return importer ((TJson) input);
|
||||
};
|
||||
|
||||
RegisterImporter (custom_importers_table, typeof (TJson),
|
||||
typeof (TValue), importer_wrapper);
|
||||
}
|
||||
|
||||
public static void UnregisterExporters ()
|
||||
{
|
||||
custom_exporters_table.Clear ();
|
||||
}
|
||||
|
||||
public static void UnregisterImporters ()
|
||||
{
|
||||
custom_importers_table.Clear ();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,455 @@
|
||||
#region Header
|
||||
/*
|
||||
* JsonReader.cs
|
||||
* Stream-like access to JSON text.
|
||||
*
|
||||
* The authors disclaim copyright to this source code. For more details, see
|
||||
* the COPYING file included with this distribution.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace LitJson
|
||||
{
|
||||
public enum JsonToken
|
||||
{
|
||||
None,
|
||||
|
||||
ObjectStart,
|
||||
PropertyName,
|
||||
ObjectEnd,
|
||||
|
||||
ArrayStart,
|
||||
ArrayEnd,
|
||||
|
||||
Int,
|
||||
Long,
|
||||
Double,
|
||||
|
||||
String,
|
||||
|
||||
Boolean,
|
||||
Null
|
||||
}
|
||||
|
||||
|
||||
public class JsonReader
|
||||
{
|
||||
#region Fields
|
||||
private static IDictionary<int, IDictionary<int, int[]>> parse_table;
|
||||
|
||||
private Stack<int> automaton_stack;
|
||||
private int current_input;
|
||||
private int current_symbol;
|
||||
private bool end_of_json;
|
||||
private bool end_of_input;
|
||||
private Lexer lexer;
|
||||
private bool parser_in_string;
|
||||
private bool parser_return;
|
||||
private bool read_started;
|
||||
private TextReader reader;
|
||||
private bool reader_is_owned;
|
||||
private object token_value;
|
||||
private JsonToken token;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Public Properties
|
||||
public bool AllowComments {
|
||||
get { return lexer.AllowComments; }
|
||||
set { lexer.AllowComments = value; }
|
||||
}
|
||||
|
||||
public bool AllowSingleQuotedStrings {
|
||||
get { return lexer.AllowSingleQuotedStrings; }
|
||||
set { lexer.AllowSingleQuotedStrings = value; }
|
||||
}
|
||||
|
||||
public bool EndOfInput {
|
||||
get { return end_of_input; }
|
||||
}
|
||||
|
||||
public bool EndOfJson {
|
||||
get { return end_of_json; }
|
||||
}
|
||||
|
||||
public JsonToken Token {
|
||||
get { return token; }
|
||||
}
|
||||
|
||||
public object Value {
|
||||
get { return token_value; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
static JsonReader ()
|
||||
{
|
||||
PopulateParseTable ();
|
||||
}
|
||||
|
||||
public JsonReader (string json_text) :
|
||||
this (new StringReader (json_text), true)
|
||||
{
|
||||
}
|
||||
|
||||
public JsonReader (TextReader reader) :
|
||||
this (reader, false)
|
||||
{
|
||||
}
|
||||
|
||||
private JsonReader (TextReader reader, bool owned)
|
||||
{
|
||||
if (reader == null)
|
||||
throw new ArgumentNullException ("reader");
|
||||
|
||||
parser_in_string = false;
|
||||
parser_return = false;
|
||||
|
||||
read_started = false;
|
||||
automaton_stack = new Stack<int> ();
|
||||
automaton_stack.Push ((int) ParserToken.End);
|
||||
automaton_stack.Push ((int) ParserToken.Text);
|
||||
|
||||
lexer = new Lexer (reader);
|
||||
|
||||
end_of_input = false;
|
||||
end_of_json = false;
|
||||
|
||||
this.reader = reader;
|
||||
reader_is_owned = owned;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Static Methods
|
||||
private static void PopulateParseTable ()
|
||||
{
|
||||
parse_table = new Dictionary<int, IDictionary<int, int[]>> ();
|
||||
|
||||
TableAddRow (ParserToken.Array);
|
||||
TableAddCol (ParserToken.Array, '[',
|
||||
'[',
|
||||
(int) ParserToken.ArrayPrime);
|
||||
|
||||
TableAddRow (ParserToken.ArrayPrime);
|
||||
TableAddCol (ParserToken.ArrayPrime, '"',
|
||||
(int) ParserToken.Value,
|
||||
|
||||
(int) ParserToken.ValueRest,
|
||||
']');
|
||||
TableAddCol (ParserToken.ArrayPrime, '[',
|
||||
(int) ParserToken.Value,
|
||||
(int) ParserToken.ValueRest,
|
||||
']');
|
||||
TableAddCol (ParserToken.ArrayPrime, ']',
|
||||
']');
|
||||
TableAddCol (ParserToken.ArrayPrime, '{',
|
||||
(int) ParserToken.Value,
|
||||
(int) ParserToken.ValueRest,
|
||||
']');
|
||||
TableAddCol (ParserToken.ArrayPrime, (int) ParserToken.Number,
|
||||
(int) ParserToken.Value,
|
||||
(int) ParserToken.ValueRest,
|
||||
']');
|
||||
TableAddCol (ParserToken.ArrayPrime, (int) ParserToken.True,
|
||||
(int) ParserToken.Value,
|
||||
(int) ParserToken.ValueRest,
|
||||
']');
|
||||
TableAddCol (ParserToken.ArrayPrime, (int) ParserToken.False,
|
||||
(int) ParserToken.Value,
|
||||
(int) ParserToken.ValueRest,
|
||||
']');
|
||||
TableAddCol (ParserToken.ArrayPrime, (int) ParserToken.Null,
|
||||
(int) ParserToken.Value,
|
||||
(int) ParserToken.ValueRest,
|
||||
']');
|
||||
|
||||
TableAddRow (ParserToken.Object);
|
||||
TableAddCol (ParserToken.Object, '{',
|
||||
'{',
|
||||
(int) ParserToken.ObjectPrime);
|
||||
|
||||
TableAddRow (ParserToken.ObjectPrime);
|
||||
TableAddCol (ParserToken.ObjectPrime, '"',
|
||||
(int) ParserToken.Pair,
|
||||
(int) ParserToken.PairRest,
|
||||
'}');
|
||||
TableAddCol (ParserToken.ObjectPrime, '}',
|
||||
'}');
|
||||
|
||||
TableAddRow (ParserToken.Pair);
|
||||
TableAddCol (ParserToken.Pair, '"',
|
||||
(int) ParserToken.String,
|
||||
':',
|
||||
(int) ParserToken.Value);
|
||||
|
||||
TableAddRow (ParserToken.PairRest);
|
||||
TableAddCol (ParserToken.PairRest, ',',
|
||||
',',
|
||||
(int) ParserToken.Pair,
|
||||
(int) ParserToken.PairRest);
|
||||
TableAddCol (ParserToken.PairRest, '}',
|
||||
(int) ParserToken.Epsilon);
|
||||
|
||||
TableAddRow (ParserToken.String);
|
||||
TableAddCol (ParserToken.String, '"',
|
||||
'"',
|
||||
(int) ParserToken.CharSeq,
|
||||
'"');
|
||||
|
||||
TableAddRow (ParserToken.Text);
|
||||
TableAddCol (ParserToken.Text, '[',
|
||||
(int) ParserToken.Array);
|
||||
TableAddCol (ParserToken.Text, '{',
|
||||
(int) ParserToken.Object);
|
||||
|
||||
TableAddRow (ParserToken.Value);
|
||||
TableAddCol (ParserToken.Value, '"',
|
||||
(int) ParserToken.String);
|
||||
TableAddCol (ParserToken.Value, '[',
|
||||
(int) ParserToken.Array);
|
||||
TableAddCol (ParserToken.Value, '{',
|
||||
(int) ParserToken.Object);
|
||||
TableAddCol (ParserToken.Value, (int) ParserToken.Number,
|
||||
(int) ParserToken.Number);
|
||||
TableAddCol (ParserToken.Value, (int) ParserToken.True,
|
||||
(int) ParserToken.True);
|
||||
TableAddCol (ParserToken.Value, (int) ParserToken.False,
|
||||
(int) ParserToken.False);
|
||||
TableAddCol (ParserToken.Value, (int) ParserToken.Null,
|
||||
(int) ParserToken.Null);
|
||||
|
||||
TableAddRow (ParserToken.ValueRest);
|
||||
TableAddCol (ParserToken.ValueRest, ',',
|
||||
',',
|
||||
(int) ParserToken.Value,
|
||||
(int) ParserToken.ValueRest);
|
||||
TableAddCol (ParserToken.ValueRest, ']',
|
||||
(int) ParserToken.Epsilon);
|
||||
}
|
||||
|
||||
private static void TableAddCol (ParserToken row, int col,
|
||||
params int[] symbols)
|
||||
{
|
||||
parse_table[(int) row].Add (col, symbols);
|
||||
}
|
||||
|
||||
private static void TableAddRow (ParserToken rule)
|
||||
{
|
||||
parse_table.Add ((int) rule, new Dictionary<int, int[]> ());
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Private Methods
|
||||
private void ProcessNumber (string number)
|
||||
{
|
||||
if (number.IndexOf ('.') != -1 ||
|
||||
number.IndexOf ('e') != -1 ||
|
||||
number.IndexOf ('E') != -1) {
|
||||
|
||||
double n_double;
|
||||
if (Double.TryParse (number, out n_double)) {
|
||||
token = JsonToken.Double;
|
||||
token_value = n_double;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int n_int32;
|
||||
if (Int32.TryParse (number, out n_int32)) {
|
||||
token = JsonToken.Int;
|
||||
token_value = n_int32;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
long n_int64;
|
||||
if (Int64.TryParse (number, out n_int64)) {
|
||||
token = JsonToken.Long;
|
||||
token_value = n_int64;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Shouldn't happen, but just in case, return something
|
||||
token = JsonToken.Int;
|
||||
token_value = 0;
|
||||
}
|
||||
|
||||
private void ProcessSymbol ()
|
||||
{
|
||||
if (current_symbol == '[') {
|
||||
token = JsonToken.ArrayStart;
|
||||
parser_return = true;
|
||||
|
||||
} else if (current_symbol == ']') {
|
||||
token = JsonToken.ArrayEnd;
|
||||
parser_return = true;
|
||||
|
||||
} else if (current_symbol == '{') {
|
||||
token = JsonToken.ObjectStart;
|
||||
parser_return = true;
|
||||
|
||||
} else if (current_symbol == '}') {
|
||||
token = JsonToken.ObjectEnd;
|
||||
parser_return = true;
|
||||
|
||||
} else if (current_symbol == '"') {
|
||||
if (parser_in_string) {
|
||||
parser_in_string = false;
|
||||
|
||||
parser_return = true;
|
||||
|
||||
} else {
|
||||
if (token == JsonToken.None)
|
||||
token = JsonToken.String;
|
||||
|
||||
parser_in_string = true;
|
||||
}
|
||||
|
||||
} else if (current_symbol == (int) ParserToken.CharSeq) {
|
||||
token_value = lexer.StringValue;
|
||||
|
||||
} else if (current_symbol == (int) ParserToken.False) {
|
||||
token = JsonToken.Boolean;
|
||||
token_value = false;
|
||||
parser_return = true;
|
||||
|
||||
} else if (current_symbol == (int) ParserToken.Null) {
|
||||
token = JsonToken.Null;
|
||||
parser_return = true;
|
||||
|
||||
} else if (current_symbol == (int) ParserToken.Number) {
|
||||
ProcessNumber (lexer.StringValue);
|
||||
|
||||
parser_return = true;
|
||||
|
||||
} else if (current_symbol == (int) ParserToken.Pair) {
|
||||
token = JsonToken.PropertyName;
|
||||
|
||||
} else if (current_symbol == (int) ParserToken.True) {
|
||||
token = JsonToken.Boolean;
|
||||
token_value = true;
|
||||
parser_return = true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private bool ReadToken ()
|
||||
{
|
||||
if (end_of_input)
|
||||
return false;
|
||||
|
||||
lexer.NextToken ();
|
||||
|
||||
if (lexer.EndOfInput) {
|
||||
Close ();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
current_input = lexer.Token;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
public void Close ()
|
||||
{
|
||||
if (end_of_input)
|
||||
return;
|
||||
|
||||
end_of_input = true;
|
||||
end_of_json = true;
|
||||
|
||||
if (reader_is_owned)
|
||||
reader.Close ();
|
||||
|
||||
reader = null;
|
||||
}
|
||||
|
||||
public bool Read ()
|
||||
{
|
||||
if (end_of_input)
|
||||
return false;
|
||||
|
||||
if (end_of_json) {
|
||||
end_of_json = false;
|
||||
automaton_stack.Clear ();
|
||||
automaton_stack.Push ((int) ParserToken.End);
|
||||
automaton_stack.Push ((int) ParserToken.Text);
|
||||
}
|
||||
|
||||
parser_in_string = false;
|
||||
parser_return = false;
|
||||
|
||||
token = JsonToken.None;
|
||||
token_value = null;
|
||||
|
||||
if (! read_started) {
|
||||
read_started = true;
|
||||
|
||||
if (! ReadToken ())
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
int[] entry_symbols;
|
||||
|
||||
while (true) {
|
||||
if (parser_return) {
|
||||
if (automaton_stack.Peek () == (int) ParserToken.End)
|
||||
end_of_json = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
current_symbol = automaton_stack.Pop ();
|
||||
|
||||
ProcessSymbol ();
|
||||
|
||||
if (current_symbol == current_input) {
|
||||
if (! ReadToken ()) {
|
||||
if (automaton_stack.Peek () != (int) ParserToken.End)
|
||||
throw new JsonException (
|
||||
"Input doesn't evaluate to proper JSON text");
|
||||
|
||||
if (parser_return)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
entry_symbols =
|
||||
parse_table[current_symbol][current_input];
|
||||
|
||||
} catch (KeyNotFoundException e) {
|
||||
throw new JsonException ((ParserToken) current_input, e);
|
||||
}
|
||||
|
||||
if (entry_symbols[0] == (int) ParserToken.Epsilon)
|
||||
continue;
|
||||
|
||||
for (int i = entry_symbols.Length - 1; i >= 0; i--)
|
||||
automaton_stack.Push (entry_symbols[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
#region Header
|
||||
/*
|
||||
* JsonWriter.cs
|
||||
* Stream-like facility to output JSON text.
|
||||
*
|
||||
* The authors disclaim copyright to this source code. For more details, see
|
||||
* the COPYING file included with this distribution.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace LitJson
|
||||
{
|
||||
internal enum Condition
|
||||
{
|
||||
InArray,
|
||||
InObject,
|
||||
NotAProperty,
|
||||
Property,
|
||||
Value
|
||||
}
|
||||
|
||||
internal class WriterContext
|
||||
{
|
||||
public int Count;
|
||||
public bool InArray;
|
||||
public bool InObject;
|
||||
public bool ExpectingValue;
|
||||
public int Padding;
|
||||
}
|
||||
|
||||
public class JsonWriter
|
||||
{
|
||||
#region Fields
|
||||
private static NumberFormatInfo number_format;
|
||||
|
||||
private WriterContext context;
|
||||
private Stack<WriterContext> ctx_stack;
|
||||
private bool has_reached_end;
|
||||
private char[] hex_seq;
|
||||
private int indentation;
|
||||
private int indent_value;
|
||||
private StringBuilder inst_string_builder;
|
||||
private bool pretty_print;
|
||||
private bool validate;
|
||||
private TextWriter writer;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
public int IndentValue {
|
||||
get { return indent_value; }
|
||||
set {
|
||||
indentation = (indentation / indent_value) * value;
|
||||
indent_value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool PrettyPrint {
|
||||
get { return pretty_print; }
|
||||
set { pretty_print = value; }
|
||||
}
|
||||
|
||||
public TextWriter TextWriter {
|
||||
get { return writer; }
|
||||
}
|
||||
|
||||
public bool Validate {
|
||||
get { return validate; }
|
||||
set { validate = value; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
static JsonWriter ()
|
||||
{
|
||||
number_format = NumberFormatInfo.InvariantInfo;
|
||||
}
|
||||
|
||||
public JsonWriter ()
|
||||
{
|
||||
inst_string_builder = new StringBuilder ();
|
||||
writer = new StringWriter (inst_string_builder);
|
||||
|
||||
Init ();
|
||||
}
|
||||
|
||||
public JsonWriter (StringBuilder sb) :
|
||||
this (new StringWriter (sb))
|
||||
{
|
||||
}
|
||||
|
||||
public JsonWriter (TextWriter writer)
|
||||
{
|
||||
if (writer == null)
|
||||
throw new ArgumentNullException ("writer");
|
||||
|
||||
this.writer = writer;
|
||||
|
||||
Init ();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Private Methods
|
||||
private void DoValidation (Condition cond)
|
||||
{
|
||||
if (! context.ExpectingValue)
|
||||
context.Count++;
|
||||
|
||||
if (! validate)
|
||||
return;
|
||||
|
||||
if (has_reached_end)
|
||||
throw new JsonException (
|
||||
"A complete JSON symbol has already been written");
|
||||
|
||||
switch (cond) {
|
||||
case Condition.InArray:
|
||||
if (! context.InArray)
|
||||
throw new JsonException (
|
||||
"Can't close an array here");
|
||||
break;
|
||||
|
||||
case Condition.InObject:
|
||||
if (! context.InObject || context.ExpectingValue)
|
||||
throw new JsonException (
|
||||
"Can't close an object here");
|
||||
break;
|
||||
|
||||
case Condition.NotAProperty:
|
||||
if (context.InObject && ! context.ExpectingValue)
|
||||
throw new JsonException (
|
||||
"Expected a property");
|
||||
break;
|
||||
|
||||
case Condition.Property:
|
||||
if (! context.InObject || context.ExpectingValue)
|
||||
throw new JsonException (
|
||||
"Can't add a property here");
|
||||
break;
|
||||
|
||||
case Condition.Value:
|
||||
if (! context.InArray &&
|
||||
(! context.InObject || ! context.ExpectingValue))
|
||||
throw new JsonException (
|
||||
"Can't add a value here");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void Init ()
|
||||
{
|
||||
has_reached_end = false;
|
||||
hex_seq = new char[4];
|
||||
indentation = 0;
|
||||
indent_value = 4;
|
||||
pretty_print = false;
|
||||
validate = true;
|
||||
|
||||
ctx_stack = new Stack<WriterContext> ();
|
||||
context = new WriterContext ();
|
||||
ctx_stack.Push (context);
|
||||
}
|
||||
|
||||
private static void IntToHex (int n, char[] hex)
|
||||
{
|
||||
int num;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
num = n % 16;
|
||||
|
||||
if (num < 10)
|
||||
hex[3 - i] = (char) ('0' + num);
|
||||
else
|
||||
hex[3 - i] = (char) ('A' + (num - 10));
|
||||
|
||||
n >>= 4;
|
||||
}
|
||||
}
|
||||
|
||||
private void Indent ()
|
||||
{
|
||||
if (pretty_print)
|
||||
indentation += indent_value;
|
||||
}
|
||||
|
||||
|
||||
private void Put (string str)
|
||||
{
|
||||
if (pretty_print && ! context.ExpectingValue)
|
||||
for (int i = 0; i < indentation; i++)
|
||||
writer.Write (' ');
|
||||
|
||||
writer.Write (str);
|
||||
}
|
||||
|
||||
private void PutNewline ()
|
||||
{
|
||||
PutNewline (true);
|
||||
}
|
||||
|
||||
private void PutNewline (bool add_comma)
|
||||
{
|
||||
if (add_comma && ! context.ExpectingValue &&
|
||||
context.Count > 1)
|
||||
writer.Write (',');
|
||||
|
||||
if (pretty_print && ! context.ExpectingValue)
|
||||
writer.Write ('\n');
|
||||
}
|
||||
|
||||
private void PutString (string str)
|
||||
{
|
||||
Put (String.Empty);
|
||||
|
||||
writer.Write ('"');
|
||||
|
||||
int n = str.Length;
|
||||
for (int i = 0; i < n; i++) {
|
||||
switch (str[i]) {
|
||||
case '\n':
|
||||
writer.Write ("\\n");
|
||||
continue;
|
||||
|
||||
case '\r':
|
||||
writer.Write ("\\r");
|
||||
continue;
|
||||
|
||||
case '\t':
|
||||
writer.Write ("\\t");
|
||||
continue;
|
||||
|
||||
case '"':
|
||||
case '\\':
|
||||
writer.Write ('\\');
|
||||
writer.Write (str[i]);
|
||||
continue;
|
||||
|
||||
case '\f':
|
||||
writer.Write ("\\f");
|
||||
continue;
|
||||
|
||||
case '\b':
|
||||
writer.Write ("\\b");
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((int) str[i] >= 32 && (int) str[i] <= 126) {
|
||||
writer.Write (str[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Default, turn into a \uXXXX sequence
|
||||
IntToHex ((int) str[i], hex_seq);
|
||||
writer.Write ("\\u");
|
||||
writer.Write (hex_seq);
|
||||
}
|
||||
|
||||
writer.Write ('"');
|
||||
}
|
||||
|
||||
private void Unindent ()
|
||||
{
|
||||
if (pretty_print)
|
||||
indentation -= indent_value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
if (inst_string_builder == null)
|
||||
return String.Empty;
|
||||
|
||||
return inst_string_builder.ToString ();
|
||||
}
|
||||
|
||||
public void Reset ()
|
||||
{
|
||||
has_reached_end = false;
|
||||
|
||||
ctx_stack.Clear ();
|
||||
context = new WriterContext ();
|
||||
ctx_stack.Push (context);
|
||||
|
||||
if (inst_string_builder != null)
|
||||
inst_string_builder.Remove (0, inst_string_builder.Length);
|
||||
}
|
||||
|
||||
public void Write (bool boolean)
|
||||
{
|
||||
DoValidation (Condition.Value);
|
||||
PutNewline ();
|
||||
|
||||
Put (boolean ? "true" : "false");
|
||||
|
||||
context.ExpectingValue = false;
|
||||
}
|
||||
|
||||
public void Write (decimal number)
|
||||
{
|
||||
DoValidation (Condition.Value);
|
||||
PutNewline ();
|
||||
|
||||
Put (Convert.ToString (number, number_format));
|
||||
|
||||
context.ExpectingValue = false;
|
||||
}
|
||||
|
||||
public void Write (double number)
|
||||
{
|
||||
DoValidation (Condition.Value);
|
||||
PutNewline ();
|
||||
|
||||
if (double.IsNaN(number) || double.IsInfinity(number))
|
||||
Put("null");
|
||||
else
|
||||
{
|
||||
string str = Convert.ToString(number, number_format);
|
||||
Put(str);
|
||||
|
||||
if (str.IndexOf('.') == -1 && str.IndexOf('E') == -1)
|
||||
writer.Write(".0");
|
||||
}
|
||||
|
||||
context.ExpectingValue = false;
|
||||
}
|
||||
|
||||
public void Write (int number)
|
||||
{
|
||||
DoValidation (Condition.Value);
|
||||
PutNewline ();
|
||||
|
||||
Put (Convert.ToString (number, number_format));
|
||||
|
||||
context.ExpectingValue = false;
|
||||
}
|
||||
|
||||
public void Write (long number)
|
||||
{
|
||||
DoValidation (Condition.Value);
|
||||
PutNewline ();
|
||||
|
||||
Put (Convert.ToString (number, number_format));
|
||||
|
||||
context.ExpectingValue = false;
|
||||
}
|
||||
|
||||
public void Write (string str)
|
||||
{
|
||||
DoValidation (Condition.Value);
|
||||
PutNewline ();
|
||||
|
||||
if (str == null)
|
||||
Put ("null");
|
||||
else
|
||||
PutString (str);
|
||||
|
||||
context.ExpectingValue = false;
|
||||
}
|
||||
|
||||
public void Write (ulong number)
|
||||
{
|
||||
DoValidation (Condition.Value);
|
||||
PutNewline ();
|
||||
|
||||
Put (Convert.ToString (number, number_format));
|
||||
|
||||
context.ExpectingValue = false;
|
||||
}
|
||||
|
||||
public void WriteArrayEnd ()
|
||||
{
|
||||
DoValidation (Condition.InArray);
|
||||
PutNewline (false);
|
||||
|
||||
ctx_stack.Pop ();
|
||||
if (ctx_stack.Count == 1)
|
||||
has_reached_end = true;
|
||||
else {
|
||||
context = ctx_stack.Peek ();
|
||||
context.ExpectingValue = false;
|
||||
}
|
||||
|
||||
Unindent ();
|
||||
Put ("]");
|
||||
}
|
||||
|
||||
public void WriteArrayStart ()
|
||||
{
|
||||
DoValidation (Condition.NotAProperty);
|
||||
PutNewline ();
|
||||
|
||||
Put ("[");
|
||||
|
||||
context = new WriterContext ();
|
||||
context.InArray = true;
|
||||
ctx_stack.Push (context);
|
||||
|
||||
Indent ();
|
||||
}
|
||||
|
||||
public void WriteObjectEnd ()
|
||||
{
|
||||
DoValidation (Condition.InObject);
|
||||
PutNewline (false);
|
||||
|
||||
ctx_stack.Pop ();
|
||||
if (ctx_stack.Count == 1)
|
||||
has_reached_end = true;
|
||||
else {
|
||||
context = ctx_stack.Peek ();
|
||||
context.ExpectingValue = false;
|
||||
}
|
||||
|
||||
Unindent ();
|
||||
Put ("}");
|
||||
}
|
||||
|
||||
public void WriteObjectStart ()
|
||||
{
|
||||
DoValidation (Condition.NotAProperty);
|
||||
PutNewline ();
|
||||
|
||||
Put ("{");
|
||||
|
||||
context = new WriterContext ();
|
||||
context.InObject = true;
|
||||
ctx_stack.Push (context);
|
||||
|
||||
Indent ();
|
||||
}
|
||||
|
||||
public void WritePropertyName (string property_name)
|
||||
{
|
||||
DoValidation (Condition.Property);
|
||||
PutNewline ();
|
||||
|
||||
PutString (property_name);
|
||||
|
||||
if (pretty_print) {
|
||||
if (property_name.Length > context.Padding)
|
||||
context.Padding = property_name.Length;
|
||||
|
||||
for (int i = context.Padding - property_name.Length;
|
||||
i >= 0; i--)
|
||||
writer.Write (' ');
|
||||
|
||||
writer.Write (": ");
|
||||
} else
|
||||
writer.Write (':');
|
||||
|
||||
context.ExpectingValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,910 @@
|
||||
#region Header
|
||||
/*
|
||||
* Lexer.cs
|
||||
* JSON lexer implementation based on a finite state machine.
|
||||
*
|
||||
* The authors disclaim copyright to this source code. For more details, see
|
||||
* the COPYING file included with this distribution.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
|
||||
namespace LitJson
|
||||
{
|
||||
internal class FsmContext
|
||||
{
|
||||
public bool Return;
|
||||
public int NextState;
|
||||
public Lexer L;
|
||||
public int StateStack;
|
||||
}
|
||||
|
||||
|
||||
internal class Lexer
|
||||
{
|
||||
#region Fields
|
||||
private delegate bool StateHandler (FsmContext ctx);
|
||||
|
||||
private static int[] fsm_return_table;
|
||||
private static StateHandler[] fsm_handler_table;
|
||||
|
||||
private bool allow_comments;
|
||||
private bool allow_single_quoted_strings;
|
||||
private bool end_of_input;
|
||||
private FsmContext fsm_context;
|
||||
private int input_buffer;
|
||||
private int input_char;
|
||||
private TextReader reader;
|
||||
private int state;
|
||||
private StringBuilder string_buffer;
|
||||
private string string_value;
|
||||
private int token;
|
||||
private int unichar;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
public bool AllowComments {
|
||||
get { return allow_comments; }
|
||||
set { allow_comments = value; }
|
||||
}
|
||||
|
||||
public bool AllowSingleQuotedStrings {
|
||||
get { return allow_single_quoted_strings; }
|
||||
set { allow_single_quoted_strings = value; }
|
||||
}
|
||||
|
||||
public bool EndOfInput {
|
||||
get { return end_of_input; }
|
||||
}
|
||||
|
||||
public int Token {
|
||||
get { return token; }
|
||||
}
|
||||
|
||||
public string StringValue {
|
||||
get { return string_value; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
static Lexer ()
|
||||
{
|
||||
PopulateFsmTables ();
|
||||
}
|
||||
|
||||
public Lexer (TextReader reader)
|
||||
{
|
||||
allow_comments = true;
|
||||
allow_single_quoted_strings = true;
|
||||
|
||||
input_buffer = 0;
|
||||
string_buffer = new StringBuilder (128);
|
||||
state = 1;
|
||||
end_of_input = false;
|
||||
this.reader = reader;
|
||||
|
||||
fsm_context = new FsmContext ();
|
||||
fsm_context.L = this;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Static Methods
|
||||
private static int HexValue (int digit)
|
||||
{
|
||||
switch (digit) {
|
||||
case 'a':
|
||||
case 'A':
|
||||
return 10;
|
||||
|
||||
case 'b':
|
||||
case 'B':
|
||||
return 11;
|
||||
|
||||
case 'c':
|
||||
case 'C':
|
||||
return 12;
|
||||
|
||||
case 'd':
|
||||
case 'D':
|
||||
return 13;
|
||||
|
||||
case 'e':
|
||||
case 'E':
|
||||
return 14;
|
||||
|
||||
case 'f':
|
||||
case 'F':
|
||||
return 15;
|
||||
|
||||
default:
|
||||
return digit - '0';
|
||||
}
|
||||
}
|
||||
|
||||
private static void PopulateFsmTables ()
|
||||
{
|
||||
fsm_handler_table = new StateHandler[28] {
|
||||
State1,
|
||||
State2,
|
||||
State3,
|
||||
State4,
|
||||
State5,
|
||||
State6,
|
||||
State7,
|
||||
State8,
|
||||
State9,
|
||||
State10,
|
||||
State11,
|
||||
State12,
|
||||
State13,
|
||||
State14,
|
||||
State15,
|
||||
State16,
|
||||
State17,
|
||||
State18,
|
||||
State19,
|
||||
State20,
|
||||
State21,
|
||||
State22,
|
||||
State23,
|
||||
State24,
|
||||
State25,
|
||||
State26,
|
||||
State27,
|
||||
State28
|
||||
};
|
||||
|
||||
fsm_return_table = new int[28] {
|
||||
(int) ParserToken.Char,
|
||||
0,
|
||||
(int) ParserToken.Number,
|
||||
(int) ParserToken.Number,
|
||||
0,
|
||||
(int) ParserToken.Number,
|
||||
0,
|
||||
(int) ParserToken.Number,
|
||||
0,
|
||||
0,
|
||||
(int) ParserToken.True,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
(int) ParserToken.False,
|
||||
0,
|
||||
0,
|
||||
(int) ParserToken.Null,
|
||||
(int) ParserToken.CharSeq,
|
||||
(int) ParserToken.Char,
|
||||
0,
|
||||
0,
|
||||
(int) ParserToken.CharSeq,
|
||||
(int) ParserToken.Char,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
};
|
||||
}
|
||||
|
||||
private static char ProcessEscChar (int esc_char)
|
||||
{
|
||||
switch (esc_char) {
|
||||
case '"':
|
||||
case '\'':
|
||||
case '\\':
|
||||
case '/':
|
||||
return Convert.ToChar (esc_char);
|
||||
|
||||
case 'n':
|
||||
return '\n';
|
||||
|
||||
case 't':
|
||||
return '\t';
|
||||
|
||||
case 'r':
|
||||
return '\r';
|
||||
|
||||
case 'b':
|
||||
return '\b';
|
||||
|
||||
case 'f':
|
||||
return '\f';
|
||||
|
||||
default:
|
||||
// Unreachable
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State1 (FsmContext ctx)
|
||||
{
|
||||
while (ctx.L.GetChar ()) {
|
||||
if (ctx.L.input_char == ' ' ||
|
||||
ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r')
|
||||
continue;
|
||||
|
||||
if (ctx.L.input_char >= '1' && ctx.L.input_char <= '9') {
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
ctx.NextState = 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case '"':
|
||||
ctx.NextState = 19;
|
||||
ctx.Return = true;
|
||||
return true;
|
||||
|
||||
case ',':
|
||||
case ':':
|
||||
case '[':
|
||||
case ']':
|
||||
case '{':
|
||||
case '}':
|
||||
ctx.NextState = 1;
|
||||
ctx.Return = true;
|
||||
return true;
|
||||
|
||||
case '-':
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
ctx.NextState = 2;
|
||||
return true;
|
||||
|
||||
case '0':
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
ctx.NextState = 4;
|
||||
return true;
|
||||
|
||||
case 'f':
|
||||
ctx.NextState = 12;
|
||||
return true;
|
||||
|
||||
case 'n':
|
||||
ctx.NextState = 16;
|
||||
return true;
|
||||
|
||||
case 't':
|
||||
ctx.NextState = 9;
|
||||
return true;
|
||||
|
||||
case '\'':
|
||||
if (! ctx.L.allow_single_quoted_strings)
|
||||
return false;
|
||||
|
||||
ctx.L.input_char = '"';
|
||||
ctx.NextState = 23;
|
||||
ctx.Return = true;
|
||||
return true;
|
||||
|
||||
case '/':
|
||||
if (! ctx.L.allow_comments)
|
||||
return false;
|
||||
|
||||
ctx.NextState = 25;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool State2 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
if (ctx.L.input_char >= '1' && ctx.L.input_char<= '9') {
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
ctx.NextState = 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case '0':
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
ctx.NextState = 4;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State3 (FsmContext ctx)
|
||||
{
|
||||
while (ctx.L.GetChar ()) {
|
||||
if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9') {
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ctx.L.input_char == ' ' ||
|
||||
ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r') {
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case ',':
|
||||
case ']':
|
||||
case '}':
|
||||
ctx.L.UngetChar ();
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
|
||||
case '.':
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
ctx.NextState = 5;
|
||||
return true;
|
||||
|
||||
case 'e':
|
||||
case 'E':
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
ctx.NextState = 7;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool State4 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
if (ctx.L.input_char == ' ' ||
|
||||
ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r') {
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case ',':
|
||||
case ']':
|
||||
case '}':
|
||||
ctx.L.UngetChar ();
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
|
||||
case '.':
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
ctx.NextState = 5;
|
||||
return true;
|
||||
|
||||
case 'e':
|
||||
case 'E':
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
ctx.NextState = 7;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State5 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9') {
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
ctx.NextState = 6;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool State6 (FsmContext ctx)
|
||||
{
|
||||
while (ctx.L.GetChar ()) {
|
||||
if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9') {
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ctx.L.input_char == ' ' ||
|
||||
ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r') {
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case ',':
|
||||
case ']':
|
||||
case '}':
|
||||
ctx.L.UngetChar ();
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
|
||||
case 'e':
|
||||
case 'E':
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
ctx.NextState = 7;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool State7 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
if (ctx.L.input_char >= '0' && ctx.L.input_char<= '9') {
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
ctx.NextState = 8;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case '+':
|
||||
case '-':
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
ctx.NextState = 8;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State8 (FsmContext ctx)
|
||||
{
|
||||
while (ctx.L.GetChar ()) {
|
||||
if (ctx.L.input_char >= '0' && ctx.L.input_char<= '9') {
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ctx.L.input_char == ' ' ||
|
||||
ctx.L.input_char >= '\t' && ctx.L.input_char<= '\r') {
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case ',':
|
||||
case ']':
|
||||
case '}':
|
||||
ctx.L.UngetChar ();
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool State9 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case 'r':
|
||||
ctx.NextState = 10;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State10 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case 'u':
|
||||
ctx.NextState = 11;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State11 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case 'e':
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State12 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case 'a':
|
||||
ctx.NextState = 13;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State13 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case 'l':
|
||||
ctx.NextState = 14;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State14 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case 's':
|
||||
ctx.NextState = 15;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State15 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case 'e':
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State16 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case 'u':
|
||||
ctx.NextState = 17;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State17 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case 'l':
|
||||
ctx.NextState = 18;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State18 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case 'l':
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State19 (FsmContext ctx)
|
||||
{
|
||||
while (ctx.L.GetChar ()) {
|
||||
switch (ctx.L.input_char) {
|
||||
case '"':
|
||||
ctx.L.UngetChar ();
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 20;
|
||||
return true;
|
||||
|
||||
case '\\':
|
||||
ctx.StateStack = 19;
|
||||
ctx.NextState = 21;
|
||||
return true;
|
||||
|
||||
default:
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool State20 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case '"':
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State21 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case 'u':
|
||||
ctx.NextState = 22;
|
||||
return true;
|
||||
|
||||
case '"':
|
||||
case '\'':
|
||||
case '/':
|
||||
case '\\':
|
||||
case 'b':
|
||||
case 'f':
|
||||
case 'n':
|
||||
case 'r':
|
||||
case 't':
|
||||
ctx.L.string_buffer.Append (
|
||||
ProcessEscChar (ctx.L.input_char));
|
||||
ctx.NextState = ctx.StateStack;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State22 (FsmContext ctx)
|
||||
{
|
||||
int counter = 0;
|
||||
int mult = 4096;
|
||||
|
||||
ctx.L.unichar = 0;
|
||||
|
||||
while (ctx.L.GetChar ()) {
|
||||
|
||||
if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9' ||
|
||||
ctx.L.input_char >= 'A' && ctx.L.input_char <= 'F' ||
|
||||
ctx.L.input_char >= 'a' && ctx.L.input_char <= 'f') {
|
||||
|
||||
ctx.L.unichar += HexValue (ctx.L.input_char) * mult;
|
||||
|
||||
counter++;
|
||||
mult /= 16;
|
||||
|
||||
if (counter == 4) {
|
||||
ctx.L.string_buffer.Append (
|
||||
Convert.ToChar (ctx.L.unichar));
|
||||
ctx.NextState = ctx.StateStack;
|
||||
return true;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool State23 (FsmContext ctx)
|
||||
{
|
||||
while (ctx.L.GetChar ()) {
|
||||
switch (ctx.L.input_char) {
|
||||
case '\'':
|
||||
ctx.L.UngetChar ();
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 24;
|
||||
return true;
|
||||
|
||||
case '\\':
|
||||
ctx.StateStack = 23;
|
||||
ctx.NextState = 21;
|
||||
return true;
|
||||
|
||||
default:
|
||||
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool State24 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case '\'':
|
||||
ctx.L.input_char = '"';
|
||||
ctx.Return = true;
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State25 (FsmContext ctx)
|
||||
{
|
||||
ctx.L.GetChar ();
|
||||
|
||||
switch (ctx.L.input_char) {
|
||||
case '*':
|
||||
ctx.NextState = 27;
|
||||
return true;
|
||||
|
||||
case '/':
|
||||
ctx.NextState = 26;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool State26 (FsmContext ctx)
|
||||
{
|
||||
while (ctx.L.GetChar ()) {
|
||||
if (ctx.L.input_char == '\n') {
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool State27 (FsmContext ctx)
|
||||
{
|
||||
while (ctx.L.GetChar ()) {
|
||||
if (ctx.L.input_char == '*') {
|
||||
ctx.NextState = 28;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool State28 (FsmContext ctx)
|
||||
{
|
||||
while (ctx.L.GetChar ()) {
|
||||
if (ctx.L.input_char == '*')
|
||||
continue;
|
||||
|
||||
if (ctx.L.input_char == '/') {
|
||||
ctx.NextState = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
ctx.NextState = 27;
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
private bool GetChar ()
|
||||
{
|
||||
if ((input_char = NextChar ()) != -1)
|
||||
return true;
|
||||
|
||||
end_of_input = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private int NextChar ()
|
||||
{
|
||||
if (input_buffer != 0) {
|
||||
int tmp = input_buffer;
|
||||
input_buffer = 0;
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
return reader.Read ();
|
||||
}
|
||||
|
||||
public bool NextToken ()
|
||||
{
|
||||
StateHandler handler;
|
||||
fsm_context.Return = false;
|
||||
|
||||
while (true) {
|
||||
handler = fsm_handler_table[state - 1];
|
||||
|
||||
if (! handler (fsm_context))
|
||||
throw new JsonException (input_char);
|
||||
|
||||
if (end_of_input)
|
||||
return false;
|
||||
|
||||
if (fsm_context.Return) {
|
||||
string_value = string_buffer.ToString ();
|
||||
string_buffer.Remove (0, string_buffer.Length);
|
||||
token = fsm_return_table[state - 1];
|
||||
|
||||
if (token == (int) ParserToken.Char)
|
||||
token = input_char;
|
||||
|
||||
state = fsm_context.NextState;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
state = fsm_context.NextState;
|
||||
}
|
||||
}
|
||||
|
||||
private void UngetChar ()
|
||||
{
|
||||
input_buffer = input_char;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using LitJson;
|
||||
|
||||
namespace OpenMetaverse.StructuredData
|
||||
{
|
||||
public static partial class OSDParser
|
||||
{
|
||||
public static OSD DeserializeJson(Stream json)
|
||||
{
|
||||
using (StreamReader streamReader = new StreamReader(json))
|
||||
{
|
||||
JsonReader reader = new JsonReader(streamReader);
|
||||
return DeserializeJson(JsonMapper.ToObject(reader));
|
||||
}
|
||||
}
|
||||
|
||||
public static OSD DeserializeJson(string json)
|
||||
{
|
||||
return DeserializeJson(JsonMapper.ToObject(json));
|
||||
}
|
||||
|
||||
public static OSD DeserializeJson(JsonData json)
|
||||
{
|
||||
if (json == null) return new OSD();
|
||||
|
||||
switch (json.GetJsonType())
|
||||
{
|
||||
case JsonType.Boolean:
|
||||
return OSD.FromBoolean((bool)json);
|
||||
case JsonType.Int:
|
||||
return OSD.FromInteger((int)json);
|
||||
case JsonType.Long:
|
||||
return OSD.FromLong((long)json);
|
||||
case JsonType.Double:
|
||||
return OSD.FromReal((double)json);
|
||||
case JsonType.String:
|
||||
string str = (string)json;
|
||||
if (String.IsNullOrEmpty(str))
|
||||
return new OSD();
|
||||
else
|
||||
return OSD.FromString(str);
|
||||
case JsonType.Array:
|
||||
OSDArray array = new OSDArray(json.Count);
|
||||
for (int i = 0; i < json.Count; i++)
|
||||
array.Add(DeserializeJson(json[i]));
|
||||
return array;
|
||||
case JsonType.Object:
|
||||
OSDMap map = new OSDMap(json.Count);
|
||||
IDictionaryEnumerator e = ((IOrderedDictionary)json).GetEnumerator();
|
||||
while (e.MoveNext())
|
||||
map.Add((string)e.Key, DeserializeJson((JsonData)e.Value));
|
||||
return map;
|
||||
case JsonType.None:
|
||||
default:
|
||||
return new OSD();
|
||||
}
|
||||
}
|
||||
|
||||
public static string SerializeJsonString(OSD osd)
|
||||
{
|
||||
return SerializeJson(osd, false).ToJson();
|
||||
}
|
||||
|
||||
public static string SerializeJsonString(OSD osd, bool preserveDefaults)
|
||||
{
|
||||
return SerializeJson(osd, preserveDefaults).ToJson();
|
||||
}
|
||||
|
||||
public static void SerializeJsonString(OSD osd, bool preserveDefaults, ref JsonWriter writer)
|
||||
{
|
||||
SerializeJson(osd, preserveDefaults).ToJson(writer);
|
||||
}
|
||||
|
||||
public static JsonData SerializeJson(OSD osd, bool preserveDefaults)
|
||||
{
|
||||
switch (osd.Type)
|
||||
{
|
||||
case OSDType.Boolean:
|
||||
return new JsonData(osd.AsBoolean());
|
||||
case OSDType.Integer:
|
||||
return new JsonData(osd.AsInteger());
|
||||
case OSDType.Real:
|
||||
return new JsonData(osd.AsReal());
|
||||
case OSDType.String:
|
||||
case OSDType.Date:
|
||||
case OSDType.URI:
|
||||
case OSDType.UUID:
|
||||
return new JsonData(osd.AsString());
|
||||
case OSDType.Binary:
|
||||
byte[] binary = osd.AsBinary();
|
||||
JsonData jsonbinarray = new JsonData();
|
||||
jsonbinarray.SetJsonType(JsonType.Array);
|
||||
for (int i = 0; i < binary.Length; i++)
|
||||
jsonbinarray.Add(new JsonData(binary[i]));
|
||||
return jsonbinarray;
|
||||
case OSDType.Array:
|
||||
JsonData jsonarray = new JsonData();
|
||||
jsonarray.SetJsonType(JsonType.Array);
|
||||
OSDArray array = (OSDArray)osd;
|
||||
for (int i = 0; i < array.Count; i++)
|
||||
jsonarray.Add(SerializeJson(array[i], preserveDefaults));
|
||||
return jsonarray;
|
||||
case OSDType.Map:
|
||||
JsonData jsonmap = new JsonData();
|
||||
jsonmap.SetJsonType(JsonType.Object);
|
||||
OSDMap map = (OSDMap)osd;
|
||||
foreach (KeyValuePair<string, OSD> kvp in map)
|
||||
{
|
||||
JsonData data;
|
||||
|
||||
if (preserveDefaults)
|
||||
data = SerializeJson(kvp.Value, preserveDefaults);
|
||||
else
|
||||
data = SerializeJsonNoDefaults(kvp.Value);
|
||||
|
||||
if (data != null)
|
||||
jsonmap[kvp.Key] = data;
|
||||
}
|
||||
return jsonmap;
|
||||
case OSDType.Unknown:
|
||||
default:
|
||||
return new JsonData(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonData SerializeJsonNoDefaults(OSD osd)
|
||||
{
|
||||
switch (osd.Type)
|
||||
{
|
||||
case OSDType.Boolean:
|
||||
bool b = osd.AsBoolean();
|
||||
if (!b)
|
||||
return null;
|
||||
|
||||
return new JsonData(b);
|
||||
case OSDType.Integer:
|
||||
int v = osd.AsInteger();
|
||||
if (v == 0)
|
||||
return null;
|
||||
|
||||
return new JsonData(v);
|
||||
case OSDType.Real:
|
||||
double d = osd.AsReal();
|
||||
if (d == 0.0d)
|
||||
return null;
|
||||
|
||||
return new JsonData(d);
|
||||
case OSDType.String:
|
||||
case OSDType.Date:
|
||||
case OSDType.URI:
|
||||
string str = osd.AsString();
|
||||
if (String.IsNullOrEmpty(str))
|
||||
return null;
|
||||
|
||||
return new JsonData(str);
|
||||
case OSDType.UUID:
|
||||
UUID uuid = osd.AsUUID();
|
||||
if (uuid == UUID.Zero)
|
||||
return null;
|
||||
|
||||
return new JsonData(uuid.ToString());
|
||||
case OSDType.Binary:
|
||||
byte[] binary = osd.AsBinary();
|
||||
if (binary == Utils.EmptyBytes)
|
||||
return null;
|
||||
|
||||
JsonData jsonbinarray = new JsonData();
|
||||
jsonbinarray.SetJsonType(JsonType.Array);
|
||||
for (int i = 0; i < binary.Length; i++)
|
||||
jsonbinarray.Add(new JsonData(binary[i]));
|
||||
return jsonbinarray;
|
||||
case OSDType.Array:
|
||||
JsonData jsonarray = new JsonData();
|
||||
jsonarray.SetJsonType(JsonType.Array);
|
||||
OSDArray array = (OSDArray)osd;
|
||||
for (int i = 0; i < array.Count; i++)
|
||||
jsonarray.Add(SerializeJson(array[i], false));
|
||||
return jsonarray;
|
||||
case OSDType.Map:
|
||||
JsonData jsonmap = new JsonData();
|
||||
jsonmap.SetJsonType(JsonType.Object);
|
||||
OSDMap map = (OSDMap)osd;
|
||||
foreach (KeyValuePair<string, OSD> kvp in map)
|
||||
{
|
||||
JsonData data = SerializeJsonNoDefaults(kvp.Value);
|
||||
if (data != null)
|
||||
jsonmap[kvp.Key] = data;
|
||||
}
|
||||
return jsonmap;
|
||||
case OSDType.Unknown:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#region Header
|
||||
/*
|
||||
* ParserToken.cs
|
||||
* Internal representation of the tokens used by the lexer and the parser.
|
||||
*
|
||||
* The authors disclaim copyright to this source code. For more details, see
|
||||
* the COPYING file included with this distribution.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
|
||||
namespace LitJson
|
||||
{
|
||||
internal enum ParserToken
|
||||
{
|
||||
// Lexer tokens
|
||||
None = System.Char.MaxValue + 1,
|
||||
Number,
|
||||
True,
|
||||
False,
|
||||
Null,
|
||||
CharSeq,
|
||||
// Single char
|
||||
Char,
|
||||
|
||||
// Parser Rules
|
||||
Text,
|
||||
Object,
|
||||
ObjectPrime,
|
||||
Pair,
|
||||
PairRest,
|
||||
Array,
|
||||
ArrayPrime,
|
||||
Value,
|
||||
ValueRest,
|
||||
String,
|
||||
|
||||
// End of input
|
||||
End,
|
||||
|
||||
// The empty rule
|
||||
Epsilon
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,502 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
*
|
||||
* This implementation is based upon the description at
|
||||
*
|
||||
* http://wiki.secondlife.com/wiki/LLSD
|
||||
*
|
||||
* and (partially) tested against the (supposed) reference implementation at
|
||||
*
|
||||
* http://svn.secondlife.com/svn/linden/release/indra/lib/python/indra/base/osd.py
|
||||
*
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenMetaverse.StructuredData
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static partial class OSDParser
|
||||
{
|
||||
private const int initialBufferSize = 128;
|
||||
private const int int32Length = 4;
|
||||
private const int doubleLength = 8;
|
||||
|
||||
private const string llsdBinaryHead = "<? llsd/binary ?>";
|
||||
private const string llsdBinaryHead2 = "<?llsd/binary?>";
|
||||
private const byte undefBinaryValue = (byte)'!';
|
||||
private const byte trueBinaryValue = (byte)'1';
|
||||
private const byte falseBinaryValue = (byte)'0';
|
||||
private const byte integerBinaryMarker = (byte)'i';
|
||||
private const byte realBinaryMarker = (byte)'r';
|
||||
private const byte uuidBinaryMarker = (byte)'u';
|
||||
private const byte binaryBinaryMarker = (byte)'b';
|
||||
private const byte stringBinaryMarker = (byte)'s';
|
||||
private const byte uriBinaryMarker = (byte)'l';
|
||||
private const byte dateBinaryMarker = (byte)'d';
|
||||
private const byte arrayBeginBinaryMarker = (byte)'[';
|
||||
private const byte arrayEndBinaryMarker = (byte)']';
|
||||
private const byte mapBeginBinaryMarker = (byte)'{';
|
||||
private const byte mapEndBinaryMarker = (byte)'}';
|
||||
private const byte keyBinaryMarker = (byte)'k';
|
||||
|
||||
private static readonly byte[] llsdBinaryHeadBytes = Encoding.ASCII.GetBytes(llsdBinaryHead2);
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes binary LLSD
|
||||
/// </summary>
|
||||
/// <param name="binaryData">Serialized data</param>
|
||||
/// <returns>OSD containting deserialized data</returns>
|
||||
public static OSD DeserializeLLSDBinary(byte[] binaryData)
|
||||
{
|
||||
|
||||
MemoryStream stream = new MemoryStream(binaryData);
|
||||
OSD osd = DeserializeLLSDBinary(stream);
|
||||
stream.Close();
|
||||
return osd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes binary LLSD
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream to read the data from</param>
|
||||
/// <returns>OSD containting deserialized data</returns>
|
||||
public static OSD DeserializeLLSDBinary(Stream stream)
|
||||
{
|
||||
if (!stream.CanSeek)
|
||||
throw new OSDException("Cannot deserialize binary LLSD from unseekable streams");
|
||||
|
||||
SkipWhiteSpace(stream);
|
||||
|
||||
if (!FindString(stream, llsdBinaryHead) && !FindString(stream, llsdBinaryHead2))
|
||||
{
|
||||
//throw new OSDException("Failed to decode binary LLSD");
|
||||
}
|
||||
|
||||
SkipWhiteSpace(stream);
|
||||
|
||||
return ParseLLSDBinaryElement(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes OSD to binary format. It does no prepend header
|
||||
/// </summary>
|
||||
/// <param name="osd">OSD to serialize</param>
|
||||
/// <returns>Serialized data</returns>
|
||||
public static byte[] SerializeLLSDBinary(OSD osd)
|
||||
{
|
||||
return SerializeLLSDBinary(osd, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes OSD to binary format
|
||||
/// </summary>
|
||||
/// <param name="osd">OSD to serialize</param>
|
||||
/// <param name="prependHeader"></param>
|
||||
/// <returns>Serialized data</returns>
|
||||
public static byte[] SerializeLLSDBinary(OSD osd, bool prependHeader)
|
||||
{
|
||||
MemoryStream stream = SerializeLLSDBinaryStream(osd, prependHeader);
|
||||
byte[] binaryData = stream.ToArray();
|
||||
|
||||
stream.Close();
|
||||
|
||||
return binaryData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes OSD to binary format. It does no prepend header
|
||||
/// </summary>
|
||||
/// <param name="data">OSD to serialize</param>
|
||||
/// <returns>Serialized data</returns>
|
||||
public static MemoryStream SerializeLLSDBinaryStream(OSD data)
|
||||
{
|
||||
return SerializeLLSDBinaryStream(data, true);
|
||||
}
|
||||
/// <summary>
|
||||
/// Serializes OSD to binary format
|
||||
/// </summary>
|
||||
/// <param name="data">OSD to serialize</param>
|
||||
/// <param name="prependHeader"></param>
|
||||
/// <returns>Serialized data</returns>
|
||||
public static MemoryStream SerializeLLSDBinaryStream(OSD data, bool prependHeader)
|
||||
{
|
||||
MemoryStream stream = new MemoryStream(initialBufferSize);
|
||||
|
||||
if (prependHeader)
|
||||
{
|
||||
stream.Write(llsdBinaryHeadBytes, 0, llsdBinaryHeadBytes.Length);
|
||||
stream.WriteByte((byte)'\n');
|
||||
}
|
||||
SerializeLLSDBinaryElement(stream, data);
|
||||
return stream;
|
||||
}
|
||||
|
||||
private static void SerializeLLSDBinaryElement(MemoryStream stream, OSD osd)
|
||||
{
|
||||
switch (osd.Type)
|
||||
{
|
||||
case OSDType.Unknown:
|
||||
stream.WriteByte(undefBinaryValue);
|
||||
break;
|
||||
case OSDType.Boolean:
|
||||
stream.Write(osd.AsBinary(), 0, 1);
|
||||
break;
|
||||
case OSDType.Integer:
|
||||
stream.WriteByte(integerBinaryMarker);
|
||||
stream.Write(osd.AsBinary(), 0, int32Length);
|
||||
break;
|
||||
case OSDType.Real:
|
||||
stream.WriteByte(realBinaryMarker);
|
||||
stream.Write(osd.AsBinary(), 0, doubleLength);
|
||||
break;
|
||||
case OSDType.UUID:
|
||||
stream.WriteByte(uuidBinaryMarker);
|
||||
stream.Write(osd.AsBinary(), 0, 16);
|
||||
break;
|
||||
case OSDType.String:
|
||||
stream.WriteByte(stringBinaryMarker);
|
||||
byte[] rawString = osd.AsBinary();
|
||||
byte[] stringLengthNetEnd = HostToNetworkIntBytes(rawString.Length);
|
||||
stream.Write(stringLengthNetEnd, 0, int32Length);
|
||||
stream.Write(rawString, 0, rawString.Length);
|
||||
break;
|
||||
case OSDType.Binary:
|
||||
stream.WriteByte(binaryBinaryMarker);
|
||||
byte[] rawBinary = osd.AsBinary();
|
||||
byte[] binaryLengthNetEnd = HostToNetworkIntBytes(rawBinary.Length);
|
||||
stream.Write(binaryLengthNetEnd, 0, int32Length);
|
||||
stream.Write(rawBinary, 0, rawBinary.Length);
|
||||
break;
|
||||
case OSDType.Date:
|
||||
stream.WriteByte(dateBinaryMarker);
|
||||
stream.Write(osd.AsBinary(), 0, doubleLength);
|
||||
break;
|
||||
case OSDType.URI:
|
||||
stream.WriteByte(uriBinaryMarker);
|
||||
byte[] rawURI = osd.AsBinary();
|
||||
byte[] uriLengthNetEnd = HostToNetworkIntBytes(rawURI.Length);
|
||||
stream.Write(uriLengthNetEnd, 0, int32Length);
|
||||
stream.Write(rawURI, 0, rawURI.Length);
|
||||
break;
|
||||
case OSDType.Array:
|
||||
SerializeLLSDBinaryArray(stream, (OSDArray)osd);
|
||||
break;
|
||||
case OSDType.Map:
|
||||
SerializeLLSDBinaryMap(stream, (OSDMap)osd);
|
||||
break;
|
||||
default:
|
||||
throw new OSDException("Binary serialization: Not existing element discovered.");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void SerializeLLSDBinaryArray(MemoryStream stream, OSDArray osdArray)
|
||||
{
|
||||
stream.WriteByte(arrayBeginBinaryMarker);
|
||||
byte[] binaryNumElementsHostEnd = HostToNetworkIntBytes(osdArray.Count);
|
||||
stream.Write(binaryNumElementsHostEnd, 0, int32Length);
|
||||
|
||||
foreach (OSD osd in osdArray)
|
||||
{
|
||||
SerializeLLSDBinaryElement(stream, osd);
|
||||
}
|
||||
stream.WriteByte(arrayEndBinaryMarker);
|
||||
}
|
||||
|
||||
private static void SerializeLLSDBinaryMap(MemoryStream stream, OSDMap osdMap)
|
||||
{
|
||||
stream.WriteByte(mapBeginBinaryMarker);
|
||||
byte[] binaryNumElementsNetEnd = HostToNetworkIntBytes(osdMap.Count);
|
||||
stream.Write(binaryNumElementsNetEnd, 0, int32Length);
|
||||
|
||||
foreach (KeyValuePair<string, OSD> kvp in osdMap)
|
||||
{
|
||||
stream.WriteByte(keyBinaryMarker);
|
||||
byte[] binaryKey = Encoding.UTF8.GetBytes(kvp.Key);
|
||||
byte[] binaryKeyLength = HostToNetworkIntBytes(binaryKey.Length);
|
||||
stream.Write(binaryKeyLength, 0, int32Length);
|
||||
stream.Write(binaryKey, 0, binaryKey.Length);
|
||||
SerializeLLSDBinaryElement(stream, kvp.Value);
|
||||
}
|
||||
stream.WriteByte(mapEndBinaryMarker);
|
||||
}
|
||||
|
||||
private static OSD ParseLLSDBinaryElement(Stream stream)
|
||||
{
|
||||
SkipWhiteSpace(stream);
|
||||
OSD osd;
|
||||
|
||||
int marker = stream.ReadByte();
|
||||
if (marker < 0)
|
||||
throw new OSDException("Binary LLSD parsing: Unexpected end of stream.");
|
||||
|
||||
switch ((byte)marker)
|
||||
{
|
||||
case undefBinaryValue:
|
||||
osd = new OSD();
|
||||
break;
|
||||
case trueBinaryValue:
|
||||
osd = OSD.FromBoolean(true);
|
||||
break;
|
||||
case falseBinaryValue:
|
||||
osd = OSD.FromBoolean(false);
|
||||
break;
|
||||
case integerBinaryMarker:
|
||||
int integer = NetworkToHostInt(ConsumeBytes(stream, int32Length));
|
||||
osd = OSD.FromInteger(integer);
|
||||
break;
|
||||
case realBinaryMarker:
|
||||
double dbl = NetworkToHostDouble(ConsumeBytes(stream, doubleLength));
|
||||
osd = OSD.FromReal(dbl);
|
||||
break;
|
||||
case uuidBinaryMarker:
|
||||
osd = OSD.FromUUID(new UUID(ConsumeBytes(stream, 16), 0));
|
||||
break;
|
||||
case binaryBinaryMarker:
|
||||
int binaryLength = NetworkToHostInt(ConsumeBytes(stream, int32Length));
|
||||
osd = OSD.FromBinary(ConsumeBytes(stream, binaryLength));
|
||||
break;
|
||||
case stringBinaryMarker:
|
||||
int stringLength = NetworkToHostInt(ConsumeBytes(stream, int32Length));
|
||||
string ss = Encoding.UTF8.GetString(ConsumeBytes(stream, stringLength));
|
||||
osd = OSD.FromString(ss);
|
||||
break;
|
||||
case uriBinaryMarker:
|
||||
int uriLength = NetworkToHostInt(ConsumeBytes(stream, int32Length));
|
||||
string sUri = Encoding.UTF8.GetString(ConsumeBytes(stream, uriLength));
|
||||
Uri uri;
|
||||
try
|
||||
{
|
||||
uri = new Uri(sUri, UriKind.RelativeOrAbsolute);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new OSDException("Binary LLSD parsing: Invalid Uri format detected.");
|
||||
}
|
||||
osd = OSD.FromUri(uri);
|
||||
break;
|
||||
case dateBinaryMarker:
|
||||
double timestamp = Utils.BytesToDouble(ConsumeBytes(stream, doubleLength), 0);
|
||||
DateTime dateTime = DateTime.SpecifyKind(Utils.Epoch, DateTimeKind.Utc);
|
||||
dateTime = dateTime.AddSeconds(timestamp);
|
||||
osd = OSD.FromDate(dateTime.ToLocalTime());
|
||||
break;
|
||||
case arrayBeginBinaryMarker:
|
||||
osd = ParseLLSDBinaryArray(stream);
|
||||
break;
|
||||
case mapBeginBinaryMarker:
|
||||
osd = ParseLLSDBinaryMap(stream);
|
||||
break;
|
||||
default:
|
||||
throw new OSDException("Binary LLSD parsing: Unknown type marker.");
|
||||
|
||||
}
|
||||
return osd;
|
||||
}
|
||||
|
||||
private static OSD ParseLLSDBinaryArray(Stream stream)
|
||||
{
|
||||
int numElements = NetworkToHostInt(ConsumeBytes(stream, int32Length));
|
||||
int crrElement = 0;
|
||||
OSDArray osdArray = new OSDArray();
|
||||
while (crrElement < numElements)
|
||||
{
|
||||
osdArray.Add(ParseLLSDBinaryElement(stream));
|
||||
crrElement++;
|
||||
}
|
||||
|
||||
if (!FindByte(stream, arrayEndBinaryMarker))
|
||||
throw new OSDException("Binary LLSD parsing: Missing end marker in array.");
|
||||
|
||||
return (OSD)osdArray;
|
||||
}
|
||||
|
||||
private static OSD ParseLLSDBinaryMap(Stream stream)
|
||||
{
|
||||
int numElements = NetworkToHostInt(ConsumeBytes(stream, int32Length));
|
||||
int crrElement = 0;
|
||||
OSDMap osdMap = new OSDMap();
|
||||
while (crrElement < numElements)
|
||||
{
|
||||
if (!FindByte(stream, keyBinaryMarker))
|
||||
throw new OSDException("Binary LLSD parsing: Missing key marker in map.");
|
||||
int keyLength = NetworkToHostInt(ConsumeBytes(stream, int32Length));
|
||||
string key = Encoding.UTF8.GetString(ConsumeBytes(stream, keyLength));
|
||||
osdMap[key] = ParseLLSDBinaryElement(stream);
|
||||
crrElement++;
|
||||
}
|
||||
|
||||
if (!FindByte(stream, mapEndBinaryMarker))
|
||||
throw new OSDException("Binary LLSD parsing: Missing end marker in map.");
|
||||
|
||||
return (OSD)osdMap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
public static void SkipWhiteSpace(Stream stream)
|
||||
{
|
||||
int bt;
|
||||
|
||||
while (((bt = stream.ReadByte()) > 0) &&
|
||||
((byte)bt == ' ' || (byte)bt == '\t' ||
|
||||
(byte)bt == '\n' || (byte)bt == '\r')
|
||||
)
|
||||
{
|
||||
}
|
||||
stream.Seek(-1, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="toFind"></param>
|
||||
/// <returns></returns>
|
||||
public static bool FindByte(Stream stream, byte toFind)
|
||||
{
|
||||
int bt = stream.ReadByte();
|
||||
if (bt < 0)
|
||||
return false;
|
||||
if ((byte)bt == toFind)
|
||||
return true;
|
||||
else
|
||||
{
|
||||
stream.Seek(-1L, SeekOrigin.Current);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="toFind"></param>
|
||||
/// <returns></returns>
|
||||
public static bool FindString(Stream stream, string toFind)
|
||||
{
|
||||
int lastIndexToFind = toFind.Length - 1;
|
||||
int crrIndex = 0;
|
||||
bool found = true;
|
||||
int bt;
|
||||
long lastPosition = stream.Position;
|
||||
|
||||
while (found &&
|
||||
((bt = stream.ReadByte()) > 0) &&
|
||||
(crrIndex <= lastIndexToFind)
|
||||
)
|
||||
{
|
||||
if (toFind[crrIndex].ToString().Equals(((char)bt).ToString(), StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
found = true;
|
||||
crrIndex++;
|
||||
}
|
||||
else
|
||||
found = false;
|
||||
}
|
||||
|
||||
if (found && crrIndex > lastIndexToFind)
|
||||
{
|
||||
stream.Seek(-1L, SeekOrigin.Current);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.Position = lastPosition;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="consumeBytes"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] ConsumeBytes(Stream stream, int consumeBytes)
|
||||
{
|
||||
byte[] bytes = new byte[consumeBytes];
|
||||
if (stream.Read(bytes, 0, consumeBytes) < consumeBytes)
|
||||
throw new OSDException("Binary LLSD parsing: Unexpected end of stream.");
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="binaryNetEnd"></param>
|
||||
/// <returns></returns>
|
||||
public static int NetworkToHostInt(byte[] binaryNetEnd)
|
||||
{
|
||||
if (binaryNetEnd == null)
|
||||
return -1;
|
||||
|
||||
int intNetEnd = BitConverter.ToInt32(binaryNetEnd, 0);
|
||||
int intHostEnd = System.Net.IPAddress.NetworkToHostOrder(intNetEnd);
|
||||
return intHostEnd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="binaryNetEnd"></param>
|
||||
/// <returns></returns>
|
||||
public static double NetworkToHostDouble(byte[] binaryNetEnd)
|
||||
{
|
||||
if (binaryNetEnd == null)
|
||||
return -1d;
|
||||
long longNetEnd = BitConverter.ToInt64(binaryNetEnd, 0);
|
||||
long longHostEnd = System.Net.IPAddress.NetworkToHostOrder(longNetEnd);
|
||||
byte[] binaryHostEnd = BitConverter.GetBytes(longHostEnd);
|
||||
double doubleHostEnd = BitConverter.ToDouble(binaryHostEnd, 0);
|
||||
return doubleHostEnd;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="intHostEnd"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] HostToNetworkIntBytes(int intHostEnd)
|
||||
{
|
||||
int intNetEnd = System.Net.IPAddress.HostToNetworkOrder(intHostEnd);
|
||||
byte[] bytesNetEnd = BitConverter.GetBytes(intNetEnd);
|
||||
return bytesNetEnd;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,774 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace OpenMetaverse.StructuredData
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static partial class OSDParser
|
||||
{
|
||||
private const string baseIndent = " ";
|
||||
|
||||
private const char undefNotationValue = '!';
|
||||
|
||||
private const char trueNotationValueOne = '1';
|
||||
private const char trueNotationValueTwo = 't';
|
||||
private static readonly char[] trueNotationValueTwoFull = { 't', 'r', 'u', 'e' };
|
||||
private const char trueNotationValueThree = 'T';
|
||||
private static readonly char[] trueNotationValueThreeFull = { 'T', 'R', 'U', 'E' };
|
||||
|
||||
private const char falseNotationValueOne = '0';
|
||||
private const char falseNotationValueTwo = 'f';
|
||||
private static readonly char[] falseNotationValueTwoFull = { 'f', 'a', 'l', 's', 'e' };
|
||||
private const char falseNotationValueThree = 'F';
|
||||
private static readonly char[] falseNotationValueThreeFull = { 'F', 'A', 'L', 'S', 'E' };
|
||||
|
||||
private const char integerNotationMarker = 'i';
|
||||
private const char realNotationMarker = 'r';
|
||||
private const char uuidNotationMarker = 'u';
|
||||
private const char binaryNotationMarker = 'b';
|
||||
private const char stringNotationMarker = 's';
|
||||
private const char uriNotationMarker = 'l';
|
||||
private const char dateNotationMarker = 'd';
|
||||
|
||||
private const char arrayBeginNotationMarker = '[';
|
||||
private const char arrayEndNotationMarker = ']';
|
||||
|
||||
private const char mapBeginNotationMarker = '{';
|
||||
private const char mapEndNotationMarker = '}';
|
||||
private const char kommaNotationDelimiter = ',';
|
||||
private const char keyNotationDelimiter = ':';
|
||||
|
||||
private const char sizeBeginNotationMarker = '(';
|
||||
private const char sizeEndNotationMarker = ')';
|
||||
private const char doubleQuotesNotationMarker = '"';
|
||||
private const char singleQuotesNotationMarker = '\'';
|
||||
|
||||
public static OSD DeserializeLLSDNotation(string notationData)
|
||||
{
|
||||
StringReader reader = new StringReader(notationData);
|
||||
OSD osd = DeserializeLLSDNotation(reader);
|
||||
reader.Close();
|
||||
return osd;
|
||||
}
|
||||
|
||||
public static OSD DeserializeLLSDNotation(StringReader reader)
|
||||
{
|
||||
OSD osd = DeserializeLLSDNotationElement(reader);
|
||||
return osd;
|
||||
}
|
||||
|
||||
public static string SerializeLLSDNotation(OSD osd)
|
||||
{
|
||||
StringWriter writer = SerializeLLSDNotationStream(osd);
|
||||
string s = writer.ToString();
|
||||
writer.Close();
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public static StringWriter SerializeLLSDNotationStream(OSD osd)
|
||||
{
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
SerializeLLSDNotationElement(writer, osd);
|
||||
return writer;
|
||||
}
|
||||
|
||||
public static string SerializeLLSDNotationFormatted(OSD osd)
|
||||
{
|
||||
StringWriter writer = SerializeLLSDNotationStreamFormatted(osd);
|
||||
string s = writer.ToString();
|
||||
writer.Close();
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
public static StringWriter SerializeLLSDNotationStreamFormatted(OSD osd)
|
||||
{
|
||||
StringWriter writer = new StringWriter();
|
||||
|
||||
string indent = String.Empty;
|
||||
SerializeLLSDNotationElementFormatted(writer, indent, osd);
|
||||
return writer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <returns></returns>
|
||||
private static OSD DeserializeLLSDNotationElement(StringReader reader)
|
||||
{
|
||||
int character = ReadAndSkipWhitespace(reader);
|
||||
if (character < 0)
|
||||
return new OSD(); // server returned an empty file, so we're going to pass along a null LLSD object
|
||||
|
||||
OSD osd;
|
||||
int matching;
|
||||
switch ((char)character)
|
||||
{
|
||||
case undefNotationValue:
|
||||
osd = new OSD();
|
||||
break;
|
||||
case trueNotationValueOne:
|
||||
osd = OSD.FromBoolean(true);
|
||||
break;
|
||||
case trueNotationValueTwo:
|
||||
matching = BufferCharactersEqual(reader, trueNotationValueTwoFull, 1);
|
||||
if (matching > 1 && matching < trueNotationValueTwoFull.Length)
|
||||
throw new OSDException("Notation LLSD parsing: True value parsing error:");
|
||||
osd = OSD.FromBoolean(true);
|
||||
break;
|
||||
case trueNotationValueThree:
|
||||
matching = BufferCharactersEqual(reader, trueNotationValueThreeFull, 1);
|
||||
if (matching > 1 && matching < trueNotationValueThreeFull.Length)
|
||||
throw new OSDException("Notation LLSD parsing: True value parsing error:");
|
||||
osd = OSD.FromBoolean(true);
|
||||
break;
|
||||
case falseNotationValueOne:
|
||||
osd = OSD.FromBoolean(false);
|
||||
break;
|
||||
case falseNotationValueTwo:
|
||||
matching = BufferCharactersEqual(reader, falseNotationValueTwoFull, 1);
|
||||
if (matching > 1 && matching < falseNotationValueTwoFull.Length)
|
||||
throw new OSDException("Notation LLSD parsing: True value parsing error:");
|
||||
osd = OSD.FromBoolean(false);
|
||||
break;
|
||||
case falseNotationValueThree:
|
||||
matching = BufferCharactersEqual(reader, falseNotationValueThreeFull, 1);
|
||||
if (matching > 1 && matching < falseNotationValueThreeFull.Length)
|
||||
throw new OSDException("Notation LLSD parsing: True value parsing error:");
|
||||
osd = OSD.FromBoolean(false);
|
||||
break;
|
||||
case integerNotationMarker:
|
||||
osd = DeserializeLLSDNotationInteger(reader);
|
||||
break;
|
||||
case realNotationMarker:
|
||||
osd = DeserializeLLSDNotationReal(reader);
|
||||
break;
|
||||
case uuidNotationMarker:
|
||||
char[] uuidBuf = new char[36];
|
||||
if (reader.Read(uuidBuf, 0, 36) < 36)
|
||||
throw new OSDException("Notation LLSD parsing: Unexpected end of stream in UUID.");
|
||||
UUID lluuid;
|
||||
if (!UUID.TryParse(new String(uuidBuf), out lluuid))
|
||||
throw new OSDException("Notation LLSD parsing: Invalid UUID discovered.");
|
||||
osd = OSD.FromUUID(lluuid);
|
||||
break;
|
||||
case binaryNotationMarker:
|
||||
byte[] bytes = Utils.EmptyBytes;
|
||||
int bChar = reader.Peek();
|
||||
if (bChar < 0)
|
||||
throw new OSDException("Notation LLSD parsing: Unexpected end of stream in binary.");
|
||||
if ((char)bChar == sizeBeginNotationMarker)
|
||||
{
|
||||
throw new OSDException("Notation LLSD parsing: Raw binary encoding not supported.");
|
||||
}
|
||||
else if (Char.IsDigit((char)bChar))
|
||||
{
|
||||
char[] charsBaseEncoding = new char[2];
|
||||
if (reader.Read(charsBaseEncoding, 0, 2) < 2)
|
||||
throw new OSDException("Notation LLSD parsing: Unexpected end of stream in binary.");
|
||||
int baseEncoding;
|
||||
if (!Int32.TryParse(new String(charsBaseEncoding), out baseEncoding))
|
||||
throw new OSDException("Notation LLSD parsing: Invalid binary encoding base.");
|
||||
if (baseEncoding == 64)
|
||||
{
|
||||
if (reader.Read() < 0)
|
||||
throw new OSDException("Notation LLSD parsing: Unexpected end of stream in binary.");
|
||||
string bytes64 = GetStringDelimitedBy(reader, doubleQuotesNotationMarker);
|
||||
bytes = Convert.FromBase64String(bytes64);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new OSDException("Notation LLSD parsing: Encoding base" + baseEncoding + " + not supported.");
|
||||
}
|
||||
}
|
||||
osd = OSD.FromBinary(bytes);
|
||||
break;
|
||||
case stringNotationMarker:
|
||||
int numChars = GetLengthInBrackets(reader);
|
||||
if (reader.Read() < 0)
|
||||
throw new OSDException("Notation LLSD parsing: Unexpected end of stream in string.");
|
||||
char[] chars = new char[numChars];
|
||||
if (reader.Read(chars, 0, numChars) < numChars)
|
||||
throw new OSDException("Notation LLSD parsing: Unexpected end of stream in string.");
|
||||
if (reader.Read() < 0)
|
||||
throw new OSDException("Notation LLSD parsing: Unexpected end of stream in string.");
|
||||
osd = OSD.FromString(new String(chars));
|
||||
break;
|
||||
case singleQuotesNotationMarker:
|
||||
string sOne = GetStringDelimitedBy(reader, singleQuotesNotationMarker);
|
||||
osd = OSD.FromString(sOne);
|
||||
break;
|
||||
case doubleQuotesNotationMarker:
|
||||
string sTwo = GetStringDelimitedBy(reader, doubleQuotesNotationMarker);
|
||||
osd = OSD.FromString(sTwo);
|
||||
break;
|
||||
case uriNotationMarker:
|
||||
if (reader.Read() < 0)
|
||||
throw new OSDException("Notation LLSD parsing: Unexpected end of stream in string.");
|
||||
string sUri = GetStringDelimitedBy(reader, doubleQuotesNotationMarker);
|
||||
|
||||
Uri uri;
|
||||
try
|
||||
{
|
||||
uri = new Uri(sUri, UriKind.RelativeOrAbsolute);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new OSDException("Notation LLSD parsing: Invalid Uri format detected.");
|
||||
}
|
||||
osd = OSD.FromUri(uri);
|
||||
break;
|
||||
case dateNotationMarker:
|
||||
if (reader.Read() < 0)
|
||||
throw new OSDException("Notation LLSD parsing: Unexpected end of stream in date.");
|
||||
string date = GetStringDelimitedBy(reader, doubleQuotesNotationMarker);
|
||||
DateTime dt;
|
||||
if (!DateTime.TryParse(date, out dt))
|
||||
throw new OSDException("Notation LLSD parsing: Invalid date discovered.");
|
||||
osd = OSD.FromDate(dt);
|
||||
break;
|
||||
case arrayBeginNotationMarker:
|
||||
osd = DeserializeLLSDNotationArray(reader);
|
||||
break;
|
||||
case mapBeginNotationMarker:
|
||||
osd = DeserializeLLSDNotationMap(reader);
|
||||
break;
|
||||
default:
|
||||
throw new OSDException("Notation LLSD parsing: Unknown type marker '" + (char)character + "'.");
|
||||
}
|
||||
return osd;
|
||||
}
|
||||
|
||||
private static OSD DeserializeLLSDNotationInteger(StringReader reader)
|
||||
{
|
||||
int character;
|
||||
StringBuilder s = new StringBuilder();
|
||||
if (((character = reader.Peek()) > 0) && ((char)character == '-'))
|
||||
{
|
||||
s.Append((char)character);
|
||||
reader.Read();
|
||||
}
|
||||
|
||||
while ((character = reader.Peek()) > 0 &&
|
||||
Char.IsDigit((char)character))
|
||||
{
|
||||
s.Append((char)character);
|
||||
reader.Read();
|
||||
}
|
||||
int integer;
|
||||
if (!Int32.TryParse(s.ToString(), out integer))
|
||||
throw new OSDException("Notation LLSD parsing: Can't parse integer value." + s.ToString());
|
||||
|
||||
return OSD.FromInteger(integer);
|
||||
}
|
||||
|
||||
private static OSD DeserializeLLSDNotationReal(StringReader reader)
|
||||
{
|
||||
int character;
|
||||
StringBuilder s = new StringBuilder();
|
||||
if (((character = reader.Peek()) > 0) &&
|
||||
((char)character == '-' && (char)character == '+'))
|
||||
{
|
||||
s.Append((char)character);
|
||||
reader.Read();
|
||||
}
|
||||
while (((character = reader.Peek()) > 0) &&
|
||||
(Char.IsDigit((char)character) || (char)character == '.' ||
|
||||
(char)character == 'e' || (char)character == 'E' ||
|
||||
(char)character == '+' || (char)character == '-'))
|
||||
{
|
||||
s.Append((char)character);
|
||||
reader.Read();
|
||||
}
|
||||
double dbl;
|
||||
if (!Utils.TryParseDouble(s.ToString(), out dbl))
|
||||
throw new OSDException("Notation LLSD parsing: Can't parse real value: " + s.ToString());
|
||||
|
||||
return OSD.FromReal(dbl);
|
||||
}
|
||||
|
||||
private static OSD DeserializeLLSDNotationArray(StringReader reader)
|
||||
{
|
||||
int character;
|
||||
OSDArray osdArray = new OSDArray();
|
||||
while (((character = PeekAndSkipWhitespace(reader)) > 0) &&
|
||||
((char)character != arrayEndNotationMarker))
|
||||
{
|
||||
osdArray.Add(DeserializeLLSDNotationElement(reader));
|
||||
|
||||
character = ReadAndSkipWhitespace(reader);
|
||||
if (character < 0)
|
||||
throw new OSDException("Notation LLSD parsing: Unexpected end of array discovered.");
|
||||
else if ((char)character == kommaNotationDelimiter)
|
||||
continue;
|
||||
else if ((char)character == arrayEndNotationMarker)
|
||||
break;
|
||||
}
|
||||
if (character < 0)
|
||||
throw new OSDException("Notation LLSD parsing: Unexpected end of array discovered.");
|
||||
|
||||
return (OSD)osdArray;
|
||||
}
|
||||
|
||||
private static OSD DeserializeLLSDNotationMap(StringReader reader)
|
||||
{
|
||||
int character;
|
||||
OSDMap osdMap = new OSDMap();
|
||||
while (((character = PeekAndSkipWhitespace(reader)) > 0) &&
|
||||
((char)character != mapEndNotationMarker))
|
||||
{
|
||||
OSD osdKey = DeserializeLLSDNotationElement(reader);
|
||||
if (osdKey.Type != OSDType.String)
|
||||
throw new OSDException("Notation LLSD parsing: Invalid key in map");
|
||||
string key = osdKey.AsString();
|
||||
|
||||
character = ReadAndSkipWhitespace(reader);
|
||||
if ((char)character != keyNotationDelimiter)
|
||||
throw new OSDException("Notation LLSD parsing: Unexpected end of stream in map.");
|
||||
if ((char)character != keyNotationDelimiter)
|
||||
throw new OSDException("Notation LLSD parsing: Invalid delimiter in map.");
|
||||
|
||||
osdMap[key] = DeserializeLLSDNotationElement(reader);
|
||||
character = ReadAndSkipWhitespace(reader);
|
||||
if (character < 0)
|
||||
throw new OSDException("Notation LLSD parsing: Unexpected end of map discovered.");
|
||||
else if ((char)character == kommaNotationDelimiter)
|
||||
continue;
|
||||
else if ((char)character == mapEndNotationMarker)
|
||||
break;
|
||||
}
|
||||
if (character < 0)
|
||||
throw new OSDException("Notation LLSD parsing: Unexpected end of map discovered.");
|
||||
|
||||
return (OSD)osdMap;
|
||||
}
|
||||
|
||||
private static void SerializeLLSDNotationElement(StringWriter writer, OSD osd)
|
||||
{
|
||||
|
||||
switch (osd.Type)
|
||||
{
|
||||
case OSDType.Unknown:
|
||||
writer.Write(undefNotationValue);
|
||||
break;
|
||||
case OSDType.Boolean:
|
||||
if (osd.AsBoolean())
|
||||
writer.Write(trueNotationValueTwo);
|
||||
else
|
||||
writer.Write(falseNotationValueTwo);
|
||||
break;
|
||||
case OSDType.Integer:
|
||||
writer.Write(integerNotationMarker);
|
||||
writer.Write(osd.AsString());
|
||||
break;
|
||||
case OSDType.Real:
|
||||
writer.Write(realNotationMarker);
|
||||
writer.Write(osd.AsString());
|
||||
break;
|
||||
case OSDType.UUID:
|
||||
writer.Write(uuidNotationMarker);
|
||||
writer.Write(osd.AsString());
|
||||
break;
|
||||
case OSDType.String:
|
||||
writer.Write(singleQuotesNotationMarker);
|
||||
writer.Write(EscapeCharacter(osd.AsString(), singleQuotesNotationMarker));
|
||||
writer.Write(singleQuotesNotationMarker);
|
||||
break;
|
||||
case OSDType.Binary:
|
||||
writer.Write(binaryNotationMarker);
|
||||
writer.Write("64");
|
||||
writer.Write(doubleQuotesNotationMarker);
|
||||
writer.Write(osd.AsString());
|
||||
writer.Write(doubleQuotesNotationMarker);
|
||||
break;
|
||||
case OSDType.Date:
|
||||
writer.Write(dateNotationMarker);
|
||||
writer.Write(doubleQuotesNotationMarker);
|
||||
writer.Write(osd.AsString());
|
||||
writer.Write(doubleQuotesNotationMarker);
|
||||
break;
|
||||
case OSDType.URI:
|
||||
writer.Write(uriNotationMarker);
|
||||
writer.Write(doubleQuotesNotationMarker);
|
||||
writer.Write(EscapeCharacter(osd.AsString(), doubleQuotesNotationMarker));
|
||||
writer.Write(doubleQuotesNotationMarker);
|
||||
break;
|
||||
case OSDType.Array:
|
||||
SerializeLLSDNotationArray(writer, (OSDArray)osd);
|
||||
break;
|
||||
case OSDType.Map:
|
||||
SerializeLLSDNotationMap(writer, (OSDMap)osd);
|
||||
break;
|
||||
default:
|
||||
throw new OSDException("Notation serialization: Not existing element discovered.");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void SerializeLLSDNotationArray(StringWriter writer, OSDArray osdArray)
|
||||
{
|
||||
writer.Write(arrayBeginNotationMarker);
|
||||
int lastIndex = osdArray.Count - 1;
|
||||
|
||||
for (int idx = 0; idx <= lastIndex; idx++)
|
||||
{
|
||||
SerializeLLSDNotationElement(writer, osdArray[idx]);
|
||||
if (idx < lastIndex)
|
||||
writer.Write(kommaNotationDelimiter);
|
||||
}
|
||||
writer.Write(arrayEndNotationMarker);
|
||||
}
|
||||
|
||||
private static void SerializeLLSDNotationMap(StringWriter writer, OSDMap osdMap)
|
||||
{
|
||||
writer.Write(mapBeginNotationMarker);
|
||||
int lastIndex = osdMap.Count - 1;
|
||||
int idx = 0;
|
||||
|
||||
foreach (KeyValuePair<string, OSD> kvp in osdMap)
|
||||
{
|
||||
writer.Write(singleQuotesNotationMarker);
|
||||
writer.Write(EscapeCharacter(kvp.Key, singleQuotesNotationMarker));
|
||||
writer.Write(singleQuotesNotationMarker);
|
||||
writer.Write(keyNotationDelimiter);
|
||||
SerializeLLSDNotationElement(writer, kvp.Value);
|
||||
if (idx < lastIndex)
|
||||
writer.Write(kommaNotationDelimiter);
|
||||
|
||||
idx++;
|
||||
}
|
||||
writer.Write(mapEndNotationMarker);
|
||||
}
|
||||
|
||||
private static void SerializeLLSDNotationElementFormatted(StringWriter writer, string indent, OSD osd)
|
||||
{
|
||||
switch (osd.Type)
|
||||
{
|
||||
case OSDType.Unknown:
|
||||
writer.Write(undefNotationValue);
|
||||
break;
|
||||
case OSDType.Boolean:
|
||||
if (osd.AsBoolean())
|
||||
writer.Write(trueNotationValueTwo);
|
||||
else
|
||||
writer.Write(falseNotationValueTwo);
|
||||
break;
|
||||
case OSDType.Integer:
|
||||
writer.Write(integerNotationMarker);
|
||||
writer.Write(osd.AsString());
|
||||
break;
|
||||
case OSDType.Real:
|
||||
writer.Write(realNotationMarker);
|
||||
writer.Write(osd.AsString());
|
||||
break;
|
||||
case OSDType.UUID:
|
||||
writer.Write(uuidNotationMarker);
|
||||
writer.Write(osd.AsString());
|
||||
break;
|
||||
case OSDType.String:
|
||||
writer.Write(singleQuotesNotationMarker);
|
||||
writer.Write(EscapeCharacter(osd.AsString(), singleQuotesNotationMarker));
|
||||
writer.Write(singleQuotesNotationMarker);
|
||||
break;
|
||||
case OSDType.Binary:
|
||||
writer.Write(binaryNotationMarker);
|
||||
writer.Write("64");
|
||||
writer.Write(doubleQuotesNotationMarker);
|
||||
writer.Write(osd.AsString());
|
||||
writer.Write(doubleQuotesNotationMarker);
|
||||
break;
|
||||
case OSDType.Date:
|
||||
writer.Write(dateNotationMarker);
|
||||
writer.Write(doubleQuotesNotationMarker);
|
||||
writer.Write(osd.AsString());
|
||||
writer.Write(doubleQuotesNotationMarker);
|
||||
break;
|
||||
case OSDType.URI:
|
||||
writer.Write(uriNotationMarker);
|
||||
writer.Write(doubleQuotesNotationMarker);
|
||||
writer.Write(EscapeCharacter(osd.AsString(), doubleQuotesNotationMarker));
|
||||
writer.Write(doubleQuotesNotationMarker);
|
||||
break;
|
||||
case OSDType.Array:
|
||||
SerializeLLSDNotationArrayFormatted(writer, indent + baseIndent, (OSDArray)osd);
|
||||
break;
|
||||
case OSDType.Map:
|
||||
SerializeLLSDNotationMapFormatted(writer, indent + baseIndent, (OSDMap)osd);
|
||||
break;
|
||||
default:
|
||||
throw new OSDException("Notation serialization: Not existing element discovered.");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void SerializeLLSDNotationArrayFormatted(StringWriter writer, string intend, OSDArray osdArray)
|
||||
{
|
||||
writer.WriteLine();
|
||||
writer.Write(intend);
|
||||
writer.Write(arrayBeginNotationMarker);
|
||||
int lastIndex = osdArray.Count - 1;
|
||||
|
||||
for (int idx = 0; idx <= lastIndex; idx++)
|
||||
{
|
||||
if (osdArray[idx].Type != OSDType.Array && osdArray[idx].Type != OSDType.Map)
|
||||
writer.WriteLine();
|
||||
writer.Write(intend + baseIndent);
|
||||
SerializeLLSDNotationElementFormatted(writer, intend, osdArray[idx]);
|
||||
if (idx < lastIndex)
|
||||
{
|
||||
writer.Write(kommaNotationDelimiter);
|
||||
}
|
||||
}
|
||||
writer.WriteLine();
|
||||
writer.Write(intend);
|
||||
writer.Write(arrayEndNotationMarker);
|
||||
}
|
||||
|
||||
private static void SerializeLLSDNotationMapFormatted(StringWriter writer, string intend, OSDMap osdMap)
|
||||
{
|
||||
writer.WriteLine();
|
||||
writer.Write(intend);
|
||||
writer.WriteLine(mapBeginNotationMarker);
|
||||
int lastIndex = osdMap.Count - 1;
|
||||
int idx = 0;
|
||||
|
||||
foreach (KeyValuePair<string, OSD> kvp in osdMap)
|
||||
{
|
||||
writer.Write(intend + baseIndent);
|
||||
writer.Write(singleQuotesNotationMarker);
|
||||
writer.Write(EscapeCharacter(kvp.Key, singleQuotesNotationMarker));
|
||||
writer.Write(singleQuotesNotationMarker);
|
||||
writer.Write(keyNotationDelimiter);
|
||||
SerializeLLSDNotationElementFormatted(writer, intend, kvp.Value);
|
||||
if (idx < lastIndex)
|
||||
{
|
||||
writer.WriteLine();
|
||||
writer.Write(intend + baseIndent);
|
||||
writer.WriteLine(kommaNotationDelimiter);
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
writer.WriteLine();
|
||||
writer.Write(intend);
|
||||
writer.Write(mapEndNotationMarker);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <returns></returns>
|
||||
public static int PeekAndSkipWhitespace(StringReader reader)
|
||||
{
|
||||
int character;
|
||||
while ((character = reader.Peek()) > 0)
|
||||
{
|
||||
char c = (char)character;
|
||||
if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
|
||||
{
|
||||
reader.Read();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
return character;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <returns></returns>
|
||||
public static int ReadAndSkipWhitespace(StringReader reader)
|
||||
{
|
||||
int character = PeekAndSkipWhitespace(reader);
|
||||
reader.Read();
|
||||
return character;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <returns></returns>
|
||||
public static int GetLengthInBrackets(StringReader reader)
|
||||
{
|
||||
int character;
|
||||
StringBuilder s = new StringBuilder();
|
||||
if (((character = PeekAndSkipWhitespace(reader)) > 0) &&
|
||||
((char)character == sizeBeginNotationMarker))
|
||||
{
|
||||
reader.Read();
|
||||
}
|
||||
while (((character = reader.Read()) > 0) &&
|
||||
Char.IsDigit((char)character) &&
|
||||
((char)character != sizeEndNotationMarker))
|
||||
{
|
||||
s.Append((char)character);
|
||||
}
|
||||
if (character < 0)
|
||||
throw new OSDException("Notation LLSD parsing: Can't parse length value cause unexpected end of stream.");
|
||||
int length;
|
||||
if (!Int32.TryParse(s.ToString(), out length))
|
||||
throw new OSDException("Notation LLSD parsing: Can't parse length value.");
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <param name="delimiter"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetStringDelimitedBy(StringReader reader, char delimiter)
|
||||
{
|
||||
int character;
|
||||
bool foundEscape = false;
|
||||
StringBuilder s = new StringBuilder();
|
||||
while (((character = reader.Read()) > 0) &&
|
||||
(((char)character != delimiter) ||
|
||||
((char)character == delimiter && foundEscape)))
|
||||
{
|
||||
|
||||
if (foundEscape)
|
||||
{
|
||||
foundEscape = false;
|
||||
switch ((char)character)
|
||||
{
|
||||
case 'a':
|
||||
s.Append('\a');
|
||||
break;
|
||||
case 'b':
|
||||
s.Append('\b');
|
||||
break;
|
||||
case 'f':
|
||||
s.Append('\f');
|
||||
break;
|
||||
case 'n':
|
||||
s.Append('\n');
|
||||
break;
|
||||
case 'r':
|
||||
s.Append('\r');
|
||||
break;
|
||||
case 't':
|
||||
s.Append('\t');
|
||||
break;
|
||||
case 'v':
|
||||
s.Append('\v');
|
||||
break;
|
||||
default:
|
||||
s.Append((char)character);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if ((char)character == '\\')
|
||||
foundEscape = true;
|
||||
else
|
||||
s.Append((char)character);
|
||||
|
||||
}
|
||||
if (character < 0)
|
||||
throw new OSDException("Notation LLSD parsing: Can't parse text because unexpected end of stream while expecting a '"
|
||||
+ delimiter + "' character.");
|
||||
|
||||
return s.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns></returns>
|
||||
public static int BufferCharactersEqual(StringReader reader, char[] buffer, int offset)
|
||||
{
|
||||
|
||||
int character;
|
||||
int lastIndex = buffer.Length - 1;
|
||||
int crrIndex = offset;
|
||||
bool charactersEqual = true;
|
||||
|
||||
while ((character = reader.Peek()) > 0 &&
|
||||
crrIndex <= lastIndex &&
|
||||
charactersEqual)
|
||||
{
|
||||
if (((char)character) != buffer[crrIndex])
|
||||
{
|
||||
charactersEqual = false;
|
||||
break;
|
||||
}
|
||||
crrIndex++;
|
||||
reader.Read();
|
||||
}
|
||||
|
||||
return crrIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <param name="c"></param>
|
||||
/// <returns></returns>
|
||||
public static string UnescapeCharacter(String s, char c)
|
||||
{
|
||||
string oldOne = "\\" + c;
|
||||
string newOne = new String(c, 1);
|
||||
|
||||
String sOne = s.Replace("\\\\", "\\").Replace(oldOne, newOne);
|
||||
return sOne;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <param name="c"></param>
|
||||
/// <returns></returns>
|
||||
public static string EscapeCharacter(String s, char c)
|
||||
{
|
||||
string oldOne = new String(c, 1);
|
||||
string newOne = "\\" + c;
|
||||
|
||||
String sOne = s.Replace("\\", "\\\\").Replace(oldOne, newOne);
|
||||
return sOne;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,652 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Schema;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenMetaverse.StructuredData
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static partial class OSDParser
|
||||
{
|
||||
private static XmlSchema XmlSchema;
|
||||
private static XmlTextReader XmlTextReader;
|
||||
private static string LastXmlErrors = String.Empty;
|
||||
private static object XmlValidationLock = new object();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="xmlData"></param>
|
||||
/// <returns></returns>
|
||||
public static OSD DeserializeLLSDXml(byte[] xmlData)
|
||||
{
|
||||
return DeserializeLLSDXml(new XmlTextReader(new MemoryStream(xmlData, false)));
|
||||
}
|
||||
|
||||
public static OSD DeserializeLLSDXml(Stream xmlStream)
|
||||
{
|
||||
return DeserializeLLSDXml(new XmlTextReader(xmlStream));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="xmlData"></param>
|
||||
/// <returns></returns>
|
||||
public static OSD DeserializeLLSDXml(string xmlData)
|
||||
{
|
||||
byte[] bytes = Utils.StringToBytes(xmlData);
|
||||
return DeserializeLLSDXml(new XmlTextReader(new MemoryStream(bytes, false)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="xmlData"></param>
|
||||
/// <returns></returns>
|
||||
public static OSD DeserializeLLSDXml(XmlTextReader xmlData)
|
||||
{
|
||||
try
|
||||
{
|
||||
xmlData.Read();
|
||||
SkipWhitespace(xmlData);
|
||||
|
||||
xmlData.Read();
|
||||
OSD ret = ParseLLSDXmlElement(xmlData);
|
||||
|
||||
return ret;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new OSD();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public static byte[] SerializeLLSDXmlBytes(OSD data)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(SerializeLLSDXmlString(data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public static string SerializeLLSDXmlString(OSD data)
|
||||
{
|
||||
StringWriter sw = new StringWriter();
|
||||
XmlTextWriter writer = new XmlTextWriter(sw);
|
||||
writer.Formatting = Formatting.None;
|
||||
|
||||
writer.WriteStartElement(String.Empty, "llsd", String.Empty);
|
||||
SerializeLLSDXmlElement(writer, data);
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.Close();
|
||||
|
||||
return sw.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
/// <param name="data"></param>
|
||||
public static void SerializeLLSDXmlElement(XmlTextWriter writer, OSD data)
|
||||
{
|
||||
switch (data.Type)
|
||||
{
|
||||
case OSDType.Unknown:
|
||||
writer.WriteStartElement(String.Empty, "undef", String.Empty);
|
||||
writer.WriteEndElement();
|
||||
break;
|
||||
case OSDType.Boolean:
|
||||
writer.WriteStartElement(String.Empty, "boolean", String.Empty);
|
||||
writer.WriteString(data.AsString());
|
||||
writer.WriteEndElement();
|
||||
break;
|
||||
case OSDType.Integer:
|
||||
writer.WriteStartElement(String.Empty, "integer", String.Empty);
|
||||
writer.WriteString(data.AsString());
|
||||
writer.WriteEndElement();
|
||||
break;
|
||||
case OSDType.Real:
|
||||
writer.WriteStartElement(String.Empty, "real", String.Empty);
|
||||
writer.WriteString(data.AsString());
|
||||
writer.WriteEndElement();
|
||||
break;
|
||||
case OSDType.String:
|
||||
writer.WriteStartElement(String.Empty, "string", String.Empty);
|
||||
writer.WriteString(data.AsString());
|
||||
writer.WriteEndElement();
|
||||
break;
|
||||
case OSDType.UUID:
|
||||
writer.WriteStartElement(String.Empty, "uuid", String.Empty);
|
||||
writer.WriteString(data.AsString());
|
||||
writer.WriteEndElement();
|
||||
break;
|
||||
case OSDType.Date:
|
||||
writer.WriteStartElement(String.Empty, "date", String.Empty);
|
||||
writer.WriteString(data.AsString());
|
||||
writer.WriteEndElement();
|
||||
break;
|
||||
case OSDType.URI:
|
||||
writer.WriteStartElement(String.Empty, "uri", String.Empty);
|
||||
writer.WriteString(data.AsString());
|
||||
writer.WriteEndElement();
|
||||
break;
|
||||
case OSDType.Binary:
|
||||
writer.WriteStartElement(String.Empty, "binary", String.Empty);
|
||||
writer.WriteStartAttribute(String.Empty, "encoding", String.Empty);
|
||||
writer.WriteString("base64");
|
||||
writer.WriteEndAttribute();
|
||||
writer.WriteString(data.AsString());
|
||||
writer.WriteEndElement();
|
||||
break;
|
||||
case OSDType.Map:
|
||||
OSDMap map = (OSDMap)data;
|
||||
writer.WriteStartElement(String.Empty, "map", String.Empty);
|
||||
foreach (KeyValuePair<string, OSD> kvp in map)
|
||||
{
|
||||
writer.WriteStartElement(String.Empty, "key", String.Empty);
|
||||
writer.WriteString(kvp.Key);
|
||||
writer.WriteEndElement();
|
||||
|
||||
SerializeLLSDXmlElement(writer, kvp.Value);
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
break;
|
||||
case OSDType.Array:
|
||||
OSDArray array = (OSDArray)data;
|
||||
writer.WriteStartElement(String.Empty, "array", String.Empty);
|
||||
for (int i = 0; i < array.Count; i++)
|
||||
{
|
||||
SerializeLLSDXmlElement(writer, array[i]);
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="xmlData"></param>
|
||||
/// <param name="error"></param>
|
||||
/// <returns></returns>
|
||||
public static bool TryValidateLLSDXml(XmlTextReader xmlData, out string error)
|
||||
{
|
||||
lock (XmlValidationLock)
|
||||
{
|
||||
LastXmlErrors = String.Empty;
|
||||
XmlTextReader = xmlData;
|
||||
|
||||
CreateLLSDXmlSchema();
|
||||
|
||||
XmlReaderSettings readerSettings = new XmlReaderSettings();
|
||||
readerSettings.ValidationType = ValidationType.Schema;
|
||||
readerSettings.Schemas.Add(XmlSchema);
|
||||
readerSettings.ValidationEventHandler += new ValidationEventHandler(LLSDXmlSchemaValidationHandler);
|
||||
|
||||
XmlReader reader = XmlReader.Create(xmlData, readerSettings);
|
||||
|
||||
try
|
||||
{
|
||||
while (reader.Read()) { }
|
||||
}
|
||||
catch (XmlException)
|
||||
{
|
||||
error = LastXmlErrors;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (LastXmlErrors == String.Empty)
|
||||
{
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
error = LastXmlErrors;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <returns></returns>
|
||||
private static OSD ParseLLSDXmlElement(XmlTextReader reader)
|
||||
{
|
||||
SkipWhitespace(reader);
|
||||
|
||||
if (reader.NodeType != XmlNodeType.Element)
|
||||
throw new OSDException("Expected an element");
|
||||
|
||||
string type = reader.LocalName;
|
||||
OSD ret;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "undef":
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
return new OSD();
|
||||
}
|
||||
|
||||
reader.Read();
|
||||
SkipWhitespace(reader);
|
||||
ret = new OSD();
|
||||
break;
|
||||
case "boolean":
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
return OSD.FromBoolean(false);
|
||||
}
|
||||
|
||||
if (reader.Read())
|
||||
{
|
||||
string s = reader.ReadString().Trim();
|
||||
|
||||
if (!String.IsNullOrEmpty(s) && (s == "true" || s == "1"))
|
||||
{
|
||||
ret = OSD.FromBoolean(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret = OSD.FromBoolean(false);
|
||||
break;
|
||||
case "integer":
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
return OSD.FromInteger(0);
|
||||
}
|
||||
|
||||
if (reader.Read())
|
||||
{
|
||||
int value = 0;
|
||||
Int32.TryParse(reader.ReadString().Trim(), out value);
|
||||
ret = OSD.FromInteger(value);
|
||||
break;
|
||||
}
|
||||
|
||||
ret = OSD.FromInteger(0);
|
||||
break;
|
||||
case "real":
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
return OSD.FromReal(0d);
|
||||
}
|
||||
|
||||
if (reader.Read())
|
||||
{
|
||||
double value = 0d;
|
||||
string str = reader.ReadString().Trim().ToLower();
|
||||
|
||||
if (str == "nan")
|
||||
value = Double.NaN;
|
||||
else
|
||||
Utils.TryParseDouble(str, out value);
|
||||
|
||||
ret = OSD.FromReal(value);
|
||||
break;
|
||||
}
|
||||
|
||||
ret = OSD.FromReal(0d);
|
||||
break;
|
||||
case "uuid":
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
return OSD.FromUUID(UUID.Zero);
|
||||
}
|
||||
|
||||
if (reader.Read())
|
||||
{
|
||||
UUID value = UUID.Zero;
|
||||
UUID.TryParse(reader.ReadString().Trim(), out value);
|
||||
ret = OSD.FromUUID(value);
|
||||
break;
|
||||
}
|
||||
|
||||
ret = OSD.FromUUID(UUID.Zero);
|
||||
break;
|
||||
case "date":
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
return OSD.FromDate(Utils.Epoch);
|
||||
}
|
||||
|
||||
if (reader.Read())
|
||||
{
|
||||
DateTime value = Utils.Epoch;
|
||||
DateTime.TryParse(reader.ReadString().Trim(), out value);
|
||||
ret = OSD.FromDate(value);
|
||||
break;
|
||||
}
|
||||
|
||||
ret = OSD.FromDate(Utils.Epoch);
|
||||
break;
|
||||
case "string":
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
return OSD.FromString(String.Empty);
|
||||
}
|
||||
|
||||
if (reader.Read())
|
||||
{
|
||||
ret = OSD.FromString(reader.ReadString());
|
||||
break;
|
||||
}
|
||||
|
||||
ret = OSD.FromString(String.Empty);
|
||||
break;
|
||||
case "binary":
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
return OSD.FromBinary(Utils.EmptyBytes);
|
||||
}
|
||||
|
||||
if (reader.GetAttribute("encoding") != null && reader.GetAttribute("encoding") != "base64")
|
||||
throw new OSDException("Unsupported binary encoding: " + reader.GetAttribute("encoding"));
|
||||
|
||||
if (reader.Read())
|
||||
{
|
||||
try
|
||||
{
|
||||
ret = OSD.FromBinary(Convert.FromBase64String(reader.ReadString().Trim()));
|
||||
break;
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
throw new OSDException("Binary decoding exception: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
ret = OSD.FromBinary(Utils.EmptyBytes);
|
||||
break;
|
||||
case "uri":
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
return OSD.FromUri(new Uri(String.Empty, UriKind.RelativeOrAbsolute));
|
||||
}
|
||||
|
||||
if (reader.Read())
|
||||
{
|
||||
ret = OSD.FromUri(new Uri(reader.ReadString(), UriKind.RelativeOrAbsolute));
|
||||
break;
|
||||
}
|
||||
|
||||
ret = OSD.FromUri(new Uri(String.Empty, UriKind.RelativeOrAbsolute));
|
||||
break;
|
||||
case "map":
|
||||
return ParseLLSDXmlMap(reader);
|
||||
case "array":
|
||||
return ParseLLSDXmlArray(reader);
|
||||
default:
|
||||
reader.Read();
|
||||
ret = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != type)
|
||||
{
|
||||
throw new OSDException("Expected </" + type + ">");
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Read();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
private static OSDMap ParseLLSDXmlMap(XmlTextReader reader)
|
||||
{
|
||||
if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "map")
|
||||
throw new NotImplementedException("Expected <map>");
|
||||
|
||||
OSDMap map = new OSDMap();
|
||||
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
return map;
|
||||
}
|
||||
|
||||
if (reader.Read())
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
SkipWhitespace(reader);
|
||||
|
||||
if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "map")
|
||||
{
|
||||
reader.Read();
|
||||
break;
|
||||
}
|
||||
|
||||
if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "key")
|
||||
throw new OSDException("Expected <key>");
|
||||
|
||||
string key = reader.ReadString();
|
||||
|
||||
if (reader.NodeType != XmlNodeType.EndElement || reader.LocalName != "key")
|
||||
throw new OSDException("Expected </key>");
|
||||
|
||||
if (reader.Read())
|
||||
map[key] = ParseLLSDXmlElement(reader);
|
||||
else
|
||||
throw new OSDException("Failed to parse a value for key " + key);
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static OSDArray ParseLLSDXmlArray(XmlTextReader reader)
|
||||
{
|
||||
if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "array")
|
||||
throw new OSDException("Expected <array>");
|
||||
|
||||
OSDArray array = new OSDArray();
|
||||
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
return array;
|
||||
}
|
||||
|
||||
if (reader.Read())
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
SkipWhitespace(reader);
|
||||
|
||||
if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "array")
|
||||
{
|
||||
reader.Read();
|
||||
break;
|
||||
}
|
||||
|
||||
array.Add(ParseLLSDXmlElement(reader));
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
private static void SkipWhitespace(XmlTextReader reader)
|
||||
{
|
||||
while (
|
||||
reader.NodeType == XmlNodeType.Comment ||
|
||||
reader.NodeType == XmlNodeType.Whitespace ||
|
||||
reader.NodeType == XmlNodeType.SignificantWhitespace ||
|
||||
reader.NodeType == XmlNodeType.XmlDeclaration)
|
||||
{
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateLLSDXmlSchema()
|
||||
{
|
||||
if (XmlSchema == null)
|
||||
{
|
||||
#region XSD
|
||||
string schemaText = @"
|
||||
<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<xs:schema elementFormDefault=""qualified"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
|
||||
<xs:import schemaLocation=""xml.xsd"" namespace=""http://www.w3.org/XML/1998/namespace"" />
|
||||
<xs:element name=""uri"" type=""xs:string"" />
|
||||
<xs:element name=""uuid"" type=""xs:string"" />
|
||||
<xs:element name=""KEYDATA"">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref=""key"" />
|
||||
<xs:element ref=""DATA"" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name=""date"" type=""xs:string"" />
|
||||
<xs:element name=""key"" type=""xs:string"" />
|
||||
<xs:element name=""boolean"" type=""xs:string"" />
|
||||
<xs:element name=""undef"">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref=""EMPTY"" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name=""map"">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs=""0"" maxOccurs=""unbounded"" ref=""KEYDATA"" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name=""real"" type=""xs:string"" />
|
||||
<xs:element name=""ATOMIC"">
|
||||
<xs:complexType>
|
||||
<xs:choice>
|
||||
<xs:element ref=""undef"" />
|
||||
<xs:element ref=""boolean"" />
|
||||
<xs:element ref=""integer"" />
|
||||
<xs:element ref=""real"" />
|
||||
<xs:element ref=""uuid"" />
|
||||
<xs:element ref=""string"" />
|
||||
<xs:element ref=""date"" />
|
||||
<xs:element ref=""uri"" />
|
||||
<xs:element ref=""binary"" />
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name=""DATA"">
|
||||
<xs:complexType>
|
||||
<xs:choice>
|
||||
<xs:element ref=""ATOMIC"" />
|
||||
<xs:element ref=""map"" />
|
||||
<xs:element ref=""array"" />
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name=""llsd"">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element ref=""DATA"" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name=""binary"">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base=""xs:string"">
|
||||
<xs:attribute default=""base64"" name=""encoding"" type=""xs:string"" />
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name=""array"">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs=""0"" maxOccurs=""unbounded"" ref=""DATA"" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name=""integer"" type=""xs:string"" />
|
||||
<xs:element name=""string"">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base=""xs:string"">
|
||||
<xs:attribute ref=""xml:space"" />
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
";
|
||||
#endregion XSD
|
||||
|
||||
MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(schemaText));
|
||||
|
||||
XmlSchema = new XmlSchema();
|
||||
XmlSchema = XmlSchema.Read(stream, new ValidationEventHandler(LLSDXmlSchemaValidationHandler));
|
||||
}
|
||||
}
|
||||
|
||||
private static void LLSDXmlSchemaValidationHandler(object sender, ValidationEventArgs args)
|
||||
{
|
||||
string error = String.Format("Line: {0} - Position: {1} - {2}", XmlTextReader.LineNumber, XmlTextReader.LinePosition,
|
||||
args.Message);
|
||||
|
||||
if (LastXmlErrors == String.Empty)
|
||||
LastXmlErrors = error;
|
||||
else
|
||||
LastXmlErrors += Environment.NewLine + error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" ?>
|
||||
<project name="OpenMetaverse.StructuredData" default="build">
|
||||
<target name="build">
|
||||
<echo message="Build Directory is ${build.dir}" />
|
||||
<mkdir dir="${build.dir}" />
|
||||
<csc target="library" debug="${build.debug}" unsafe="True" warnaserror="False" define="TRACE" nostdlib="False" main="" doc="${build.dir}/OpenMetaverse.StructuredData.XML" output="${build.dir}/${project::get-name()}.dll">
|
||||
<resources prefix="OpenMetaverse.StructuredData" dynamicprefix="true" >
|
||||
</resources>
|
||||
<sources failonempty="true">
|
||||
<include name="StructuredData.cs" />
|
||||
<include name="JSON/IJsonWrapper.cs" />
|
||||
<include name="JSON/JsonData.cs" />
|
||||
<include name="JSON/JsonException.cs" />
|
||||
<include name="JSON/JsonMapper.cs" />
|
||||
<include name="JSON/JsonReader.cs" />
|
||||
<include name="JSON/JsonWriter.cs" />
|
||||
<include name="JSON/Lexer.cs" />
|
||||
<include name="JSON/OSDJson.cs" />
|
||||
<include name="JSON/ParserToken.cs" />
|
||||
<include name="LLSD/BinaryLLSD.cs" />
|
||||
<include name="LLSD/NotationLLSD.cs" />
|
||||
<include name="LLSD/XmlLLSD.cs" />
|
||||
</sources>
|
||||
<references basedir="${project::get-base-directory()}">
|
||||
<lib>
|
||||
<include name="${project::get-base-directory()}" />
|
||||
<include name="${build.dir}" />
|
||||
</lib>
|
||||
<include name="System.dll" />
|
||||
<include name="System.Xml.dll" />
|
||||
<include name="${build.dir}/OpenMetaverseTypes.dll" />
|
||||
</references>
|
||||
<nowarn>
|
||||
<warning number="1591" />
|
||||
<warning number="1574" />
|
||||
<warning number="0419" />
|
||||
<warning number="0618" />
|
||||
</nowarn>
|
||||
</csc>
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="${bin.dir}" failonerror="false" />
|
||||
<delete dir="${obj.dir}" failonerror="false" />
|
||||
</target>
|
||||
<target name="doc" description="Creates documentation.">
|
||||
<property name="doc.target" value="" />
|
||||
<if test="${platform::is-unix()}">
|
||||
<property name="doc.target" value="Web" />
|
||||
</if>
|
||||
<ndoc failonerror="false" verbose="true">
|
||||
<assemblies basedir="${project::get-base-directory()}">
|
||||
<include name="${build.dir}/${project::get-name()}.dll" />
|
||||
</assemblies>
|
||||
<summaries basedir="${project::get-base-directory()}">
|
||||
<include name="${build.dir}/${project::get-name()}.xml"/>
|
||||
</summaries>
|
||||
<referencepaths basedir="${project::get-base-directory()}">
|
||||
<include name="${build.dir}" />
|
||||
</referencepaths>
|
||||
<documenters>
|
||||
<documenter name="MSDN">
|
||||
<property name="OutputDirectory" value="${build.dir}/doc/${project::get-name()}" />
|
||||
<property name="OutputTarget" value="${doc.target}" />
|
||||
<property name="HtmlHelpName" value="${project::get-name()}" />
|
||||
<property name="IncludeFavorites" value="False" />
|
||||
<property name="Title" value="${project::get-name()} SDK Documentation" />
|
||||
<property name="SplitTOCs" value="False" />
|
||||
<property name="DefaulTOC" value="" />
|
||||
<property name="ShowVisualBasic" value="True" />
|
||||
<property name="AutoDocumentConstructors" value="True" />
|
||||
<property name="ShowMissingSummaries" value="${build.debug}" />
|
||||
<property name="ShowMissingRemarks" value="${build.debug}" />
|
||||
<property name="ShowMissingParams" value="${build.debug}" />
|
||||
<property name="ShowMissingReturns" value="${build.debug}" />
|
||||
<property name="ShowMissingValues" value="${build.debug}" />
|
||||
<property name="DocumentInternals" value="False" />
|
||||
<property name="DocumentPrivates" value="False" />
|
||||
<property name="DocumentProtected" value="True" />
|
||||
<property name="DocumentEmptyNamespaces" value="${build.debug}" />
|
||||
<property name="IncludeAssemblyVersion" value="True" />
|
||||
</documenter>
|
||||
</documenters>
|
||||
</ndoc>
|
||||
</target>
|
||||
</project>
|
||||
@@ -0,0 +1,39 @@
|
||||
<Project name="OpenMetaverse.StructuredData" description="" standardNamespace="OpenMetaverse.StructuredData" newfilesearch="None" enableviewstate="True" fileversion="2.0" language="C#" clr-version="Net_2_0" ctype="DotNetProject">
|
||||
<Configurations active="Debug">
|
||||
<Configuration name="Release" ctype="DotNetProjectConfiguration">
|
||||
<Output directory="./../bin/" assembly="OpenMetaverse.StructuredData" executeScript="" executeBeforeBuild="" executeAfterBuild="" executeBeforeBuildArguments="" executeAfterBuildArguments="" />
|
||||
<Build debugmode="True" target="Library" />
|
||||
<Execution runwithwarnings="True" consolepause="True" runtime="MsNet" clr-version="Net_2_0" />
|
||||
<CodeGeneration compiler="Csc" warninglevel="4" nowarn="1591,1574,0419,0618" includedebuginformation="False" optimize="True" unsafecodeallowed="True" generateoverflowchecks="False" mainclass="" target="Library" definesymbols="TRACE" generatexmldocumentation="True" win32Icon="" ctype="CSharpCompilerParameters" />
|
||||
</Configuration>
|
||||
<Configuration name="Debug" ctype="DotNetProjectConfiguration">
|
||||
<Output directory="./../bin/" assembly="OpenMetaverse.StructuredData" executeScript="" executeBeforeBuild="" executeAfterBuild="" executeBeforeBuildArguments="" executeAfterBuildArguments="" />
|
||||
<Build debugmode="True" target="Library" />
|
||||
<Execution runwithwarnings="True" consolepause="True" runtime="MsNet" clr-version="Net_2_0" />
|
||||
<CodeGeneration compiler="Csc" warninglevel="4" nowarn="1591,1574,0419,0618" includedebuginformation="True" optimize="False" unsafecodeallowed="True" generateoverflowchecks="False" mainclass="" target="Library" definesymbols="TRACE;DEBUG" generatexmldocumentation="False" win32Icon="" ctype="CSharpCompilerParameters" />
|
||||
</Configuration>
|
||||
</Configurations>
|
||||
<DeploymentInformation target="" script="" strategy="File">
|
||||
<excludeFiles />
|
||||
</DeploymentInformation>
|
||||
<Contents>
|
||||
<File name="./StructuredData.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./JSON/IJsonWrapper.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./JSON/JsonData.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./JSON/JsonException.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./JSON/JsonMapper.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./JSON/JsonReader.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./JSON/JsonWriter.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./JSON/Lexer.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./JSON/OSDJson.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./JSON/ParserToken.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./LLSD/BinaryLLSD.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./LLSD/NotationLLSD.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./LLSD/XmlLLSD.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
</Contents>
|
||||
<References>
|
||||
<ProjectReference type="Gac" localcopy="False" refto="System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<ProjectReference type="Gac" localcopy="False" refto="System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<ProjectReference type="Project" localcopy="False" refto="OpenMetaverseTypes" />
|
||||
</References>
|
||||
</Project>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" ?>
|
||||
<project name="OpenMetaverse.Utilities" default="build">
|
||||
<target name="build">
|
||||
<echo message="Build Directory is ${build.dir}" />
|
||||
<mkdir dir="${build.dir}" />
|
||||
<csc target="library" debug="${build.debug}" unsafe="True" warnaserror="False" define="TRACE" nostdlib="False" main="" doc="${build.dir}/OpenMetaverse.Utilities.XML" output="${build.dir}/${project::get-name()}.dll">
|
||||
<resources prefix="OpenMetaverse.Utilities" dynamicprefix="true" >
|
||||
</resources>
|
||||
<sources failonempty="true">
|
||||
<include name="RegistrationApi.cs" />
|
||||
<include name="Utilities.cs" />
|
||||
<include name="VoiceManager.cs" />
|
||||
<include name="VoiceManagerBlocking.cs" />
|
||||
<include name="Properties/AssemblyInfo.cs" />
|
||||
</sources>
|
||||
<references basedir="${project::get-base-directory()}">
|
||||
<lib>
|
||||
<include name="${project::get-base-directory()}" />
|
||||
<include name="${build.dir}" />
|
||||
</lib>
|
||||
<include name="System.dll" />
|
||||
<include name="System.Xml.dll" />
|
||||
<include name="System.Data.dll" />
|
||||
<include name="${build.dir}/OpenMetaverse.dll" />
|
||||
<include name="${build.dir}/OpenMetaverseTypes.dll" />
|
||||
<include name="${build.dir}/OpenMetaverse.StructuredData.dll" />
|
||||
</references>
|
||||
<nowarn>
|
||||
<warning number="1591" />
|
||||
<warning number="1574" />
|
||||
<warning number="0419" />
|
||||
<warning number="0618" />
|
||||
</nowarn>
|
||||
</csc>
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="${bin.dir}" failonerror="false" />
|
||||
<delete dir="${obj.dir}" failonerror="false" />
|
||||
</target>
|
||||
<target name="doc" description="Creates documentation.">
|
||||
<property name="doc.target" value="" />
|
||||
<if test="${platform::is-unix()}">
|
||||
<property name="doc.target" value="Web" />
|
||||
</if>
|
||||
<ndoc failonerror="false" verbose="true">
|
||||
<assemblies basedir="${project::get-base-directory()}">
|
||||
<include name="${build.dir}/${project::get-name()}.dll" />
|
||||
</assemblies>
|
||||
<summaries basedir="${project::get-base-directory()}">
|
||||
<include name="${build.dir}/${project::get-name()}.xml"/>
|
||||
</summaries>
|
||||
<referencepaths basedir="${project::get-base-directory()}">
|
||||
<include name="${build.dir}" />
|
||||
</referencepaths>
|
||||
<documenters>
|
||||
<documenter name="MSDN">
|
||||
<property name="OutputDirectory" value="${build.dir}/doc/${project::get-name()}" />
|
||||
<property name="OutputTarget" value="${doc.target}" />
|
||||
<property name="HtmlHelpName" value="${project::get-name()}" />
|
||||
<property name="IncludeFavorites" value="False" />
|
||||
<property name="Title" value="${project::get-name()} SDK Documentation" />
|
||||
<property name="SplitTOCs" value="False" />
|
||||
<property name="DefaulTOC" value="" />
|
||||
<property name="ShowVisualBasic" value="True" />
|
||||
<property name="AutoDocumentConstructors" value="True" />
|
||||
<property name="ShowMissingSummaries" value="${build.debug}" />
|
||||
<property name="ShowMissingRemarks" value="${build.debug}" />
|
||||
<property name="ShowMissingParams" value="${build.debug}" />
|
||||
<property name="ShowMissingReturns" value="${build.debug}" />
|
||||
<property name="ShowMissingValues" value="${build.debug}" />
|
||||
<property name="DocumentInternals" value="False" />
|
||||
<property name="DocumentPrivates" value="False" />
|
||||
<property name="DocumentProtected" value="True" />
|
||||
<property name="DocumentEmptyNamespaces" value="${build.debug}" />
|
||||
<property name="IncludeAssemblyVersion" value="True" />
|
||||
</documenter>
|
||||
</documenters>
|
||||
</ndoc>
|
||||
</target>
|
||||
</project>
|
||||
@@ -0,0 +1,34 @@
|
||||
<Project name="OpenMetaverse.Utilities" description="" standardNamespace="OpenMetaverse.Utilities" newfilesearch="None" enableviewstate="True" fileversion="2.0" language="C#" clr-version="Net_2_0" ctype="DotNetProject">
|
||||
<Configurations active="Debug">
|
||||
<Configuration name="Release" ctype="DotNetProjectConfiguration">
|
||||
<Output directory="./../bin/" assembly="OpenMetaverse.Utilities" executeScript="" executeBeforeBuild="" executeAfterBuild="" executeBeforeBuildArguments="" executeAfterBuildArguments="" />
|
||||
<Build debugmode="True" target="Library" />
|
||||
<Execution runwithwarnings="True" consolepause="True" runtime="MsNet" clr-version="Net_2_0" />
|
||||
<CodeGeneration compiler="Csc" warninglevel="4" nowarn="1591,1574,0419,0618" includedebuginformation="False" optimize="True" unsafecodeallowed="True" generateoverflowchecks="False" mainclass="" target="Library" definesymbols="TRACE" generatexmldocumentation="True" win32Icon="" ctype="CSharpCompilerParameters" />
|
||||
</Configuration>
|
||||
<Configuration name="Debug" ctype="DotNetProjectConfiguration">
|
||||
<Output directory="./../bin/" assembly="OpenMetaverse.Utilities" executeScript="" executeBeforeBuild="" executeAfterBuild="" executeBeforeBuildArguments="" executeAfterBuildArguments="" />
|
||||
<Build debugmode="True" target="Library" />
|
||||
<Execution runwithwarnings="True" consolepause="True" runtime="MsNet" clr-version="Net_2_0" />
|
||||
<CodeGeneration compiler="Csc" warninglevel="4" nowarn="1591,1574,0419,0618" includedebuginformation="True" optimize="False" unsafecodeallowed="True" generateoverflowchecks="False" mainclass="" target="Library" definesymbols="TRACE;DEBUG" generatexmldocumentation="False" win32Icon="" ctype="CSharpCompilerParameters" />
|
||||
</Configuration>
|
||||
</Configurations>
|
||||
<DeploymentInformation target="" script="" strategy="File">
|
||||
<excludeFiles />
|
||||
</DeploymentInformation>
|
||||
<Contents>
|
||||
<File name="./RegistrationApi.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./Utilities.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./VoiceManager.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./VoiceManagerBlocking.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
<File name="./Properties/AssemblyInfo.cs" subtype="Code" buildaction="Compile" dependson="" data="" />
|
||||
</Contents>
|
||||
<References>
|
||||
<ProjectReference type="Gac" localcopy="False" refto="System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<ProjectReference type="Gac" localcopy="False" refto="System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<ProjectReference type="Gac" localcopy="False" refto="System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<ProjectReference type="Project" localcopy="False" refto="OpenMetaverse" />
|
||||
<ProjectReference type="Project" localcopy="False" refto="OpenMetaverseTypes" />
|
||||
<ProjectReference type="Project" localcopy="False" refto="OpenMetaverse.StructuredData" />
|
||||
</References>
|
||||
</Project>
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("OpenMetaverse.Utilities")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("OpenMetaverse.Utilities")]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("b77aa07a-daa3-431f-b5aa-0c9c5e2151fd")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Revision and Build Numbers
|
||||
// by using the '*' as shown below:
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Text;
|
||||
using OpenMetaverse.StructuredData;
|
||||
using OpenMetaverse.Http;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
public class RegistrationApi
|
||||
{
|
||||
const int REQUEST_TIMEOUT = 1000 * 100;
|
||||
|
||||
private struct UserInfo
|
||||
{
|
||||
public string FirstName;
|
||||
public string LastName;
|
||||
public string Password;
|
||||
}
|
||||
|
||||
private struct RegistrationCaps
|
||||
{
|
||||
public Uri CreateUser;
|
||||
public Uri CheckName;
|
||||
public Uri GetLastNames;
|
||||
public Uri GetErrorCodes;
|
||||
}
|
||||
|
||||
public struct LastName
|
||||
{
|
||||
public int ID;
|
||||
public string Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See https://secure-web6.secondlife.com/developers/third_party_reg/#service_create_user or
|
||||
/// https://wiki.secondlife.com/wiki/RegAPIDoc for description
|
||||
/// </summary>
|
||||
public class CreateUserParam
|
||||
{
|
||||
public string FirstName;
|
||||
public LastName LastName;
|
||||
public string Email;
|
||||
public string Password;
|
||||
public DateTime Birthdate;
|
||||
|
||||
// optional:
|
||||
public Nullable<int> LimitedToEstate;
|
||||
public string StartRegionName;
|
||||
public Nullable<Vector3> StartLocation;
|
||||
public Nullable<Vector3> StartLookAt;
|
||||
}
|
||||
|
||||
private UserInfo _userInfo;
|
||||
private RegistrationCaps _caps;
|
||||
private int _initializing;
|
||||
private List<LastName> _lastNames = new List<LastName>();
|
||||
private Dictionary<int, string> _errors = new Dictionary<int, string>();
|
||||
|
||||
public bool Initializing
|
||||
{
|
||||
get
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(_initializing <= 0);
|
||||
return (_initializing < 0);
|
||||
}
|
||||
}
|
||||
|
||||
public List<LastName> LastNames
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lastNames)
|
||||
{
|
||||
if (_lastNames.Count <= 0)
|
||||
GatherLastNames();
|
||||
}
|
||||
|
||||
return _lastNames;
|
||||
}
|
||||
}
|
||||
|
||||
public RegistrationApi(string firstName, string lastName, string password)
|
||||
{
|
||||
_initializing = -2;
|
||||
|
||||
_userInfo = new UserInfo();
|
||||
|
||||
_userInfo.FirstName = firstName;
|
||||
_userInfo.LastName = lastName;
|
||||
_userInfo.Password = password;
|
||||
|
||||
GatherCaps();
|
||||
}
|
||||
|
||||
public void WaitForInitialization()
|
||||
{
|
||||
while (Initializing)
|
||||
System.Threading.Thread.Sleep(10);
|
||||
}
|
||||
|
||||
public Uri RegistrationApiCaps
|
||||
{
|
||||
get { return new Uri("https://cap.secondlife.com/get_reg_capabilities"); }
|
||||
}
|
||||
|
||||
private void GatherCaps()
|
||||
{
|
||||
// build post data
|
||||
byte[] postData = Encoding.ASCII.GetBytes(
|
||||
String.Format("first_name={0}&last_name={1}&password={2}", _userInfo.FirstName, _userInfo.LastName,
|
||||
_userInfo.Password));
|
||||
|
||||
CapsClient request = new CapsClient(RegistrationApiCaps);
|
||||
request.OnComplete += new CapsClient.CompleteCallback(GatherCapsResponse);
|
||||
request.BeginGetResponse(postData, "application/x-www-form-urlencoded", REQUEST_TIMEOUT);
|
||||
}
|
||||
|
||||
private void GatherCapsResponse(CapsClient client, OSD response, Exception error)
|
||||
{
|
||||
if (response is OSDMap)
|
||||
{
|
||||
OSDMap respTable = (OSDMap)response;
|
||||
|
||||
// parse
|
||||
_caps = new RegistrationCaps();
|
||||
|
||||
_caps.CreateUser = respTable["create_user"].AsUri();
|
||||
_caps.CheckName = respTable["check_name"].AsUri();
|
||||
_caps.GetLastNames = respTable["get_last_names"].AsUri();
|
||||
_caps.GetErrorCodes = respTable["get_error_codes"].AsUri();
|
||||
|
||||
// finalize
|
||||
_initializing++;
|
||||
|
||||
GatherErrorMessages();
|
||||
}
|
||||
}
|
||||
|
||||
private void GatherErrorMessages()
|
||||
{
|
||||
if (_caps.GetErrorCodes == null)
|
||||
throw new InvalidOperationException("access denied"); // this should work even for not-approved users
|
||||
|
||||
CapsClient request = new CapsClient(_caps.GetErrorCodes);
|
||||
request.OnComplete += new CapsClient.CompleteCallback(GatherErrorMessagesResponse);
|
||||
request.BeginGetResponse(REQUEST_TIMEOUT);
|
||||
}
|
||||
|
||||
private void GatherErrorMessagesResponse(CapsClient client, OSD response, Exception error)
|
||||
{
|
||||
if (response is OSDMap)
|
||||
{
|
||||
// parse
|
||||
|
||||
//FIXME: wtf?
|
||||
//foreach (KeyValuePair<string, object> error in (Dictionary<string, object>)response)
|
||||
//{
|
||||
//StringBuilder sb = new StringBuilder();
|
||||
|
||||
//sb.Append(error[1]);
|
||||
//sb.Append(" (");
|
||||
//sb.Append(error[0]);
|
||||
//sb.Append("): ");
|
||||
//sb.Append(error[2]);
|
||||
|
||||
//_errors.Add((int)error[0], sb.ToString());
|
||||
//}
|
||||
|
||||
// finalize
|
||||
_initializing++;
|
||||
}
|
||||
}
|
||||
|
||||
public void GatherLastNames()
|
||||
{
|
||||
if (Initializing)
|
||||
throw new InvalidOperationException("still initializing");
|
||||
|
||||
if (_caps.GetLastNames == null)
|
||||
throw new InvalidOperationException("access denied: only approved developers have access to the registration api");
|
||||
|
||||
CapsClient request = new CapsClient(_caps.GetLastNames);
|
||||
request.OnComplete += new CapsClient.CompleteCallback(GatherLastNamesResponse);
|
||||
request.BeginGetResponse(REQUEST_TIMEOUT);
|
||||
|
||||
// FIXME: Block
|
||||
}
|
||||
|
||||
private void GatherLastNamesResponse(CapsClient client, OSD response, Exception error)
|
||||
{
|
||||
if (response is OSDMap)
|
||||
{
|
||||
//LLSDMap respTable = (LLSDMap)response;
|
||||
|
||||
//FIXME:
|
||||
//_lastNames = new List<LastName>(respTable.Count);
|
||||
|
||||
//for (Dictionary<string, object>.Enumerator it = respTable.GetEnumerator(); it.MoveNext(); )
|
||||
//{
|
||||
// LastName ln = new LastName();
|
||||
|
||||
// ln.ID = int.Parse(it.Current.Key.ToString());
|
||||
// ln.Name = it.Current.Value.ToString();
|
||||
|
||||
// _lastNames.Add(ln);
|
||||
//}
|
||||
|
||||
//_lastNames.Sort(new Comparison<LastName>(delegate(LastName a, LastName b) { return a.Name.CompareTo(b.Name); }));
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckName(string firstName, LastName lastName)
|
||||
{
|
||||
if (Initializing)
|
||||
throw new InvalidOperationException("still initializing");
|
||||
|
||||
if (_caps.CheckName == null)
|
||||
throw new InvalidOperationException("access denied; only approved developers have access to the registration api");
|
||||
|
||||
// Create the POST data
|
||||
OSDMap query = new OSDMap();
|
||||
query.Add("username", OSD.FromString(firstName));
|
||||
query.Add("last_name_id", OSD.FromInteger(lastName.ID));
|
||||
//byte[] postData = OSDParser.SerializeXmlBytes(query);
|
||||
|
||||
CapsClient request = new CapsClient(_caps.CheckName);
|
||||
request.OnComplete += new CapsClient.CompleteCallback(CheckNameResponse);
|
||||
request.BeginGetResponse(REQUEST_TIMEOUT);
|
||||
|
||||
// FIXME:
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CheckNameResponse(CapsClient client, OSD response, Exception error)
|
||||
{
|
||||
if (response.Type == OSDType.Boolean)
|
||||
{
|
||||
// FIXME:
|
||||
//(bool)response;
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME:
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the new user ID or throws an exception containing the error code
|
||||
/// The error codes can be found here: https://wiki.secondlife.com/wiki/RegAPIError
|
||||
/// </summary>
|
||||
/// <param name="user">New user account to create</param>
|
||||
/// <returns>The UUID of the new user account</returns>
|
||||
public UUID CreateUser(CreateUserParam user)
|
||||
{
|
||||
if (Initializing)
|
||||
throw new InvalidOperationException("still initializing");
|
||||
|
||||
if (_caps.CreateUser == null)
|
||||
throw new InvalidOperationException("access denied; only approved developers have access to the registration api");
|
||||
|
||||
// Create the POST data
|
||||
OSDMap query = new OSDMap();
|
||||
query.Add("username", OSD.FromString(user.FirstName));
|
||||
query.Add("last_name_id", OSD.FromInteger(user.LastName.ID));
|
||||
query.Add("email", OSD.FromString(user.Email));
|
||||
query.Add("password", OSD.FromString(user.Password));
|
||||
query.Add("dob", OSD.FromString(user.Birthdate.ToString("yyyy-MM-dd")));
|
||||
|
||||
if (user.LimitedToEstate != null)
|
||||
query.Add("limited_to_estate", OSD.FromInteger(user.LimitedToEstate.Value));
|
||||
|
||||
if (!string.IsNullOrEmpty(user.StartRegionName))
|
||||
query.Add("start_region_name", OSD.FromInteger(user.LimitedToEstate.Value));
|
||||
|
||||
if (user.StartLocation != null)
|
||||
{
|
||||
query.Add("start_local_x", OSD.FromReal(user.StartLocation.Value.X));
|
||||
query.Add("start_local_y", OSD.FromReal(user.StartLocation.Value.Y));
|
||||
query.Add("start_local_z", OSD.FromReal(user.StartLocation.Value.Z));
|
||||
}
|
||||
|
||||
if (user.StartLookAt != null)
|
||||
{
|
||||
query.Add("start_look_at_x", OSD.FromReal(user.StartLookAt.Value.X));
|
||||
query.Add("start_look_at_y", OSD.FromReal(user.StartLookAt.Value.Y));
|
||||
query.Add("start_look_at_z", OSD.FromReal(user.StartLookAt.Value.Z));
|
||||
}
|
||||
|
||||
//byte[] postData = OSDParser.SerializeXmlBytes(query);
|
||||
|
||||
// Make the request
|
||||
CapsClient request = new CapsClient(_caps.CreateUser);
|
||||
request.OnComplete += new CapsClient.CompleteCallback(CreateUserResponse);
|
||||
request.BeginGetResponse(REQUEST_TIMEOUT);
|
||||
|
||||
// FIXME: Block
|
||||
return UUID.Zero;
|
||||
}
|
||||
|
||||
private void CreateUserResponse(CapsClient client, OSD response, Exception error)
|
||||
{
|
||||
if (response is OSDMap)
|
||||
{
|
||||
// everything is okay
|
||||
// FIXME:
|
||||
//return new UUID(((Dictionary<string, object>)response)["agent_id"].ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
// an error happened
|
||||
OSDArray al = (OSDArray)response;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
foreach (OSD ec in al)
|
||||
{
|
||||
if (sb.Length > 0)
|
||||
sb.Append("; ");
|
||||
|
||||
sb.Append(_errors[ec.AsInteger()]);
|
||||
}
|
||||
|
||||
// FIXME:
|
||||
//throw new Exception("failed to create user: " + sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using OpenMetaverse;
|
||||
using OpenMetaverse.Packets;
|
||||
|
||||
namespace OpenMetaverse.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public enum WaterType
|
||||
{
|
||||
/// <summary></summary>
|
||||
Unknown,
|
||||
/// <summary></summary>
|
||||
Dry,
|
||||
/// <summary></summary>
|
||||
Waterfront,
|
||||
/// <summary></summary>
|
||||
Underwater
|
||||
}
|
||||
|
||||
public static class Realism
|
||||
{
|
||||
/// <summary>
|
||||
/// Aims at the specified position, enters mouselook, presses and
|
||||
/// releases the left mouse button, and leaves mouselook
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="target">Target to shoot at</param>
|
||||
/// <returns></returns>
|
||||
public static bool Shoot(GridClient client, Vector3 target)
|
||||
{
|
||||
if (client.Self.Movement.TurnToward(target))
|
||||
return Shoot(client);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enters mouselook, presses and releases the left mouse button, and leaves mouselook
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool Shoot(GridClient client)
|
||||
{
|
||||
if (client.Settings.SEND_AGENT_UPDATES)
|
||||
{
|
||||
client.Self.Movement.Mouselook = true;
|
||||
client.Self.Movement.MLButtonDown = true;
|
||||
client.Self.Movement.SendUpdate();
|
||||
|
||||
client.Self.Movement.MLButtonUp = true;
|
||||
client.Self.Movement.MLButtonDown = false;
|
||||
client.Self.Movement.FinishAnim = true;
|
||||
client.Self.Movement.SendUpdate();
|
||||
|
||||
client.Self.Movement.Mouselook = false;
|
||||
client.Self.Movement.MLButtonUp = false;
|
||||
client.Self.Movement.FinishAnim = false;
|
||||
client.Self.Movement.SendUpdate();
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("Attempted Shoot but agent updates are disabled", Helpers.LogLevel.Warning, client);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A psuedo-realistic chat function that uses the typing sound and
|
||||
/// animation, types at three characters per second, and randomly
|
||||
/// pauses. This function will block until the message has been sent
|
||||
/// </summary>
|
||||
/// <param name="client">A reference to the client that will chat</param>
|
||||
/// <param name="message">The chat message to send</param>
|
||||
public static void Chat(GridClient client, string message)
|
||||
{
|
||||
Chat(client, message, ChatType.Normal, 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A psuedo-realistic chat function that uses the typing sound and
|
||||
/// animation, types at a given rate, and randomly pauses. This
|
||||
/// function will block until the message has been sent
|
||||
/// </summary>
|
||||
/// <param name="client">A reference to the client that will chat</param>
|
||||
/// <param name="message">The chat message to send</param>
|
||||
/// <param name="type">The chat type (usually Normal, Whisper or Shout)</param>
|
||||
/// <param name="cps">Characters per second rate for chatting</param>
|
||||
public static void Chat(GridClient client, string message, ChatType type, int cps)
|
||||
{
|
||||
Random rand = new Random();
|
||||
int characters = 0;
|
||||
bool typing = true;
|
||||
|
||||
// Start typing
|
||||
client.Self.Chat(String.Empty, 0, ChatType.StartTyping);
|
||||
client.Self.AnimationStart(Animations.TYPE, false);
|
||||
|
||||
while (characters < message.Length)
|
||||
{
|
||||
if (!typing)
|
||||
{
|
||||
// Start typing again
|
||||
client.Self.Chat(String.Empty, 0, ChatType.StartTyping);
|
||||
client.Self.AnimationStart(Animations.TYPE, false);
|
||||
typing = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Randomly pause typing
|
||||
if (rand.Next(10) >= 9)
|
||||
{
|
||||
client.Self.Chat(String.Empty, 0, ChatType.StopTyping);
|
||||
client.Self.AnimationStop(Animations.TYPE, false);
|
||||
typing = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Sleep for a second and increase the amount of characters we've typed
|
||||
System.Threading.Thread.Sleep(1000);
|
||||
characters += cps;
|
||||
}
|
||||
|
||||
// Send the message
|
||||
client.Self.Chat(message, 0, type);
|
||||
|
||||
// Stop typing
|
||||
client.Self.Chat(String.Empty, 0, ChatType.StopTyping);
|
||||
client.Self.AnimationStop(Animations.TYPE, false);
|
||||
}
|
||||
}
|
||||
|
||||
public class ConnectionManager
|
||||
{
|
||||
private GridClient Client;
|
||||
private ulong SimHandle;
|
||||
private Vector3 Position = Vector3.Zero;
|
||||
private System.Timers.Timer CheckTimer;
|
||||
|
||||
public ConnectionManager(GridClient client, int timerFrequency)
|
||||
{
|
||||
Client = client;
|
||||
|
||||
CheckTimer = new System.Timers.Timer(timerFrequency);
|
||||
CheckTimer.Elapsed += new System.Timers.ElapsedEventHandler(CheckTimer_Elapsed);
|
||||
}
|
||||
|
||||
public static bool PersistentLogin(GridClient client, string firstName, string lastName, string password,
|
||||
string userAgent, string start, string author)
|
||||
{
|
||||
int unknownLogins = 0;
|
||||
|
||||
Start:
|
||||
|
||||
if (client.Network.Login(firstName, lastName, password, userAgent, start, author))
|
||||
{
|
||||
Logger.Log("Logged in to " + client.Network.CurrentSim, Helpers.LogLevel.Info, client);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (client.Network.LoginErrorKey == "god")
|
||||
{
|
||||
Logger.Log("Grid is down, waiting 10 minutes", Helpers.LogLevel.Warning, client);
|
||||
LoginWait(10);
|
||||
goto Start;
|
||||
}
|
||||
else if (client.Network.LoginErrorKey == "key")
|
||||
{
|
||||
Logger.Log("Bad username or password, giving up on login", Helpers.LogLevel.Error, client);
|
||||
return false;
|
||||
}
|
||||
else if (client.Network.LoginErrorKey == "presence")
|
||||
{
|
||||
Logger.Log("Server is still logging us out, waiting 1 minute", Helpers.LogLevel.Warning, client);
|
||||
LoginWait(1);
|
||||
goto Start;
|
||||
}
|
||||
else if (client.Network.LoginErrorKey == "disabled")
|
||||
{
|
||||
Logger.Log("This account has been banned! Giving up on login", Helpers.LogLevel.Error, client);
|
||||
return false;
|
||||
}
|
||||
else if (client.Network.LoginErrorKey == "timed out" ||client.Network.LoginErrorKey == "no connection" )
|
||||
{
|
||||
Logger.Log("Login request timed out, waiting 1 minute", Helpers.LogLevel.Warning, client);
|
||||
LoginWait(1);
|
||||
goto Start;
|
||||
} else if (client.Network.LoginErrorKey == "bad response") {
|
||||
Logger.Log("Login server returned unparsable result", Helpers.LogLevel.Warning, client);
|
||||
LoginWait(1);
|
||||
goto Start;
|
||||
} else
|
||||
{
|
||||
++unknownLogins;
|
||||
|
||||
if (unknownLogins < 5)
|
||||
{
|
||||
Logger.Log("Unknown login error, waiting 2 minutes: " + client.Network.LoginErrorKey,
|
||||
Helpers.LogLevel.Warning, client);
|
||||
LoginWait(2);
|
||||
goto Start;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("Too many unknown login error codes, giving up", Helpers.LogLevel.Error, client);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void StayInSim(ulong handle, Vector3 desiredPosition)
|
||||
{
|
||||
SimHandle = handle;
|
||||
Position = desiredPosition;
|
||||
CheckTimer.Start();
|
||||
}
|
||||
|
||||
private static void LoginWait(int minutes)
|
||||
{
|
||||
Thread.Sleep(1000 * 60 * minutes);
|
||||
}
|
||||
|
||||
private void CheckTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
if (SimHandle != 0)
|
||||
{
|
||||
if (Client.Network.CurrentSim.Handle != 0 &&
|
||||
Client.Network.CurrentSim.Handle != SimHandle)
|
||||
{
|
||||
// Attempt to move to our target sim
|
||||
Client.Self.Teleport(SimHandle, Position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,827 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Threading;
|
||||
using OpenMetaverse;
|
||||
using OpenMetaverse.StructuredData;
|
||||
using OpenMetaverse.Http;
|
||||
using OpenMetaverse.Interfaces;
|
||||
using OpenMetaverse.Messages.Linden;
|
||||
|
||||
namespace OpenMetaverse.Utilities
|
||||
{
|
||||
public enum VoiceStatus
|
||||
{
|
||||
StatusLoginRetry,
|
||||
StatusLoggedIn,
|
||||
StatusJoining,
|
||||
StatusJoined,
|
||||
StatusLeftChannel,
|
||||
BeginErrorStatus,
|
||||
ErrorChannelFull,
|
||||
ErrorChannelLocked,
|
||||
ErrorNotAvailable,
|
||||
ErrorUnknown
|
||||
}
|
||||
|
||||
public enum VoiceServiceType
|
||||
{
|
||||
/// <summary>Unknown voice service level</summary>
|
||||
Unknown,
|
||||
/// <summary>Spatialized local chat</summary>
|
||||
TypeA,
|
||||
/// <summary>Remote multi-party chat</summary>
|
||||
TypeB,
|
||||
/// <summary>One-to-one and small group chat</summary>
|
||||
TypeC
|
||||
}
|
||||
|
||||
public partial class VoiceManager
|
||||
{
|
||||
public const int VOICE_MAJOR_VERSION = 1;
|
||||
public const string DAEMON_ARGS = " -p tcp -h -c -ll ";
|
||||
public const int DAEMON_LOG_LEVEL = 1;
|
||||
public const int DAEMON_PORT = 44124;
|
||||
public const string VOICE_RELEASE_SERVER = "bhr.vivox.com";
|
||||
public const string VOICE_DEBUG_SERVER = "bhd.vivox.com";
|
||||
public const string REQUEST_TERMINATOR = "\n\n\n";
|
||||
|
||||
public delegate void LoginStateChangeCallback(int cookie, string accountHandle, int statusCode, string statusString, int state);
|
||||
public delegate void NewSessionCallback(int cookie, string accountHandle, string eventSessionHandle, int state, string nameString, string uriString);
|
||||
public delegate void SessionStateChangeCallback(int cookie, string uriString, int statusCode, string statusString, string eventSessionHandle, int state, bool isChannel, string nameString);
|
||||
public delegate void ParticipantStateChangeCallback(int cookie, string uriString, int statusCode, string statusString, int state, string nameString, string displayNameString, int participantType);
|
||||
public delegate void ParticipantPropertiesCallback(int cookie, string uriString, int statusCode, string statusString, bool isLocallyMuted, bool isModeratorMuted, bool isSpeaking, int volume, float energy);
|
||||
public delegate void AuxAudioPropertiesCallback(int cookie, float energy);
|
||||
public delegate void BasicActionCallback(int cookie, int statusCode, string statusString);
|
||||
public delegate void ConnectorCreatedCallback(int cookie, int statusCode, string statusString, string connectorHandle);
|
||||
public delegate void LoginCallback(int cookie, int statusCode, string statusString, string accountHandle);
|
||||
public delegate void SessionCreatedCallback(int cookie, int statusCode, string statusString, string sessionHandle);
|
||||
public delegate void DevicesCallback(int cookie, int statusCode, string statusString, string currentDevice);
|
||||
public delegate void ProvisionAccountCallback(string username, string password);
|
||||
public delegate void ParcelVoiceInfoCallback(string regionName, int localID, string channelURI);
|
||||
|
||||
public event LoginStateChangeCallback OnLoginStateChange;
|
||||
public event NewSessionCallback OnNewSession;
|
||||
public event SessionStateChangeCallback OnSessionStateChange;
|
||||
public event ParticipantStateChangeCallback OnParticipantStateChange;
|
||||
public event ParticipantPropertiesCallback OnParticipantProperties;
|
||||
public event AuxAudioPropertiesCallback OnAuxAudioProperties;
|
||||
public event ConnectorCreatedCallback OnConnectorCreated;
|
||||
public event LoginCallback OnLogin;
|
||||
public event SessionCreatedCallback OnSessionCreated;
|
||||
public event BasicActionCallback OnSessionConnected;
|
||||
public event BasicActionCallback OnAccountLogout;
|
||||
public event BasicActionCallback OnConnectorInitiateShutdown;
|
||||
public event BasicActionCallback OnAccountChannelGetList;
|
||||
public event BasicActionCallback OnSessionTerminated;
|
||||
public event DevicesCallback OnCaptureDevices;
|
||||
public event DevicesCallback OnRenderDevices;
|
||||
public event ProvisionAccountCallback OnProvisionAccount;
|
||||
public event ParcelVoiceInfoCallback OnParcelVoiceInfo;
|
||||
|
||||
public GridClient Client;
|
||||
public string VoiceServer = VOICE_RELEASE_SERVER;
|
||||
public bool Enabled;
|
||||
|
||||
protected Voice.TCPPipe _DaemonPipe;
|
||||
protected VoiceStatus _Status;
|
||||
protected int _CommandCookie = 0;
|
||||
protected string _TuningSoundFile = String.Empty;
|
||||
protected Dictionary<string, string> _ChannelMap = new Dictionary<string, string>();
|
||||
protected List<string> _CaptureDevices = new List<string>();
|
||||
protected List<string> _RenderDevices = new List<string>();
|
||||
|
||||
#region Response Processing Variables
|
||||
|
||||
private bool isEvent = false;
|
||||
private bool isChannel = false;
|
||||
private bool isLocallyMuted = false;
|
||||
private bool isModeratorMuted = false;
|
||||
private bool isSpeaking = false;
|
||||
private int cookie = 0;
|
||||
//private int returnCode = 0;
|
||||
private int statusCode = 0;
|
||||
private int volume = 0;
|
||||
private int state = 0;
|
||||
private int participantType = 0;
|
||||
private float energy = 0f;
|
||||
private string statusString = String.Empty;
|
||||
//private string uuidString = String.Empty;
|
||||
private string actionString = String.Empty;
|
||||
private string connectorHandle = String.Empty;
|
||||
private string accountHandle = String.Empty;
|
||||
private string sessionHandle = String.Empty;
|
||||
private string eventSessionHandle = String.Empty;
|
||||
private string eventTypeString = String.Empty;
|
||||
private string uriString = String.Empty;
|
||||
private string nameString = String.Empty;
|
||||
//private string audioMediaString = String.Empty;
|
||||
private string displayNameString = String.Empty;
|
||||
|
||||
#endregion Response Processing Variables
|
||||
|
||||
public VoiceManager(GridClient client)
|
||||
{
|
||||
Client = client;
|
||||
Client.Network.RegisterEventCallback("RequiredVoiceVersion", new Caps.EventQueueCallback(RequiredVoiceVersionEventHandler));
|
||||
|
||||
// Register callback handlers for the blocking functions
|
||||
RegisterCallbacks();
|
||||
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
public bool IsDaemonRunning()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool StartDaemon()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void StopDaemon()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool ConnectToDaemon()
|
||||
{
|
||||
if (!Enabled) return false;
|
||||
|
||||
return ConnectToDaemon("127.0.0.1", DAEMON_PORT);
|
||||
}
|
||||
|
||||
public bool ConnectToDaemon(string address, int port)
|
||||
{
|
||||
if (!Enabled) return false;
|
||||
|
||||
_DaemonPipe = new Voice.TCPPipe();
|
||||
_DaemonPipe.OnDisconnected += new Voice.TCPPipe.OnDisconnectedCallback(_DaemonPipe_OnDisconnected);
|
||||
_DaemonPipe.OnReceiveLine += new Voice.TCPPipe.OnReceiveLineCallback(_DaemonPipe_OnReceiveLine);
|
||||
|
||||
SocketException se = _DaemonPipe.Connect(address, port);
|
||||
|
||||
if (se == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Connection failed: " + se.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, string> GetChannelMap()
|
||||
{
|
||||
return new Dictionary<string, string>(_ChannelMap);
|
||||
}
|
||||
|
||||
public List<string> CurrentCaptureDevices()
|
||||
{
|
||||
return new List<string>(_CaptureDevices);
|
||||
}
|
||||
|
||||
public List<string> CurrentRenderDevices()
|
||||
{
|
||||
return new List<string>(_RenderDevices);
|
||||
}
|
||||
|
||||
public string VoiceAccountFromUUID(UUID id)
|
||||
{
|
||||
string result = "x" + Convert.ToBase64String(id.GetBytes());
|
||||
return result.Replace('+', '-').Replace('/', '_');
|
||||
}
|
||||
|
||||
public UUID UUIDFromVoiceAccount(string accountName)
|
||||
{
|
||||
if (accountName.Length == 25 && accountName[0] == 'x' && accountName[23] == '=' && accountName[24] == '=')
|
||||
{
|
||||
accountName = accountName.Replace('/', '_').Replace('+', '-');
|
||||
byte[] idBytes = Convert.FromBase64String(accountName);
|
||||
|
||||
if (idBytes.Length == 16)
|
||||
return new UUID(idBytes, 0);
|
||||
else
|
||||
return UUID.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
return UUID.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public string SIPURIFromVoiceAccount(string account)
|
||||
{
|
||||
return String.Format("sip:{0}@{1}", account, VoiceServer);
|
||||
}
|
||||
|
||||
public int RequestCaptureDevices()
|
||||
{
|
||||
if (_DaemonPipe.Connected)
|
||||
{
|
||||
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
|
||||
"<Request requestId=\"{0}\" action=\"Aux.GetCaptureDevices.1\"></Request>{1}",
|
||||
_CommandCookie++,
|
||||
REQUEST_TERMINATOR)));
|
||||
|
||||
return _CommandCookie - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("VoiceManager.RequestCaptureDevices() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public int RequestRenderDevices()
|
||||
{
|
||||
if (_DaemonPipe.Connected)
|
||||
{
|
||||
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
|
||||
"<Request requestId=\"{0}\" action=\"Aux.GetRenderDevices.1\"></Request>{1}",
|
||||
_CommandCookie++,
|
||||
REQUEST_TERMINATOR)));
|
||||
|
||||
return _CommandCookie - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("VoiceManager.RequestRenderDevices() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public int RequestCreateConnector()
|
||||
{
|
||||
return RequestCreateConnector(VoiceServer);
|
||||
}
|
||||
|
||||
public int RequestCreateConnector(string voiceServer)
|
||||
{
|
||||
if (_DaemonPipe.Connected)
|
||||
{
|
||||
VoiceServer = voiceServer;
|
||||
|
||||
string accountServer = String.Format("https://www.{0}/api2/", VoiceServer);
|
||||
string logPath = ".";
|
||||
|
||||
StringBuilder request = new StringBuilder();
|
||||
request.Append(String.Format("<Request requestId=\"{0}\" action=\"Connector.Create.1\">", _CommandCookie++));
|
||||
request.Append("<ClientName>V2 SDK</ClientName>");
|
||||
request.Append(String.Format("<AccountManagementServer>{0}</AccountManagementServer>", accountServer));
|
||||
request.Append("<Logging>");
|
||||
request.Append("<Enabled>false</Enabled>");
|
||||
request.Append(String.Format("<Folder>{0}</Folder>", logPath));
|
||||
request.Append("<FileNamePrefix>vivox-gateway</FileNamePrefix>");
|
||||
request.Append("<FileNameSuffix>.log</FileNameSuffix>");
|
||||
request.Append("<LogLevel>0</LogLevel>");
|
||||
request.Append("</Logging>");
|
||||
request.Append("</Request>");
|
||||
request.Append(REQUEST_TERMINATOR);
|
||||
|
||||
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(request.ToString()));
|
||||
|
||||
return _CommandCookie - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("VoiceManager.CreateConnector() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private bool RequestVoiceInternal(string me, CapsClient.CompleteCallback callback, string capsName)
|
||||
{
|
||||
if (Enabled && Client.Network.Connected)
|
||||
{
|
||||
if (Client.Network.CurrentSim != null && Client.Network.CurrentSim.Caps != null)
|
||||
{
|
||||
Uri url = Client.Network.CurrentSim.Caps.CapabilityURI(capsName);
|
||||
|
||||
if (url != null)
|
||||
{
|
||||
CapsClient request = new CapsClient(url);
|
||||
OSDMap body = new OSDMap();
|
||||
request.OnComplete += new CapsClient.CompleteCallback(callback);
|
||||
request.BeginGetResponse(body, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("VoiceManager." + me + "(): " + capsName + " capability is missing",
|
||||
Helpers.LogLevel.Info, Client);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Log("VoiceManager.RequestVoiceInternal(): Voice system is currently disabled",
|
||||
Helpers.LogLevel.Info, Client);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
public bool RequestProvisionAccount()
|
||||
{
|
||||
return RequestVoiceInternal("RequestProvisionAccount", ProvisionCapsResponse, "ProvisionVoiceAccountRequest");
|
||||
}
|
||||
|
||||
public bool RequestParcelVoiceInfo()
|
||||
{
|
||||
return RequestVoiceInternal("RequestParcelVoiceInfo", ParcelVoiceInfoResponse, "ParcelVoiceInfoRequest");
|
||||
}
|
||||
|
||||
public int RequestLogin(string accountName, string password, string connectorHandle)
|
||||
{
|
||||
if (_DaemonPipe.Connected)
|
||||
{
|
||||
StringBuilder request = new StringBuilder();
|
||||
request.Append(String.Format("<Request requestId=\"{0}\" action=\"Account.Login.1\">", _CommandCookie++));
|
||||
request.Append(String.Format("<ConnectorHandle>{0}</ConnectorHandle>", connectorHandle));
|
||||
request.Append(String.Format("<AccountName>{0}</AccountName>", accountName));
|
||||
request.Append(String.Format("<AccountPassword>{0}</AccountPassword>", password));
|
||||
request.Append("<AudioSessionAnswerMode>VerifyAnswer</AudioSessionAnswerMode>");
|
||||
request.Append("<AccountURI />");
|
||||
request.Append("<ParticipantPropertyFrequency>10</ParticipantPropertyFrequency>");
|
||||
request.Append("<EnableBuddiesAndPresence>false</EnableBuddiesAndPresence>");
|
||||
request.Append("</Request>");
|
||||
request.Append(REQUEST_TERMINATOR);
|
||||
|
||||
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(request.ToString()));
|
||||
|
||||
return _CommandCookie - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("VoiceManager.Login() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public int RequestSetRenderDevice(string deviceName)
|
||||
{
|
||||
if (_DaemonPipe.Connected)
|
||||
{
|
||||
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
|
||||
"<Request requestId=\"{0}\" action=\"Aux.SetRenderDevice.1\"><RenderDeviceSpecifier>{1}</RenderDeviceSpecifier></Request>{2}",
|
||||
_CommandCookie, deviceName, REQUEST_TERMINATOR)));
|
||||
|
||||
return _CommandCookie - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("VoiceManager.RequestSetRenderDevice() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public int RequestStartTuningMode(int duration)
|
||||
{
|
||||
if (_DaemonPipe.Connected)
|
||||
{
|
||||
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
|
||||
"<Request requestId=\"{0}\" action=\"Aux.CaptureAudioStart.1\"><Duration>{1}</Duration></Request>{2}",
|
||||
_CommandCookie, duration, REQUEST_TERMINATOR)));
|
||||
|
||||
return _CommandCookie - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("VoiceManager.RequestStartTuningMode() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public int RequestStopTuningMode()
|
||||
{
|
||||
if (_DaemonPipe.Connected)
|
||||
{
|
||||
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
|
||||
"<Request requestId=\"{0}\" action=\"Aux.CaptureAudioStop.1\"></Request>{1}",
|
||||
_CommandCookie, REQUEST_TERMINATOR)));
|
||||
|
||||
return _CommandCookie - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("VoiceManager.RequestStopTuningMode() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client);
|
||||
return _CommandCookie - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public int RequestSetSpeakerVolume(int volume)
|
||||
{
|
||||
if (volume < 0 || volume > 100)
|
||||
throw new ArgumentException("volume must be between 0 and 100", "volume");
|
||||
|
||||
if (_DaemonPipe.Connected)
|
||||
{
|
||||
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
|
||||
"<Request requestId=\"{0}\" action=\"Aux.SetSpeakerLevel.1\"><Level>{1}</Level></Request>{2}",
|
||||
_CommandCookie, volume, REQUEST_TERMINATOR)));
|
||||
|
||||
return _CommandCookie - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("VoiceManager.RequestSetSpeakerVolume() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public int RequestSetCaptureVolume(int volume)
|
||||
{
|
||||
if (volume < 0 || volume > 100)
|
||||
throw new ArgumentException("volume must be between 0 and 100", "volume");
|
||||
|
||||
if (_DaemonPipe.Connected)
|
||||
{
|
||||
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
|
||||
"<Request requestId=\"{0}\" action=\"Aux.SetMicLevel.1\"><Level>{1}</Level></Request>{2}",
|
||||
_CommandCookie, volume, REQUEST_TERMINATOR)));
|
||||
|
||||
return _CommandCookie - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("VoiceManager.RequestSetCaptureVolume() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does not appear to be working
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="loop"></param>
|
||||
public int RequestRenderAudioStart(string fileName, bool loop)
|
||||
{
|
||||
if (_DaemonPipe.Connected)
|
||||
{
|
||||
_TuningSoundFile = fileName;
|
||||
|
||||
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
|
||||
"<Request requestId=\"{0}\" action=\"Aux.RenderAudioStart.1\"><SoundFilePath>{1}</SoundFilePath><Loop>{2}</Loop></Request>{3}",
|
||||
_CommandCookie++, _TuningSoundFile, (loop ? "1" : "0"), REQUEST_TERMINATOR)));
|
||||
|
||||
return _CommandCookie - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("VoiceManager.RequestRenderAudioStart() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public int RequestRenderAudioStop()
|
||||
{
|
||||
if (_DaemonPipe.Connected)
|
||||
{
|
||||
_DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
|
||||
"<Request requestId=\"{0}\" action=\"Aux.RenderAudioStop.1\"><SoundFilePath>{1}</SoundFilePath></Request>{2}",
|
||||
_CommandCookie++, _TuningSoundFile, REQUEST_TERMINATOR)));
|
||||
|
||||
return _CommandCookie - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("VoiceManager.RequestRenderAudioStop() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#region Callbacks
|
||||
|
||||
private void RequiredVoiceVersionEventHandler(string capsKey, IMessage message, Simulator simulator)
|
||||
{
|
||||
RequiredVoiceVersionMessage msg = (RequiredVoiceVersionMessage)message;
|
||||
|
||||
if (VOICE_MAJOR_VERSION != msg.MajorVersion)
|
||||
{
|
||||
Logger.Log(String.Format("Voice version mismatch! Got {0}, expecting {1}. Disabling the voice manager",
|
||||
msg.MajorVersion, VOICE_MAJOR_VERSION), Helpers.LogLevel.Error, Client);
|
||||
Enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.DebugLog("Voice version " + msg.MajorVersion + " verified", Client);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProvisionCapsResponse(CapsClient client, OSD response, Exception error)
|
||||
{
|
||||
if (response is OSDMap)
|
||||
{
|
||||
OSDMap respTable = (OSDMap)response;
|
||||
|
||||
if (OnProvisionAccount != null)
|
||||
{
|
||||
try { OnProvisionAccount(respTable["username"].AsString(), respTable["password"].AsString()); }
|
||||
catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ParcelVoiceInfoResponse(CapsClient client, OSD response, Exception error)
|
||||
{
|
||||
if (response is OSDMap)
|
||||
{
|
||||
OSDMap respTable = (OSDMap)response;
|
||||
|
||||
string regionName = respTable["region_name"].AsString();
|
||||
int localID = (int)respTable["parcel_local_id"].AsInteger();
|
||||
|
||||
string channelURI = null;
|
||||
if (respTable["voice_credentials"] is OSDMap)
|
||||
{
|
||||
OSDMap creds = (OSDMap)respTable["voice_credentials"];
|
||||
channelURI = creds["channel_uri"].AsString();
|
||||
}
|
||||
|
||||
if (OnParcelVoiceInfo != null) OnParcelVoiceInfo(regionName, localID, channelURI);
|
||||
}
|
||||
}
|
||||
|
||||
private void _DaemonPipe_OnDisconnected(SocketException se)
|
||||
{
|
||||
if (se != null) Console.WriteLine("Disconnected! " + se.Message);
|
||||
else Console.WriteLine("Disconnected!");
|
||||
}
|
||||
|
||||
private void _DaemonPipe_OnReceiveLine(string line)
|
||||
{
|
||||
XmlTextReader reader = new XmlTextReader(new StringReader(line));
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
switch (reader.NodeType)
|
||||
{
|
||||
case XmlNodeType.Element:
|
||||
{
|
||||
if (reader.Depth == 0)
|
||||
{
|
||||
isEvent = (reader.Name == "Event");
|
||||
|
||||
if (isEvent || reader.Name == "Response")
|
||||
{
|
||||
for (int i = 0; i < reader.AttributeCount; i++)
|
||||
{
|
||||
reader.MoveToAttribute(i);
|
||||
|
||||
switch (reader.Name)
|
||||
{
|
||||
// case "requestId":
|
||||
// uuidString = reader.Value;
|
||||
// break;
|
||||
case "action":
|
||||
actionString = reader.Value;
|
||||
break;
|
||||
case "type":
|
||||
eventTypeString = reader.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (reader.Name)
|
||||
{
|
||||
case "InputXml":
|
||||
cookie = -1;
|
||||
|
||||
// Parse through here to get the cookie value
|
||||
reader.Read();
|
||||
if (reader.Name == "Request")
|
||||
{
|
||||
for (int i = 0; i < reader.AttributeCount; i++)
|
||||
{
|
||||
reader.MoveToAttribute(i);
|
||||
|
||||
if (reader.Name == "requestId")
|
||||
{
|
||||
Int32.TryParse(reader.Value, out cookie);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cookie == -1)
|
||||
{
|
||||
Logger.Log("VoiceManager._DaemonPipe_OnReceiveLine(): Failed to parse InputXml for the cookie",
|
||||
Helpers.LogLevel.Warning, Client);
|
||||
}
|
||||
break;
|
||||
case "CaptureDevices":
|
||||
_CaptureDevices.Clear();
|
||||
break;
|
||||
case "RenderDevices":
|
||||
_RenderDevices.Clear();
|
||||
break;
|
||||
// case "ReturnCode":
|
||||
// returnCode = reader.ReadElementContentAsInt();
|
||||
// break;
|
||||
case "StatusCode":
|
||||
statusCode = reader.ReadElementContentAsInt();
|
||||
break;
|
||||
case "StatusString":
|
||||
statusString = reader.ReadElementContentAsString();
|
||||
break;
|
||||
case "State":
|
||||
state = reader.ReadElementContentAsInt();
|
||||
break;
|
||||
case "ConnectorHandle":
|
||||
connectorHandle = reader.ReadElementContentAsString();
|
||||
break;
|
||||
case "AccountHandle":
|
||||
accountHandle = reader.ReadElementContentAsString();
|
||||
break;
|
||||
case "SessionHandle":
|
||||
sessionHandle = reader.ReadElementContentAsString();
|
||||
break;
|
||||
case "URI":
|
||||
uriString = reader.ReadElementContentAsString();
|
||||
break;
|
||||
case "IsChannel":
|
||||
isChannel = reader.ReadElementContentAsBoolean();
|
||||
break;
|
||||
case "Name":
|
||||
nameString = reader.ReadElementContentAsString();
|
||||
break;
|
||||
// case "AudioMedia":
|
||||
// audioMediaString = reader.ReadElementContentAsString();
|
||||
// break;
|
||||
case "ChannelName":
|
||||
nameString = reader.ReadElementContentAsString();
|
||||
break;
|
||||
case "ParticipantURI":
|
||||
uriString = reader.ReadElementContentAsString();
|
||||
break;
|
||||
case "DisplayName":
|
||||
displayNameString = reader.ReadElementContentAsString();
|
||||
break;
|
||||
case "AccountName":
|
||||
nameString = reader.ReadElementContentAsString();
|
||||
break;
|
||||
case "ParticipantType":
|
||||
participantType = reader.ReadElementContentAsInt();
|
||||
break;
|
||||
case "IsLocallyMuted":
|
||||
isLocallyMuted = reader.ReadElementContentAsBoolean();
|
||||
break;
|
||||
case "IsModeratorMuted":
|
||||
isModeratorMuted = reader.ReadElementContentAsBoolean();
|
||||
break;
|
||||
case "IsSpeaking":
|
||||
isSpeaking = reader.ReadElementContentAsBoolean();
|
||||
break;
|
||||
case "Volume":
|
||||
volume = reader.ReadElementContentAsInt();
|
||||
break;
|
||||
case "Energy":
|
||||
energy = reader.ReadElementContentAsFloat();
|
||||
break;
|
||||
case "MicEnergy":
|
||||
energy = reader.ReadElementContentAsFloat();
|
||||
break;
|
||||
case "ChannelURI":
|
||||
uriString = reader.ReadElementContentAsString();
|
||||
break;
|
||||
case "ChannelListResult":
|
||||
_ChannelMap[nameString] = uriString;
|
||||
break;
|
||||
case "CaptureDevice":
|
||||
reader.Read();
|
||||
_CaptureDevices.Add(reader.ReadElementContentAsString());
|
||||
break;
|
||||
case "CurrentCaptureDevice":
|
||||
reader.Read();
|
||||
nameString = reader.ReadElementContentAsString();
|
||||
break;
|
||||
case "RenderDevice":
|
||||
reader.Read();
|
||||
_RenderDevices.Add(reader.ReadElementContentAsString());
|
||||
break;
|
||||
case "CurrentRenderDevice":
|
||||
reader.Read();
|
||||
nameString = reader.ReadElementContentAsString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case XmlNodeType.EndElement:
|
||||
if (reader.Depth == 0)
|
||||
ProcessEvent();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isEvent)
|
||||
{
|
||||
}
|
||||
|
||||
//Client.DebugLog("VOICE: " + line);
|
||||
}
|
||||
|
||||
private void ProcessEvent()
|
||||
{
|
||||
if (isEvent)
|
||||
{
|
||||
switch (eventTypeString)
|
||||
{
|
||||
case "LoginStateChangeEvent":
|
||||
if (OnLoginStateChange != null) OnLoginStateChange(cookie, accountHandle, statusCode, statusString, state);
|
||||
break;
|
||||
case "SessionNewEvent":
|
||||
if (OnNewSession != null) OnNewSession(cookie, accountHandle, eventSessionHandle, state, nameString, uriString);
|
||||
break;
|
||||
case "SessionStateChangeEvent":
|
||||
if (OnSessionStateChange != null) OnSessionStateChange(cookie, uriString, statusCode, statusString, eventSessionHandle, state, isChannel, nameString);
|
||||
break;
|
||||
case "ParticipantStateChangeEvent":
|
||||
if (OnParticipantStateChange != null) OnParticipantStateChange(cookie, uriString, statusCode, statusString, state, nameString, displayNameString, participantType);
|
||||
break;
|
||||
case "ParticipantPropertiesEvent":
|
||||
if (OnParticipantProperties != null) OnParticipantProperties(cookie, uriString, statusCode, statusString, isLocallyMuted, isModeratorMuted, isSpeaking, volume, energy);
|
||||
break;
|
||||
case "AuxAudioPropertiesEvent":
|
||||
if (OnAuxAudioProperties != null) OnAuxAudioProperties(cookie, energy);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (actionString)
|
||||
{
|
||||
case "Connector.Create.1":
|
||||
if (OnConnectorCreated != null) OnConnectorCreated(cookie, statusCode, statusString, connectorHandle);
|
||||
break;
|
||||
case "Account.Login.1":
|
||||
if (OnLogin != null) OnLogin(cookie, statusCode, statusString, accountHandle);
|
||||
break;
|
||||
case "Session.Create.1":
|
||||
if (OnSessionCreated != null) OnSessionCreated(cookie, statusCode, statusString, sessionHandle);
|
||||
break;
|
||||
case "Session.Connect.1":
|
||||
if (OnSessionConnected != null) OnSessionConnected(cookie, statusCode, statusString);
|
||||
break;
|
||||
case "Session.Terminate.1":
|
||||
if (OnSessionTerminated != null) OnSessionTerminated(cookie, statusCode, statusString);
|
||||
break;
|
||||
case "Account.Logout.1":
|
||||
if (OnAccountLogout != null) OnAccountLogout(cookie, statusCode, statusString);
|
||||
break;
|
||||
case "Connector.InitiateShutdown.1":
|
||||
if (OnConnectorInitiateShutdown != null) OnConnectorInitiateShutdown(cookie, statusCode, statusString);
|
||||
break;
|
||||
case "Account.ChannelGetList.1":
|
||||
if (OnAccountChannelGetList != null) OnAccountChannelGetList(cookie, statusCode, statusString);
|
||||
break;
|
||||
case "Aux.GetCaptureDevices.1":
|
||||
if (OnCaptureDevices != null) OnCaptureDevices(cookie, statusCode, statusString, nameString);
|
||||
break;
|
||||
case "Aux.GetRenderDevices.1":
|
||||
if (OnRenderDevices != null) OnRenderDevices(cookie, statusCode, statusString, nameString);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Callbacks
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Utilities
|
||||
{
|
||||
public partial class VoiceManager
|
||||
{
|
||||
/// <summary>Amount of time to wait for the voice daemon to respond.
|
||||
/// The value needs to stay relatively high because some of the calls
|
||||
/// require the voice daemon to make remote queries before replying</summary>
|
||||
public int BlockingTimeout = 30 * 1000;
|
||||
|
||||
protected Dictionary<int, AutoResetEvent> Events = new Dictionary<int, AutoResetEvent>();
|
||||
|
||||
public List<string> CaptureDevices()
|
||||
{
|
||||
AutoResetEvent evt = new AutoResetEvent(false);
|
||||
Events[_CommandCookie] = evt;
|
||||
|
||||
if (RequestCaptureDevices() == -1)
|
||||
{
|
||||
Events.Remove(_CommandCookie);
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
if (evt.WaitOne(BlockingTimeout, false))
|
||||
return CurrentCaptureDevices();
|
||||
else
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
public List<string> RenderDevices()
|
||||
{
|
||||
AutoResetEvent evt = new AutoResetEvent(false);
|
||||
Events[_CommandCookie] = evt;
|
||||
|
||||
if (RequestRenderDevices() == -1)
|
||||
{
|
||||
Events.Remove(_CommandCookie);
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
if (evt.WaitOne(BlockingTimeout, false))
|
||||
return CurrentRenderDevices();
|
||||
else
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
public string CreateConnector(out int status)
|
||||
{
|
||||
status = 0;
|
||||
|
||||
AutoResetEvent evt = new AutoResetEvent(false);
|
||||
Events[_CommandCookie] = evt;
|
||||
|
||||
if (RequestCreateConnector() == -1)
|
||||
{
|
||||
Events.Remove(_CommandCookie);
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
bool success = evt.WaitOne(BlockingTimeout, false);
|
||||
status = statusCode;
|
||||
|
||||
if (success && statusCode == 0)
|
||||
return connectorHandle;
|
||||
else
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
public string Login(string accountName, string password, string connectorHandle, out int status)
|
||||
{
|
||||
status = 0;
|
||||
|
||||
AutoResetEvent evt = new AutoResetEvent(false);
|
||||
Events[_CommandCookie] = evt;
|
||||
|
||||
if (RequestLogin(accountName, password, connectorHandle) == -1)
|
||||
{
|
||||
Events.Remove(_CommandCookie);
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
bool success = evt.WaitOne(BlockingTimeout, false);
|
||||
status = statusCode;
|
||||
|
||||
if (success && statusCode == 0)
|
||||
return accountHandle;
|
||||
else
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
protected void RegisterCallbacks()
|
||||
{
|
||||
OnCaptureDevices += new DevicesCallback(VoiceManager_OnCaptureDevices);
|
||||
OnRenderDevices += new DevicesCallback(VoiceManager_OnRenderDevices);
|
||||
OnConnectorCreated += new ConnectorCreatedCallback(VoiceManager_OnConnectorCreated);
|
||||
OnLogin += new LoginCallback(VoiceManager_OnLogin);
|
||||
}
|
||||
|
||||
#region Callbacks
|
||||
|
||||
private void VoiceManager_OnCaptureDevices(int cookie, int statusCode, string statusString, string currentDevice)
|
||||
{
|
||||
if (Events.ContainsKey(cookie))
|
||||
Events[cookie].Set();
|
||||
}
|
||||
|
||||
private void VoiceManager_OnRenderDevices(int cookie, int statusCode, string statusString, string currentDevice)
|
||||
{
|
||||
if (Events.ContainsKey(cookie))
|
||||
Events[cookie].Set();
|
||||
}
|
||||
|
||||
private void VoiceManager_OnConnectorCreated(int cookie, int statusCode, string statusString, string connectorHandle)
|
||||
{
|
||||
if (Events.ContainsKey(cookie))
|
||||
Events[cookie].Set();
|
||||
}
|
||||
|
||||
private void VoiceManager_OnLogin(int cookie, int statusCode, string statusString, string accountHandle)
|
||||
{
|
||||
if (Events.ContainsKey(cookie))
|
||||
Events[cookie].Set();
|
||||
}
|
||||
|
||||
#endregion Callbacks
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
public partial class AgentManager
|
||||
{
|
||||
public partial class AgentMovement
|
||||
{
|
||||
/// <summary>
|
||||
/// Camera controls for the agent, mostly a thin wrapper around
|
||||
/// CoordinateFrame. This class is only responsible for state
|
||||
/// tracking and math, it does not send any packets
|
||||
/// </summary>
|
||||
public class AgentCamera
|
||||
{
|
||||
/// <summary></summary>
|
||||
public float Far;
|
||||
|
||||
/// <summary>The camera is a local frame of reference inside of
|
||||
/// the larger grid space. This is where the math happens</summary>
|
||||
private CoordinateFrame Frame;
|
||||
|
||||
/// <summary></summary>
|
||||
public Vector3 Position
|
||||
{
|
||||
get { return Frame.Origin; }
|
||||
set { Frame.Origin = value; }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public Vector3 AtAxis
|
||||
{
|
||||
get { return Frame.YAxis; }
|
||||
set { Frame.YAxis = value; }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public Vector3 LeftAxis
|
||||
{
|
||||
get { return Frame.XAxis; }
|
||||
set { Frame.XAxis = value; }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public Vector3 UpAxis
|
||||
{
|
||||
get { return Frame.ZAxis; }
|
||||
set { Frame.ZAxis = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public AgentCamera()
|
||||
{
|
||||
Frame = new CoordinateFrame(new Vector3(128f, 128f, 20f));
|
||||
Far = 128f;
|
||||
}
|
||||
|
||||
public void Roll(float angle)
|
||||
{
|
||||
Frame.Roll(angle);
|
||||
}
|
||||
|
||||
public void Pitch(float angle)
|
||||
{
|
||||
Frame.Pitch(angle);
|
||||
}
|
||||
|
||||
public void Yaw(float angle)
|
||||
{
|
||||
Frame.Yaw(angle);
|
||||
}
|
||||
|
||||
public void LookDirection(Vector3 target)
|
||||
{
|
||||
Frame.LookDirection(target);
|
||||
}
|
||||
|
||||
public void LookDirection(Vector3 target, Vector3 upDirection)
|
||||
{
|
||||
Frame.LookDirection(target, upDirection);
|
||||
}
|
||||
|
||||
public void LookDirection(double heading)
|
||||
{
|
||||
Frame.LookDirection(heading);
|
||||
}
|
||||
|
||||
public void LookAt(Vector3 position, Vector3 target)
|
||||
{
|
||||
Frame.LookAt(position, target);
|
||||
}
|
||||
|
||||
public void LookAt(Vector3 position, Vector3 target, Vector3 upDirection)
|
||||
{
|
||||
Frame.LookAt(position, target, upDirection);
|
||||
}
|
||||
|
||||
public void SetPositionOrientation(Vector3 position, float roll, float pitch, float yaw)
|
||||
{
|
||||
Frame.Origin = position;
|
||||
|
||||
Frame.ResetAxes();
|
||||
|
||||
Frame.Roll(roll);
|
||||
Frame.Pitch(pitch);
|
||||
Frame.Yaw(yaw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,760 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using OpenMetaverse;
|
||||
using OpenMetaverse.Packets;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
public partial class AgentManager
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Used to specify movement actions for your agent
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ControlFlags
|
||||
{
|
||||
/// <summary>Empty flag</summary>
|
||||
NONE = 0,
|
||||
/// <summary>Move Forward (SL Keybinding: W/Up Arrow)</summary>
|
||||
AGENT_CONTROL_AT_POS = 0x1 << CONTROL_AT_POS_INDEX,
|
||||
/// <summary>Move Backward (SL Keybinding: S/Down Arrow)</summary>
|
||||
AGENT_CONTROL_AT_NEG = 0x1 << CONTROL_AT_NEG_INDEX,
|
||||
/// <summary>Move Left (SL Keybinding: Shift-(A/Left Arrow))</summary>
|
||||
AGENT_CONTROL_LEFT_POS = 0x1 << CONTROL_LEFT_POS_INDEX,
|
||||
/// <summary>Move Right (SL Keybinding: Shift-(D/Right Arrow))</summary>
|
||||
AGENT_CONTROL_LEFT_NEG = 0x1 << CONTROL_LEFT_NEG_INDEX,
|
||||
/// <summary>Not Flying: Jump/Flying: Move Up (SL Keybinding: E)</summary>
|
||||
AGENT_CONTROL_UP_POS = 0x1 << CONTROL_UP_POS_INDEX,
|
||||
/// <summary>Not Flying: Croutch/Flying: Move Down (SL Keybinding: C)</summary>
|
||||
AGENT_CONTROL_UP_NEG = 0x1 << CONTROL_UP_NEG_INDEX,
|
||||
/// <summary>Unused</summary>
|
||||
AGENT_CONTROL_PITCH_POS = 0x1 << CONTROL_PITCH_POS_INDEX,
|
||||
/// <summary>Unused</summary>
|
||||
AGENT_CONTROL_PITCH_NEG = 0x1 << CONTROL_PITCH_NEG_INDEX,
|
||||
/// <summary>Unused</summary>
|
||||
AGENT_CONTROL_YAW_POS = 0x1 << CONTROL_YAW_POS_INDEX,
|
||||
/// <summary>Unused</summary>
|
||||
AGENT_CONTROL_YAW_NEG = 0x1 << CONTROL_YAW_NEG_INDEX,
|
||||
/// <summary>ORed with AGENT_CONTROL_AT_* if the keyboard is being used</summary>
|
||||
AGENT_CONTROL_FAST_AT = 0x1 << CONTROL_FAST_AT_INDEX,
|
||||
/// <summary>ORed with AGENT_CONTROL_LEFT_* if the keyboard is being used</summary>
|
||||
AGENT_CONTROL_FAST_LEFT = 0x1 << CONTROL_FAST_LEFT_INDEX,
|
||||
/// <summary>ORed with AGENT_CONTROL_UP_* if the keyboard is being used</summary>
|
||||
AGENT_CONTROL_FAST_UP = 0x1 << CONTROL_FAST_UP_INDEX,
|
||||
/// <summary>Fly</summary>
|
||||
AGENT_CONTROL_FLY = 0x1 << CONTROL_FLY_INDEX,
|
||||
/// <summary></summary>
|
||||
AGENT_CONTROL_STOP = 0x1 << CONTROL_STOP_INDEX,
|
||||
/// <summary>Finish our current animation</summary>
|
||||
AGENT_CONTROL_FINISH_ANIM = 0x1 << CONTROL_FINISH_ANIM_INDEX,
|
||||
/// <summary>Stand up from the ground or a prim seat</summary>
|
||||
AGENT_CONTROL_STAND_UP = 0x1 << CONTROL_STAND_UP_INDEX,
|
||||
/// <summary>Sit on the ground at our current location</summary>
|
||||
AGENT_CONTROL_SIT_ON_GROUND = 0x1 << CONTROL_SIT_ON_GROUND_INDEX,
|
||||
/// <summary>Whether mouselook is currently enabled</summary>
|
||||
AGENT_CONTROL_MOUSELOOK = 0x1 << CONTROL_MOUSELOOK_INDEX,
|
||||
/// <summary>Legacy, used if a key was pressed for less than a certain amount of time</summary>
|
||||
AGENT_CONTROL_NUDGE_AT_POS = 0x1 << CONTROL_NUDGE_AT_POS_INDEX,
|
||||
/// <summary>Legacy, used if a key was pressed for less than a certain amount of time</summary>
|
||||
AGENT_CONTROL_NUDGE_AT_NEG = 0x1 << CONTROL_NUDGE_AT_NEG_INDEX,
|
||||
/// <summary>Legacy, used if a key was pressed for less than a certain amount of time</summary>
|
||||
AGENT_CONTROL_NUDGE_LEFT_POS = 0x1 << CONTROL_NUDGE_LEFT_POS_INDEX,
|
||||
/// <summary>Legacy, used if a key was pressed for less than a certain amount of time</summary>
|
||||
AGENT_CONTROL_NUDGE_LEFT_NEG = 0x1 << CONTROL_NUDGE_LEFT_NEG_INDEX,
|
||||
/// <summary>Legacy, used if a key was pressed for less than a certain amount of time</summary>
|
||||
AGENT_CONTROL_NUDGE_UP_POS = 0x1 << CONTROL_NUDGE_UP_POS_INDEX,
|
||||
/// <summary>Legacy, used if a key was pressed for less than a certain amount of time</summary>
|
||||
AGENT_CONTROL_NUDGE_UP_NEG = 0x1 << CONTROL_NUDGE_UP_NEG_INDEX,
|
||||
/// <summary></summary>
|
||||
AGENT_CONTROL_TURN_LEFT = 0x1 << CONTROL_TURN_LEFT_INDEX,
|
||||
/// <summary></summary>
|
||||
AGENT_CONTROL_TURN_RIGHT = 0x1 << CONTROL_TURN_RIGHT_INDEX,
|
||||
/// <summary>Set when the avatar is idled or set to away. Note that the away animation is
|
||||
/// activated separately from setting this flag</summary>
|
||||
AGENT_CONTROL_AWAY = 0x1 << CONTROL_AWAY_INDEX,
|
||||
/// <summary></summary>
|
||||
AGENT_CONTROL_LBUTTON_DOWN = 0x1 << CONTROL_LBUTTON_DOWN_INDEX,
|
||||
/// <summary></summary>
|
||||
AGENT_CONTROL_LBUTTON_UP = 0x1 << CONTROL_LBUTTON_UP_INDEX,
|
||||
/// <summary></summary>
|
||||
AGENT_CONTROL_ML_LBUTTON_DOWN = 0x1 << CONTROL_ML_LBUTTON_DOWN_INDEX,
|
||||
/// <summary></summary>
|
||||
AGENT_CONTROL_ML_LBUTTON_UP = 0x1 << CONTROL_ML_LBUTTON_UP_INDEX
|
||||
}
|
||||
|
||||
#endregion Enums
|
||||
|
||||
#region AgentUpdate Constants
|
||||
|
||||
private const int CONTROL_AT_POS_INDEX = 0;
|
||||
private const int CONTROL_AT_NEG_INDEX = 1;
|
||||
private const int CONTROL_LEFT_POS_INDEX = 2;
|
||||
private const int CONTROL_LEFT_NEG_INDEX = 3;
|
||||
private const int CONTROL_UP_POS_INDEX = 4;
|
||||
private const int CONTROL_UP_NEG_INDEX = 5;
|
||||
private const int CONTROL_PITCH_POS_INDEX = 6;
|
||||
private const int CONTROL_PITCH_NEG_INDEX = 7;
|
||||
private const int CONTROL_YAW_POS_INDEX = 8;
|
||||
private const int CONTROL_YAW_NEG_INDEX = 9;
|
||||
private const int CONTROL_FAST_AT_INDEX = 10;
|
||||
private const int CONTROL_FAST_LEFT_INDEX = 11;
|
||||
private const int CONTROL_FAST_UP_INDEX = 12;
|
||||
private const int CONTROL_FLY_INDEX = 13;
|
||||
private const int CONTROL_STOP_INDEX = 14;
|
||||
private const int CONTROL_FINISH_ANIM_INDEX = 15;
|
||||
private const int CONTROL_STAND_UP_INDEX = 16;
|
||||
private const int CONTROL_SIT_ON_GROUND_INDEX = 17;
|
||||
private const int CONTROL_MOUSELOOK_INDEX = 18;
|
||||
private const int CONTROL_NUDGE_AT_POS_INDEX = 19;
|
||||
private const int CONTROL_NUDGE_AT_NEG_INDEX = 20;
|
||||
private const int CONTROL_NUDGE_LEFT_POS_INDEX = 21;
|
||||
private const int CONTROL_NUDGE_LEFT_NEG_INDEX = 22;
|
||||
private const int CONTROL_NUDGE_UP_POS_INDEX = 23;
|
||||
private const int CONTROL_NUDGE_UP_NEG_INDEX = 24;
|
||||
private const int CONTROL_TURN_LEFT_INDEX = 25;
|
||||
private const int CONTROL_TURN_RIGHT_INDEX = 26;
|
||||
private const int CONTROL_AWAY_INDEX = 27;
|
||||
private const int CONTROL_LBUTTON_DOWN_INDEX = 28;
|
||||
private const int CONTROL_LBUTTON_UP_INDEX = 29;
|
||||
private const int CONTROL_ML_LBUTTON_DOWN_INDEX = 30;
|
||||
private const int CONTROL_ML_LBUTTON_UP_INDEX = 31;
|
||||
private const int TOTAL_CONTROLS = 32;
|
||||
|
||||
#endregion AgentUpdate Constants
|
||||
|
||||
/// <summary>
|
||||
/// Agent movement and camera control
|
||||
///
|
||||
/// Agent movement is controlled by setting specific <seealso cref="T:AgentManager.ControlFlags"/>
|
||||
/// After the control flags are set, An AgentUpdate is required to update the simulator of the specified flags
|
||||
/// This is most easily accomplished by setting one or more of the AgentMovement properties
|
||||
///
|
||||
/// Movement of an avatar is always based on a compass direction, for example AtPos will move the
|
||||
/// agent from West to East or forward on the X Axis, AtNeg will of course move agent from
|
||||
/// East to West or backward on the X Axis, LeftPos will be South to North or forward on the Y Axis
|
||||
/// The Z axis is Up, finer grained control of movements can be done using the Nudge properties
|
||||
/// </summary>
|
||||
public partial class AgentMovement
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>Move agent positive along the X axis</summary>
|
||||
public bool AtPos
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_AT_POS); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_AT_POS, value); }
|
||||
}
|
||||
/// <summary>Move agent negative along the X axis</summary>
|
||||
public bool AtNeg
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_AT_NEG); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_AT_NEG, value); }
|
||||
}
|
||||
/// <summary>Move agent positive along the Y axis</summary>
|
||||
public bool LeftPos
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_LEFT_POS); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_LEFT_POS, value); }
|
||||
}
|
||||
/// <summary>Move agent negative along the Y axis</summary>
|
||||
public bool LeftNeg
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_LEFT_NEG); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_LEFT_NEG, value); }
|
||||
}
|
||||
/// <summary>Move agent positive along the Z axis</summary>
|
||||
public bool UpPos
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_UP_POS); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_UP_POS, value); }
|
||||
}
|
||||
/// <summary>Move agent negative along the Z axis</summary>
|
||||
public bool UpNeg
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_UP_NEG); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_UP_NEG, value); }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool PitchPos
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_PITCH_POS); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_PITCH_POS, value); }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool PitchNeg
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_PITCH_NEG); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_PITCH_NEG, value); }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool YawPos
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_YAW_POS); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_YAW_POS, value); }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool YawNeg
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_YAW_NEG); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_YAW_NEG, value); }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool FastAt
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_FAST_AT); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_FAST_AT, value); }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool FastLeft
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_FAST_LEFT); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_FAST_LEFT, value); }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool FastUp
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_FAST_UP); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_FAST_UP, value); }
|
||||
}
|
||||
/// <summary>Causes simulator to make agent fly</summary>
|
||||
public bool Fly
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_FLY); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_FLY, value); }
|
||||
}
|
||||
/// <summary>Stop movement</summary>
|
||||
public bool Stop
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_STOP); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_STOP, value); }
|
||||
}
|
||||
/// <summary>Finish animation</summary>
|
||||
public bool FinishAnim
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_FINISH_ANIM); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_FINISH_ANIM, value); }
|
||||
}
|
||||
/// <summary>Stand up from a sit</summary>
|
||||
public bool StandUp
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_STAND_UP); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_STAND_UP, value); }
|
||||
}
|
||||
/// <summary>Tells simulator to sit agent on ground</summary>
|
||||
public bool SitOnGround
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_SIT_ON_GROUND); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_SIT_ON_GROUND, value); }
|
||||
}
|
||||
/// <summary>Place agent into mouselook mode</summary>
|
||||
public bool Mouselook
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_MOUSELOOK); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_MOUSELOOK, value); }
|
||||
}
|
||||
/// <summary>Nudge agent positive along the X axis</summary>
|
||||
public bool NudgeAtPos
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_AT_POS); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_AT_POS, value); }
|
||||
}
|
||||
/// <summary>Nudge agent negative along the X axis</summary>
|
||||
public bool NudgeAtNeg
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_AT_NEG); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_AT_NEG, value); }
|
||||
}
|
||||
/// <summary>Nudge agent positive along the Y axis</summary>
|
||||
public bool NudgeLeftPos
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_LEFT_POS); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_LEFT_POS, value); }
|
||||
}
|
||||
/// <summary>Nudge agent negative along the Y axis</summary>
|
||||
public bool NudgeLeftNeg
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_LEFT_NEG); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_LEFT_NEG, value); }
|
||||
}
|
||||
/// <summary>Nudge agent positive along the Z axis</summary>
|
||||
public bool NudgeUpPos
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_UP_POS); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_UP_POS, value); }
|
||||
}
|
||||
/// <summary>Nudge agent negative along the Z axis</summary>
|
||||
public bool NudgeUpNeg
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_UP_NEG); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_NUDGE_UP_NEG, value); }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool TurnLeft
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_TURN_LEFT); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_TURN_LEFT, value); }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool TurnRight
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_TURN_RIGHT); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_TURN_RIGHT, value); }
|
||||
}
|
||||
/// <summary>Tell simulator to mark agent as away</summary>
|
||||
public bool Away
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_AWAY); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_AWAY, value); }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool LButtonDown
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_LBUTTON_DOWN); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_LBUTTON_DOWN, value); }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool LButtonUp
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_LBUTTON_UP); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_LBUTTON_UP, value); }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool MLButtonDown
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_ML_LBUTTON_DOWN); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_ML_LBUTTON_DOWN, value); }
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool MLButtonUp
|
||||
{
|
||||
get { return GetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_ML_LBUTTON_UP); }
|
||||
set { SetControlFlag(AgentManager.ControlFlags.AGENT_CONTROL_ML_LBUTTON_UP, value); }
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns "always run" value, or changes it by sending a SetAlwaysRunPacket
|
||||
/// </summary>
|
||||
public bool AlwaysRun
|
||||
{
|
||||
get
|
||||
{
|
||||
return alwaysRun;
|
||||
}
|
||||
set
|
||||
{
|
||||
alwaysRun = value;
|
||||
SetAlwaysRunPacket run = new SetAlwaysRunPacket();
|
||||
run.AgentData.AgentID = Client.Self.AgentID;
|
||||
run.AgentData.SessionID = Client.Self.SessionID;
|
||||
run.AgentData.AlwaysRun = alwaysRun;
|
||||
Client.Network.SendPacket(run);
|
||||
}
|
||||
}
|
||||
/// <summary>The current value of the agent control flags</summary>
|
||||
public uint AgentControls
|
||||
{
|
||||
get { return agentControls; }
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the interval in milliseconds at which
|
||||
/// AgentUpdate packets are sent to the current simulator. Setting
|
||||
/// this to a non-zero value will also enable the packet sending if
|
||||
/// it was previously off, and setting it to zero will disable</summary>
|
||||
public int UpdateInterval
|
||||
{
|
||||
get
|
||||
{
|
||||
return updateInterval;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value > 0)
|
||||
{
|
||||
if (updateTimer != null)
|
||||
{
|
||||
updateTimer.Change(value, value);
|
||||
}
|
||||
updateInterval = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (updateTimer != null)
|
||||
{
|
||||
updateTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
updateInterval = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>Gets or sets whether AgentUpdate packets are sent to
|
||||
/// the current simulator</summary>
|
||||
public bool UpdateEnabled
|
||||
{
|
||||
get { return (updateInterval != 0); }
|
||||
}
|
||||
|
||||
/// <summary>Reset movement controls every time we send an update</summary>
|
||||
public bool AutoResetControls
|
||||
{
|
||||
get { return autoResetControls; }
|
||||
set { autoResetControls = value; }
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
/// <summary>Agent camera controls</summary>
|
||||
public AgentCamera Camera;
|
||||
/// <summary>Currently only used for hiding your group title</summary>
|
||||
public AgentFlags Flags = AgentFlags.None;
|
||||
/// <summary>Action state of the avatar, which can currently be
|
||||
/// typing and editing</summary>
|
||||
public AgentState State = AgentState.None;
|
||||
/// <summary></summary>
|
||||
public Quaternion BodyRotation = Quaternion.Identity;
|
||||
/// <summary></summary>
|
||||
public Quaternion HeadRotation = Quaternion.Identity;
|
||||
|
||||
#region Change tracking
|
||||
/// <summary></summary>
|
||||
private Quaternion LastBodyRotation;
|
||||
/// <summary></summary>
|
||||
private Quaternion LastHeadRotation;
|
||||
/// <summary></summary>
|
||||
private Vector3 LastCameraCenter;
|
||||
/// <summary></summary>
|
||||
private Vector3 LastCameraXAxis;
|
||||
/// <summary></summary>
|
||||
private Vector3 LastCameraYAxis;
|
||||
/// <summary></summary>
|
||||
private Vector3 LastCameraZAxis;
|
||||
/// <summary></summary>
|
||||
private float LastFar;
|
||||
#endregion Change tracking
|
||||
|
||||
private bool alwaysRun;
|
||||
private GridClient Client;
|
||||
private uint agentControls;
|
||||
private int duplicateCount;
|
||||
private AgentState lastState;
|
||||
/// <summary>Timer for sending AgentUpdate packets</summary>
|
||||
private Timer updateTimer;
|
||||
private int updateInterval;
|
||||
private bool autoResetControls;
|
||||
|
||||
/// <summary>Default constructor</summary>
|
||||
public AgentMovement(GridClient client)
|
||||
{
|
||||
Client = client;
|
||||
Camera = new AgentCamera();
|
||||
Client.Network.LoginProgress += Network_OnConnected;
|
||||
Client.Network.Disconnected += Network_OnDisconnected;
|
||||
updateInterval = Settings.DEFAULT_AGENT_UPDATE_INTERVAL;
|
||||
}
|
||||
|
||||
private void CleanupTimer()
|
||||
{
|
||||
if (updateTimer != null)
|
||||
{
|
||||
updateTimer.Dispose();
|
||||
updateTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void Network_OnDisconnected(object sender, DisconnectedEventArgs e)
|
||||
{
|
||||
CleanupTimer();
|
||||
}
|
||||
|
||||
private void Network_OnConnected(object sender, LoginProgressEventArgs e)
|
||||
{
|
||||
if (e.Status == LoginStatus.Success)
|
||||
{
|
||||
CleanupTimer();
|
||||
updateTimer = new Timer(new TimerCallback(UpdateTimer_Elapsed), null, updateInterval, updateInterval);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send an AgentUpdate with the camera set at the current agent
|
||||
/// position and pointing towards the heading specified
|
||||
/// </summary>
|
||||
/// <param name="heading">Camera rotation in radians</param>
|
||||
/// <param name="reliable">Whether to send the AgentUpdate reliable
|
||||
/// or not</param>
|
||||
public void UpdateFromHeading(double heading, bool reliable)
|
||||
{
|
||||
Camera.Position = Client.Self.SimPosition;
|
||||
Camera.LookDirection(heading);
|
||||
|
||||
BodyRotation.Z = (float)Math.Sin(heading / 2.0d);
|
||||
BodyRotation.W = (float)Math.Cos(heading / 2.0d);
|
||||
HeadRotation = BodyRotation;
|
||||
|
||||
SendUpdate(reliable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the avatar body and camera toward a target position.
|
||||
/// This will also anchor the camera position on the avatar
|
||||
/// </summary>
|
||||
/// <param name="target">Region coordinates to turn toward</param>
|
||||
public bool TurnToward(Vector3 target)
|
||||
{
|
||||
return TurnToward(target, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the avatar body and camera toward a target position.
|
||||
/// This will also anchor the camera position on the avatar
|
||||
/// </summary>
|
||||
/// <param name="target">Region coordinates to turn toward</param>
|
||||
/// <param name="sendUpdate">whether to send update or not</param>
|
||||
public bool TurnToward(Vector3 target, bool sendUpdate)
|
||||
{
|
||||
if (Client.Settings.SEND_AGENT_UPDATES)
|
||||
{
|
||||
Quaternion parentRot = Quaternion.Identity;
|
||||
|
||||
if (Client.Self.SittingOn > 0)
|
||||
{
|
||||
if (!Client.Network.CurrentSim.ObjectsPrimitives.ContainsKey(Client.Self.SittingOn))
|
||||
{
|
||||
Logger.Log("Attempted TurnToward but parent prim is not in dictionary", Helpers.LogLevel.Warning, Client);
|
||||
return false;
|
||||
}
|
||||
else parentRot = Client.Network.CurrentSim.ObjectsPrimitives[Client.Self.SittingOn].Rotation;
|
||||
}
|
||||
|
||||
Quaternion between = Vector3.RotationBetween(Vector3.UnitX, Vector3.Normalize(target - Client.Self.SimPosition));
|
||||
Quaternion rot = between * (Quaternion.Identity / parentRot);
|
||||
|
||||
BodyRotation = rot;
|
||||
HeadRotation = rot;
|
||||
Camera.LookAt(Client.Self.SimPosition, target);
|
||||
|
||||
if (sendUpdate) SendUpdate();
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("Attempted TurnToward but agent updates are disabled", Helpers.LogLevel.Warning, Client);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send new AgentUpdate packet to update our current camera
|
||||
/// position and rotation
|
||||
/// </summary>
|
||||
public void SendUpdate()
|
||||
{
|
||||
SendUpdate(false, Client.Network.CurrentSim);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send new AgentUpdate packet to update our current camera
|
||||
/// position and rotation
|
||||
/// </summary>
|
||||
/// <param name="reliable">Whether to require server acknowledgement
|
||||
/// of this packet</param>
|
||||
public void SendUpdate(bool reliable)
|
||||
{
|
||||
SendUpdate(reliable, Client.Network.CurrentSim);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send new AgentUpdate packet to update our current camera
|
||||
/// position and rotation
|
||||
/// </summary>
|
||||
/// <param name="reliable">Whether to require server acknowledgement
|
||||
/// of this packet</param>
|
||||
/// <param name="simulator">Simulator to send the update to</param>
|
||||
public void SendUpdate(bool reliable, Simulator simulator)
|
||||
{
|
||||
// Since version 1.40.4 of the Linden simulator, sending this update
|
||||
// causes corruption of the agent position in the simulator
|
||||
if (simulator != null && (!simulator.AgentMovementComplete))
|
||||
return;
|
||||
|
||||
Vector3 origin = Camera.Position;
|
||||
Vector3 xAxis = Camera.LeftAxis;
|
||||
Vector3 yAxis = Camera.AtAxis;
|
||||
Vector3 zAxis = Camera.UpAxis;
|
||||
|
||||
// Attempted to sort these in a rough order of how often they might change
|
||||
if (agentControls == 0 &&
|
||||
yAxis == LastCameraYAxis &&
|
||||
origin == LastCameraCenter &&
|
||||
State == lastState &&
|
||||
HeadRotation == LastHeadRotation &&
|
||||
BodyRotation == LastBodyRotation &&
|
||||
xAxis == LastCameraXAxis &&
|
||||
Camera.Far == LastFar &&
|
||||
zAxis == LastCameraZAxis)
|
||||
{
|
||||
++duplicateCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
duplicateCount = 0;
|
||||
}
|
||||
|
||||
if (Client.Settings.DISABLE_AGENT_UPDATE_DUPLICATE_CHECK || duplicateCount < 10)
|
||||
{
|
||||
// Store the current state to do duplicate checking
|
||||
LastHeadRotation = HeadRotation;
|
||||
LastBodyRotation = BodyRotation;
|
||||
LastCameraYAxis = yAxis;
|
||||
LastCameraCenter = origin;
|
||||
LastCameraXAxis = xAxis;
|
||||
LastCameraZAxis = zAxis;
|
||||
LastFar = Camera.Far;
|
||||
lastState = State;
|
||||
|
||||
// Build the AgentUpdate packet and send it
|
||||
AgentUpdatePacket update = new AgentUpdatePacket();
|
||||
update.Header.Reliable = reliable;
|
||||
|
||||
update.AgentData.AgentID = Client.Self.AgentID;
|
||||
update.AgentData.SessionID = Client.Self.SessionID;
|
||||
update.AgentData.HeadRotation = HeadRotation;
|
||||
update.AgentData.BodyRotation = BodyRotation;
|
||||
update.AgentData.CameraAtAxis = xAxis;
|
||||
update.AgentData.CameraCenter = origin;
|
||||
update.AgentData.CameraLeftAxis = yAxis;
|
||||
update.AgentData.CameraUpAxis = zAxis;
|
||||
update.AgentData.Far = Camera.Far;
|
||||
update.AgentData.State = (byte)State;
|
||||
update.AgentData.ControlFlags = agentControls;
|
||||
update.AgentData.Flags = (byte)Flags;
|
||||
|
||||
Client.Network.SendPacket(update, simulator);
|
||||
|
||||
if (autoResetControls) {
|
||||
ResetControlFlags();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an AgentUpdate packet entirely from parameters. This
|
||||
/// will not touch the state of Self.Movement or
|
||||
/// Self.Movement.Camera in any way
|
||||
/// </summary>
|
||||
/// <param name="controlFlags"></param>
|
||||
/// <param name="position"></param>
|
||||
/// <param name="forwardAxis"></param>
|
||||
/// <param name="leftAxis"></param>
|
||||
/// <param name="upAxis"></param>
|
||||
/// <param name="bodyRotation"></param>
|
||||
/// <param name="headRotation"></param>
|
||||
/// <param name="farClip"></param>
|
||||
/// <param name="reliable"></param>
|
||||
/// <param name="flags"></param>
|
||||
/// <param name="state"></param>
|
||||
public void SendManualUpdate(AgentManager.ControlFlags controlFlags, Vector3 position, Vector3 forwardAxis,
|
||||
Vector3 leftAxis, Vector3 upAxis, Quaternion bodyRotation, Quaternion headRotation, float farClip,
|
||||
AgentFlags flags, AgentState state, bool reliable)
|
||||
{
|
||||
// Since version 1.40.4 of the Linden simulator, sending this update
|
||||
// causes corruption of the agent position in the simulator
|
||||
if (Client.Network.CurrentSim != null && (!Client.Network.CurrentSim.HandshakeComplete))
|
||||
return;
|
||||
|
||||
AgentUpdatePacket update = new AgentUpdatePacket();
|
||||
|
||||
update.AgentData.AgentID = Client.Self.AgentID;
|
||||
update.AgentData.SessionID = Client.Self.SessionID;
|
||||
update.AgentData.BodyRotation = bodyRotation;
|
||||
update.AgentData.HeadRotation = headRotation;
|
||||
update.AgentData.CameraCenter = position;
|
||||
update.AgentData.CameraAtAxis = forwardAxis;
|
||||
update.AgentData.CameraLeftAxis = leftAxis;
|
||||
update.AgentData.CameraUpAxis = upAxis;
|
||||
update.AgentData.Far = farClip;
|
||||
update.AgentData.ControlFlags = (uint)controlFlags;
|
||||
update.AgentData.Flags = (byte)flags;
|
||||
update.AgentData.State = (byte)state;
|
||||
|
||||
update.Header.Reliable = reliable;
|
||||
|
||||
Client.Network.SendPacket(update);
|
||||
}
|
||||
|
||||
private bool GetControlFlag(ControlFlags flag)
|
||||
{
|
||||
return (agentControls & (uint)flag) != 0;
|
||||
}
|
||||
|
||||
private void SetControlFlag(ControlFlags flag, bool value)
|
||||
{
|
||||
if (value) agentControls |= (uint)flag;
|
||||
else agentControls &= ~((uint)flag);
|
||||
}
|
||||
|
||||
public void ResetControlFlags()
|
||||
{
|
||||
// Reset all of the flags except for persistent settings like
|
||||
// away, fly, mouselook, and crouching
|
||||
agentControls &=
|
||||
(uint)(ControlFlags.AGENT_CONTROL_AWAY |
|
||||
ControlFlags.AGENT_CONTROL_FLY |
|
||||
ControlFlags.AGENT_CONTROL_MOUSELOOK |
|
||||
ControlFlags.AGENT_CONTROL_UP_NEG);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sends update of Field of Vision vertical angle to the simulator
|
||||
/// </summary>
|
||||
/// <param name="angle">Angle in radians</param>
|
||||
public void SetFOVVerticalAngle(float angle)
|
||||
{
|
||||
OpenMetaverse.Packets.AgentFOVPacket msg = new OpenMetaverse.Packets.AgentFOVPacket();
|
||||
msg.AgentData.AgentID = Client.Self.AgentID;
|
||||
msg.AgentData.SessionID = Client.Self.SessionID;
|
||||
msg.AgentData.CircuitCode = Client.Network.CircuitCode;
|
||||
msg.FOVBlock.GenCounter = 0;
|
||||
msg.FOVBlock.VerticalAngle = angle;
|
||||
Client.Network.SendPacket(msg);
|
||||
}
|
||||
|
||||
private void UpdateTimer_Elapsed(object obj)
|
||||
{
|
||||
if (Client.Network.Connected && Client.Settings.SEND_AGENT_UPDATES)
|
||||
{
|
||||
//Send an AgentUpdate packet
|
||||
SendUpdate(false, Client.Network.CurrentSim);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse.Packets;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// Throttles the network traffic for various different traffic types.
|
||||
/// Access this class through GridClient.Throttle
|
||||
/// </summary>
|
||||
public class AgentThrottle
|
||||
{
|
||||
/// <summary>Maximum bits per second for resending unacknowledged packets</summary>
|
||||
public float Resend
|
||||
{
|
||||
get { return resend; }
|
||||
set
|
||||
{
|
||||
if (value > 150000.0f) resend = 150000.0f;
|
||||
else if (value < 10000.0f) resend = 10000.0f;
|
||||
else resend = value;
|
||||
}
|
||||
}
|
||||
/// <summary>Maximum bits per second for LayerData terrain</summary>
|
||||
public float Land
|
||||
{
|
||||
get { return land; }
|
||||
set
|
||||
{
|
||||
if (value > 170000.0f) land = 170000.0f;
|
||||
else if (value < 0.0f) land = 0.0f; // We don't have control of these so allow throttling to 0
|
||||
else land = value;
|
||||
}
|
||||
}
|
||||
/// <summary>Maximum bits per second for LayerData wind data</summary>
|
||||
public float Wind
|
||||
{
|
||||
get { return wind; }
|
||||
set
|
||||
{
|
||||
if (value > 34000.0f) wind = 34000.0f;
|
||||
else if (value < 0.0f) wind = 0.0f; // We don't have control of these so allow throttling to 0
|
||||
else wind = value;
|
||||
}
|
||||
}
|
||||
/// <summary>Maximum bits per second for LayerData clouds</summary>
|
||||
public float Cloud
|
||||
{
|
||||
get { return cloud; }
|
||||
set
|
||||
{
|
||||
if (value > 34000.0f) cloud = 34000.0f;
|
||||
else if (value < 0.0f) cloud = 0.0f; // We don't have control of these so allow throttling to 0
|
||||
else cloud = value;
|
||||
}
|
||||
}
|
||||
/// <summary>Unknown, includes object data</summary>
|
||||
public float Task
|
||||
{
|
||||
get { return task; }
|
||||
set
|
||||
{
|
||||
if (value > 446000.0f) task = 446000.0f;
|
||||
else if (value < 4000.0f) task = 4000.0f;
|
||||
else task = value;
|
||||
}
|
||||
}
|
||||
/// <summary>Maximum bits per second for textures</summary>
|
||||
public float Texture
|
||||
{
|
||||
get { return texture; }
|
||||
set
|
||||
{
|
||||
if (value > 446000.0f) texture = 446000.0f;
|
||||
else if (value < 4000.0f) texture = 4000.0f;
|
||||
else texture = value;
|
||||
}
|
||||
}
|
||||
/// <summary>Maximum bits per second for downloaded assets</summary>
|
||||
public float Asset
|
||||
{
|
||||
get { return asset; }
|
||||
set
|
||||
{
|
||||
if (value > 220000.0f) asset = 220000.0f;
|
||||
else if (value < 10000.0f) asset = 10000.0f;
|
||||
else asset = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Maximum bits per second the entire connection, divided up
|
||||
/// between invidiual streams using default multipliers</summary>
|
||||
public float Total
|
||||
{
|
||||
get { return Resend + Land + Wind + Cloud + Task + Texture + Asset; }
|
||||
set
|
||||
{
|
||||
// Sane initial values
|
||||
Resend = (value * 0.1f);
|
||||
Land = (float)(value * 0.52f / 3f);
|
||||
Wind = (float)(value * 0.05f);
|
||||
Cloud = (float)(value * 0.05f);
|
||||
Task = (float)(value * 0.704f / 3f);
|
||||
Texture = (float)(value * 0.704f / 3f);
|
||||
Asset = (float)(value * 0.484f / 3f);
|
||||
}
|
||||
}
|
||||
|
||||
private GridClient Client;
|
||||
private float resend;
|
||||
private float land;
|
||||
private float wind;
|
||||
private float cloud;
|
||||
private float task;
|
||||
private float texture;
|
||||
private float asset;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor, uses a default high total of 1500 KBps (1536000)
|
||||
/// </summary>
|
||||
public AgentThrottle(GridClient client)
|
||||
{
|
||||
Client = client;
|
||||
Total = 1536000.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that decodes an existing AgentThrottle packet in to
|
||||
/// individual values
|
||||
/// </summary>
|
||||
/// <param name="data">Reference to the throttle data in an AgentThrottle
|
||||
/// packet</param>
|
||||
/// <param name="pos">Offset position to start reading at in the
|
||||
/// throttle data</param>
|
||||
/// <remarks>This is generally not needed in clients as the server will
|
||||
/// never send a throttle packet to the client</remarks>
|
||||
public AgentThrottle(byte[] data, int pos)
|
||||
{
|
||||
byte[] adjData;
|
||||
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
byte[] newData = new byte[7 * 4];
|
||||
Buffer.BlockCopy(data, pos, newData, 0, 7 * 4);
|
||||
|
||||
for (int i = 0; i < 7; i++)
|
||||
Array.Reverse(newData, i * 4, 4);
|
||||
|
||||
adjData = newData;
|
||||
}
|
||||
else
|
||||
{
|
||||
adjData = data;
|
||||
}
|
||||
|
||||
Resend = BitConverter.ToSingle(adjData, pos); pos += 4;
|
||||
Land = BitConverter.ToSingle(adjData, pos); pos += 4;
|
||||
Wind = BitConverter.ToSingle(adjData, pos); pos += 4;
|
||||
Cloud = BitConverter.ToSingle(adjData, pos); pos += 4;
|
||||
Task = BitConverter.ToSingle(adjData, pos); pos += 4;
|
||||
Texture = BitConverter.ToSingle(adjData, pos); pos += 4;
|
||||
Asset = BitConverter.ToSingle(adjData, pos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send an AgentThrottle packet to the current server using the
|
||||
/// current values
|
||||
/// </summary>
|
||||
public void Set()
|
||||
{
|
||||
Set(Client.Network.CurrentSim);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send an AgentThrottle packet to the specified server using the
|
||||
/// current values
|
||||
/// </summary>
|
||||
public void Set(Simulator simulator)
|
||||
{
|
||||
AgentThrottlePacket throttle = new AgentThrottlePacket();
|
||||
throttle.AgentData.AgentID = Client.Self.AgentID;
|
||||
throttle.AgentData.SessionID = Client.Self.SessionID;
|
||||
throttle.AgentData.CircuitCode = Client.Network.CircuitCode;
|
||||
throttle.Throttle.GenCounter = 0;
|
||||
throttle.Throttle.Throttles = this.ToBytes();
|
||||
|
||||
Client.Network.SendPacket(throttle, simulator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the current throttle values to a byte array that can be put
|
||||
/// in an AgentThrottle packet
|
||||
/// </summary>
|
||||
/// <returns>Byte array containing all the throttle values</returns>
|
||||
public byte[] ToBytes()
|
||||
{
|
||||
byte[] data = new byte[7 * 4];
|
||||
int i = 0;
|
||||
|
||||
Buffer.BlockCopy(Utils.FloatToBytes(Resend), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes(Land), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes(Wind), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes(Cloud), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes(Task), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes(Texture), 0, data, i, 4); i += 4;
|
||||
Buffer.BlockCopy(Utils.FloatToBytes(Asset), 0, data, i, 4); i += 4;
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// Static pre-defined animations available to all agents
|
||||
/// </summary>
|
||||
public static class Animations
|
||||
{
|
||||
/// <summary>Agent with afraid expression on face</summary>
|
||||
public readonly static UUID AFRAID = new UUID("6b61c8e8-4747-0d75-12d7-e49ff207a4ca");
|
||||
/// <summary>Agent aiming a bazooka (right handed)</summary>
|
||||
public readonly static UUID AIM_BAZOOKA_R = new UUID("b5b4a67d-0aee-30d2-72cd-77b333e932ef");
|
||||
/// <summary>Agent aiming a bow (left handed)</summary>
|
||||
public readonly static UUID AIM_BOW_L = new UUID("46bb4359-de38-4ed8-6a22-f1f52fe8f506");
|
||||
/// <summary>Agent aiming a hand gun (right handed)</summary>
|
||||
public readonly static UUID AIM_HANDGUN_R = new UUID("3147d815-6338-b932-f011-16b56d9ac18b");
|
||||
/// <summary>Agent aiming a rifle (right handed)</summary>
|
||||
public readonly static UUID AIM_RIFLE_R = new UUID("ea633413-8006-180a-c3ba-96dd1d756720");
|
||||
/// <summary>Agent with angry expression on face</summary>
|
||||
public readonly static UUID ANGRY = new UUID("5747a48e-073e-c331-f6f3-7c2149613d3e");
|
||||
/// <summary>Agent hunched over (away)</summary>
|
||||
public readonly static UUID AWAY = new UUID("fd037134-85d4-f241-72c6-4f42164fedee");
|
||||
/// <summary>Agent doing a backflip</summary>
|
||||
public readonly static UUID BACKFLIP = new UUID("c4ca6188-9127-4f31-0158-23c4e2f93304");
|
||||
/// <summary>Agent laughing while holding belly</summary>
|
||||
public readonly static UUID BELLY_LAUGH = new UUID("18b3a4b5-b463-bd48-e4b6-71eaac76c515");
|
||||
/// <summary>Agent blowing a kiss</summary>
|
||||
public readonly static UUID BLOW_KISS = new UUID("db84829b-462c-ee83-1e27-9bbee66bd624");
|
||||
/// <summary>Agent with bored expression on face</summary>
|
||||
public readonly static UUID BORED = new UUID("b906c4ba-703b-1940-32a3-0c7f7d791510");
|
||||
/// <summary>Agent bowing to audience</summary>
|
||||
public readonly static UUID BOW = new UUID("82e99230-c906-1403-4d9c-3889dd98daba");
|
||||
/// <summary>Agent brushing himself/herself off</summary>
|
||||
public readonly static UUID BRUSH = new UUID("349a3801-54f9-bf2c-3bd0-1ac89772af01");
|
||||
/// <summary>Agent in busy mode</summary>
|
||||
public readonly static UUID BUSY = new UUID("efcf670c-2d18-8128-973a-034ebc806b67");
|
||||
/// <summary>Agent clapping hands</summary>
|
||||
public readonly static UUID CLAP = new UUID("9b0c1c4e-8ac7-7969-1494-28c874c4f668");
|
||||
/// <summary>Agent doing a curtsey bow</summary>
|
||||
public readonly static UUID COURTBOW = new UUID("9ba1c942-08be-e43a-fb29-16ad440efc50");
|
||||
/// <summary>Agent crouching</summary>
|
||||
public readonly static UUID CROUCH = new UUID("201f3fdf-cb1f-dbec-201f-7333e328ae7c");
|
||||
/// <summary>Agent crouching while walking</summary>
|
||||
public readonly static UUID CROUCHWALK = new UUID("47f5f6fb-22e5-ae44-f871-73aaaf4a6022");
|
||||
/// <summary>Agent crying</summary>
|
||||
public readonly static UUID CRY = new UUID("92624d3e-1068-f1aa-a5ec-8244585193ed");
|
||||
/// <summary>Agent unanimated with arms out (e.g. setting appearance)</summary>
|
||||
public readonly static UUID CUSTOMIZE = new UUID("038fcec9-5ebd-8a8e-0e2e-6e71a0a1ac53");
|
||||
/// <summary>Agent re-animated after set appearance finished</summary>
|
||||
public readonly static UUID CUSTOMIZE_DONE = new UUID("6883a61a-b27b-5914-a61e-dda118a9ee2c");
|
||||
/// <summary>Agent dancing</summary>
|
||||
public readonly static UUID DANCE1 = new UUID("b68a3d7c-de9e-fc87-eec8-543d787e5b0d");
|
||||
/// <summary>Agent dancing</summary>
|
||||
public readonly static UUID DANCE2 = new UUID("928cae18-e31d-76fd-9cc9-2f55160ff818");
|
||||
/// <summary>Agent dancing</summary>
|
||||
public readonly static UUID DANCE3 = new UUID("30047778-10ea-1af7-6881-4db7a3a5a114");
|
||||
/// <summary>Agent dancing</summary>
|
||||
public readonly static UUID DANCE4 = new UUID("951469f4-c7b2-c818-9dee-ad7eea8c30b7");
|
||||
/// <summary>Agent dancing</summary>
|
||||
public readonly static UUID DANCE5 = new UUID("4bd69a1d-1114-a0b4-625f-84e0a5237155");
|
||||
/// <summary>Agent dancing</summary>
|
||||
public readonly static UUID DANCE6 = new UUID("cd28b69b-9c95-bb78-3f94-8d605ff1bb12");
|
||||
/// <summary>Agent dancing</summary>
|
||||
public readonly static UUID DANCE7 = new UUID("a54d8ee2-28bb-80a9-7f0c-7afbbe24a5d6");
|
||||
/// <summary>Agent dancing</summary>
|
||||
public readonly static UUID DANCE8 = new UUID("b0dc417c-1f11-af36-2e80-7e7489fa7cdc");
|
||||
/// <summary>Agent on ground unanimated</summary>
|
||||
public readonly static UUID DEAD = new UUID("57abaae6-1d17-7b1b-5f98-6d11a6411276");
|
||||
/// <summary>Agent boozing it up</summary>
|
||||
public readonly static UUID DRINK = new UUID("0f86e355-dd31-a61c-fdb0-3a96b9aad05f");
|
||||
/// <summary>Agent with embarassed expression on face</summary>
|
||||
public readonly static UUID EMBARRASSED = new UUID("514af488-9051-044a-b3fc-d4dbf76377c6");
|
||||
/// <summary>Agent with afraid expression on face</summary>
|
||||
public readonly static UUID EXPRESS_AFRAID = new UUID("aa2df84d-cf8f-7218-527b-424a52de766e");
|
||||
/// <summary>Agent with angry expression on face</summary>
|
||||
public readonly static UUID EXPRESS_ANGER = new UUID("1a03b575-9634-b62a-5767-3a679e81f4de");
|
||||
/// <summary>Agent with bored expression on face</summary>
|
||||
public readonly static UUID EXPRESS_BORED = new UUID("214aa6c1-ba6a-4578-f27c-ce7688f61d0d");
|
||||
/// <summary>Agent crying</summary>
|
||||
public readonly static UUID EXPRESS_CRY = new UUID("d535471b-85bf-3b4d-a542-93bea4f59d33");
|
||||
/// <summary>Agent showing disdain (dislike) for something</summary>
|
||||
public readonly static UUID EXPRESS_DISDAIN = new UUID("d4416ff1-09d3-300f-4183-1b68a19b9fc1");
|
||||
/// <summary>Agent with embarassed expression on face</summary>
|
||||
public readonly static UUID EXPRESS_EMBARRASSED = new UUID("0b8c8211-d78c-33e8-fa28-c51a9594e424");
|
||||
/// <summary>Agent with frowning expression on face</summary>
|
||||
public readonly static UUID EXPRESS_FROWN = new UUID("fee3df48-fa3d-1015-1e26-a205810e3001");
|
||||
/// <summary>Agent with kissy face</summary>
|
||||
public readonly static UUID EXPRESS_KISS = new UUID("1e8d90cc-a84e-e135-884c-7c82c8b03a14");
|
||||
/// <summary>Agent expressing laughgter</summary>
|
||||
public readonly static UUID EXPRESS_LAUGH = new UUID("62570842-0950-96f8-341c-809e65110823");
|
||||
/// <summary>Agent with open mouth</summary>
|
||||
public readonly static UUID EXPRESS_OPEN_MOUTH = new UUID("d63bc1f9-fc81-9625-a0c6-007176d82eb7");
|
||||
/// <summary>Agent with repulsed expression on face</summary>
|
||||
public readonly static UUID EXPRESS_REPULSED = new UUID("f76cda94-41d4-a229-2872-e0296e58afe1");
|
||||
/// <summary>Agent expressing sadness</summary>
|
||||
public readonly static UUID EXPRESS_SAD = new UUID("eb6ebfb2-a4b3-a19c-d388-4dd5c03823f7");
|
||||
/// <summary>Agent shrugging shoulders</summary>
|
||||
public readonly static UUID EXPRESS_SHRUG = new UUID("a351b1bc-cc94-aac2-7bea-a7e6ebad15ef");
|
||||
/// <summary>Agent with a smile</summary>
|
||||
public readonly static UUID EXPRESS_SMILE = new UUID("b7c7c833-e3d3-c4e3-9fc0-131237446312");
|
||||
/// <summary>Agent expressing surprise</summary>
|
||||
public readonly static UUID EXPRESS_SURPRISE = new UUID("728646d9-cc79-08b2-32d6-937f0a835c24");
|
||||
/// <summary>Agent sticking tongue out</summary>
|
||||
public readonly static UUID EXPRESS_TONGUE_OUT = new UUID("835965c6-7f2f-bda2-5deb-2478737f91bf");
|
||||
/// <summary>Agent with big toothy smile</summary>
|
||||
public readonly static UUID EXPRESS_TOOTHSMILE = new UUID("b92ec1a5-e7ce-a76b-2b05-bcdb9311417e");
|
||||
/// <summary>Agent winking</summary>
|
||||
public readonly static UUID EXPRESS_WINK = new UUID("da020525-4d94-59d6-23d7-81fdebf33148");
|
||||
/// <summary>Agent expressing worry</summary>
|
||||
public readonly static UUID EXPRESS_WORRY = new UUID("9c05e5c7-6f07-6ca4-ed5a-b230390c3950");
|
||||
/// <summary>Agent falling down</summary>
|
||||
public readonly static UUID FALLDOWN = new UUID("666307d9-a860-572d-6fd4-c3ab8865c094");
|
||||
/// <summary>Agent walking (feminine version)</summary>
|
||||
public readonly static UUID FEMALE_WALK = new UUID("f5fc7433-043d-e819-8298-f519a119b688");
|
||||
/// <summary>Agent wagging finger (disapproval)</summary>
|
||||
public readonly static UUID FINGER_WAG = new UUID("c1bc7f36-3ba0-d844-f93c-93be945d644f");
|
||||
/// <summary>I'm not sure I want to know</summary>
|
||||
public readonly static UUID FIST_PUMP = new UUID("7db00ccd-f380-f3ee-439d-61968ec69c8a");
|
||||
/// <summary>Agent in superman position</summary>
|
||||
public readonly static UUID FLY = new UUID("aec4610c-757f-bc4e-c092-c6e9caf18daf");
|
||||
/// <summary>Agent in superman position</summary>
|
||||
public readonly static UUID FLYSLOW = new UUID("2b5a38b2-5e00-3a97-a495-4c826bc443e6");
|
||||
/// <summary>Agent greeting another</summary>
|
||||
public readonly static UUID HELLO = new UUID("9b29cd61-c45b-5689-ded2-91756b8d76a9");
|
||||
/// <summary>Agent holding bazooka (right handed)</summary>
|
||||
public readonly static UUID HOLD_BAZOOKA_R = new UUID("ef62d355-c815-4816-2474-b1acc21094a6");
|
||||
/// <summary>Agent holding a bow (left handed)</summary>
|
||||
public readonly static UUID HOLD_BOW_L = new UUID("8b102617-bcba-037b-86c1-b76219f90c88");
|
||||
/// <summary>Agent holding a handgun (right handed)</summary>
|
||||
public readonly static UUID HOLD_HANDGUN_R = new UUID("efdc1727-8b8a-c800-4077-975fc27ee2f2");
|
||||
/// <summary>Agent holding a rifle (right handed)</summary>
|
||||
public readonly static UUID HOLD_RIFLE_R = new UUID("3d94bad0-c55b-7dcc-8763-033c59405d33");
|
||||
/// <summary>Agent throwing an object (right handed)</summary>
|
||||
public readonly static UUID HOLD_THROW_R = new UUID("7570c7b5-1f22-56dd-56ef-a9168241bbb6");
|
||||
/// <summary>Agent in static hover</summary>
|
||||
public readonly static UUID HOVER = new UUID("4ae8016b-31b9-03bb-c401-b1ea941db41d");
|
||||
/// <summary>Agent hovering downward</summary>
|
||||
public readonly static UUID HOVER_DOWN = new UUID("20f063ea-8306-2562-0b07-5c853b37b31e");
|
||||
/// <summary>Agent hovering upward</summary>
|
||||
public readonly static UUID HOVER_UP = new UUID("62c5de58-cb33-5743-3d07-9e4cd4352864");
|
||||
/// <summary>Agent being impatient</summary>
|
||||
public readonly static UUID IMPATIENT = new UUID("5ea3991f-c293-392e-6860-91dfa01278a3");
|
||||
/// <summary>Agent jumping</summary>
|
||||
public readonly static UUID JUMP = new UUID("2305bd75-1ca9-b03b-1faa-b176b8a8c49e");
|
||||
/// <summary>Agent jumping with fervor</summary>
|
||||
public readonly static UUID JUMP_FOR_JOY = new UUID("709ea28e-1573-c023-8bf8-520c8bc637fa");
|
||||
/// <summary>Agent point to lips then rear end</summary>
|
||||
public readonly static UUID KISS_MY_BUTT = new UUID("19999406-3a3a-d58c-a2ac-d72e555dcf51");
|
||||
/// <summary>Agent landing from jump, finished flight, etc</summary>
|
||||
public readonly static UUID LAND = new UUID("7a17b059-12b2-41b1-570a-186368b6aa6f");
|
||||
/// <summary>Agent laughing</summary>
|
||||
public readonly static UUID LAUGH_SHORT = new UUID("ca5b3f14-3194-7a2b-c894-aa699b718d1f");
|
||||
/// <summary>Agent landing from jump, finished flight, etc</summary>
|
||||
public readonly static UUID MEDIUM_LAND = new UUID("f4f00d6e-b9fe-9292-f4cb-0ae06ea58d57");
|
||||
/// <summary>Agent sitting on a motorcycle</summary>
|
||||
public readonly static UUID MOTORCYCLE_SIT = new UUID("08464f78-3a8e-2944-cba5-0c94aff3af29");
|
||||
/// <summary></summary>
|
||||
public readonly static UUID MUSCLE_BEACH = new UUID("315c3a41-a5f3-0ba4-27da-f893f769e69b");
|
||||
/// <summary>Agent moving head side to side</summary>
|
||||
public readonly static UUID NO = new UUID("5a977ed9-7f72-44e9-4c4c-6e913df8ae74");
|
||||
/// <summary>Agent moving head side to side with unhappy expression</summary>
|
||||
public readonly static UUID NO_UNHAPPY = new UUID("d83fa0e5-97ed-7eb2-e798-7bd006215cb4");
|
||||
/// <summary>Agent taunting another</summary>
|
||||
public readonly static UUID NYAH_NYAH = new UUID("f061723d-0a18-754f-66ee-29a44795a32f");
|
||||
/// <summary></summary>
|
||||
public readonly static UUID ONETWO_PUNCH = new UUID("eefc79be-daae-a239-8c04-890f5d23654a");
|
||||
/// <summary>Agent giving peace sign</summary>
|
||||
public readonly static UUID PEACE = new UUID("b312b10e-65ab-a0a4-8b3c-1326ea8e3ed9");
|
||||
/// <summary>Agent pointing at self</summary>
|
||||
public readonly static UUID POINT_ME = new UUID("17c024cc-eef2-f6a0-3527-9869876d7752");
|
||||
/// <summary>Agent pointing at another</summary>
|
||||
public readonly static UUID POINT_YOU = new UUID("ec952cca-61ef-aa3b-2789-4d1344f016de");
|
||||
/// <summary>Agent preparing for jump (bending knees)</summary>
|
||||
public readonly static UUID PRE_JUMP = new UUID("7a4e87fe-de39-6fcb-6223-024b00893244");
|
||||
/// <summary>Agent punching with left hand</summary>
|
||||
public readonly static UUID PUNCH_LEFT = new UUID("f3300ad9-3462-1d07-2044-0fef80062da0");
|
||||
/// <summary>Agent punching with right hand</summary>
|
||||
public readonly static UUID PUNCH_RIGHT = new UUID("c8e42d32-7310-6906-c903-cab5d4a34656");
|
||||
/// <summary>Agent acting repulsed</summary>
|
||||
public readonly static UUID REPULSED = new UUID("36f81a92-f076-5893-dc4b-7c3795e487cf");
|
||||
/// <summary>Agent trying to be Chuck Norris</summary>
|
||||
public readonly static UUID ROUNDHOUSE_KICK = new UUID("49aea43b-5ac3-8a44-b595-96100af0beda");
|
||||
/// <summary>Rocks, Paper, Scissors 1, 2, 3</summary>
|
||||
public readonly static UUID RPS_COUNTDOWN = new UUID("35db4f7e-28c2-6679-cea9-3ee108f7fc7f");
|
||||
/// <summary>Agent with hand flat over other hand</summary>
|
||||
public readonly static UUID RPS_PAPER = new UUID("0836b67f-7f7b-f37b-c00a-460dc1521f5a");
|
||||
/// <summary>Agent with fist over other hand</summary>
|
||||
public readonly static UUID RPS_ROCK = new UUID("42dd95d5-0bc6-6392-f650-777304946c0f");
|
||||
/// <summary>Agent with two fingers spread over other hand</summary>
|
||||
public readonly static UUID RPS_SCISSORS = new UUID("16803a9f-5140-e042-4d7b-d28ba247c325");
|
||||
/// <summary>Agent running</summary>
|
||||
public readonly static UUID RUN = new UUID("05ddbff8-aaa9-92a1-2b74-8fe77a29b445");
|
||||
/// <summary>Agent appearing sad</summary>
|
||||
public readonly static UUID SAD = new UUID("0eb702e2-cc5a-9a88-56a5-661a55c0676a");
|
||||
/// <summary>Agent saluting</summary>
|
||||
public readonly static UUID SALUTE = new UUID("cd7668a6-7011-d7e2-ead8-fc69eff1a104");
|
||||
/// <summary>Agent shooting bow (left handed)</summary>
|
||||
public readonly static UUID SHOOT_BOW_L = new UUID("e04d450d-fdb5-0432-fd68-818aaf5935f8");
|
||||
/// <summary>Agent cupping mouth as if shouting</summary>
|
||||
public readonly static UUID SHOUT = new UUID("6bd01860-4ebd-127a-bb3d-d1427e8e0c42");
|
||||
/// <summary>Agent shrugging shoulders</summary>
|
||||
public readonly static UUID SHRUG = new UUID("70ea714f-3a97-d742-1b01-590a8fcd1db5");
|
||||
/// <summary>Agent in sit position</summary>
|
||||
public readonly static UUID SIT = new UUID("1a5fe8ac-a804-8a5d-7cbd-56bd83184568");
|
||||
/// <summary>Agent in sit position (feminine)</summary>
|
||||
public readonly static UUID SIT_FEMALE = new UUID("b1709c8d-ecd3-54a1-4f28-d55ac0840782");
|
||||
/// <summary>Agent in sit position (generic)</summary>
|
||||
public readonly static UUID SIT_GENERIC = new UUID("245f3c54-f1c0-bf2e-811f-46d8eeb386e7");
|
||||
/// <summary>Agent sitting on ground</summary>
|
||||
public readonly static UUID SIT_GROUND = new UUID("1c7600d6-661f-b87b-efe2-d7421eb93c86");
|
||||
/// <summary>Agent sitting on ground</summary>
|
||||
public readonly static UUID SIT_GROUND_staticRAINED = new UUID("1a2bd58e-87ff-0df8-0b4c-53e047b0bb6e");
|
||||
/// <summary></summary>
|
||||
public readonly static UUID SIT_TO_STAND = new UUID("a8dee56f-2eae-9e7a-05a2-6fb92b97e21e");
|
||||
/// <summary>Agent sleeping on side</summary>
|
||||
public readonly static UUID SLEEP = new UUID("f2bed5f9-9d44-39af-b0cd-257b2a17fe40");
|
||||
/// <summary>Agent smoking</summary>
|
||||
public readonly static UUID SMOKE_IDLE = new UUID("d2f2ee58-8ad1-06c9-d8d3-3827ba31567a");
|
||||
/// <summary>Agent inhaling smoke</summary>
|
||||
public readonly static UUID SMOKE_INHALE = new UUID("6802d553-49da-0778-9f85-1599a2266526");
|
||||
/// <summary></summary>
|
||||
public readonly static UUID SMOKE_THROW_DOWN = new UUID("0a9fb970-8b44-9114-d3a9-bf69cfe804d6");
|
||||
/// <summary>Agent taking a picture</summary>
|
||||
public readonly static UUID SNAPSHOT = new UUID("eae8905b-271a-99e2-4c0e-31106afd100c");
|
||||
/// <summary>Agent standing</summary>
|
||||
public readonly static UUID STAND = new UUID("2408fe9e-df1d-1d7d-f4ff-1384fa7b350f");
|
||||
/// <summary>Agent standing up</summary>
|
||||
public readonly static UUID STANDUP = new UUID("3da1d753-028a-5446-24f3-9c9b856d9422");
|
||||
/// <summary>Agent standing</summary>
|
||||
public readonly static UUID STAND_1 = new UUID("15468e00-3400-bb66-cecc-646d7c14458e");
|
||||
/// <summary>Agent standing</summary>
|
||||
public readonly static UUID STAND_2 = new UUID("370f3a20-6ca6-9971-848c-9a01bc42ae3c");
|
||||
/// <summary>Agent standing</summary>
|
||||
public readonly static UUID STAND_3 = new UUID("42b46214-4b44-79ae-deb8-0df61424ff4b");
|
||||
/// <summary>Agent standing</summary>
|
||||
public readonly static UUID STAND_4 = new UUID("f22fed8b-a5ed-2c93-64d5-bdd8b93c889f");
|
||||
/// <summary>Agent stretching</summary>
|
||||
public readonly static UUID STRETCH = new UUID("80700431-74ec-a008-14f8-77575e73693f");
|
||||
/// <summary>Agent in stride (fast walk)</summary>
|
||||
public readonly static UUID STRIDE = new UUID("1cb562b0-ba21-2202-efb3-30f82cdf9595");
|
||||
/// <summary>Agent surfing</summary>
|
||||
public readonly static UUID SURF = new UUID("41426836-7437-7e89-025d-0aa4d10f1d69");
|
||||
/// <summary>Agent acting surprised</summary>
|
||||
public readonly static UUID SURPRISE = new UUID("313b9881-4302-73c0-c7d0-0e7a36b6c224");
|
||||
/// <summary>Agent striking with a sword</summary>
|
||||
public readonly static UUID SWORD_STRIKE = new UUID("85428680-6bf9-3e64-b489-6f81087c24bd");
|
||||
/// <summary>Agent talking (lips moving)</summary>
|
||||
public readonly static UUID TALK = new UUID("5c682a95-6da4-a463-0bf6-0f5b7be129d1");
|
||||
/// <summary>Agent throwing a tantrum</summary>
|
||||
public readonly static UUID TANTRUM = new UUID("11000694-3f41-adc2-606b-eee1d66f3724");
|
||||
/// <summary>Agent throwing an object (right handed)</summary>
|
||||
public readonly static UUID THROW_R = new UUID("aa134404-7dac-7aca-2cba-435f9db875ca");
|
||||
/// <summary>Agent trying on a shirt</summary>
|
||||
public readonly static UUID TRYON_SHIRT = new UUID("83ff59fe-2346-f236-9009-4e3608af64c1");
|
||||
/// <summary>Agent turning to the left</summary>
|
||||
public readonly static UUID TURNLEFT = new UUID("56e0ba0d-4a9f-7f27-6117-32f2ebbf6135");
|
||||
/// <summary>Agent turning to the right</summary>
|
||||
public readonly static UUID TURNRIGHT = new UUID("2d6daa51-3192-6794-8e2e-a15f8338ec30");
|
||||
/// <summary>Agent typing</summary>
|
||||
public readonly static UUID TYPE = new UUID("c541c47f-e0c0-058b-ad1a-d6ae3a4584d9");
|
||||
/// <summary>Agent walking</summary>
|
||||
public readonly static UUID WALK = new UUID("6ed24bd8-91aa-4b12-ccc7-c97c857ab4e0");
|
||||
/// <summary>Agent whispering</summary>
|
||||
public readonly static UUID WHISPER = new UUID("7693f268-06c7-ea71-fa21-2b30d6533f8f");
|
||||
/// <summary>Agent whispering with fingers in mouth</summary>
|
||||
public readonly static UUID WHISTLE = new UUID("b1ed7982-c68e-a982-7561-52a88a5298c0");
|
||||
/// <summary>Agent winking</summary>
|
||||
public readonly static UUID WINK = new UUID("869ecdad-a44b-671e-3266-56aef2e3ac2e");
|
||||
/// <summary>Agent winking</summary>
|
||||
public readonly static UUID WINK_HOLLYWOOD = new UUID("c0c4030f-c02b-49de-24ba-2331f43fe41c");
|
||||
/// <summary>Agent worried</summary>
|
||||
public readonly static UUID WORRY = new UUID("9f496bd2-589a-709f-16cc-69bf7df1d36c");
|
||||
/// <summary>Agent nodding yes</summary>
|
||||
public readonly static UUID YES = new UUID("15dd911d-be82-2856-26db-27659b142875");
|
||||
/// <summary>Agent nodding yes with happy face</summary>
|
||||
public readonly static UUID YES_HAPPY = new UUID("b8c8b2a3-9008-1771-3bfc-90924955ab2d");
|
||||
/// <summary>Agent floating with legs and arms crossed</summary>
|
||||
public readonly static UUID YOGA_FLOAT = new UUID("42ecd00b-9947-a97c-400a-bbc9174c7aeb");
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary containing all pre-defined animations
|
||||
/// </summary>
|
||||
/// <returns>A dictionary containing the pre-defined animations,
|
||||
/// where the key is the animations ID, and the value is a string
|
||||
/// containing a name to identify the purpose of the animation</returns>
|
||||
public static Dictionary<UUID, string> ToDictionary()
|
||||
{
|
||||
Dictionary<UUID, string> dict = new Dictionary<UUID, string>();
|
||||
Type type = typeof(Animations);
|
||||
foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.Static))
|
||||
{
|
||||
dict.Add((UUID)field.GetValue(type), field.Name);
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
//
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("OpenMetaverse")]
|
||||
[assembly: AssemblyDescription("OpenMetaverse library")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("openmetaverse.org")]
|
||||
[assembly: AssemblyProduct("OpenMetaverse")]
|
||||
[assembly: AssemblyCopyright("Copyright © openmetaverse.org 2006-2014")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
//
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Revision and Build Numbers
|
||||
// by using the '*' as shown below:
|
||||
|
||||
[assembly: AssemblyVersion("0.9.3.0")]
|
||||
|
||||
//
|
||||
// In order to sign your assembly you must specify a key to use. Refer to the
|
||||
// Microsoft .NET Framework documentation for more information on assembly signing.
|
||||
//
|
||||
// Use the attributes below to control which key is used for signing.
|
||||
//
|
||||
// Notes:
|
||||
// (*) If no key is specified, the assembly is not signed.
|
||||
// (*) KeyName refers to a key that has been installed in the Crypto Service
|
||||
// Provider (CSP) on your machine. KeyFile refers to a file which contains
|
||||
// a key.
|
||||
// (*) If the KeyFile and the KeyName values are both specified, the
|
||||
// following processing occurs:
|
||||
// (1) If the KeyName can be found in the CSP, that key is used.
|
||||
// (2) If the KeyName does not exist and the KeyFile does exist, the key
|
||||
// in the KeyFile is installed into the CSP and used.
|
||||
// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.
|
||||
// When specifying the KeyFile, the location of the KeyFile should be
|
||||
// relative to the project output directory which is
|
||||
// %Project Directory%\obj\<configuration>. For example, if your KeyFile is
|
||||
// located in the project directory, you would specify the AssemblyKeyFile
|
||||
// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]
|
||||
// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework
|
||||
// documentation for more information on this.
|
||||
//
|
||||
[assembly: AssemblyDelaySign(false)]
|
||||
[assembly: AssemblyKeyFile("")]
|
||||
[assembly: AssemblyKeyName("")]
|
||||
[assembly: AssemblyFileVersionAttribute("0.9.3.0")]
|
||||
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that handles the local asset cache
|
||||
/// </summary>
|
||||
public class AssetCache
|
||||
{
|
||||
// User can plug in a routine to compute the asset cache location
|
||||
public delegate string ComputeAssetCacheFilenameDelegate(string cacheDir, UUID assetID);
|
||||
|
||||
public ComputeAssetCacheFilenameDelegate ComputeAssetCacheFilename = null;
|
||||
|
||||
private GridClient Client;
|
||||
private Thread cleanerThread;
|
||||
private System.Timers.Timer cleanerTimer;
|
||||
private double pruneInterval = 1000 * 60 * 5;
|
||||
private bool autoPruneEnabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Allows setting weather to periodicale prune the cache if it grows too big
|
||||
/// Default is enabled, when caching is enabled
|
||||
/// </summary>
|
||||
public bool AutoPruneEnabled
|
||||
{
|
||||
set
|
||||
{
|
||||
autoPruneEnabled = value;
|
||||
|
||||
if (autoPruneEnabled)
|
||||
{
|
||||
SetupTimer();
|
||||
}
|
||||
else
|
||||
{
|
||||
DestroyTimer();
|
||||
}
|
||||
}
|
||||
get { return autoPruneEnabled; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How long (in ms) between cache checks (default is 5 min.)
|
||||
/// </summary>
|
||||
public double AutoPruneInterval
|
||||
{
|
||||
set
|
||||
{
|
||||
pruneInterval = value;
|
||||
SetupTimer();
|
||||
}
|
||||
get { return pruneInterval; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
/// <param name="client">A reference to the GridClient object</param>
|
||||
public AssetCache(GridClient client)
|
||||
{
|
||||
Client = client;
|
||||
Client.Network.LoginProgress += delegate(object sender, LoginProgressEventArgs e)
|
||||
{
|
||||
if (e.Status == LoginStatus.Success)
|
||||
{
|
||||
SetupTimer();
|
||||
}
|
||||
};
|
||||
|
||||
Client.Network.Disconnected += delegate(object sender, DisconnectedEventArgs e) { DestroyTimer(); };
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Disposes cleanup timer
|
||||
/// </summary>
|
||||
private void DestroyTimer()
|
||||
{
|
||||
if (cleanerTimer != null)
|
||||
{
|
||||
cleanerTimer.Dispose();
|
||||
cleanerTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only create timer when needed
|
||||
/// </summary>
|
||||
private void SetupTimer()
|
||||
{
|
||||
if (Operational() && autoPruneEnabled && Client.Network.Connected)
|
||||
{
|
||||
if (cleanerTimer == null)
|
||||
{
|
||||
cleanerTimer = new System.Timers.Timer(pruneInterval);
|
||||
cleanerTimer.Elapsed += new System.Timers.ElapsedEventHandler(cleanerTimer_Elapsed);
|
||||
}
|
||||
cleanerTimer.Interval = pruneInterval;
|
||||
cleanerTimer.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return bytes read from the local asset cache, null if it does not exist
|
||||
/// </summary>
|
||||
/// <param name="assetID">UUID of the asset we want to get</param>
|
||||
/// <returns>Raw bytes of the asset, or null on failure</returns>
|
||||
public byte[] GetCachedAssetBytes(UUID assetID)
|
||||
{
|
||||
if (!Operational())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
byte[] data;
|
||||
|
||||
if (File.Exists(FileName(assetID)))
|
||||
{
|
||||
DebugLog("Reading " + FileName(assetID) + " from asset cache.");
|
||||
data = File.ReadAllBytes(FileName(assetID));
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugLog("Reading " + StaticFileName(assetID) + " from static asset cache.");
|
||||
data = File.ReadAllBytes(StaticFileName(assetID));
|
||||
|
||||
}
|
||||
return data;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugLog("Failed reading asset from cache (" + ex.Message + ")");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns ImageDownload object of the
|
||||
/// image from the local image cache, null if it does not exist
|
||||
/// </summary>
|
||||
/// <param name="imageID">UUID of the image we want to get</param>
|
||||
/// <returns>ImageDownload object containing the image, or null on failure</returns>
|
||||
public ImageDownload GetCachedImage(UUID imageID)
|
||||
{
|
||||
if (!Operational())
|
||||
return null;
|
||||
|
||||
byte[] imageData = GetCachedAssetBytes(imageID);
|
||||
if (imageData == null)
|
||||
return null;
|
||||
ImageDownload transfer = new ImageDownload();
|
||||
transfer.AssetType = AssetType.Texture;
|
||||
transfer.ID = imageID;
|
||||
transfer.Simulator = Client.Network.CurrentSim;
|
||||
transfer.Size = imageData.Length;
|
||||
transfer.Success = true;
|
||||
transfer.Transferred = imageData.Length;
|
||||
transfer.AssetData = imageData;
|
||||
return transfer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a file name of the cached asset
|
||||
/// </summary>
|
||||
/// <param name="assetID">UUID of the asset</param>
|
||||
/// <returns>String with the file name of the cahced asset</returns>
|
||||
private string FileName(UUID assetID)
|
||||
{
|
||||
if (ComputeAssetCacheFilename != null)
|
||||
{
|
||||
return ComputeAssetCacheFilename(Client.Settings.ASSET_CACHE_DIR, assetID);
|
||||
}
|
||||
return Client.Settings.ASSET_CACHE_DIR + Path.DirectorySeparatorChar + assetID.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a file name of the static cached asset
|
||||
/// </summary>
|
||||
/// <param name="assetID">UUID of the asset</param>
|
||||
/// <returns>String with the file name of the static cached asset</returns>
|
||||
private string StaticFileName(UUID assetID)
|
||||
{
|
||||
return Settings.RESOURCE_DIR + Path.DirectorySeparatorChar + "static_assets" + Path.DirectorySeparatorChar + assetID.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves an asset to the local cache
|
||||
/// </summary>
|
||||
/// <param name="assetID">UUID of the asset</param>
|
||||
/// <param name="assetData">Raw bytes the asset consists of</param>
|
||||
/// <returns>Weather the operation was successfull</returns>
|
||||
public bool SaveAssetToCache(UUID assetID, byte[] assetData)
|
||||
{
|
||||
if (!Operational())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DebugLog("Saving " + FileName(assetID) + " to asset cache.");
|
||||
|
||||
if (!Directory.Exists(Client.Settings.ASSET_CACHE_DIR))
|
||||
{
|
||||
Directory.CreateDirectory(Client.Settings.ASSET_CACHE_DIR);
|
||||
}
|
||||
|
||||
File.WriteAllBytes(FileName(assetID), assetData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Failed saving asset to cache (" + ex.Message + ")", Helpers.LogLevel.Warning, Client);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void DebugLog(string message)
|
||||
{
|
||||
if (Client.Settings.LOG_DISKCACHE) Logger.DebugLog(message, Client);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the file name of the asset stored with gived UUID
|
||||
/// </summary>
|
||||
/// <param name="assetID">UUID of the asset</param>
|
||||
/// <returns>Null if we don't have that UUID cached on disk, file name if found in the cache folder</returns>
|
||||
public string AssetFileName(UUID assetID)
|
||||
{
|
||||
if (!Operational())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string fileName = FileName(assetID);
|
||||
|
||||
if (File.Exists(fileName))
|
||||
return fileName;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the asset exists in the local cache
|
||||
/// </summary>
|
||||
/// <param name="assetID">UUID of the asset</param>
|
||||
/// <returns>True is the asset is stored in the cache, otherwise false</returns>
|
||||
public bool HasAsset(UUID assetID)
|
||||
{
|
||||
if (!Operational())
|
||||
return false;
|
||||
else
|
||||
if (File.Exists(FileName(assetID)))
|
||||
return true;
|
||||
else
|
||||
return File.Exists(StaticFileName(assetID));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wipes out entire cache
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
string cacheDir = Client.Settings.ASSET_CACHE_DIR;
|
||||
if (!Directory.Exists(cacheDir))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DirectoryInfo di = new DirectoryInfo(cacheDir);
|
||||
// We save file with UUID as file name, only delete those
|
||||
FileInfo[] files = di.GetFiles("????????-????-????-????-????????????", SearchOption.TopDirectoryOnly);
|
||||
|
||||
int num = 0;
|
||||
foreach (FileInfo file in files)
|
||||
{
|
||||
file.Delete();
|
||||
++num;
|
||||
}
|
||||
|
||||
DebugLog("Wiped out " + num + " files from the cache directory.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Brings cache size to the 90% of the max size
|
||||
/// </summary>
|
||||
public void Prune()
|
||||
{
|
||||
string cacheDir = Client.Settings.ASSET_CACHE_DIR;
|
||||
if (!Directory.Exists(cacheDir))
|
||||
{
|
||||
return;
|
||||
}
|
||||
DirectoryInfo di = new DirectoryInfo(cacheDir);
|
||||
// We save file with UUID as file name, only count those
|
||||
FileInfo[] files = di.GetFiles("????????-????-????-????-????????????", SearchOption.TopDirectoryOnly);
|
||||
|
||||
long size = GetFileSize(files);
|
||||
|
||||
if (size > Client.Settings.ASSET_CACHE_MAX_SIZE)
|
||||
{
|
||||
Array.Sort(files, new SortFilesByAccesTimeHelper());
|
||||
long targetSize = (long)(Client.Settings.ASSET_CACHE_MAX_SIZE * 0.9);
|
||||
int num = 0;
|
||||
foreach (FileInfo file in files)
|
||||
{
|
||||
++num;
|
||||
size -= file.Length;
|
||||
file.Delete();
|
||||
if (size < targetSize)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
DebugLog(num + " files deleted from the cache, cache size now: " + NiceFileSize(size));
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugLog("Cache size is " + NiceFileSize(size) + ", file deletion not needed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously brings cache size to the 90% of the max size
|
||||
/// </summary>
|
||||
public void BeginPrune()
|
||||
{
|
||||
// Check if the background cache cleaning thread is active first
|
||||
if (cleanerThread != null && cleanerThread.IsAlive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
cleanerThread = new Thread(new ThreadStart(this.Prune));
|
||||
cleanerThread.IsBackground = true;
|
||||
cleanerThread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds up file sizes passes in a FileInfo array
|
||||
/// </summary>
|
||||
long GetFileSize(FileInfo[] files)
|
||||
{
|
||||
long ret = 0;
|
||||
foreach (FileInfo file in files)
|
||||
{
|
||||
ret += file.Length;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether caching is enabled
|
||||
/// </summary>
|
||||
private bool Operational()
|
||||
{
|
||||
return Client.Settings.USE_ASSET_CACHE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Periodically prune the cache
|
||||
/// </summary>
|
||||
private void cleanerTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
BeginPrune();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nicely formats file sizes
|
||||
/// </summary>
|
||||
/// <param name="byteCount">Byte size we want to output</param>
|
||||
/// <returns>String with humanly readable file size</returns>
|
||||
private string NiceFileSize(long byteCount)
|
||||
{
|
||||
string size = "0 Bytes";
|
||||
if (byteCount >= 1073741824)
|
||||
size = String.Format("{0:##.##}", byteCount / 1073741824) + " GB";
|
||||
else if (byteCount >= 1048576)
|
||||
size = String.Format("{0:##.##}", byteCount / 1048576) + " MB";
|
||||
else if (byteCount >= 1024)
|
||||
size = String.Format("{0:##.##}", byteCount / 1024) + " KB";
|
||||
else if (byteCount > 0 && byteCount < 1024)
|
||||
size = byteCount.ToString() + " Bytes";
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for sorting files by their last accessed time
|
||||
/// </summary>
|
||||
private class SortFilesByAccesTimeHelper : IComparer<FileInfo>
|
||||
{
|
||||
int IComparer<FileInfo>.Compare(FileInfo f1, FileInfo f2)
|
||||
{
|
||||
if (f1.LastAccessTime > f2.LastAccessTime)
|
||||
return 1;
|
||||
if (f1.LastAccessTime < f2.LastAccessTime)
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Constants for the archiving module
|
||||
/// </summary>
|
||||
public class ArchiveConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// The location of the archive control file
|
||||
/// </summary>
|
||||
public static readonly string CONTROL_FILE_PATH = "archive.xml";
|
||||
|
||||
/// <summary>
|
||||
/// Path for the assets held in an archive
|
||||
/// </summary>
|
||||
public static readonly string ASSETS_PATH = "assets/";
|
||||
|
||||
/// <summary>
|
||||
/// Path for the prims file
|
||||
/// </summary>
|
||||
public static readonly string OBJECTS_PATH = "objects/";
|
||||
|
||||
/// <summary>
|
||||
/// Path for terrains. Technically these may be assets, but I think it's quite nice to split them out.
|
||||
/// </summary>
|
||||
public static readonly string TERRAINS_PATH = "terrains/";
|
||||
|
||||
/// <summary>
|
||||
/// Path for region settings.
|
||||
/// </summary>
|
||||
public static readonly string SETTINGS_PATH = "settings/";
|
||||
|
||||
/// <value>
|
||||
/// Path for region settings.
|
||||
/// </value>
|
||||
public const string LANDDATA_PATH = "landdata/";
|
||||
|
||||
/// <summary>
|
||||
/// The character the separates the uuid from extension information in an archived asset filename
|
||||
/// </summary>
|
||||
public static readonly string ASSET_EXTENSION_SEPARATOR = "_";
|
||||
|
||||
/// <summary>
|
||||
/// Extensions used for asset types in the archive
|
||||
/// </summary>
|
||||
public static readonly IDictionary<AssetType, string> ASSET_TYPE_TO_EXTENSION = new Dictionary<AssetType, string>();
|
||||
public static readonly IDictionary<string, AssetType> EXTENSION_TO_ASSET_TYPE = new Dictionary<string, AssetType>();
|
||||
|
||||
static ArchiveConstants()
|
||||
{
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.Animation] = ASSET_EXTENSION_SEPARATOR + "animation.bvh";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.Bodypart] = ASSET_EXTENSION_SEPARATOR + "bodypart.txt";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.CallingCard] = ASSET_EXTENSION_SEPARATOR + "callingcard.txt";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.Clothing] = ASSET_EXTENSION_SEPARATOR + "clothing.txt";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.Folder] = ASSET_EXTENSION_SEPARATOR + "folder.txt"; // Not sure if we'll ever see this
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.Gesture] = ASSET_EXTENSION_SEPARATOR + "gesture.txt";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.ImageJPEG] = ASSET_EXTENSION_SEPARATOR + "image.jpg";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.ImageTGA] = ASSET_EXTENSION_SEPARATOR + "image.tga";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.Landmark] = ASSET_EXTENSION_SEPARATOR + "landmark.txt";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.LostAndFoundFolder] = ASSET_EXTENSION_SEPARATOR + "lostandfoundfolder.txt"; // Not sure if we'll ever see this
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.LSLBytecode] = ASSET_EXTENSION_SEPARATOR + "bytecode.lso";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.LSLText] = ASSET_EXTENSION_SEPARATOR + "script.lsl";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.Notecard] = ASSET_EXTENSION_SEPARATOR + "notecard.txt";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.Object] = ASSET_EXTENSION_SEPARATOR + "object.xml";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.RootFolder] = ASSET_EXTENSION_SEPARATOR + "rootfolder.txt"; // Not sure if we'll ever see this
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.Simstate] = ASSET_EXTENSION_SEPARATOR + "simstate.bin"; // Not sure if we'll ever see this
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.SnapshotFolder] = ASSET_EXTENSION_SEPARATOR + "snapshotfolder.txt"; // Not sure if we'll ever see this
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.Sound] = ASSET_EXTENSION_SEPARATOR + "sound.ogg";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.SoundWAV] = ASSET_EXTENSION_SEPARATOR + "sound.wav";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.Texture] = ASSET_EXTENSION_SEPARATOR + "texture.jp2";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.TextureTGA] = ASSET_EXTENSION_SEPARATOR + "texture.tga";
|
||||
ASSET_TYPE_TO_EXTENSION[AssetType.TrashFolder] = ASSET_EXTENSION_SEPARATOR + "trashfolder.txt"; // Not sure if we'll ever see this
|
||||
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "animation.bvh"] = AssetType.Animation;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "bodypart.txt"] = AssetType.Bodypart;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "callingcard.txt"] = AssetType.CallingCard;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "clothing.txt"] = AssetType.Clothing;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "folder.txt"] = AssetType.Folder;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "gesture.txt"] = AssetType.Gesture;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "image.jpg"] = AssetType.ImageJPEG;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "image.tga"] = AssetType.ImageTGA;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "landmark.txt"] = AssetType.Landmark;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "lostandfoundfolder.txt"] = AssetType.LostAndFoundFolder;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "bytecode.lso"] = AssetType.LSLBytecode;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "script.lsl"] = AssetType.LSLText;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "notecard.txt"] = AssetType.Notecard;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "object.xml"] = AssetType.Object;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "rootfolder.txt"] = AssetType.RootFolder;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "simstate.bin"] = AssetType.Simstate;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "snapshotfolder.txt"] = AssetType.SnapshotFolder;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "sound.ogg"] = AssetType.Sound;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "sound.wav"] = AssetType.SoundWAV;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "texture.jp2"] = AssetType.Texture;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "texture.tga"] = AssetType.TextureTGA;
|
||||
EXTENSION_TO_ASSET_TYPE[ASSET_EXTENSION_SEPARATOR + "trashfolder.txt"] = AssetType.TrashFolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Xml;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Archives assets
|
||||
/// </summary>
|
||||
public class AssetsArchiver
|
||||
{
|
||||
///// <value>
|
||||
///// Post a message to the log every x assets as a progress bar
|
||||
///// </value>
|
||||
//static int LOG_ASSET_LOAD_NOTIFICATION_INTERVAL = 50;
|
||||
|
||||
/// <summary>
|
||||
/// Archive assets
|
||||
/// </summary>
|
||||
protected IDictionary<UUID, Asset> m_assets;
|
||||
|
||||
public AssetsArchiver(IDictionary<UUID, Asset> assets)
|
||||
{
|
||||
m_assets = assets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Archive the assets given to this archiver to the given archive.
|
||||
/// </summary>
|
||||
/// <param name="archive"></param>
|
||||
public void Archive(TarArchiveWriter archive)
|
||||
{
|
||||
//WriteMetadata(archive);
|
||||
WriteData(archive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write an assets metadata file to the given archive
|
||||
/// </summary>
|
||||
/// <param name="archive"></param>
|
||||
protected void WriteMetadata(TarArchiveWriter archive)
|
||||
{
|
||||
StringWriter sw = new StringWriter();
|
||||
using (XmlTextWriter xtw = new XmlTextWriter(sw))
|
||||
{
|
||||
xtw.Formatting = Formatting.Indented;
|
||||
xtw.WriteStartDocument();
|
||||
|
||||
xtw.WriteStartElement("assets");
|
||||
|
||||
foreach (UUID uuid in m_assets.Keys)
|
||||
{
|
||||
Asset asset = m_assets[uuid];
|
||||
|
||||
if (asset != null)
|
||||
{
|
||||
xtw.WriteStartElement("asset");
|
||||
|
||||
string extension = string.Empty;
|
||||
|
||||
if (ArchiveConstants.ASSET_TYPE_TO_EXTENSION.ContainsKey(asset.AssetType))
|
||||
{
|
||||
extension = ArchiveConstants.ASSET_TYPE_TO_EXTENSION[asset.AssetType];
|
||||
}
|
||||
|
||||
xtw.WriteElementString("filename", uuid.ToString() + extension);
|
||||
|
||||
xtw.WriteElementString("name", uuid.ToString());
|
||||
xtw.WriteElementString("description", String.Empty);
|
||||
xtw.WriteElementString("asset-type", asset.AssetType.ToString());
|
||||
|
||||
xtw.WriteEndElement();
|
||||
}
|
||||
}
|
||||
|
||||
xtw.WriteEndElement();
|
||||
xtw.WriteEndDocument();
|
||||
archive.WriteFile("assets.xml", sw.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write asset data files to the given archive
|
||||
/// </summary>
|
||||
/// <param name="archive"></param>
|
||||
protected void WriteData(TarArchiveWriter archive)
|
||||
{
|
||||
// It appears that gtar, at least, doesn't need the intermediate directory entries in the tar
|
||||
//archive.AddDir("assets");
|
||||
|
||||
int assetsAdded = 0;
|
||||
|
||||
foreach (UUID uuid in m_assets.Keys)
|
||||
{
|
||||
Asset asset = m_assets[uuid];
|
||||
|
||||
string extension = string.Empty;
|
||||
|
||||
if (ArchiveConstants.ASSET_TYPE_TO_EXTENSION.ContainsKey(asset.AssetType))
|
||||
{
|
||||
extension = ArchiveConstants.ASSET_TYPE_TO_EXTENSION[asset.AssetType];
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log(String.Format(
|
||||
"Unrecognized asset type {0} with uuid {1}. This asset will be saved but not reloaded",
|
||||
asset.AssetType, asset.AssetID), Helpers.LogLevel.Warning);
|
||||
}
|
||||
|
||||
asset.Encode();
|
||||
|
||||
archive.WriteFile(
|
||||
ArchiveConstants.ASSETS_PATH + uuid.ToString() + extension,
|
||||
asset.AssetData);
|
||||
|
||||
assetsAdded++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,879 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Threading;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
public static class OarFile
|
||||
{
|
||||
public delegate void AssetLoadedCallback(Asset asset, long bytesRead, long totalBytes);
|
||||
public delegate void TerrainLoadedCallback(float[,] terrain, long bytesRead, long totalBytes);
|
||||
public delegate void SceneObjectLoadedCallback(AssetPrim linkset, long bytesRead, long totalBytes);
|
||||
public delegate void SettingsLoadedCallback(string regionName, RegionSettings settings);
|
||||
|
||||
#region Archive Loading
|
||||
|
||||
public static void UnpackageArchive(string filename, AssetLoadedCallback assetCallback, TerrainLoadedCallback terrainCallback,
|
||||
SceneObjectLoadedCallback objectCallback, SettingsLoadedCallback settingsCallback)
|
||||
{
|
||||
int successfulAssetRestores = 0;
|
||||
int failedAssetRestores = 0;
|
||||
|
||||
try
|
||||
{
|
||||
using (FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
using (GZipStream loadStream = new GZipStream(fileStream, CompressionMode.Decompress))
|
||||
{
|
||||
TarArchiveReader archive = new TarArchiveReader(loadStream);
|
||||
|
||||
string filePath;
|
||||
byte[] data;
|
||||
TarArchiveReader.TarEntryType entryType;
|
||||
|
||||
while ((data = archive.ReadEntry(out filePath, out entryType)) != null)
|
||||
{
|
||||
if (filePath.StartsWith(ArchiveConstants.OBJECTS_PATH))
|
||||
{
|
||||
// Deserialize the XML bytes
|
||||
if (objectCallback != null)
|
||||
LoadObjects(data, objectCallback, fileStream.Position, fileStream.Length);
|
||||
}
|
||||
else if (filePath.StartsWith(ArchiveConstants.ASSETS_PATH))
|
||||
{
|
||||
if (assetCallback != null)
|
||||
{
|
||||
if (LoadAsset(filePath, data, assetCallback, fileStream.Position, fileStream.Length))
|
||||
successfulAssetRestores++;
|
||||
else
|
||||
failedAssetRestores++;
|
||||
}
|
||||
}
|
||||
else if (filePath.StartsWith(ArchiveConstants.TERRAINS_PATH))
|
||||
{
|
||||
if (terrainCallback != null)
|
||||
LoadTerrain(filePath, data, terrainCallback, fileStream.Position, fileStream.Length);
|
||||
}
|
||||
else if (filePath.StartsWith(ArchiveConstants.SETTINGS_PATH))
|
||||
{
|
||||
if (settingsCallback != null)
|
||||
LoadRegionSettings(filePath, data, settingsCallback);
|
||||
}
|
||||
}
|
||||
|
||||
archive.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("[OarFile] Error loading OAR file: " + e.Message, Helpers.LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (failedAssetRestores > 0)
|
||||
Logger.Log(String.Format("[OarFile]: Failed to load {0} assets", failedAssetRestores), Helpers.LogLevel.Warning);
|
||||
}
|
||||
|
||||
private static bool LoadAsset(string assetPath, byte[] data, AssetLoadedCallback assetCallback, long bytesRead, long totalBytes)
|
||||
{
|
||||
// Right now we're nastily obtaining the UUID from the filename
|
||||
string filename = assetPath.Remove(0, ArchiveConstants.ASSETS_PATH.Length);
|
||||
int i = filename.LastIndexOf(ArchiveConstants.ASSET_EXTENSION_SEPARATOR);
|
||||
|
||||
if (i == -1)
|
||||
{
|
||||
Logger.Log(String.Format(
|
||||
"[OarFile]: Could not find extension information in asset path {0} since it's missing the separator {1}. Skipping",
|
||||
assetPath, ArchiveConstants.ASSET_EXTENSION_SEPARATOR), Helpers.LogLevel.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
string extension = filename.Substring(i);
|
||||
UUID uuid;
|
||||
UUID.TryParse(filename.Remove(filename.Length - extension.Length), out uuid);
|
||||
|
||||
if (ArchiveConstants.EXTENSION_TO_ASSET_TYPE.ContainsKey(extension))
|
||||
{
|
||||
AssetType assetType = ArchiveConstants.EXTENSION_TO_ASSET_TYPE[extension];
|
||||
Asset asset = null;
|
||||
|
||||
switch (assetType)
|
||||
{
|
||||
case AssetType.Animation:
|
||||
asset = new AssetAnimation(uuid, data);
|
||||
break;
|
||||
case AssetType.Bodypart:
|
||||
asset = new AssetBodypart(uuid, data);
|
||||
break;
|
||||
case AssetType.Clothing:
|
||||
asset = new AssetClothing(uuid, data);
|
||||
break;
|
||||
case AssetType.Gesture:
|
||||
asset = new AssetGesture(uuid, data);
|
||||
break;
|
||||
case AssetType.Landmark:
|
||||
asset = new AssetLandmark(uuid, data);
|
||||
break;
|
||||
case AssetType.LSLBytecode:
|
||||
asset = new AssetScriptBinary(uuid, data);
|
||||
break;
|
||||
case AssetType.LSLText:
|
||||
asset = new AssetScriptText(uuid, data);
|
||||
break;
|
||||
case AssetType.Notecard:
|
||||
asset = new AssetNotecard(uuid, data);
|
||||
break;
|
||||
case AssetType.Object:
|
||||
asset = new AssetPrim(uuid, data);
|
||||
break;
|
||||
case AssetType.Sound:
|
||||
asset = new AssetSound(uuid, data);
|
||||
break;
|
||||
case AssetType.Texture:
|
||||
asset = new AssetTexture(uuid, data);
|
||||
break;
|
||||
default:
|
||||
Logger.Log("[OarFile] Unhandled asset type " + assetType, Helpers.LogLevel.Error);
|
||||
break;
|
||||
}
|
||||
|
||||
if (asset != null)
|
||||
{
|
||||
assetCallback(asset, bytesRead, totalBytes);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Log("[OarFile] Failed to load asset", Helpers.LogLevel.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool LoadRegionSettings(string filePath, byte[] data, SettingsLoadedCallback settingsCallback)
|
||||
{
|
||||
RegionSettings settings = null;
|
||||
bool loaded = false;
|
||||
|
||||
try
|
||||
{
|
||||
using (MemoryStream stream = new MemoryStream(data))
|
||||
settings = RegionSettings.FromStream(stream);
|
||||
loaded = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("[OarFile] Failed to parse region settings file " + filePath + ": " + ex.Message, Helpers.LogLevel.Warning);
|
||||
}
|
||||
|
||||
// Parse the region name out of the filename
|
||||
string regionName = Path.GetFileNameWithoutExtension(filePath);
|
||||
|
||||
if (loaded)
|
||||
settingsCallback(regionName, settings);
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
private static bool LoadTerrain(string filePath, byte[] data, TerrainLoadedCallback terrainCallback, long bytesRead, long totalBytes)
|
||||
{
|
||||
float[,] terrain = new float[256, 256];
|
||||
bool loaded = false;
|
||||
|
||||
switch (Path.GetExtension(filePath))
|
||||
{
|
||||
case ".r32":
|
||||
case ".f32":
|
||||
// RAW32
|
||||
if (data.Length == 256 * 256 * 4)
|
||||
{
|
||||
int pos = 0;
|
||||
for (int y = 0; y < 256; y++)
|
||||
{
|
||||
for (int x = 0; x < 256; x++)
|
||||
{
|
||||
terrain[y, x] = Utils.Clamp(Utils.BytesToFloat(data, pos), 0.0f, 255.0f);
|
||||
pos += 4;
|
||||
}
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("[OarFile] RAW32 terrain file " + filePath + " has the wrong number of bytes: " + data.Length,
|
||||
Helpers.LogLevel.Warning);
|
||||
}
|
||||
break;
|
||||
case ".ter":
|
||||
// Terragen
|
||||
case ".raw":
|
||||
// LLRAW
|
||||
case ".jpg":
|
||||
case ".jpeg":
|
||||
// JPG
|
||||
case ".bmp":
|
||||
// BMP
|
||||
case ".png":
|
||||
// PNG
|
||||
case ".gif":
|
||||
// GIF
|
||||
case ".tif":
|
||||
case ".tiff":
|
||||
// TIFF
|
||||
default:
|
||||
Logger.Log("[OarFile] Unrecognized terrain format in " + filePath, Helpers.LogLevel.Warning);
|
||||
break;
|
||||
}
|
||||
|
||||
if (loaded)
|
||||
terrainCallback(terrain, bytesRead, totalBytes);
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public static void LoadObjects(byte[] objectData, SceneObjectLoadedCallback objectCallback, long bytesRead, long totalBytes)
|
||||
{
|
||||
XmlDocument doc = new XmlDocument();
|
||||
|
||||
using (XmlTextReader reader = new XmlTextReader(new MemoryStream(objectData)))
|
||||
{
|
||||
reader.WhitespaceHandling = WhitespaceHandling.None;
|
||||
doc.Load(reader);
|
||||
}
|
||||
|
||||
XmlNode rootNode = doc.FirstChild;
|
||||
|
||||
if (rootNode.LocalName.Equals("scene"))
|
||||
{
|
||||
foreach (XmlNode node in rootNode.ChildNodes)
|
||||
{
|
||||
AssetPrim linkset = new AssetPrim(node.OuterXml);
|
||||
if (linkset != null)
|
||||
objectCallback(linkset, bytesRead, totalBytes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AssetPrim linkset = new AssetPrim(rootNode.OuterXml);
|
||||
if (linkset != null)
|
||||
objectCallback(linkset, bytesRead, totalBytes);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Archive Loading
|
||||
|
||||
#region Archive Saving
|
||||
|
||||
public static void PackageArchive(string directoryName, string filename)
|
||||
{
|
||||
const string ARCHIVE_XML = "<?xml version=\"1.0\" encoding=\"utf-16\"?>\n<archive major_version=\"0\" minor_version=\"1\" />";
|
||||
|
||||
TarArchiveWriter archive = new TarArchiveWriter(new GZipStream(new FileStream(filename, FileMode.Create), CompressionMode.Compress));
|
||||
|
||||
// Create the archive.xml file
|
||||
archive.WriteFile("archive.xml", ARCHIVE_XML);
|
||||
|
||||
// Add the assets
|
||||
string[] files = Directory.GetFiles(directoryName + "/" + ArchiveConstants.ASSETS_PATH);
|
||||
foreach (string file in files)
|
||||
archive.WriteFile(ArchiveConstants.ASSETS_PATH + Path.GetFileName(file), File.ReadAllBytes(file));
|
||||
|
||||
// Add the objects
|
||||
files = Directory.GetFiles(directoryName + "/" + ArchiveConstants.OBJECTS_PATH);
|
||||
foreach (string file in files)
|
||||
archive.WriteFile(ArchiveConstants.OBJECTS_PATH + Path.GetFileName(file), File.ReadAllBytes(file));
|
||||
|
||||
// Add the terrain(s)
|
||||
files = Directory.GetFiles(directoryName + "/" + ArchiveConstants.TERRAINS_PATH);
|
||||
foreach (string file in files)
|
||||
archive.WriteFile(ArchiveConstants.TERRAINS_PATH + Path.GetFileName(file), File.ReadAllBytes(file));
|
||||
|
||||
// Add the parcels(s)
|
||||
files = Directory.GetFiles(directoryName + "/" + ArchiveConstants.LANDDATA_PATH);
|
||||
foreach (string file in files)
|
||||
archive.WriteFile(ArchiveConstants.LANDDATA_PATH + Path.GetFileName(file), File.ReadAllBytes(file));
|
||||
|
||||
// Add the setting(s)
|
||||
files = Directory.GetFiles(directoryName + "/" + ArchiveConstants.SETTINGS_PATH);
|
||||
foreach (string file in files)
|
||||
archive.WriteFile(ArchiveConstants.SETTINGS_PATH + Path.GetFileName(file), File.ReadAllBytes(file));
|
||||
|
||||
archive.Close();
|
||||
}
|
||||
|
||||
public static void SaveTerrain(Simulator sim, string terrainPath)
|
||||
{
|
||||
if (Directory.Exists(terrainPath))
|
||||
Directory.Delete(terrainPath, true);
|
||||
Thread.Sleep(100);
|
||||
Directory.CreateDirectory(terrainPath);
|
||||
Thread.Sleep(100);
|
||||
FileInfo file = new FileInfo(Path.Combine(terrainPath, sim.Name + ".r32"));
|
||||
FileStream s = file.Open(FileMode.Create, FileAccess.Write);
|
||||
SaveTerrainStream(s, sim);
|
||||
|
||||
s.Close();
|
||||
}
|
||||
|
||||
private static void SaveTerrainStream(Stream s, Simulator sim)
|
||||
{
|
||||
BinaryWriter bs = new BinaryWriter(s);
|
||||
|
||||
int y;
|
||||
for (y = 0; y < 256; y++)
|
||||
{
|
||||
int x;
|
||||
for (x = 0; x < 256; x++)
|
||||
{
|
||||
float height;
|
||||
sim.TerrainHeightAtPoint(x, y, out height);
|
||||
bs.Write(height);
|
||||
}
|
||||
}
|
||||
|
||||
bs.Close();
|
||||
}
|
||||
|
||||
public static void SaveParcels(Simulator sim, string parcelPath)
|
||||
{
|
||||
if (Directory.Exists(parcelPath))
|
||||
Directory.Delete(parcelPath, true);
|
||||
Thread.Sleep(100);
|
||||
Directory.CreateDirectory(parcelPath);
|
||||
Thread.Sleep(100);
|
||||
sim.Parcels.ForEach((Parcel parcel) =>
|
||||
{
|
||||
UUID globalID = UUID.Random();
|
||||
SerializeParcel(parcel, globalID, Path.Combine(parcelPath, globalID + ".xml"));
|
||||
});
|
||||
}
|
||||
|
||||
private static void SerializeParcel(Parcel parcel, UUID globalID, string filename)
|
||||
{
|
||||
StringWriter sw = new StringWriter();
|
||||
XmlTextWriter xtw = new XmlTextWriter(sw) { Formatting = Formatting.Indented };
|
||||
|
||||
xtw.WriteStartDocument();
|
||||
xtw.WriteStartElement("LandData");
|
||||
|
||||
xtw.WriteElementString("Area", Convert.ToString(parcel.Area));
|
||||
xtw.WriteElementString("AuctionID", Convert.ToString(parcel.AuctionID));
|
||||
xtw.WriteElementString("AuthBuyerID", parcel.AuthBuyerID.ToString());
|
||||
xtw.WriteElementString("Category", Convert.ToString((sbyte)parcel.Category));
|
||||
TimeSpan t = parcel.ClaimDate.ToUniversalTime() - Utils.Epoch;
|
||||
xtw.WriteElementString("ClaimDate", Convert.ToString((int)t.TotalSeconds));
|
||||
xtw.WriteElementString("ClaimPrice", Convert.ToString(parcel.ClaimPrice));
|
||||
xtw.WriteElementString("GlobalID", globalID.ToString());
|
||||
xtw.WriteElementString("GroupID", parcel.GroupID.ToString());
|
||||
xtw.WriteElementString("IsGroupOwned", Convert.ToString(parcel.IsGroupOwned));
|
||||
xtw.WriteElementString("Bitmap", Convert.ToBase64String(parcel.Bitmap));
|
||||
xtw.WriteElementString("Description", parcel.Desc);
|
||||
xtw.WriteElementString("Flags", Convert.ToString((uint)parcel.Flags));
|
||||
xtw.WriteElementString("LandingType", Convert.ToString((byte)parcel.Landing));
|
||||
xtw.WriteElementString("Name", parcel.Name);
|
||||
xtw.WriteElementString("Status", Convert.ToString((sbyte)parcel.Status));
|
||||
xtw.WriteElementString("LocalID", parcel.LocalID.ToString());
|
||||
xtw.WriteElementString("MediaAutoScale", Convert.ToString(parcel.Media.MediaAutoScale ? 1 : 0));
|
||||
xtw.WriteElementString("MediaID", parcel.Media.MediaID.ToString());
|
||||
xtw.WriteElementString("MediaURL", parcel.Media.MediaURL);
|
||||
xtw.WriteElementString("MusicURL", parcel.MusicURL);
|
||||
xtw.WriteElementString("OwnerID", parcel.OwnerID.ToString());
|
||||
|
||||
xtw.WriteStartElement("ParcelAccessList");
|
||||
foreach (ParcelManager.ParcelAccessEntry pal in parcel.AccessBlackList)
|
||||
{
|
||||
xtw.WriteStartElement("ParcelAccessEntry");
|
||||
xtw.WriteElementString("AgentID", pal.AgentID.ToString());
|
||||
xtw.WriteElementString("Time", pal.Time.ToString("s"));
|
||||
xtw.WriteElementString("AccessList", Convert.ToString((uint)pal.Flags));
|
||||
xtw.WriteEndElement();
|
||||
}
|
||||
foreach (ParcelManager.ParcelAccessEntry pal in parcel.AccessWhiteList)
|
||||
{
|
||||
xtw.WriteStartElement("ParcelAccessEntry");
|
||||
xtw.WriteElementString("AgentID", pal.AgentID.ToString());
|
||||
xtw.WriteElementString("Time", pal.Time.ToString("s"));
|
||||
xtw.WriteElementString("AccessList", Convert.ToString((uint)pal.Flags));
|
||||
xtw.WriteEndElement();
|
||||
}
|
||||
xtw.WriteEndElement();
|
||||
|
||||
xtw.WriteElementString("PassHours", Convert.ToString(parcel.PassHours));
|
||||
xtw.WriteElementString("PassPrice", Convert.ToString(parcel.PassPrice));
|
||||
xtw.WriteElementString("SalePrice", Convert.ToString(parcel.SalePrice));
|
||||
xtw.WriteElementString("SnapshotID", parcel.SnapshotID.ToString());
|
||||
xtw.WriteElementString("UserLocation", parcel.UserLocation.ToString());
|
||||
xtw.WriteElementString("UserLookAt", parcel.UserLookAt.ToString());
|
||||
xtw.WriteElementString("Dwell", "0");
|
||||
xtw.WriteElementString("OtherCleanTime", Convert.ToString(parcel.OtherCleanTime));
|
||||
|
||||
xtw.WriteEndElement();
|
||||
|
||||
xtw.Close();
|
||||
sw.Close();
|
||||
File.WriteAllText(filename, sw.ToString());
|
||||
}
|
||||
|
||||
public static void SaveRegionSettings(Simulator sim, string settingsPath)
|
||||
{
|
||||
if (Directory.Exists(settingsPath))
|
||||
Directory.Delete(settingsPath, true);
|
||||
Thread.Sleep(100);
|
||||
Directory.CreateDirectory(settingsPath);
|
||||
Thread.Sleep(100);
|
||||
|
||||
RegionSettings settings = new RegionSettings();
|
||||
//settings.AgentLimit;
|
||||
settings.AllowDamage = (sim.Flags & RegionFlags.AllowDamage) == RegionFlags.AllowDamage;
|
||||
//settings.AllowLandJoinDivide;
|
||||
settings.AllowLandResell = (sim.Flags & RegionFlags.BlockLandResell) != RegionFlags.BlockLandResell;
|
||||
settings.BlockFly = (sim.Flags & RegionFlags.NoFly) == RegionFlags.NoFly;
|
||||
settings.BlockLandShowInSearch = (sim.Flags & RegionFlags.BlockParcelSearch) == RegionFlags.BlockParcelSearch;
|
||||
settings.BlockTerraform = (sim.Flags & RegionFlags.BlockTerraform) == RegionFlags.BlockTerraform;
|
||||
settings.DisableCollisions = (sim.Flags & RegionFlags.SkipCollisions) == RegionFlags.SkipCollisions;
|
||||
settings.DisablePhysics = (sim.Flags & RegionFlags.SkipPhysics) == RegionFlags.SkipPhysics;
|
||||
settings.DisableScripts = (sim.Flags & RegionFlags.SkipScripts) == RegionFlags.SkipScripts;
|
||||
settings.FixedSun = (sim.Flags & RegionFlags.SunFixed) == RegionFlags.SunFixed;
|
||||
settings.MaturityRating = (int)(sim.Access & SimAccess.Mature & SimAccess.Adult & SimAccess.PG);
|
||||
//settings.ObjectBonus;
|
||||
settings.RestrictPushing = (sim.Flags & RegionFlags.RestrictPushObject) == RegionFlags.RestrictPushObject;
|
||||
settings.TerrainDetail0 = sim.TerrainDetail0;
|
||||
settings.TerrainDetail1 = sim.TerrainDetail1;
|
||||
settings.TerrainDetail2 = sim.TerrainDetail2;
|
||||
settings.TerrainDetail3 = sim.TerrainDetail3;
|
||||
settings.TerrainHeightRange00 = sim.TerrainHeightRange00;
|
||||
settings.TerrainHeightRange01 = sim.TerrainHeightRange01;
|
||||
settings.TerrainHeightRange10 = sim.TerrainHeightRange10;
|
||||
settings.TerrainHeightRange11 = sim.TerrainHeightRange11;
|
||||
settings.TerrainStartHeight00 = sim.TerrainStartHeight00;
|
||||
settings.TerrainStartHeight01 = sim.TerrainStartHeight01;
|
||||
settings.TerrainStartHeight10 = sim.TerrainStartHeight10;
|
||||
settings.TerrainStartHeight11 = sim.TerrainStartHeight11;
|
||||
//settings.UseEstateSun;
|
||||
settings.WaterHeight = sim.WaterHeight;
|
||||
|
||||
settings.ToXML(Path.Combine(settingsPath, sim.Name + ".xml"));
|
||||
}
|
||||
|
||||
public static void SavePrims(AssetManager manager, IList<AssetPrim> prims, string primsPath, string assetsPath)
|
||||
{
|
||||
Dictionary<UUID, UUID> textureList = new Dictionary<UUID, UUID>();
|
||||
|
||||
// Delete all of the old linkset files
|
||||
try { Directory.Delete(primsPath, true); }
|
||||
catch (Exception) { }
|
||||
|
||||
Thread.Sleep(100);
|
||||
// Create a new folder for the linkset files
|
||||
try { Directory.CreateDirectory(primsPath); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Failed saving prims: " + ex.Message, Helpers.LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
Thread.Sleep(100);
|
||||
try
|
||||
{
|
||||
foreach (AssetPrim assetPrim in prims)
|
||||
{
|
||||
SavePrim(assetPrim, Path.Combine(primsPath, "Primitive_" + assetPrim.Parent.ID + ".xml"));
|
||||
|
||||
CollectTextures(assetPrim.Parent, textureList);
|
||||
if (assetPrim.Children != null)
|
||||
{
|
||||
foreach (PrimObject child in assetPrim.Children)
|
||||
CollectTextures(child, textureList);
|
||||
}
|
||||
}
|
||||
|
||||
SaveAssets(manager, AssetType.Texture, new List<UUID>(textureList.Keys), assetsPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
static void CollectTextures(PrimObject prim, Dictionary<UUID, UUID> textureList)
|
||||
{
|
||||
if (prim.Textures != null)
|
||||
{
|
||||
// Add all of the textures on this prim to the save list
|
||||
if (prim.Textures.DefaultTexture != null)
|
||||
textureList[prim.Textures.DefaultTexture.TextureID] = prim.Textures.DefaultTexture.TextureID;
|
||||
|
||||
if (prim.Textures.FaceTextures != null)
|
||||
{
|
||||
for (int i = 0; i < prim.Textures.FaceTextures.Length; i++)
|
||||
{
|
||||
Primitive.TextureEntryFace face = prim.Textures.FaceTextures[i];
|
||||
if (face != null)
|
||||
textureList[face.TextureID] = face.TextureID;
|
||||
}
|
||||
}
|
||||
if(prim.Sculpt != null && prim.Sculpt.Texture != UUID.Zero)
|
||||
textureList[prim.Sculpt.Texture] = prim.Sculpt.Texture;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearAssetFolder(string assetsPath)
|
||||
{
|
||||
// Delete the assets folder
|
||||
try { Directory.Delete(assetsPath, true); }
|
||||
catch (Exception) { }
|
||||
Thread.Sleep(100);
|
||||
|
||||
// Create a new assets folder
|
||||
try { Directory.CreateDirectory(assetsPath); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Failed saving assets: " + ex.Message, Helpers.LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
public static void SaveAssets(AssetManager assetManager, AssetType assetType, IList<UUID> assets, string assetsPath)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
List<UUID> remainingTextures = new List<UUID>(assets);
|
||||
AutoResetEvent AllPropertiesReceived = new AutoResetEvent(false);
|
||||
for (int i = 0; i < assets.Count; i++)
|
||||
{
|
||||
UUID texture = assets[i];
|
||||
if(assetType == AssetType.Texture)
|
||||
{
|
||||
assetManager.RequestImage(texture, (state, assetTexture) =>
|
||||
{
|
||||
string extension = string.Empty;
|
||||
|
||||
if (assetTexture == null)
|
||||
{
|
||||
Console.WriteLine("Missing asset " + texture);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ArchiveConstants.ASSET_TYPE_TO_EXTENSION.ContainsKey(assetType))
|
||||
extension = ArchiveConstants.ASSET_TYPE_TO_EXTENSION[assetType];
|
||||
|
||||
File.WriteAllBytes(Path.Combine(assetsPath, texture.ToString() + extension), assetTexture.AssetData);
|
||||
remainingTextures.Remove(assetTexture.AssetID);
|
||||
if (remainingTextures.Count == 0)
|
||||
AllPropertiesReceived.Set();
|
||||
++count;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
assetManager.RequestAsset(texture, assetType, false, (transfer, asset) =>
|
||||
{
|
||||
string extension = string.Empty;
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
Console.WriteLine("Missing asset " + texture);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ArchiveConstants.ASSET_TYPE_TO_EXTENSION.ContainsKey(assetType))
|
||||
extension = ArchiveConstants.ASSET_TYPE_TO_EXTENSION[assetType];
|
||||
|
||||
File.WriteAllBytes(Path.Combine(assetsPath, texture.ToString() + extension), asset.AssetData);
|
||||
remainingTextures.Remove(asset.AssetID);
|
||||
if (remainingTextures.Count == 0)
|
||||
AllPropertiesReceived.Set();
|
||||
++count;
|
||||
});
|
||||
}
|
||||
|
||||
Thread.Sleep(200);
|
||||
if (i % 5 == 0)
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
AllPropertiesReceived.WaitOne(5000 + 350 * assets.Count);
|
||||
|
||||
Logger.Log("Copied " + count + " textures to the asset archive folder", Helpers.LogLevel.Info);
|
||||
}
|
||||
|
||||
public static void SaveSimAssets(AssetManager assetManager, AssetType assetType, UUID assetID, UUID itemID, UUID primID, string assetsPath)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
AutoResetEvent AllPropertiesReceived = new AutoResetEvent(false);
|
||||
assetManager.RequestAsset(assetID, itemID, primID, assetType, false, SourceType.SimInventoryItem, UUID.Random(), (transfer, asset) =>
|
||||
{
|
||||
string extension = string.Empty;
|
||||
|
||||
if (ArchiveConstants.ASSET_TYPE_TO_EXTENSION.ContainsKey(assetType))
|
||||
extension = ArchiveConstants.ASSET_TYPE_TO_EXTENSION[assetType];
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
AllPropertiesReceived.Set();
|
||||
return;
|
||||
}
|
||||
File.WriteAllBytes(Path.Combine(assetsPath, assetID.ToString() + extension), asset.AssetData);
|
||||
++count;
|
||||
AllPropertiesReceived.Set();
|
||||
});
|
||||
AllPropertiesReceived.WaitOne(5000);
|
||||
|
||||
Logger.Log("Copied " + count + " textures to the asset archive folder", Helpers.LogLevel.Info);
|
||||
}
|
||||
|
||||
static void SavePrim(AssetPrim prim, string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (StreamWriter stream = new StreamWriter(filename))
|
||||
{
|
||||
XmlTextWriter writer = new XmlTextWriter(stream);
|
||||
writer.Formatting = Formatting.Indented;
|
||||
writer.Indentation = 4;
|
||||
writer.IndentChar = ' ';
|
||||
SOGToXml2(writer, prim);
|
||||
writer.Flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Failed saving linkset: " + ex.Message, Helpers.LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SOGToXml2(XmlTextWriter writer, AssetPrim prim)
|
||||
{
|
||||
writer.WriteStartElement(String.Empty, "SceneObjectGroup", String.Empty);
|
||||
SOPToXml(writer, prim.Parent, null);
|
||||
writer.WriteStartElement(String.Empty, "OtherParts", String.Empty);
|
||||
|
||||
foreach (PrimObject child in prim.Children)
|
||||
SOPToXml(writer, child, prim.Parent);
|
||||
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
static void SOPToXml(XmlTextWriter writer, PrimObject prim, PrimObject parent)
|
||||
{
|
||||
writer.WriteStartElement("SceneObjectPart");
|
||||
writer.WriteAttributeString("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
|
||||
writer.WriteAttributeString("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
|
||||
|
||||
WriteUUID(writer, "CreatorID", prim.CreatorID);
|
||||
WriteUUID(writer, "FolderID", prim.FolderID);
|
||||
writer.WriteElementString("InventorySerial", (prim.Inventory != null) ? prim.Inventory.Serial.ToString() : "0");
|
||||
|
||||
// FIXME: Task inventory
|
||||
writer.WriteStartElement("TaskInventory");
|
||||
if (prim.Inventory != null)
|
||||
{
|
||||
foreach (PrimObject.InventoryBlock.ItemBlock item in prim.Inventory.Items)
|
||||
{
|
||||
writer.WriteStartElement("", "TaskInventoryItem", "");
|
||||
|
||||
WriteUUID(writer, "AssetID", item.AssetID);
|
||||
writer.WriteElementString("BasePermissions", item.PermsBase.ToString());
|
||||
writer.WriteElementString("CreationDate", (item.CreationDate.ToUniversalTime() - Utils.Epoch).TotalSeconds.ToString());
|
||||
WriteUUID(writer, "CreatorID", item.CreatorID);
|
||||
writer.WriteElementString("Description", item.Description);
|
||||
writer.WriteElementString("EveryonePermissions", item.PermsEveryone.ToString());
|
||||
writer.WriteElementString("Flags", item.Flags.ToString());
|
||||
WriteUUID(writer, "GroupID", item.GroupID);
|
||||
writer.WriteElementString("GroupPermissions", item.PermsGroup.ToString());
|
||||
writer.WriteElementString("InvType", ((int)item.InvType).ToString());
|
||||
WriteUUID(writer, "ItemID", item.ID);
|
||||
WriteUUID(writer, "OldItemID", UUID.Zero);
|
||||
WriteUUID(writer, "LastOwnerID", item.LastOwnerID);
|
||||
writer.WriteElementString("Name", item.Name);
|
||||
writer.WriteElementString("NextPermissions", item.PermsNextOwner.ToString());
|
||||
WriteUUID(writer, "OwnerID", item.OwnerID);
|
||||
writer.WriteElementString("CurrentPermissions", item.PermsOwner.ToString());
|
||||
WriteUUID(writer, "ParentID", prim.ID);
|
||||
WriteUUID(writer, "ParentPartID", prim.ID);
|
||||
WriteUUID(writer, "PermsGranter", item.PermsGranterID);
|
||||
writer.WriteElementString("PermsMask", "0");
|
||||
writer.WriteElementString("Type", ((int)item.Type).ToString());
|
||||
writer.WriteElementString("OwnerChanged", "false");
|
||||
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
|
||||
PrimFlags flags = PrimFlags.None;
|
||||
if (prim.UsePhysics) flags |= PrimFlags.Physics;
|
||||
if (prim.Phantom) flags |= PrimFlags.Phantom;
|
||||
if (prim.DieAtEdge) flags |= PrimFlags.DieAtEdge;
|
||||
if (prim.ReturnAtEdge) flags |= PrimFlags.ReturnAtEdge;
|
||||
if (prim.Temporary) flags |= PrimFlags.Temporary;
|
||||
if (prim.Sandbox) flags |= PrimFlags.Sandbox;
|
||||
writer.WriteElementString("ObjectFlags", ((int)flags).ToString());
|
||||
|
||||
WriteUUID(writer, "UUID", prim.ID);
|
||||
writer.WriteElementString("LocalId", prim.LocalID.ToString());
|
||||
writer.WriteElementString("Name", prim.Name);
|
||||
writer.WriteElementString("Material", ((int)prim.Material).ToString());
|
||||
writer.WriteElementString("RegionHandle", prim.RegionHandle.ToString());
|
||||
writer.WriteElementString("ScriptAccessPin", prim.RemoteScriptAccessPIN.ToString());
|
||||
|
||||
Vector3 groupPosition;
|
||||
if (parent == null)
|
||||
groupPosition = prim.Position;
|
||||
else
|
||||
groupPosition = parent.Position;
|
||||
|
||||
WriteVector(writer, "GroupPosition", groupPosition);
|
||||
if (prim.ParentID == 0)
|
||||
WriteVector(writer, "OffsetPosition", Vector3.Zero);
|
||||
else
|
||||
WriteVector(writer, "OffsetPosition", prim.Position);
|
||||
WriteQuaternion(writer, "RotationOffset", prim.Rotation);
|
||||
WriteVector(writer, "Velocity", prim.Velocity);
|
||||
WriteVector(writer, "RotationalVelocity", Vector3.Zero);
|
||||
WriteVector(writer, "AngularVelocity", prim.AngularVelocity);
|
||||
WriteVector(writer, "Acceleration", prim.Acceleration);
|
||||
writer.WriteElementString("Description", prim.Description);
|
||||
writer.WriteStartElement("Color");
|
||||
writer.WriteElementString("R", prim.TextColor.R.ToString(Utils.EnUsCulture));
|
||||
writer.WriteElementString("G", prim.TextColor.G.ToString(Utils.EnUsCulture));
|
||||
writer.WriteElementString("B", prim.TextColor.B.ToString(Utils.EnUsCulture));
|
||||
writer.WriteElementString("A", prim.TextColor.G.ToString(Utils.EnUsCulture));
|
||||
writer.WriteEndElement();
|
||||
writer.WriteElementString("Text", prim.Text);
|
||||
writer.WriteElementString("SitName", prim.SitName);
|
||||
writer.WriteElementString("TouchName", prim.TouchName);
|
||||
|
||||
writer.WriteElementString("LinkNum", prim.LinkNumber.ToString());
|
||||
writer.WriteElementString("ClickAction", prim.ClickAction.ToString());
|
||||
writer.WriteStartElement("Shape");
|
||||
|
||||
writer.WriteElementString("PathBegin", Primitive.PackBeginCut(prim.Shape.PathBegin).ToString());
|
||||
writer.WriteElementString("PathCurve", prim.Shape.PathCurve.ToString());
|
||||
writer.WriteElementString("PathEnd", Primitive.PackEndCut(prim.Shape.PathEnd).ToString());
|
||||
writer.WriteElementString("PathRadiusOffset", Primitive.PackPathTwist(prim.Shape.PathRadiusOffset).ToString());
|
||||
writer.WriteElementString("PathRevolutions", Primitive.PackPathRevolutions(prim.Shape.PathRevolutions).ToString());
|
||||
writer.WriteElementString("PathScaleX", Primitive.PackPathScale(prim.Shape.PathScaleX).ToString());
|
||||
writer.WriteElementString("PathScaleY", Primitive.PackPathScale(prim.Shape.PathScaleY).ToString());
|
||||
writer.WriteElementString("PathShearX", ((byte)Primitive.PackPathShear(prim.Shape.PathShearX)).ToString());
|
||||
writer.WriteElementString("PathShearY", ((byte)Primitive.PackPathShear(prim.Shape.PathShearY)).ToString());
|
||||
writer.WriteElementString("PathSkew", Primitive.PackPathTwist(prim.Shape.PathSkew).ToString());
|
||||
writer.WriteElementString("PathTaperX", Primitive.PackPathTaper(prim.Shape.PathTaperX).ToString());
|
||||
writer.WriteElementString("PathTaperY", Primitive.PackPathTaper(prim.Shape.PathTaperY).ToString());
|
||||
writer.WriteElementString("PathTwist", Primitive.PackPathTwist(prim.Shape.PathTwist).ToString());
|
||||
writer.WriteElementString("PathTwistBegin", Primitive.PackPathTwist(prim.Shape.PathTwistBegin).ToString());
|
||||
writer.WriteElementString("PCode", prim.PCode.ToString());
|
||||
writer.WriteElementString("ProfileBegin", Primitive.PackBeginCut(prim.Shape.ProfileBegin).ToString());
|
||||
writer.WriteElementString("ProfileEnd", Primitive.PackEndCut(prim.Shape.ProfileEnd).ToString());
|
||||
writer.WriteElementString("ProfileHollow", Primitive.PackProfileHollow(prim.Shape.ProfileHollow).ToString());
|
||||
WriteVector(writer, "Scale", prim.Scale);
|
||||
writer.WriteElementString("State", prim.State.ToString());
|
||||
|
||||
AssetPrim.ProfileShape shape = (AssetPrim.ProfileShape)(prim.Shape.ProfileCurve & 0x0F);
|
||||
HoleType hole = (HoleType)(prim.Shape.ProfileCurve & 0xF0);
|
||||
writer.WriteElementString("ProfileShape", shape.ToString());
|
||||
writer.WriteElementString("HollowShape", hole.ToString());
|
||||
writer.WriteElementString("ProfileCurve", prim.Shape.ProfileCurve.ToString());
|
||||
|
||||
writer.WriteStartElement("TextureEntry");
|
||||
|
||||
byte[] te;
|
||||
if (prim.Textures != null)
|
||||
te = prim.Textures.GetBytes();
|
||||
else
|
||||
te = Utils.EmptyBytes;
|
||||
|
||||
writer.WriteBase64(te, 0, te.Length);
|
||||
writer.WriteEndElement();
|
||||
|
||||
// FIXME: ExtraParams
|
||||
writer.WriteStartElement("ExtraParams"); writer.WriteEndElement();
|
||||
|
||||
writer.WriteEndElement();
|
||||
|
||||
WriteVector(writer, "Scale", prim.Scale);
|
||||
writer.WriteElementString("UpdateFlag", "0");
|
||||
WriteVector(writer, "SitTargetOrientation", Vector3.UnitZ); // TODO: Is this really a vector and not a quaternion?
|
||||
WriteVector(writer, "SitTargetPosition", prim.SitOffset);
|
||||
WriteVector(writer, "SitTargetPositionLL", prim.SitOffset);
|
||||
WriteQuaternion(writer, "SitTargetOrientationLL", prim.SitRotation);
|
||||
writer.WriteElementString("ParentID", prim.ParentID.ToString());
|
||||
writer.WriteElementString("CreationDate", ((int)Utils.DateTimeToUnixTime(prim.CreationDate)).ToString());
|
||||
writer.WriteElementString("Category", "0");
|
||||
writer.WriteElementString("SalePrice", prim.SalePrice.ToString());
|
||||
writer.WriteElementString("ObjectSaleType", ((int)prim.SaleType).ToString());
|
||||
writer.WriteElementString("OwnershipCost", "0");
|
||||
WriteUUID(writer, "GroupID", prim.GroupID);
|
||||
WriteUUID(writer, "OwnerID", prim.OwnerID);
|
||||
WriteUUID(writer, "LastOwnerID", prim.LastOwnerID);
|
||||
writer.WriteElementString("BaseMask", ((uint)PermissionMask.All).ToString());
|
||||
writer.WriteElementString("OwnerMask", ((uint)PermissionMask.All).ToString());
|
||||
writer.WriteElementString("GroupMask", ((uint)PermissionMask.All).ToString());
|
||||
writer.WriteElementString("EveryoneMask", ((uint)PermissionMask.All).ToString());
|
||||
writer.WriteElementString("NextOwnerMask", ((uint)PermissionMask.All).ToString());
|
||||
writer.WriteElementString("Flags", "None");
|
||||
WriteUUID(writer, "SitTargetAvatar", UUID.Zero);
|
||||
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
static void WriteUUID(XmlTextWriter writer, string name, UUID id)
|
||||
{
|
||||
writer.WriteStartElement(name);
|
||||
writer.WriteElementString("UUID", id.ToString());
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
static void WriteVector(XmlTextWriter writer, string name, Vector3 vec)
|
||||
{
|
||||
writer.WriteStartElement(name);
|
||||
writer.WriteElementString("X", vec.X.ToString(Utils.EnUsCulture));
|
||||
writer.WriteElementString("Y", vec.Y.ToString(Utils.EnUsCulture));
|
||||
writer.WriteElementString("Z", vec.Z.ToString(Utils.EnUsCulture));
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
static void WriteQuaternion(XmlTextWriter writer, string name, Quaternion quat)
|
||||
{
|
||||
writer.WriteStartElement(name);
|
||||
writer.WriteElementString("X", quat.X.ToString(Utils.EnUsCulture));
|
||||
writer.WriteElementString("Y", quat.Y.ToString(Utils.EnUsCulture));
|
||||
writer.WriteElementString("Z", quat.Z.ToString(Utils.EnUsCulture));
|
||||
writer.WriteElementString("W", quat.W.ToString(Utils.EnUsCulture));
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
|
||||
#endregion Archive Saving
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
public class RegionSettings
|
||||
{
|
||||
public bool AllowDamage;
|
||||
public bool AllowLandResell;
|
||||
public bool AllowLandJoinDivide;
|
||||
public bool BlockFly;
|
||||
public bool BlockLandShowInSearch;
|
||||
public bool BlockTerraform;
|
||||
public bool DisableCollisions;
|
||||
public bool DisablePhysics;
|
||||
public bool DisableScripts;
|
||||
public int MaturityRating;
|
||||
public bool RestrictPushing;
|
||||
public int AgentLimit;
|
||||
public float ObjectBonus;
|
||||
|
||||
public UUID TerrainDetail0;
|
||||
public UUID TerrainDetail1;
|
||||
public UUID TerrainDetail2;
|
||||
public UUID TerrainDetail3;
|
||||
public float TerrainHeightRange00;
|
||||
public float TerrainHeightRange01;
|
||||
public float TerrainHeightRange10;
|
||||
public float TerrainHeightRange11;
|
||||
public float TerrainStartHeight00;
|
||||
public float TerrainStartHeight01;
|
||||
public float TerrainStartHeight10;
|
||||
public float TerrainStartHeight11;
|
||||
|
||||
public float WaterHeight;
|
||||
public float TerrainRaiseLimit;
|
||||
public float TerrainLowerLimit;
|
||||
public bool UseEstateSun;
|
||||
public bool FixedSun;
|
||||
|
||||
public static RegionSettings FromStream(Stream stream)
|
||||
{
|
||||
RegionSettings settings = new RegionSettings();
|
||||
System.Globalization.NumberFormatInfo nfi = Utils.EnUsCulture.NumberFormat;
|
||||
|
||||
using (XmlTextReader xtr = new XmlTextReader(stream))
|
||||
{
|
||||
xtr.ReadStartElement("RegionSettings");
|
||||
xtr.ReadStartElement("General");
|
||||
|
||||
while (xtr.Read() && xtr.NodeType != XmlNodeType.EndElement)
|
||||
{
|
||||
switch (xtr.Name)
|
||||
{
|
||||
case "AllowDamage":
|
||||
settings.AllowDamage = Boolean.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "AllowLandResell":
|
||||
settings.AllowLandResell = Boolean.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "AllowLandJoinDivide":
|
||||
settings.AllowLandJoinDivide = Boolean.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "BlockFly":
|
||||
settings.BlockFly = Boolean.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "BlockLandShowInSearch":
|
||||
settings.BlockLandShowInSearch = Boolean.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "BlockTerraform":
|
||||
settings.BlockTerraform = Boolean.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "DisableCollisions":
|
||||
settings.DisableCollisions = Boolean.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "DisablePhysics":
|
||||
settings.DisablePhysics = Boolean.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "DisableScripts":
|
||||
settings.DisableScripts = Boolean.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "MaturityRating":
|
||||
settings.MaturityRating = Int32.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "RestrictPushing":
|
||||
settings.RestrictPushing = Boolean.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "AgentLimit":
|
||||
settings.AgentLimit = Int32.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "ObjectBonus":
|
||||
settings.ObjectBonus = Single.Parse(xtr.ReadElementContentAsString(), nfi);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
xtr.ReadEndElement();
|
||||
xtr.ReadStartElement("GroundTextures");
|
||||
|
||||
while (xtr.Read() && xtr.NodeType != XmlNodeType.EndElement)
|
||||
{
|
||||
switch (xtr.Name)
|
||||
{
|
||||
case "Texture1":
|
||||
settings.TerrainDetail0 = UUID.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "Texture2":
|
||||
settings.TerrainDetail1 = UUID.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "Texture3":
|
||||
settings.TerrainDetail2 = UUID.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "Texture4":
|
||||
settings.TerrainDetail3 = UUID.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "ElevationLowSW":
|
||||
settings.TerrainStartHeight00 = Single.Parse(xtr.ReadElementContentAsString(), nfi);
|
||||
break;
|
||||
case "ElevationLowNW":
|
||||
settings.TerrainStartHeight01 = Single.Parse(xtr.ReadElementContentAsString(), nfi);
|
||||
break;
|
||||
case "ElevationLowSE":
|
||||
settings.TerrainStartHeight10 = Single.Parse(xtr.ReadElementContentAsString(), nfi);
|
||||
break;
|
||||
case "ElevationLowNE":
|
||||
settings.TerrainStartHeight11 = Single.Parse(xtr.ReadElementContentAsString(), nfi);
|
||||
break;
|
||||
case "ElevationHighSW":
|
||||
settings.TerrainHeightRange00 = Single.Parse(xtr.ReadElementContentAsString(), nfi);
|
||||
break;
|
||||
case "ElevationHighNW":
|
||||
settings.TerrainHeightRange01 = Single.Parse(xtr.ReadElementContentAsString(), nfi);
|
||||
break;
|
||||
case "ElevationHighSE":
|
||||
settings.TerrainHeightRange10 = Single.Parse(xtr.ReadElementContentAsString(), nfi);
|
||||
break;
|
||||
case "ElevationHighNE":
|
||||
settings.TerrainHeightRange11 = Single.Parse(xtr.ReadElementContentAsString(), nfi);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
xtr.ReadEndElement();
|
||||
xtr.ReadStartElement("Terrain");
|
||||
|
||||
while (xtr.Read() && xtr.NodeType != XmlNodeType.EndElement)
|
||||
{
|
||||
switch (xtr.Name)
|
||||
{
|
||||
case "WaterHeight":
|
||||
settings.WaterHeight = Single.Parse(xtr.ReadElementContentAsString(), nfi);
|
||||
break;
|
||||
case "TerrainRaiseLimit":
|
||||
settings.TerrainRaiseLimit = Single.Parse(xtr.ReadElementContentAsString(), nfi);
|
||||
break;
|
||||
case "TerrainLowerLimit":
|
||||
settings.TerrainLowerLimit = Single.Parse(xtr.ReadElementContentAsString(), nfi);
|
||||
break;
|
||||
case "UseEstateSun":
|
||||
settings.UseEstateSun = Boolean.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
case "FixedSun":
|
||||
settings.FixedSun = Boolean.Parse(xtr.ReadElementContentAsString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
public void ToXML(string filename)
|
||||
{
|
||||
StringWriter sw = new StringWriter();
|
||||
XmlTextWriter writer = new XmlTextWriter(sw) { Formatting = Formatting.Indented };
|
||||
writer.WriteStartDocument();
|
||||
|
||||
writer.WriteStartElement(String.Empty, "RegionSettings", String.Empty);
|
||||
writer.WriteStartElement(String.Empty, "General", String.Empty);
|
||||
|
||||
WriteBoolean(writer, "AllowDamage", AllowDamage);
|
||||
WriteBoolean(writer, "AllowLandResell", AllowLandResell);
|
||||
WriteBoolean(writer, "AllowLandJoinDivide", AllowLandJoinDivide);
|
||||
WriteBoolean(writer, "BlockFly", BlockFly);
|
||||
WriteBoolean(writer, "BlockLandShowInSearch", BlockLandShowInSearch);
|
||||
WriteBoolean(writer, "BlockTerraform", BlockTerraform);
|
||||
WriteBoolean(writer, "DisableCollisions", DisableCollisions);
|
||||
WriteBoolean(writer, "DisablePhysics", DisablePhysics);
|
||||
WriteBoolean(writer, "DisableScripts", DisableScripts);
|
||||
writer.WriteElementString("MaturityRating", MaturityRating.ToString());
|
||||
WriteBoolean(writer, "RestrictPushing", RestrictPushing);
|
||||
writer.WriteElementString("AgentLimit", AgentLimit.ToString());
|
||||
writer.WriteElementString("ObjectBonus", ObjectBonus.ToString());
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement(String.Empty, "GroundTextures", String.Empty);
|
||||
|
||||
writer.WriteElementString("Texture1", TerrainDetail0.ToString());
|
||||
writer.WriteElementString("Texture2", TerrainDetail1.ToString());
|
||||
writer.WriteElementString("Texture3", TerrainDetail2.ToString());
|
||||
writer.WriteElementString("Texture4", TerrainDetail3.ToString());
|
||||
writer.WriteElementString("ElevationLowSW", TerrainStartHeight00.ToString());
|
||||
writer.WriteElementString("ElevationLowNW", TerrainStartHeight01.ToString());
|
||||
writer.WriteElementString("ElevationLowSE", TerrainStartHeight10.ToString());
|
||||
writer.WriteElementString("ElevationLowNE", TerrainStartHeight11.ToString());
|
||||
writer.WriteElementString("ElevationHighSW", TerrainHeightRange00.ToString());
|
||||
writer.WriteElementString("ElevationHighNW", TerrainHeightRange01.ToString());
|
||||
writer.WriteElementString("ElevationHighSE", TerrainHeightRange10.ToString());
|
||||
writer.WriteElementString("ElevationHighNE", TerrainHeightRange11.ToString());
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement(String.Empty, "Terrain", String.Empty);
|
||||
|
||||
writer.WriteElementString("WaterHeight", WaterHeight.ToString());
|
||||
writer.WriteElementString("TerrainRaiseLimit", TerrainRaiseLimit.ToString());
|
||||
writer.WriteElementString("TerrainLowerLimit", TerrainLowerLimit.ToString());
|
||||
WriteBoolean(writer, "UseEstateSun", UseEstateSun);
|
||||
WriteBoolean(writer, "FixedSun", FixedSun);
|
||||
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndElement();
|
||||
writer.Close();
|
||||
sw.Close();
|
||||
File.WriteAllText(filename, sw.ToString());
|
||||
}
|
||||
|
||||
private void WriteBoolean(XmlTextWriter writer, string name, bool value)
|
||||
{
|
||||
writer.WriteElementString(name, value ? "True" : "False");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Temporary code to do the bare minimum required to read a tar archive for our purposes
|
||||
/// </summary>
|
||||
public class TarArchiveReader
|
||||
{
|
||||
public enum TarEntryType
|
||||
{
|
||||
TYPE_UNKNOWN = 0,
|
||||
TYPE_NORMAL_FILE = 1,
|
||||
TYPE_HARD_LINK = 2,
|
||||
TYPE_SYMBOLIC_LINK = 3,
|
||||
TYPE_CHAR_SPECIAL = 4,
|
||||
TYPE_BLOCK_SPECIAL = 5,
|
||||
TYPE_DIRECTORY = 6,
|
||||
TYPE_FIFO = 7,
|
||||
TYPE_CONTIGUOUS_FILE = 8,
|
||||
}
|
||||
|
||||
protected static ASCIIEncoding m_asciiEncoding = new ASCIIEncoding();
|
||||
|
||||
/// <summary>
|
||||
/// Binary reader for the underlying stream
|
||||
/// </summary>
|
||||
protected BinaryReader m_br;
|
||||
|
||||
/// <summary>
|
||||
/// Used to trim off null chars
|
||||
/// </summary>
|
||||
protected static readonly char[] m_nullCharArray = new char[] { '\0' };
|
||||
|
||||
/// <summary>
|
||||
/// Used to trim off space chars
|
||||
/// </summary>
|
||||
protected static readonly char[] m_spaceCharArray = new char[] { ' ' };
|
||||
|
||||
/// <summary>
|
||||
/// Generate a tar reader which reads from the given stream.
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
public TarArchiveReader(Stream s)
|
||||
{
|
||||
m_br = new BinaryReader(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the next entry in the tar file.
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="entryType"></param>
|
||||
/// <returns>the data for the entry. Returns null if there are no more entries</returns>
|
||||
public byte[] ReadEntry(out string filePath, out TarEntryType entryType)
|
||||
{
|
||||
filePath = String.Empty;
|
||||
entryType = TarEntryType.TYPE_UNKNOWN;
|
||||
TarHeader header = ReadHeader();
|
||||
|
||||
if (null == header)
|
||||
return null;
|
||||
|
||||
entryType = header.EntryType;
|
||||
filePath = header.FilePath;
|
||||
return ReadData(header.FileSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the next 512 byte chunk of data as a tar header.
|
||||
/// </summary>
|
||||
/// <returns>A tar header struct. null if we have reached the end of the archive.</returns>
|
||||
protected TarHeader ReadHeader()
|
||||
{
|
||||
byte[] header = m_br.ReadBytes(512);
|
||||
|
||||
// If we've reached the end of the archive we'll be in null block territory, which means
|
||||
// the next byte will be 0
|
||||
if (header[0] == 0)
|
||||
return null;
|
||||
|
||||
TarHeader tarHeader = new TarHeader();
|
||||
|
||||
// If we're looking at a GNU tar long link then extract the long name and pull up the next header
|
||||
if (header[156] == (byte)'L')
|
||||
{
|
||||
int longNameLength = ConvertOctalBytesToDecimal(header, 124, 11);
|
||||
tarHeader.FilePath = m_asciiEncoding.GetString(ReadData(longNameLength));
|
||||
//m_log.DebugFormat("[TAR ARCHIVE READER]: Got long file name {0}", tarHeader.FilePath);
|
||||
header = m_br.ReadBytes(512);
|
||||
}
|
||||
else
|
||||
{
|
||||
tarHeader.FilePath = m_asciiEncoding.GetString(header, 0, 100);
|
||||
tarHeader.FilePath = tarHeader.FilePath.Trim(m_nullCharArray);
|
||||
//m_log.DebugFormat("[TAR ARCHIVE READER]: Got short file name {0}", tarHeader.FilePath);
|
||||
}
|
||||
|
||||
tarHeader.FileSize = ConvertOctalBytesToDecimal(header, 124, 11);
|
||||
|
||||
switch (header[156])
|
||||
{
|
||||
case 0:
|
||||
tarHeader.EntryType = TarEntryType.TYPE_NORMAL_FILE;
|
||||
break;
|
||||
case (byte)'0':
|
||||
tarHeader.EntryType = TarEntryType.TYPE_NORMAL_FILE;
|
||||
break;
|
||||
case (byte)'1':
|
||||
tarHeader.EntryType = TarEntryType.TYPE_HARD_LINK;
|
||||
break;
|
||||
case (byte)'2':
|
||||
tarHeader.EntryType = TarEntryType.TYPE_SYMBOLIC_LINK;
|
||||
break;
|
||||
case (byte)'3':
|
||||
tarHeader.EntryType = TarEntryType.TYPE_CHAR_SPECIAL;
|
||||
break;
|
||||
case (byte)'4':
|
||||
tarHeader.EntryType = TarEntryType.TYPE_BLOCK_SPECIAL;
|
||||
break;
|
||||
case (byte)'5':
|
||||
tarHeader.EntryType = TarEntryType.TYPE_DIRECTORY;
|
||||
break;
|
||||
case (byte)'6':
|
||||
tarHeader.EntryType = TarEntryType.TYPE_FIFO;
|
||||
break;
|
||||
case (byte)'7':
|
||||
tarHeader.EntryType = TarEntryType.TYPE_CONTIGUOUS_FILE;
|
||||
break;
|
||||
}
|
||||
|
||||
return tarHeader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read data following a header
|
||||
/// </summary>
|
||||
/// <param name="fileSize"></param>
|
||||
/// <returns></returns>
|
||||
protected byte[] ReadData(int fileSize)
|
||||
{
|
||||
byte[] data = m_br.ReadBytes(fileSize);
|
||||
|
||||
//m_log.DebugFormat("[TAR ARCHIVE READER]: fileSize {0}", fileSize);
|
||||
|
||||
// Read the rest of the empty padding in the 512 byte block
|
||||
if (fileSize % 512 != 0)
|
||||
{
|
||||
int paddingLeft = 512 - (fileSize % 512);
|
||||
|
||||
//m_log.DebugFormat("[TAR ARCHIVE READER]: Reading {0} padding bytes", paddingLeft);
|
||||
|
||||
m_br.ReadBytes(paddingLeft);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
m_br.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert octal bytes to a decimal representation
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="startIndex"></param>
|
||||
/// <returns></returns>
|
||||
public static int ConvertOctalBytesToDecimal(byte[] bytes, int startIndex, int count)
|
||||
{
|
||||
// Trim leading white space: ancient tars do that instead
|
||||
// of leading 0s :-( don't ask. really.
|
||||
string oString = m_asciiEncoding.GetString(bytes, startIndex, count).TrimStart(m_spaceCharArray);
|
||||
|
||||
int d = 0;
|
||||
|
||||
foreach (char c in oString)
|
||||
{
|
||||
d <<= 3;
|
||||
d |= c - '0';
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
public class TarHeader
|
||||
{
|
||||
public string FilePath;
|
||||
public int FileSize;
|
||||
public TarArchiveReader.TarEntryType EntryType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Temporary code to produce a tar archive in tar v7 format
|
||||
/// </summary>
|
||||
public class TarArchiveWriter
|
||||
{
|
||||
protected static ASCIIEncoding m_asciiEncoding = new ASCIIEncoding();
|
||||
|
||||
/// <summary>
|
||||
/// Binary writer for the underlying stream
|
||||
/// </summary>
|
||||
protected BinaryWriter m_bw;
|
||||
|
||||
public TarArchiveWriter(Stream s)
|
||||
{
|
||||
m_bw = new BinaryWriter(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a directory entry to the tar archive. We can only handle one path level right now!
|
||||
/// </summary>
|
||||
/// <param name="dirName"></param>
|
||||
public void WriteDir(string dirName)
|
||||
{
|
||||
// Directories are signalled by a final /
|
||||
if (!dirName.EndsWith("/"))
|
||||
dirName += "/";
|
||||
|
||||
WriteFile(dirName, new byte[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a file to the tar archive
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="data"></param>
|
||||
public void WriteFile(string filePath, string data)
|
||||
{
|
||||
WriteFile(filePath, m_asciiEncoding.GetBytes(data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a file to the tar archive
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="data"></param>
|
||||
public void WriteFile(string filePath, byte[] data)
|
||||
{
|
||||
if (filePath.Length > 100)
|
||||
WriteEntry("././@LongLink", m_asciiEncoding.GetBytes(filePath), 'L');
|
||||
|
||||
char fileType;
|
||||
|
||||
if (filePath.EndsWith("/"))
|
||||
{
|
||||
fileType = '5';
|
||||
}
|
||||
else
|
||||
{
|
||||
fileType = '0';
|
||||
}
|
||||
|
||||
WriteEntry(filePath, data, fileType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finish writing the raw tar archive data to a stream. The stream will be closed on completion.
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
//m_log.Debug("[TAR ARCHIVE WRITER]: Writing final consecutive 0 blocks");
|
||||
|
||||
// Write two consecutive 0 blocks to end the archive
|
||||
byte[] finalZeroPadding = new byte[1024];
|
||||
m_bw.Write(finalZeroPadding);
|
||||
|
||||
m_bw.Flush();
|
||||
m_bw.Close();
|
||||
}
|
||||
|
||||
public static byte[] ConvertDecimalToPaddedOctalBytes(int d, int padding)
|
||||
{
|
||||
string oString = "";
|
||||
|
||||
while (d > 0)
|
||||
{
|
||||
oString = Convert.ToString((byte)'0' + d & 7) + oString;
|
||||
d >>= 3;
|
||||
}
|
||||
|
||||
while (oString.Length < padding)
|
||||
{
|
||||
oString = "0" + oString;
|
||||
}
|
||||
|
||||
byte[] oBytes = m_asciiEncoding.GetBytes(oString);
|
||||
|
||||
return oBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a particular entry
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="fileType"></param>
|
||||
protected void WriteEntry(string filePath, byte[] data, char fileType)
|
||||
{
|
||||
byte[] header = new byte[512];
|
||||
|
||||
// file path field (100)
|
||||
byte[] nameBytes = m_asciiEncoding.GetBytes(filePath);
|
||||
int nameSize = (nameBytes.Length >= 100) ? 100 : nameBytes.Length;
|
||||
Array.Copy(nameBytes, header, nameSize);
|
||||
|
||||
// file mode (8)
|
||||
byte[] modeBytes = m_asciiEncoding.GetBytes("0000777");
|
||||
Array.Copy(modeBytes, 0, header, 100, 7);
|
||||
|
||||
// owner user id (8)
|
||||
byte[] ownerIdBytes = m_asciiEncoding.GetBytes("0000764");
|
||||
Array.Copy(ownerIdBytes, 0, header, 108, 7);
|
||||
|
||||
// group user id (8)
|
||||
byte[] groupIdBytes = m_asciiEncoding.GetBytes("0000764");
|
||||
Array.Copy(groupIdBytes, 0, header, 116, 7);
|
||||
|
||||
// file size in bytes (12)
|
||||
int fileSize = data.Length;
|
||||
//m_log.DebugFormat("[TAR ARCHIVE WRITER]: File size of {0} is {1}", filePath, fileSize);
|
||||
|
||||
byte[] fileSizeBytes = ConvertDecimalToPaddedOctalBytes(fileSize, 11);
|
||||
|
||||
Array.Copy(fileSizeBytes, 0, header, 124, 11);
|
||||
|
||||
// last modification time (12)
|
||||
byte[] lastModTimeBytes = m_asciiEncoding.GetBytes("11017037332");
|
||||
Array.Copy(lastModTimeBytes, 0, header, 136, 11);
|
||||
|
||||
// entry type indicator (1)
|
||||
header[156] = m_asciiEncoding.GetBytes(new char[] { fileType })[0];
|
||||
|
||||
Array.Copy(m_asciiEncoding.GetBytes("0000000"), 0, header, 329, 7);
|
||||
Array.Copy(m_asciiEncoding.GetBytes("0000000"), 0, header, 337, 7);
|
||||
|
||||
// check sum for header block (8) [calculated last]
|
||||
Array.Copy(m_asciiEncoding.GetBytes(" "), 0, header, 148, 8);
|
||||
|
||||
int checksum = 0;
|
||||
foreach (byte b in header)
|
||||
{
|
||||
checksum += b;
|
||||
}
|
||||
|
||||
//m_log.DebugFormat("[TAR ARCHIVE WRITER]: Decimal header checksum is {0}", checksum);
|
||||
|
||||
byte[] checkSumBytes = ConvertDecimalToPaddedOctalBytes(checksum, 6);
|
||||
|
||||
Array.Copy(checkSumBytes, 0, header, 148, 6);
|
||||
|
||||
header[154] = 0;
|
||||
|
||||
// Write out header
|
||||
m_bw.Write(header);
|
||||
|
||||
// Write out data
|
||||
m_bw.Write(data);
|
||||
|
||||
if (data.Length % 512 != 0)
|
||||
{
|
||||
int paddingRequired = 512 - (data.Length % 512);
|
||||
|
||||
//m_log.DebugFormat("[TAR ARCHIVE WRITER]: Padding data with {0} bytes", paddingRequired);
|
||||
|
||||
byte[] padding = new byte[paddingRequired];
|
||||
m_bw.Write(padding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all Asset types
|
||||
/// </summary>
|
||||
public abstract class Asset
|
||||
{
|
||||
/// <summary>A byte array containing the raw asset data</summary>
|
||||
public byte[] AssetData;
|
||||
/// <summary>True if the asset it only stored on the server temporarily</summary>
|
||||
public bool Temporary;
|
||||
/// <summary>A unique ID</summary>
|
||||
private UUID _AssetID;
|
||||
/// <summary>The assets unique ID</summary>
|
||||
public UUID AssetID
|
||||
{
|
||||
get { return _AssetID; }
|
||||
internal set { _AssetID = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "type" of asset, Notecard, Animation, etc
|
||||
/// </summary>
|
||||
public abstract AssetType AssetType
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new Asset object
|
||||
/// </summary>
|
||||
public Asset() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new Asset object
|
||||
/// </summary>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public Asset(UUID assetID, byte[] assetData)
|
||||
{
|
||||
_AssetID = assetID;
|
||||
AssetData = assetData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regenerates the <code>AssetData</code> byte array from the properties
|
||||
/// of the derived class.
|
||||
/// </summary>
|
||||
public abstract void Encode();
|
||||
|
||||
/// <summary>
|
||||
/// Decodes the AssetData, placing it in appropriate properties of the derived
|
||||
/// class.
|
||||
/// </summary>
|
||||
/// <returns>True if the asset decoding succeeded, otherwise false</returns>
|
||||
public abstract bool Decode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an Animation
|
||||
/// </summary>
|
||||
public class AssetAnimation : Asset
|
||||
{
|
||||
/// <summary>Override the base classes AssetType</summary>
|
||||
public override AssetType AssetType { get { return AssetType.Animation; } }
|
||||
|
||||
/// <summary>Default Constructor</summary>
|
||||
public AssetAnimation() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an Asset object of type Animation
|
||||
/// </summary>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public AssetAnimation(UUID assetID, byte[] assetData)
|
||||
: base(assetID, assetData)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Encode() { }
|
||||
public override bool Decode() { return true; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an <seealso cref="AssetWearable"/> that represents an avatars body ie: Hair, Etc.
|
||||
/// </summary>
|
||||
public class AssetBodypart : AssetWearable
|
||||
{
|
||||
/// <summary>Override the base classes AssetType</summary>
|
||||
public override AssetType AssetType { get { return AssetType.Bodypart; } }
|
||||
|
||||
/// <summary>Initializes a new instance of an AssetBodyPart object</summary>
|
||||
public AssetBodypart() { }
|
||||
|
||||
/// <summary>Initializes a new instance of an AssetBodyPart object with parameters</summary>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public AssetBodypart(UUID assetID, byte[] assetData) : base(assetID, assetData) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Callingcard with AvatarID and Position vector
|
||||
/// </summary>
|
||||
public class AssetCallingCard : Asset
|
||||
{
|
||||
/// <summary>Override the base classes AssetType</summary>
|
||||
public override AssetType AssetType { get { return AssetType.CallingCard; } }
|
||||
|
||||
/// <summary>UUID of the Callingcard target avatar</summary>
|
||||
public UUID AvatarID = UUID.Zero;
|
||||
|
||||
/// <summary>Construct an Asset of type Callingcard</summary>
|
||||
public AssetCallingCard() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an Asset object of type Callingcard
|
||||
/// </summary>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public AssetCallingCard(UUID assetID, byte[] assetData)
|
||||
: base(assetID, assetData)
|
||||
{
|
||||
Decode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constuct an asset of type Callingcard
|
||||
/// </summary>
|
||||
/// <param name="avatarID">UUID of the target avatar</param>
|
||||
public AssetCallingCard(UUID avatarID)
|
||||
{
|
||||
AvatarID = avatarID;
|
||||
Encode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode the raw contents of a string with the specific Callingcard format
|
||||
/// </summary>
|
||||
public override void Encode()
|
||||
{
|
||||
string temp = "Callingcard version 2\n";
|
||||
temp += "avatar_id " + AvatarID + "\n";
|
||||
AssetData = Utils.StringToBytes(temp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode the raw asset data, populating the AvatarID and Position
|
||||
/// </summary>
|
||||
/// <returns>true if the AssetData was successfully decoded to a UUID and Vector</returns>
|
||||
public override bool Decode()
|
||||
{
|
||||
String text = Utils.BytesToString(AssetData);
|
||||
if (text.ToLower().Contains("callingcard version 2"))
|
||||
{
|
||||
AvatarID = new UUID(text.Substring(text.IndexOf("avatar_id") + 10, 36));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an <seealso cref="AssetWearable"/> that can be worn on an avatar
|
||||
/// such as a Shirt, Pants, etc.
|
||||
/// </summary>
|
||||
public class AssetClothing : AssetWearable
|
||||
{
|
||||
/// <summary>Override the base classes AssetType</summary>
|
||||
public override AssetType AssetType { get { return AssetType.Clothing; } }
|
||||
|
||||
/// <summary>Initializes a new instance of an AssetScriptBinary object</summary>
|
||||
public AssetClothing() { }
|
||||
|
||||
/// <summary>Initializes a new instance of an AssetScriptBinary object with parameters</summary>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public AssetClothing(UUID assetID, byte[] assetData) : base(assetID, assetData) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
#region Enums
|
||||
/// <summary>
|
||||
/// Type of gesture step
|
||||
/// </summary>
|
||||
public enum GestureStepType : int
|
||||
{
|
||||
Animation = 0,
|
||||
Sound,
|
||||
Chat,
|
||||
Wait,
|
||||
EOF
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Gesture step classes
|
||||
/// <summary>
|
||||
/// Base class for gesture steps
|
||||
/// </summary>
|
||||
public abstract class GestureStep
|
||||
{
|
||||
/// <summary>
|
||||
/// Retururns what kind of gesture step this is
|
||||
/// </summary>
|
||||
public abstract GestureStepType GestureStepType { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes animation step of a gesture
|
||||
/// </summary>
|
||||
public class GestureStepAnimation : GestureStep
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns what kind of gesture step this is
|
||||
/// </summary>
|
||||
public override GestureStepType GestureStepType
|
||||
{
|
||||
get { return GestureStepType.Animation; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true, this step represents start of animation, otherwise animation stop
|
||||
/// </summary>
|
||||
public bool AnimationStart = true;
|
||||
|
||||
/// <summary>
|
||||
/// Animation asset <see cref="UUID"/>
|
||||
/// </summary>
|
||||
public UUID ID;
|
||||
|
||||
/// <summary>
|
||||
/// Animation inventory name
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (AnimationStart)
|
||||
{
|
||||
return "Start animation: " + Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Stop animation: " + Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes sound step of a gesture
|
||||
/// </summary>
|
||||
public class GestureStepSound : GestureStep
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns what kind of gesture step this is
|
||||
/// </summary>
|
||||
public override GestureStepType GestureStepType
|
||||
{
|
||||
get { return GestureStepType.Sound; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sound asset <see cref="UUID"/>
|
||||
/// </summary>
|
||||
public UUID ID;
|
||||
|
||||
/// <summary>
|
||||
/// Sound inventory name
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Sound: " + Name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes sound step of a gesture
|
||||
/// </summary>
|
||||
public class GestureStepChat : GestureStep
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns what kind of gesture step this is
|
||||
/// </summary>
|
||||
public override GestureStepType GestureStepType
|
||||
{
|
||||
get { return GestureStepType.Chat; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Text to output in chat
|
||||
/// </summary>
|
||||
public string Text;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Chat: " + Text;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes sound step of a gesture
|
||||
/// </summary>
|
||||
public class GestureStepWait : GestureStep
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns what kind of gesture step this is
|
||||
/// </summary>
|
||||
public override GestureStepType GestureStepType
|
||||
{
|
||||
get { return GestureStepType.Wait; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true in this step we wait for all animations to finish
|
||||
/// </summary>
|
||||
public bool WaitForAnimation;
|
||||
|
||||
/// <summary>
|
||||
/// If true gesture player should wait for the specified amount of time
|
||||
/// </summary>
|
||||
public bool WaitForTime;
|
||||
|
||||
/// <summary>
|
||||
/// Time in seconds to wait if WaitForAnimation is false
|
||||
/// </summary>
|
||||
public float WaitTime;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder ret = new StringBuilder("-- Wait for: ");
|
||||
|
||||
if (WaitForAnimation)
|
||||
{
|
||||
ret.Append("(animations to finish) ");
|
||||
}
|
||||
|
||||
if (WaitForTime)
|
||||
{
|
||||
ret.AppendFormat("(time {0:0.0}s)", WaitTime);
|
||||
}
|
||||
|
||||
return ret.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the final step of a gesture
|
||||
/// </summary>
|
||||
public class GestureStepEOF : GestureStep
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns what kind of gesture step this is
|
||||
/// </summary>
|
||||
public override GestureStepType GestureStepType
|
||||
{
|
||||
get { return GestureStepType.EOF; }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "End of guesture sequence";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Represents a sequence of animations, sounds, and chat actions
|
||||
/// </summary>
|
||||
public class AssetGesture : Asset
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns asset type
|
||||
/// </summary>
|
||||
public override AssetType AssetType
|
||||
{
|
||||
get { return AssetType.Gesture; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Keyboard key that triggers the gestyre
|
||||
/// </summary>
|
||||
public byte TriggerKey;
|
||||
|
||||
/// <summary>
|
||||
/// Modifier to the trigger key
|
||||
/// </summary>
|
||||
public uint TriggerKeyMask;
|
||||
|
||||
/// <summary>
|
||||
/// String that triggers playing of the gesture sequence
|
||||
/// </summary>
|
||||
public string Trigger;
|
||||
|
||||
/// <summary>
|
||||
/// Text that replaces trigger in chat once gesture is triggered
|
||||
/// </summary>
|
||||
public string ReplaceWith;
|
||||
|
||||
/// <summary>
|
||||
/// Sequence of gesture steps
|
||||
/// </summary>
|
||||
public List<GestureStep> Sequence;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs guesture asset
|
||||
/// </summary>
|
||||
public AssetGesture() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs guesture asset
|
||||
/// </summary>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public AssetGesture(UUID assetID, byte[] assetData)
|
||||
: base(assetID, assetData)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes gesture asset suitable for uplaod
|
||||
/// </summary>
|
||||
public override void Encode()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("2\n");
|
||||
sb.Append(TriggerKey + "\n");
|
||||
sb.Append(TriggerKeyMask + "\n");
|
||||
sb.Append(Trigger + "\n");
|
||||
sb.Append(ReplaceWith + "\n");
|
||||
|
||||
int count = 0;
|
||||
if (Sequence != null)
|
||||
{
|
||||
count = Sequence.Count;
|
||||
}
|
||||
|
||||
sb.Append(count + "\n");
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
GestureStep step = Sequence[i];
|
||||
sb.Append((int)step.GestureStepType + "\n");
|
||||
|
||||
switch (step.GestureStepType)
|
||||
{
|
||||
case GestureStepType.EOF:
|
||||
goto Finish;
|
||||
|
||||
case GestureStepType.Animation:
|
||||
GestureStepAnimation animstep = (GestureStepAnimation)step;
|
||||
sb.Append(animstep.Name + "\n");
|
||||
sb.Append(animstep.ID + "\n");
|
||||
|
||||
if (animstep.AnimationStart)
|
||||
{
|
||||
sb.Append("0\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("1\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case GestureStepType.Sound:
|
||||
GestureStepSound soundstep = (GestureStepSound)step;
|
||||
sb.Append(soundstep.Name + "\n");
|
||||
sb.Append(soundstep.ID + "\n");
|
||||
sb.Append("0\n");
|
||||
break;
|
||||
|
||||
case GestureStepType.Chat:
|
||||
GestureStepChat chatstep = (GestureStepChat)step;
|
||||
sb.Append(chatstep.Text + "\n");
|
||||
sb.Append("0\n");
|
||||
break;
|
||||
|
||||
case GestureStepType.Wait:
|
||||
GestureStepWait waitstep = (GestureStepWait)step;
|
||||
sb.AppendFormat("{0:0.000000}\n", waitstep.WaitTime);
|
||||
int waitflags = 0;
|
||||
|
||||
if (waitstep.WaitForTime)
|
||||
{
|
||||
waitflags |= 0x01;
|
||||
}
|
||||
|
||||
if (waitstep.WaitForAnimation)
|
||||
{
|
||||
waitflags |= 0x02;
|
||||
}
|
||||
|
||||
sb.Append(waitflags + "\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
Finish:
|
||||
|
||||
AssetData = Utils.StringToBytes(sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes gesture assset into play sequence
|
||||
/// </summary>
|
||||
/// <returns>true if the asset data was decoded successfully</returns>
|
||||
public override bool Decode()
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] lines = Utils.BytesToString(AssetData).Split('\n');
|
||||
Sequence = new List<GestureStep>();
|
||||
|
||||
int i = 0;
|
||||
|
||||
// version
|
||||
int version = int.Parse(lines[i++]);
|
||||
if (version != 2)
|
||||
{
|
||||
throw new Exception("Only know how to decode version 2 of gesture asset");
|
||||
}
|
||||
|
||||
TriggerKey = byte.Parse(lines[i++]);
|
||||
TriggerKeyMask = uint.Parse(lines[i++]);
|
||||
Trigger = lines[i++];
|
||||
ReplaceWith = lines[i++];
|
||||
|
||||
int count = int.Parse(lines[i++]);
|
||||
|
||||
if (count < 0)
|
||||
{
|
||||
throw new Exception("Wrong number of gesture steps");
|
||||
}
|
||||
|
||||
for (int n = 0; n < count; n++)
|
||||
{
|
||||
GestureStepType type = (GestureStepType)int.Parse(lines[i++]);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case GestureStepType.EOF:
|
||||
goto Finish;
|
||||
|
||||
case GestureStepType.Animation:
|
||||
{
|
||||
GestureStepAnimation step = new GestureStepAnimation();
|
||||
step.Name = lines[i++];
|
||||
step.ID = new UUID(lines[i++]);
|
||||
int flags = int.Parse(lines[i++]);
|
||||
|
||||
if (flags == 0)
|
||||
{
|
||||
step.AnimationStart = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
step.AnimationStart = false;
|
||||
}
|
||||
|
||||
Sequence.Add(step);
|
||||
break;
|
||||
}
|
||||
|
||||
case GestureStepType.Sound:
|
||||
{
|
||||
GestureStepSound step = new GestureStepSound();
|
||||
step.Name = lines[i++].Replace("\r", "");
|
||||
step.ID = new UUID(lines[i++]);
|
||||
int flags = int.Parse(lines[i++]);
|
||||
|
||||
Sequence.Add(step);
|
||||
break;
|
||||
}
|
||||
|
||||
case GestureStepType.Chat:
|
||||
{
|
||||
GestureStepChat step = new GestureStepChat();
|
||||
step.Text = lines[i++];
|
||||
int flags = int.Parse(lines[i++]);
|
||||
|
||||
Sequence.Add(step);
|
||||
break;
|
||||
}
|
||||
|
||||
case GestureStepType.Wait:
|
||||
{
|
||||
GestureStepWait step = new GestureStepWait();
|
||||
step.WaitTime = float.Parse(lines[i++], Utils.EnUsCulture);
|
||||
int flags = int.Parse(lines[i++]);
|
||||
|
||||
step.WaitForTime = (flags & 0x01) != 0;
|
||||
step.WaitForAnimation = (flags & 0x02) != 0;
|
||||
Sequence.Add(step);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Finish:
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Decoding gesture asset failed:" + ex.Message, Helpers.LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Landmark with RegionID and Position vector
|
||||
/// </summary>
|
||||
public class AssetLandmark : Asset
|
||||
{
|
||||
/// <summary>Override the base classes AssetType</summary>
|
||||
public override AssetType AssetType { get { return AssetType.Landmark; } }
|
||||
|
||||
/// <summary>UUID of the Landmark target region</summary>
|
||||
public UUID RegionID = UUID.Zero;
|
||||
/// <summary> Local position of the target </summary>
|
||||
public Vector3 Position = Vector3.Zero;
|
||||
|
||||
/// <summary>Construct an Asset of type Landmark</summary>
|
||||
public AssetLandmark() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an Asset object of type Landmark
|
||||
/// </summary>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public AssetLandmark(UUID assetID, byte[] assetData)
|
||||
: base(assetID, assetData)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode the raw contents of a string with the specific Landmark format
|
||||
/// </summary>
|
||||
public override void Encode()
|
||||
{
|
||||
string temp = "Landmark version 2\n";
|
||||
temp += "region_id " + RegionID + "\n";
|
||||
temp += String.Format(Utils.EnUsCulture, "local_pos {0:0.00} {1:0.00} {2:0.00}\n", Position.X, Position.Y, Position.Z);
|
||||
AssetData = Utils.StringToBytes(temp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode the raw asset data, populating the RegionID and Position
|
||||
/// </summary>
|
||||
/// <returns>true if the AssetData was successfully decoded to a UUID and Vector</returns>
|
||||
public override bool Decode()
|
||||
{
|
||||
String text = Utils.BytesToString(AssetData);
|
||||
if (text.ToLower().Contains("landmark version 2"))
|
||||
{
|
||||
RegionID = new UUID(text.Substring(text.IndexOf("region_id") + 10, 36));
|
||||
String vecDelim = " ";
|
||||
String[] vecStrings = text.Substring(text.IndexOf("local_pos") + 10).Split(vecDelim.ToCharArray());
|
||||
if (vecStrings.Length == 3)
|
||||
{
|
||||
Position = new Vector3(float.Parse(vecStrings[0], System.Globalization.CultureInfo.InvariantCulture), float.Parse(vecStrings[1], System.Globalization.CultureInfo.InvariantCulture), float.Parse(vecStrings[2], System.Globalization.CultureInfo.InvariantCulture));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using OpenMetaverse;
|
||||
using OpenMetaverse.StructuredData;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents Mesh asset
|
||||
/// </summary>
|
||||
public class AssetMesh : Asset
|
||||
{
|
||||
/// <summary>Override the base classes AssetType</summary>
|
||||
public override AssetType AssetType { get { return AssetType.Mesh; } }
|
||||
|
||||
/// <summary>
|
||||
/// Decoded mesh data
|
||||
/// </summary>
|
||||
public OSDMap MeshData;
|
||||
|
||||
/// <summary>Initializes a new instance of an AssetMesh object</summary>
|
||||
public AssetMesh() { }
|
||||
|
||||
/// <summary>Initializes a new instance of an AssetMesh object with parameters</summary>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public AssetMesh(UUID assetID, byte[] assetData)
|
||||
: base(assetID, assetData)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO: Encodes Collada file into LLMesh format
|
||||
/// </summary>
|
||||
public override void Encode() { }
|
||||
|
||||
/// <summary>
|
||||
/// Decodes mesh asset. See <see cref="OpenMetaverse.Rendering.FacetedMesh.TryDecodeFromAsset"/>
|
||||
/// to furter decode it for rendering</summary>
|
||||
/// <returns>true</returns>
|
||||
public override bool Decode()
|
||||
{
|
||||
try
|
||||
{
|
||||
MeshData = new OSDMap();
|
||||
|
||||
using (MemoryStream data = new MemoryStream(AssetData))
|
||||
{
|
||||
OSDMap header = (OSDMap)OSDParser.DeserializeLLSDBinary(data);
|
||||
MeshData["asset_header"] = header;
|
||||
long start = data.Position;
|
||||
|
||||
foreach(string partName in header.Keys)
|
||||
{
|
||||
if (header[partName].Type != OSDType.Map)
|
||||
{
|
||||
MeshData[partName] = header[partName];
|
||||
continue;
|
||||
}
|
||||
|
||||
OSDMap partInfo = (OSDMap)header[partName];
|
||||
if (partInfo["offset"] < 0 || partInfo["size"] == 0)
|
||||
{
|
||||
MeshData[partName] = partInfo;
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] part = new byte[partInfo["size"]];
|
||||
Buffer.BlockCopy(AssetData, partInfo["offset"] + (int)start, part, 0, part.Length);
|
||||
MeshData[partName] = Helpers.ZDecompressOSD(part);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Failed to decode mesh asset", Helpers.LogLevel.Error, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an Animation
|
||||
/// </summary>
|
||||
public class AssetMutable : Asset
|
||||
{
|
||||
public AssetType currentType;
|
||||
|
||||
/// <summary>Override the base classes AssetType</summary>
|
||||
public override AssetType AssetType { get { return currentType; } }
|
||||
|
||||
/// <summary>Default Constructor</summary>
|
||||
public AssetMutable(AssetType type)
|
||||
{
|
||||
currentType = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct an Asset object of type Animation
|
||||
/// </summary>
|
||||
/// <param name="type">Asset type</param>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public AssetMutable(AssetType type, UUID assetID, byte[] assetData)
|
||||
: base(assetID, assetData)
|
||||
{
|
||||
currentType = type;
|
||||
}
|
||||
|
||||
public override void Encode() { }
|
||||
public override bool Decode() { return true; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a string of characters encoded with specific formatting properties
|
||||
/// </summary>
|
||||
public class AssetNotecard : Asset
|
||||
{
|
||||
/// <summary>Override the base classes AssetType</summary>
|
||||
public override AssetType AssetType { get { return AssetType.Notecard; } }
|
||||
|
||||
/// <summary>A text string containing main text of the notecard</summary>
|
||||
public string BodyText;
|
||||
|
||||
/// <summary>List of <see cref="OpenMetaverse.InventoryItem"/>s embedded on the notecard</summary>
|
||||
public List<InventoryItem> EmbeddedItems;
|
||||
|
||||
/// <summary>Construct an Asset of type Notecard</summary>
|
||||
public AssetNotecard() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an Asset object of type Notecard
|
||||
/// </summary>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public AssetNotecard(UUID assetID, byte[] assetData)
|
||||
: base(assetID, assetData)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode the raw contents of a string with the specific Linden Text properties
|
||||
/// </summary>
|
||||
public override void Encode()
|
||||
{
|
||||
string body = BodyText ?? String.Empty;
|
||||
|
||||
StringBuilder output = new StringBuilder();
|
||||
output.Append("Linden text version 2\n");
|
||||
output.Append("{\n");
|
||||
output.Append("LLEmbeddedItems version 1\n");
|
||||
output.Append("{\n");
|
||||
|
||||
int count = 0;
|
||||
|
||||
if (EmbeddedItems != null)
|
||||
{
|
||||
count = EmbeddedItems.Count;
|
||||
}
|
||||
|
||||
output.Append("count " + count + "\n");
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
output.Append("{\n");
|
||||
|
||||
for (int i = 0; i < EmbeddedItems.Count; i++)
|
||||
{
|
||||
InventoryItem item = EmbeddedItems[i];
|
||||
|
||||
output.Append("ext char index " + i + "\n");
|
||||
|
||||
output.Append("\tinv_item\t0\n");
|
||||
output.Append("\t{\n");
|
||||
|
||||
output.Append("\t\titem_id\t" + item.UUID + "\n");
|
||||
output.Append("\t\tparent_id\t" + item.ParentUUID + "\n");
|
||||
|
||||
output.Append("\tpermissions 0\n");
|
||||
output.Append("\t{\n");
|
||||
output.Append("\t\tbase_mask\t" + ((uint)item.Permissions.BaseMask).ToString("x").PadLeft(8, '0') + "\n");
|
||||
output.Append("\t\towner_mask\t" + ((uint)item.Permissions.OwnerMask).ToString("x").PadLeft(8, '0') + "\n");
|
||||
output.Append("\t\tgroup_mask\t" + ((uint)item.Permissions.GroupMask).ToString("x").PadLeft(8, '0') + "\n");
|
||||
output.Append("\t\teveryone_mask\t" + ((uint)item.Permissions.EveryoneMask).ToString("x").PadLeft(8, '0') + "\n");
|
||||
output.Append("\t\tnext_owner_mask\t" + ((uint)item.Permissions.NextOwnerMask).ToString("x").PadLeft(8, '0') + "\n");
|
||||
output.Append("\t\tcreator_id\t" + item.CreatorID + "\n");
|
||||
output.Append("\t\towner_id\t" + item.OwnerID + "\n");
|
||||
output.Append("\t\tlast_owner_id\t" + item.LastOwnerID + "\n");
|
||||
output.Append("\t\tgroup_id\t" + item.GroupID + "\n");
|
||||
if (item.GroupOwned) output.Append("\t\tgroup_owned\t1\n");
|
||||
output.Append("\t}\n");
|
||||
|
||||
if (Permissions.HasPermissions(item.Permissions.BaseMask, PermissionMask.Modify | PermissionMask.Copy | PermissionMask.Transfer) ||
|
||||
item.AssetUUID == UUID.Zero)
|
||||
{
|
||||
output.Append("\t\tasset_id\t" + item.AssetUUID + "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
output.Append("\t\tshadow_id\t" + InventoryManager.EncryptAssetID(item.AssetUUID) + "\n");
|
||||
}
|
||||
|
||||
output.Append("\t\ttype\t" + Utils.AssetTypeToString(item.AssetType) + "\n");
|
||||
output.Append("\t\tinv_type\t" + Utils.InventoryTypeToString(item.InventoryType) + "\n");
|
||||
output.Append("\t\tflags\t" + item.Flags.ToString().PadLeft(8, '0') + "\n");
|
||||
|
||||
output.Append("\tsale_info\t0\n");
|
||||
output.Append("\t{\n");
|
||||
output.Append("\t\tsale_type\t" + Utils.SaleTypeToString(item.SaleType) + "\n");
|
||||
output.Append("\t\tsale_price\t" + item.SalePrice + "\n");
|
||||
output.Append("\t}\n");
|
||||
|
||||
output.Append("\t\tname\t" + item.Name.Replace('|', '_') + "|\n");
|
||||
output.Append("\t\tdesc\t" + item.Description.Replace('|', '_') + "|\n");
|
||||
output.Append("\t\tcreation_date\t" + Utils.DateTimeToUnixTime(item.CreationDate) + "\n");
|
||||
|
||||
output.Append("\t}\n");
|
||||
|
||||
if (i != EmbeddedItems.Count - 1)
|
||||
{
|
||||
output.Append("}\n{\n");
|
||||
}
|
||||
}
|
||||
|
||||
output.Append("}\n");
|
||||
}
|
||||
|
||||
output.Append("}\n");
|
||||
output.Append("Text length " + (Utils.StringToBytes(body).Length - 1).ToString() + "\n");
|
||||
output.Append(body + "}\n");
|
||||
|
||||
AssetData = Utils.StringToBytes(output.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode the raw asset data including the Linden Text properties
|
||||
/// </summary>
|
||||
/// <returns>true if the AssetData was successfully decoded</returns>
|
||||
public override bool Decode()
|
||||
{
|
||||
string data = Utils.BytesToString(AssetData);
|
||||
EmbeddedItems = new List<InventoryItem>();
|
||||
BodyText = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
string[] lines = data.Split('\n');
|
||||
int i = 0;
|
||||
Match m;
|
||||
|
||||
// Version
|
||||
if (!(m = Regex.Match(lines[i++], @"Linden text version\s+(\d+)")).Success)
|
||||
throw new Exception("could not determine version");
|
||||
int notecardVersion = int.Parse(m.Groups[1].Value);
|
||||
if (notecardVersion < 1 || notecardVersion > 2)
|
||||
throw new Exception("unsuported version");
|
||||
if (!(m = Regex.Match(lines[i++], @"\s*{$")).Success)
|
||||
throw new Exception("wrong format");
|
||||
|
||||
// Embedded items header
|
||||
if (!(m = Regex.Match(lines[i++], @"LLEmbeddedItems version\s+(\d+)")).Success)
|
||||
throw new Exception("could not determine embedded items version version");
|
||||
if (m.Groups[1].Value != "1")
|
||||
throw new Exception("unsuported embedded item version");
|
||||
if (!(m = Regex.Match(lines[i++], @"\s*{$")).Success)
|
||||
throw new Exception("wrong format");
|
||||
|
||||
// Item count
|
||||
if (!(m = Regex.Match(lines[i++], @"count\s+(\d+)")).Success)
|
||||
throw new Exception("wrong format");
|
||||
int count = int.Parse(m.Groups[1].Value);
|
||||
|
||||
// Decode individual items
|
||||
for (int n = 0; n < count; n++)
|
||||
{
|
||||
if (!(m = Regex.Match(lines[i++], @"\s*{$")).Success)
|
||||
throw new Exception("wrong format");
|
||||
|
||||
// Index
|
||||
if (!(m = Regex.Match(lines[i++], @"ext char index\s+(\d+)")).Success)
|
||||
throw new Exception("missing ext char index");
|
||||
//warning CS0219: The variable `index' is assigned but its value is never used
|
||||
//int index = int.Parse(m.Groups[1].Value);
|
||||
|
||||
// Inventory item
|
||||
if (!(m = Regex.Match(lines[i++], @"inv_item\s+0")).Success)
|
||||
throw new Exception("missing inv item");
|
||||
|
||||
// Item itself
|
||||
UUID uuid = UUID.Zero;
|
||||
UUID creatorID = UUID.Zero;
|
||||
UUID ownerID = UUID.Zero;
|
||||
UUID lastOwnerID = UUID.Zero;
|
||||
UUID groupID = UUID.Zero;
|
||||
Permissions permissions = Permissions.NoPermissions;
|
||||
int salePrice = 0;
|
||||
SaleType saleType = SaleType.Not;
|
||||
UUID parentUUID = UUID.Zero;
|
||||
UUID assetUUID = UUID.Zero;
|
||||
AssetType assetType = AssetType.Unknown;
|
||||
InventoryType inventoryType = InventoryType.Unknown;
|
||||
uint flags = 0;
|
||||
string name = string.Empty;
|
||||
string description = string.Empty;
|
||||
DateTime creationDate = Utils.Epoch;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!(m = Regex.Match(lines[i++], @"([^\s]+)(\s+)?(.*)?")).Success)
|
||||
throw new Exception("wrong format");
|
||||
string key = m.Groups[1].Value;
|
||||
string val = m.Groups[3].Value;
|
||||
if (key == "{")
|
||||
continue;
|
||||
if (key == "}")
|
||||
break;
|
||||
else if (key == "permissions")
|
||||
{
|
||||
uint baseMask = 0;
|
||||
uint ownerMask = 0;
|
||||
uint groupMask = 0;
|
||||
uint everyoneMask = 0;
|
||||
uint nextOwnerMask = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!(m = Regex.Match(lines[i++], @"([^\s]+)(\s+)?([^\s]+)?")).Success)
|
||||
throw new Exception("wrong format");
|
||||
string pkey = m.Groups[1].Value;
|
||||
string pval = m.Groups[3].Value;
|
||||
|
||||
if (pkey == "{")
|
||||
continue;
|
||||
if (pkey == "}")
|
||||
break;
|
||||
else if (pkey == "creator_id")
|
||||
{
|
||||
creatorID = new UUID(pval);
|
||||
}
|
||||
else if (pkey == "owner_id")
|
||||
{
|
||||
ownerID = new UUID(pval);
|
||||
}
|
||||
else if (pkey == "last_owner_id")
|
||||
{
|
||||
lastOwnerID = new UUID(pval);
|
||||
}
|
||||
else if (pkey == "group_id")
|
||||
{
|
||||
groupID = new UUID(pval);
|
||||
}
|
||||
else if (pkey == "base_mask")
|
||||
{
|
||||
baseMask = uint.Parse(pval, System.Globalization.NumberStyles.AllowHexSpecifier);
|
||||
}
|
||||
else if (pkey == "owner_mask")
|
||||
{
|
||||
ownerMask = uint.Parse(pval, System.Globalization.NumberStyles.AllowHexSpecifier);
|
||||
}
|
||||
else if (pkey == "group_mask")
|
||||
{
|
||||
groupMask = uint.Parse(pval, System.Globalization.NumberStyles.AllowHexSpecifier);
|
||||
}
|
||||
else if (pkey == "everyone_mask")
|
||||
{
|
||||
everyoneMask = uint.Parse(pval, System.Globalization.NumberStyles.AllowHexSpecifier);
|
||||
}
|
||||
else if (pkey == "next_owner_mask")
|
||||
{
|
||||
nextOwnerMask = uint.Parse(pval, System.Globalization.NumberStyles.AllowHexSpecifier);
|
||||
}
|
||||
}
|
||||
permissions = new Permissions(baseMask, everyoneMask, groupMask, nextOwnerMask, ownerMask);
|
||||
}
|
||||
else if (key == "sale_info")
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (!(m = Regex.Match(lines[i++], @"([^\s]+)(\s+)?([^\s]+)?")).Success)
|
||||
throw new Exception("wrong format");
|
||||
string pkey = m.Groups[1].Value;
|
||||
string pval = m.Groups[3].Value;
|
||||
|
||||
if (pkey == "{")
|
||||
continue;
|
||||
if (pkey == "}")
|
||||
break;
|
||||
else if (pkey == "sale_price")
|
||||
{
|
||||
salePrice = int.Parse(pval);
|
||||
}
|
||||
else if (pkey == "sale_type")
|
||||
{
|
||||
saleType = Utils.StringToSaleType(pval);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (key == "item_id")
|
||||
{
|
||||
uuid = new UUID(val);
|
||||
}
|
||||
else if (key == "parent_id")
|
||||
{
|
||||
parentUUID = new UUID(val);
|
||||
}
|
||||
else if (key == "asset_id")
|
||||
{
|
||||
assetUUID = new UUID(val);
|
||||
}
|
||||
else if (key == "type")
|
||||
{
|
||||
assetType = Utils.StringToAssetType(val);
|
||||
}
|
||||
else if (key == "inv_type")
|
||||
{
|
||||
inventoryType = Utils.StringToInventoryType(val);
|
||||
}
|
||||
else if (key == "flags")
|
||||
{
|
||||
flags = uint.Parse(val, System.Globalization.NumberStyles.AllowHexSpecifier);
|
||||
}
|
||||
else if (key == "name")
|
||||
{
|
||||
name = val.Remove(val.LastIndexOf("|"));
|
||||
}
|
||||
else if (key == "desc")
|
||||
{
|
||||
description = val.Remove(val.LastIndexOf("|"));
|
||||
}
|
||||
else if (key == "creation_date")
|
||||
{
|
||||
creationDate = Utils.UnixTimeToDateTime(int.Parse(val));
|
||||
}
|
||||
}
|
||||
InventoryItem finalEmbedded = InventoryManager.CreateInventoryItem(inventoryType, uuid);
|
||||
|
||||
finalEmbedded.CreatorID = creatorID;
|
||||
finalEmbedded.OwnerID = ownerID;
|
||||
finalEmbedded.LastOwnerID = lastOwnerID;
|
||||
finalEmbedded.GroupID = groupID;
|
||||
finalEmbedded.Permissions = permissions;
|
||||
finalEmbedded.SalePrice = salePrice;
|
||||
finalEmbedded.SaleType = saleType;
|
||||
finalEmbedded.ParentUUID = parentUUID;
|
||||
finalEmbedded.AssetUUID = assetUUID;
|
||||
finalEmbedded.AssetType = assetType;
|
||||
finalEmbedded.Flags = flags;
|
||||
finalEmbedded.Name = name;
|
||||
finalEmbedded.Description = description;
|
||||
finalEmbedded.CreationDate = creationDate;
|
||||
|
||||
EmbeddedItems.Add(finalEmbedded);
|
||||
|
||||
if (!(m = Regex.Match(lines[i++], @"\s*}$")).Success)
|
||||
throw new Exception("wrong format");
|
||||
|
||||
}
|
||||
|
||||
// Text size
|
||||
if (!(m = Regex.Match(lines[i++], @"\s*}$")).Success)
|
||||
throw new Exception("wrong format");
|
||||
if (!(m = Regex.Match(lines[i++], @"Text length\s+(\d+)")).Success)
|
||||
throw new Exception("could not determine text length");
|
||||
|
||||
// Read the rest of the notecard
|
||||
while (i < lines.Length)
|
||||
{
|
||||
BodyText += lines[i++] + "\n";
|
||||
}
|
||||
BodyText = BodyText.Remove(BodyText.LastIndexOf("}"));
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Decoding notecard asset failed: " + ex.Message, Helpers.LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an AssetScriptBinary object containing the
|
||||
/// LSO compiled bytecode of an LSL script
|
||||
/// </summary>
|
||||
public class AssetScriptBinary : Asset
|
||||
{
|
||||
/// <summary>Override the base classes AssetType</summary>
|
||||
public override AssetType AssetType { get { return AssetType.LSLBytecode; } }
|
||||
|
||||
/// <summary>Initializes a new instance of an AssetScriptBinary object</summary>
|
||||
public AssetScriptBinary() { }
|
||||
|
||||
/// <summary>Initializes a new instance of an AssetScriptBinary object with parameters</summary>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public AssetScriptBinary(UUID assetID, byte[] assetData)
|
||||
: base(assetID, assetData)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO: Encodes a scripts contents into a LSO Bytecode file
|
||||
/// </summary>
|
||||
public override void Encode() { }
|
||||
|
||||
/// <summary>
|
||||
/// TODO: Decode LSO Bytecode into a string
|
||||
/// </summary>
|
||||
/// <returns>true</returns>
|
||||
public override bool Decode() { return true; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an LSL Text object containing a string of UTF encoded characters
|
||||
/// </summary>
|
||||
public class AssetScriptText : Asset
|
||||
{
|
||||
/// <summary>Override the base classes AssetType</summary>
|
||||
public override AssetType AssetType { get { return AssetType.LSLText; } }
|
||||
|
||||
/// <summary>A string of characters represting the script contents</summary>
|
||||
public string Source;
|
||||
|
||||
/// <summary>Initializes a new AssetScriptText object</summary>
|
||||
public AssetScriptText() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new AssetScriptText object with parameters
|
||||
/// </summary>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public AssetScriptText(UUID assetID, byte[] assetData) : base(assetID, assetData) { }
|
||||
|
||||
/// <summary>
|
||||
/// Encode a string containing the scripts contents into byte encoded AssetData
|
||||
/// </summary>
|
||||
public override void Encode()
|
||||
{
|
||||
AssetData = Utils.StringToBytes(Source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a byte array containing the scripts contents into a string
|
||||
/// </summary>
|
||||
/// <returns>true if decoding is successful</returns>
|
||||
public override bool Decode()
|
||||
{
|
||||
Source = Utils.BytesToString(AssetData);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Sound Asset
|
||||
/// </summary>
|
||||
public class AssetSound : Asset
|
||||
{
|
||||
/// <summary>Override the base classes AssetType</summary>
|
||||
public override AssetType AssetType { get { return AssetType.Sound; } }
|
||||
|
||||
/// <summary>Initializes a new instance of an AssetSound object</summary>
|
||||
public AssetSound() { }
|
||||
|
||||
/// <summary>Initializes a new instance of an AssetSound object with parameters</summary>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public AssetSound(UUID assetID, byte[] assetData)
|
||||
: base(assetID, assetData)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO: Encodes a sound file
|
||||
/// </summary>
|
||||
public override void Encode() { }
|
||||
|
||||
/// <summary>
|
||||
/// TODO: Decode a sound file
|
||||
/// </summary>
|
||||
/// <returns>true</returns>
|
||||
public override bool Decode() { return true; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse;
|
||||
using OpenMetaverse.Imaging;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a texture
|
||||
/// </summary>
|
||||
public class AssetTexture : Asset
|
||||
{
|
||||
/// <summary>Override the base classes AssetType</summary>
|
||||
public override AssetType AssetType { get { return AssetType.Texture; } }
|
||||
|
||||
/// <summary>A <seealso cref="ManagedImage"/> object containing image data</summary>
|
||||
public ManagedImage Image;
|
||||
|
||||
/// <summary></summary>
|
||||
public OpenJPEG.J2KLayerInfo[] LayerInfo;
|
||||
|
||||
/// <summary></summary>
|
||||
public int Components;
|
||||
|
||||
/// <summary>Initializes a new instance of an AssetTexture object</summary>
|
||||
public AssetTexture() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of an AssetTexture object
|
||||
/// </summary>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public AssetTexture(UUID assetID, byte[] assetData) : base(assetID, assetData) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of an AssetTexture object
|
||||
/// </summary>
|
||||
/// <param name="image">A <seealso cref="ManagedImage"/> object containing texture data</param>
|
||||
public AssetTexture(ManagedImage image)
|
||||
{
|
||||
Image = image;
|
||||
Components = 0;
|
||||
if ((Image.Channels & ManagedImage.ImageChannels.Color) != 0)
|
||||
Components += 3;
|
||||
if ((Image.Channels & ManagedImage.ImageChannels.Gray) != 0)
|
||||
++Components;
|
||||
if ((Image.Channels & ManagedImage.ImageChannels.Bump) != 0)
|
||||
++Components;
|
||||
if ((Image.Channels & ManagedImage.ImageChannels.Alpha) != 0)
|
||||
++Components;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the <seealso cref="AssetData"/> byte array with a JPEG2000
|
||||
/// encoded image created from the data in <seealso cref="Image"/>
|
||||
/// </summary>
|
||||
public override void Encode()
|
||||
{
|
||||
AssetData = OpenJPEG.Encode(Image);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes the JPEG2000 data in <code>AssetData</code> to the
|
||||
/// <seealso cref="ManagedImage"/> object <seealso cref="Image"/>
|
||||
/// </summary>
|
||||
/// <returns>True if the decoding was successful, otherwise false</returns>
|
||||
public override bool Decode()
|
||||
{
|
||||
if (AssetData != null && AssetData.Length > 0)
|
||||
{
|
||||
this.Components = 0;
|
||||
|
||||
if (OpenJPEG.DecodeToImage(AssetData, out Image))
|
||||
{
|
||||
if ((Image.Channels & ManagedImage.ImageChannels.Color) != 0)
|
||||
Components += 3;
|
||||
if ((Image.Channels & ManagedImage.ImageChannels.Gray) != 0)
|
||||
++Components;
|
||||
if ((Image.Channels & ManagedImage.ImageChannels.Bump) != 0)
|
||||
++Components;
|
||||
if ((Image.Channels & ManagedImage.ImageChannels.Alpha) != 0)
|
||||
++Components;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes the begin and end byte positions for each quality layer in
|
||||
/// the image
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool DecodeLayerBoundaries()
|
||||
{
|
||||
return OpenJPEG.DecodeLayerBoundaries(AssetData, out LayerInfo, out Components);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using OpenMetaverse;
|
||||
|
||||
namespace OpenMetaverse.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Wearable Asset, Clothing, Hair, Skin, Etc
|
||||
/// </summary>
|
||||
public abstract class AssetWearable : Asset
|
||||
{
|
||||
/// <summary>A string containing the name of the asset</summary>
|
||||
public string Name = String.Empty;
|
||||
/// <summary>A string containing a short description of the asset</summary>
|
||||
public string Description = String.Empty;
|
||||
/// <summary>The Assets WearableType</summary>
|
||||
public WearableType WearableType = WearableType.Shape;
|
||||
/// <summary>The For-Sale status of the object</summary>
|
||||
public SaleType ForSale;
|
||||
/// <summary>An Integer representing the purchase price of the asset</summary>
|
||||
public int SalePrice;
|
||||
/// <summary>The <seealso cref="UUID"/> of the assets creator</summary>
|
||||
public UUID Creator;
|
||||
/// <summary>The <seealso cref="UUID"/> of the assets current owner</summary>
|
||||
public UUID Owner;
|
||||
/// <summary>The <seealso cref="UUID"/> of the assets prior owner</summary>
|
||||
public UUID LastOwner;
|
||||
/// <summary>The <seealso cref="UUID"/> of the Group this asset is set to</summary>
|
||||
public UUID Group;
|
||||
/// <summary>True if the asset is owned by a <seealso cref="Group"/></summary>
|
||||
public bool GroupOwned;
|
||||
/// <summary>The Permissions mask of the asset</summary>
|
||||
public Permissions Permissions;
|
||||
/// <summary>A Dictionary containing Key/Value pairs of the objects parameters</summary>
|
||||
public Dictionary<int, float> Params = new Dictionary<int, float>();
|
||||
/// <summary>A Dictionary containing Key/Value pairs where the Key is the textures Index and the Value is the Textures <seealso cref="UUID"/></summary>
|
||||
public Dictionary<AvatarTextureIndex, UUID> Textures = new Dictionary<AvatarTextureIndex, UUID>();
|
||||
|
||||
/// <summary>Initializes a new instance of an AssetWearable object</summary>
|
||||
public AssetWearable() { }
|
||||
|
||||
/// <summary>Initializes a new instance of an AssetWearable object with parameters</summary>
|
||||
/// <param name="assetID">A unique <see cref="UUID"/> specific to this asset</param>
|
||||
/// <param name="assetData">A byte array containing the raw asset data</param>
|
||||
public AssetWearable(UUID assetID, byte[] assetData) : base(assetID, assetData) { }
|
||||
|
||||
/// <summary>
|
||||
/// Decode an assets byte encoded data to a string
|
||||
/// </summary>
|
||||
/// <returns>true if the asset data was decoded successfully</returns>
|
||||
public override bool Decode()
|
||||
{
|
||||
if (AssetData == null || AssetData.Length == 0)
|
||||
return false;
|
||||
|
||||
int version = -1;
|
||||
Permissions = new Permissions();
|
||||
|
||||
try
|
||||
{
|
||||
string data = Utils.BytesToString(AssetData);
|
||||
|
||||
data = data.Replace("\r", String.Empty);
|
||||
string[] lines = data.Split('\n');
|
||||
for (int stri = 0; stri < lines.Length; stri++)
|
||||
{
|
||||
if (stri == 0)
|
||||
{
|
||||
string versionstring = lines[stri];
|
||||
if (versionstring.Split(' ').Length == 1)
|
||||
version = Int32.Parse(versionstring);
|
||||
else
|
||||
version = Int32.Parse(versionstring.Split(' ')[2]);
|
||||
if (version != 22 && version != 18 && version != 16 && version != 15)
|
||||
return false;
|
||||
}
|
||||
else if (stri == 1)
|
||||
{
|
||||
Name = lines[stri];
|
||||
}
|
||||
else if (stri == 2)
|
||||
{
|
||||
Description = lines[stri];
|
||||
}
|
||||
else
|
||||
{
|
||||
string line = lines[stri].Trim();
|
||||
string[] fields = line.Split('\t');
|
||||
|
||||
if (fields.Length == 1)
|
||||
{
|
||||
fields = line.Split(' ');
|
||||
if (fields[0] == "parameters")
|
||||
{
|
||||
int count = Int32.Parse(fields[1]) + stri;
|
||||
for (; stri < count; )
|
||||
{
|
||||
stri++;
|
||||
line = lines[stri].Trim();
|
||||
fields = line.Split(' ');
|
||||
|
||||
int id = 0;
|
||||
|
||||
// Special handling for -0 edge case
|
||||
if (fields[0] != "-0")
|
||||
id = Int32.Parse(fields[0]);
|
||||
|
||||
if (fields[1] == ",")
|
||||
fields[1] = "0";
|
||||
else
|
||||
fields[1] = fields[1].Replace(',', '.');
|
||||
|
||||
float weight = float.Parse(fields[1], System.Globalization.NumberStyles.Float,
|
||||
Utils.EnUsCulture.NumberFormat);
|
||||
|
||||
Params[id] = weight;
|
||||
}
|
||||
}
|
||||
else if (fields[0] == "textures")
|
||||
{
|
||||
int count = Int32.Parse(fields[1]) + stri;
|
||||
for (; stri < count; )
|
||||
{
|
||||
stri++;
|
||||
line = lines[stri].Trim();
|
||||
fields = line.Split(' ');
|
||||
|
||||
AvatarTextureIndex id = (AvatarTextureIndex)Int32.Parse(fields[0]);
|
||||
UUID texture = new UUID(fields[1]);
|
||||
|
||||
Textures[id] = texture;
|
||||
}
|
||||
}
|
||||
else if (fields[0] == "type")
|
||||
{
|
||||
WearableType = (WearableType)Int32.Parse(fields[1]);
|
||||
}
|
||||
|
||||
}
|
||||
else if (fields.Length == 2)
|
||||
{
|
||||
switch (fields[0])
|
||||
{
|
||||
case "creator_mask":
|
||||
// Deprecated, apply this as the base mask
|
||||
Permissions.BaseMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
|
||||
break;
|
||||
case "base_mask":
|
||||
Permissions.BaseMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
|
||||
break;
|
||||
case "owner_mask":
|
||||
Permissions.OwnerMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
|
||||
break;
|
||||
case "group_mask":
|
||||
Permissions.GroupMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
|
||||
break;
|
||||
case "everyone_mask":
|
||||
Permissions.EveryoneMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
|
||||
break;
|
||||
case "next_owner_mask":
|
||||
Permissions.NextOwnerMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
|
||||
break;
|
||||
case "creator_id":
|
||||
Creator = new UUID(fields[1]);
|
||||
break;
|
||||
case "owner_id":
|
||||
Owner = new UUID(fields[1]);
|
||||
break;
|
||||
case "last_owner_id":
|
||||
LastOwner = new UUID(fields[1]);
|
||||
break;
|
||||
case "group_id":
|
||||
Group = new UUID(fields[1]);
|
||||
break;
|
||||
case "group_owned":
|
||||
GroupOwned = (Int32.Parse(fields[1]) != 0);
|
||||
break;
|
||||
case "sale_type":
|
||||
ForSale = Utils.StringToSaleType(fields[1]);
|
||||
break;
|
||||
case "sale_price":
|
||||
SalePrice = Int32.Parse(fields[1]);
|
||||
break;
|
||||
case "sale_info":
|
||||
// Container for sale_type and sale_price, ignore
|
||||
break;
|
||||
case "perm_mask":
|
||||
// Deprecated, apply this as the next owner mask
|
||||
Permissions.NextOwnerMask = (PermissionMask)UInt32.Parse(fields[1], System.Globalization.NumberStyles.HexNumber);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Failed decoding wearable asset " + this.AssetID + ": " + ex.Message,
|
||||
Helpers.LogLevel.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode the assets string represantion into a format consumable by the asset server
|
||||
/// </summary>
|
||||
public override void Encode()
|
||||
{
|
||||
const string NL = "\n";
|
||||
|
||||
StringBuilder data = new StringBuilder("LLWearable version 22\n");
|
||||
data.Append(Name); data.Append(NL); data.Append(NL);
|
||||
data.Append("\tpermissions 0\n\t{\n");
|
||||
data.Append("\t\tbase_mask\t"); data.Append(Utils.UIntToHexString((uint)Permissions.BaseMask)); data.Append(NL);
|
||||
data.Append("\t\towner_mask\t"); data.Append(Utils.UIntToHexString((uint)Permissions.OwnerMask)); data.Append(NL);
|
||||
data.Append("\t\tgroup_mask\t"); data.Append(Utils.UIntToHexString((uint)Permissions.GroupMask)); data.Append(NL);
|
||||
data.Append("\t\teveryone_mask\t"); data.Append(Utils.UIntToHexString((uint)Permissions.EveryoneMask)); data.Append(NL);
|
||||
data.Append("\t\tnext_owner_mask\t"); data.Append(Utils.UIntToHexString((uint)Permissions.NextOwnerMask)); data.Append(NL);
|
||||
data.Append("\t\tcreator_id\t"); data.Append(Creator.ToString()); data.Append(NL);
|
||||
data.Append("\t\towner_id\t"); data.Append(Owner.ToString()); data.Append(NL);
|
||||
data.Append("\t\tlast_owner_id\t"); data.Append(LastOwner.ToString()); data.Append(NL);
|
||||
data.Append("\t\tgroup_id\t"); data.Append(Group.ToString()); data.Append(NL);
|
||||
if (GroupOwned) data.Append("\t\tgroup_owned\t1\n");
|
||||
data.Append("\t}\n");
|
||||
data.Append("\tsale_info\t0\n");
|
||||
data.Append("\t{\n");
|
||||
data.Append("\t\tsale_type\t"); data.Append(Utils.SaleTypeToString(ForSale)); data.Append(NL);
|
||||
data.Append("\t\tsale_price\t"); data.Append(SalePrice); data.Append(NL);
|
||||
data.Append("\t}\n");
|
||||
data.Append("type "); data.Append((int)WearableType); data.Append(NL);
|
||||
|
||||
data.Append("parameters "); data.Append(Params.Count); data.Append(NL);
|
||||
foreach (KeyValuePair<int, float> param in Params)
|
||||
{
|
||||
data.Append(param.Key); data.Append(" "); data.Append(Helpers.FloatToTerseString(param.Value)); data.Append(NL);
|
||||
}
|
||||
|
||||
data.Append("textures "); data.Append(Textures.Count); data.Append(NL);
|
||||
foreach (KeyValuePair<AvatarTextureIndex, UUID> texture in Textures)
|
||||
{
|
||||
data.Append((byte)texture.Key); data.Append(" "); data.Append(texture.Value.ToString()); data.Append(NL);
|
||||
}
|
||||
|
||||
AssetData = Utils.StringToBytes(data.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using OpenMetaverse.Packets;
|
||||
using OpenMetaverse.StructuredData;
|
||||
using System.Reflection;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Avatar profile flags
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ProfileFlags : uint
|
||||
{
|
||||
AllowPublish = 1,
|
||||
MaturePublish = 2,
|
||||
Identified = 4,
|
||||
Transacted = 8,
|
||||
Online = 16
|
||||
}
|
||||
|
||||
#endregion Enums
|
||||
|
||||
/// <summary>
|
||||
/// Represents an avatar (other than your own)
|
||||
/// </summary>
|
||||
public class Avatar : Primitive
|
||||
{
|
||||
#region Subclasses
|
||||
|
||||
/// <summary>
|
||||
/// Positive and negative ratings
|
||||
/// </summary>
|
||||
public struct Statistics
|
||||
{
|
||||
/// <summary>Positive ratings for Behavior</summary>
|
||||
public int BehaviorPositive;
|
||||
/// <summary>Negative ratings for Behavior</summary>
|
||||
public int BehaviorNegative;
|
||||
/// <summary>Positive ratings for Appearance</summary>
|
||||
public int AppearancePositive;
|
||||
/// <summary>Negative ratings for Appearance</summary>
|
||||
public int AppearanceNegative;
|
||||
/// <summary>Positive ratings for Building</summary>
|
||||
public int BuildingPositive;
|
||||
/// <summary>Negative ratings for Building</summary>
|
||||
public int BuildingNegative;
|
||||
/// <summary>Positive ratings given by this avatar</summary>
|
||||
public int GivenPositive;
|
||||
/// <summary>Negative ratings given by this avatar</summary>
|
||||
public int GivenNegative;
|
||||
|
||||
public OSD GetOSD()
|
||||
{
|
||||
OSDMap tex = new OSDMap(8);
|
||||
tex["behavior_positive"] = OSD.FromInteger(BehaviorPositive);
|
||||
tex["behavior_negative"] = OSD.FromInteger(BehaviorNegative);
|
||||
tex["appearance_positive"] = OSD.FromInteger(AppearancePositive);
|
||||
tex["appearance_negative"] = OSD.FromInteger(AppearanceNegative);
|
||||
tex["buildings_positive"] = OSD.FromInteger(BuildingPositive);
|
||||
tex["buildings_negative"] = OSD.FromInteger(BuildingNegative);
|
||||
tex["given_positive"] = OSD.FromInteger(GivenPositive);
|
||||
tex["given_negative"] = OSD.FromInteger(GivenNegative);
|
||||
return tex;
|
||||
}
|
||||
|
||||
public static Statistics FromOSD(OSD O)
|
||||
{
|
||||
Statistics S = new Statistics();
|
||||
OSDMap tex = (OSDMap)O;
|
||||
|
||||
S.BehaviorPositive = tex["behavior_positive"].AsInteger();
|
||||
S.BuildingNegative = tex["behavior_negative"].AsInteger();
|
||||
S.AppearancePositive = tex["appearance_positive"].AsInteger();
|
||||
S.AppearanceNegative = tex["appearance_negative"].AsInteger();
|
||||
S.BuildingPositive = tex["buildings_positive"].AsInteger();
|
||||
S.BuildingNegative = tex["buildings_negative"].AsInteger();
|
||||
S.GivenPositive = tex["given_positive"].AsInteger();
|
||||
S.GivenNegative = tex["given_negative"].AsInteger();
|
||||
|
||||
|
||||
return S;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Avatar properties including about text, profile URL, image IDs and
|
||||
/// publishing settings
|
||||
/// </summary>
|
||||
public struct AvatarProperties
|
||||
{
|
||||
/// <summary>First Life about text</summary>
|
||||
public string FirstLifeText;
|
||||
/// <summary>First Life image ID</summary>
|
||||
public UUID FirstLifeImage;
|
||||
/// <summary></summary>
|
||||
public UUID Partner;
|
||||
/// <summary></summary>
|
||||
public string AboutText;
|
||||
/// <summary></summary>
|
||||
public string BornOn;
|
||||
/// <summary></summary>
|
||||
public string CharterMember;
|
||||
/// <summary>Profile image ID</summary>
|
||||
public UUID ProfileImage;
|
||||
/// <summary>Flags of the profile</summary>
|
||||
public ProfileFlags Flags;
|
||||
/// <summary>Web URL for this profile</summary>
|
||||
public string ProfileURL;
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>Should this profile be published on the web</summary>
|
||||
public bool AllowPublish
|
||||
{
|
||||
get { return ((Flags & ProfileFlags.AllowPublish) != 0); }
|
||||
set
|
||||
{
|
||||
if (value == true)
|
||||
Flags |= ProfileFlags.AllowPublish;
|
||||
else
|
||||
Flags &= ~ProfileFlags.AllowPublish;
|
||||
}
|
||||
}
|
||||
/// <summary>Avatar Online Status</summary>
|
||||
public bool Online
|
||||
{
|
||||
get { return ((Flags & ProfileFlags.Online) != 0); }
|
||||
set
|
||||
{
|
||||
if (value == true)
|
||||
Flags |= ProfileFlags.Online;
|
||||
else
|
||||
Flags &= ~ProfileFlags.Online;
|
||||
}
|
||||
}
|
||||
/// <summary>Is this a mature profile</summary>
|
||||
public bool MaturePublish
|
||||
{
|
||||
get { return ((Flags & ProfileFlags.MaturePublish) != 0); }
|
||||
set
|
||||
{
|
||||
if (value == true)
|
||||
Flags |= ProfileFlags.MaturePublish;
|
||||
else
|
||||
Flags &= ~ProfileFlags.MaturePublish;
|
||||
}
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool Identified
|
||||
{
|
||||
get { return ((Flags & ProfileFlags.Identified) != 0); }
|
||||
set
|
||||
{
|
||||
if (value == true)
|
||||
Flags |= ProfileFlags.Identified;
|
||||
else
|
||||
Flags &= ~ProfileFlags.Identified;
|
||||
}
|
||||
}
|
||||
/// <summary></summary>
|
||||
public bool Transacted
|
||||
{
|
||||
get { return ((Flags & ProfileFlags.Transacted) != 0); }
|
||||
set
|
||||
{
|
||||
if (value == true)
|
||||
Flags |= ProfileFlags.Transacted;
|
||||
else
|
||||
Flags &= ~ProfileFlags.Transacted;
|
||||
}
|
||||
}
|
||||
|
||||
public OSD GetOSD()
|
||||
{
|
||||
OSDMap tex = new OSDMap(9);
|
||||
tex["first_life_text"] = OSD.FromString(FirstLifeText);
|
||||
tex["first_life_image"] = OSD.FromUUID(FirstLifeImage);
|
||||
tex["partner"] = OSD.FromUUID(Partner);
|
||||
tex["about_text"] = OSD.FromString(AboutText);
|
||||
tex["born_on"] = OSD.FromString(BornOn);
|
||||
tex["charter_member"] = OSD.FromString(CharterMember);
|
||||
tex["profile_image"] = OSD.FromUUID(ProfileImage);
|
||||
tex["flags"] = OSD.FromInteger((byte)Flags);
|
||||
tex["profile_url"] = OSD.FromString(ProfileURL);
|
||||
return tex;
|
||||
}
|
||||
|
||||
public static AvatarProperties FromOSD(OSD O)
|
||||
{
|
||||
AvatarProperties A = new AvatarProperties();
|
||||
OSDMap tex = (OSDMap)O;
|
||||
|
||||
A.FirstLifeText = tex["first_life_text"].AsString();
|
||||
A.FirstLifeImage = tex["first_life_image"].AsUUID();
|
||||
A.Partner = tex["partner"].AsUUID();
|
||||
A.AboutText = tex["about_text"].AsString();
|
||||
A.BornOn = tex["born_on"].AsString();
|
||||
A.CharterMember = tex["chart_member"].AsString();
|
||||
A.ProfileImage = tex["profile_image"].AsUUID();
|
||||
A.Flags = (ProfileFlags)tex["flags"].AsInteger();
|
||||
A.ProfileURL = tex["profile_url"].AsString();
|
||||
|
||||
return A;
|
||||
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Avatar interests including spoken languages, skills, and "want to"
|
||||
/// choices
|
||||
/// </summary>
|
||||
public struct Interests
|
||||
{
|
||||
/// <summary>Languages profile field</summary>
|
||||
public string LanguagesText;
|
||||
/// <summary></summary>
|
||||
// FIXME:
|
||||
public uint SkillsMask;
|
||||
/// <summary></summary>
|
||||
public string SkillsText;
|
||||
/// <summary></summary>
|
||||
// FIXME:
|
||||
public uint WantToMask;
|
||||
/// <summary></summary>
|
||||
public string WantToText;
|
||||
|
||||
public OSD GetOSD()
|
||||
{
|
||||
OSDMap InterestsOSD = new OSDMap(5);
|
||||
InterestsOSD["languages_text"] = OSD.FromString(LanguagesText);
|
||||
InterestsOSD["skills_mask"] = OSD.FromUInteger(SkillsMask);
|
||||
InterestsOSD["skills_text"] = OSD.FromString(SkillsText);
|
||||
InterestsOSD["want_to_mask"] = OSD.FromUInteger(WantToMask);
|
||||
InterestsOSD["want_to_text"] = OSD.FromString(WantToText);
|
||||
return InterestsOSD;
|
||||
}
|
||||
|
||||
public static Interests FromOSD(OSD O)
|
||||
{
|
||||
Interests I = new Interests();
|
||||
OSDMap tex = (OSDMap)O;
|
||||
|
||||
I.LanguagesText = tex["languages_text"].AsString();
|
||||
I.SkillsMask = tex["skills_mask"].AsUInteger();
|
||||
I.SkillsText = tex["skills_text"].AsString();
|
||||
I.WantToMask = tex["want_to_mask"].AsUInteger();
|
||||
I.WantToText = tex["want_to_text"].AsString();
|
||||
|
||||
return I;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Subclasses
|
||||
|
||||
#region Public Members
|
||||
|
||||
/// <summary>Groups that this avatar is a member of</summary>
|
||||
public List<UUID> Groups = new List<UUID>();
|
||||
/// <summary>Positive and negative ratings</summary>
|
||||
public Statistics ProfileStatistics;
|
||||
/// <summary>Avatar properties including about text, profile URL, image IDs and
|
||||
/// publishing settings</summary>
|
||||
public AvatarProperties ProfileProperties;
|
||||
/// <summary>Avatar interests including spoken languages, skills, and "want to"
|
||||
/// choices</summary>
|
||||
public Interests ProfileInterests;
|
||||
/// <summary>Movement control flags for avatars. Typically not set or used by
|
||||
/// clients. To move your avatar, use Client.Self.Movement instead</summary>
|
||||
public AgentManager.ControlFlags ControlFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the visual parameters describing the deformation of the avatar
|
||||
/// </summary>
|
||||
public byte[] VisualParameters = null;
|
||||
|
||||
/// <summary>
|
||||
/// Appearance version. Value greater than 0 indicates using server side baking
|
||||
/// </summary>
|
||||
public byte AppearanceVersion = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Version of the Current Outfit Folder that the appearance is based on
|
||||
/// </summary>
|
||||
public int COFVersion = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Appearance flags. Introduced with server side baking, currently unused.
|
||||
/// </summary>
|
||||
public AppearanceFlags AppearanceFlags = AppearanceFlags.None;
|
||||
|
||||
/// <summary>
|
||||
/// List of current avatar animations
|
||||
/// </summary>
|
||||
public List<Animation> Animations;
|
||||
|
||||
#endregion Public Members
|
||||
|
||||
protected string name;
|
||||
protected string groupName;
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>First name</summary>
|
||||
public string FirstName
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = 0; i < NameValues.Length; i++)
|
||||
{
|
||||
if (NameValues[i].Name == "FirstName" && NameValues[i].Type == NameValue.ValueType.String)
|
||||
return (string)NameValues[i].Value;
|
||||
}
|
||||
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Last name</summary>
|
||||
public string LastName
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = 0; i < NameValues.Length; i++)
|
||||
{
|
||||
if (NameValues[i].Name == "LastName" && NameValues[i].Type == NameValue.ValueType.String)
|
||||
return (string)NameValues[i].Value;
|
||||
}
|
||||
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Full name</summary>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!String.IsNullOrEmpty(name))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
else if (NameValues != null && NameValues.Length > 0)
|
||||
{
|
||||
lock (NameValues)
|
||||
{
|
||||
string firstName = String.Empty;
|
||||
string lastName = String.Empty;
|
||||
|
||||
for (int i = 0; i < NameValues.Length; i++)
|
||||
{
|
||||
if (NameValues[i].Name == "FirstName" && NameValues[i].Type == NameValue.ValueType.String)
|
||||
firstName = (string)NameValues[i].Value;
|
||||
else if (NameValues[i].Name == "LastName" && NameValues[i].Type == NameValue.ValueType.String)
|
||||
lastName = (string)NameValues[i].Value;
|
||||
}
|
||||
|
||||
if (firstName != String.Empty && lastName != String.Empty)
|
||||
{
|
||||
name = String.Format("{0} {1}", firstName, lastName);
|
||||
return name;
|
||||
}
|
||||
else
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Active group</summary>
|
||||
public string GroupName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!String.IsNullOrEmpty(groupName))
|
||||
{
|
||||
return groupName;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (NameValues == null || NameValues.Length == 0)
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (NameValues)
|
||||
{
|
||||
for (int i = 0; i < NameValues.Length; i++)
|
||||
{
|
||||
if (NameValues[i].Name == "Title" && NameValues[i].Type == NameValue.ValueType.String)
|
||||
{
|
||||
groupName = (string)NameValues[i].Value;
|
||||
return groupName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override OSD GetOSD()
|
||||
{
|
||||
OSDMap Avi = (OSDMap)base.GetOSD();
|
||||
|
||||
OSDArray grp = new OSDArray();
|
||||
Groups.ForEach(delegate(UUID u) { grp.Add(OSD.FromUUID(u)); });
|
||||
|
||||
OSDArray vp = new OSDArray();
|
||||
|
||||
for (int i = 0; i < VisualParameters.Length; i++)
|
||||
{
|
||||
vp.Add(OSD.FromInteger(VisualParameters[i]));
|
||||
}
|
||||
|
||||
Avi["groups"] = grp;
|
||||
Avi["profile_statistics"] = ProfileStatistics.GetOSD();
|
||||
Avi["profile_properties"] = ProfileProperties.GetOSD();
|
||||
Avi["profile_interest"] = ProfileInterests.GetOSD();
|
||||
Avi["control_flags"] = OSD.FromInteger((byte)ControlFlags);
|
||||
Avi["visual_parameters"] = vp;
|
||||
Avi["first_name"] = OSD.FromString(FirstName);
|
||||
Avi["last_name"] = OSD.FromString(LastName);
|
||||
Avi["group_name"] = OSD.FromString(GroupName);
|
||||
|
||||
return Avi;
|
||||
|
||||
}
|
||||
|
||||
public static new Avatar FromOSD(OSD O)
|
||||
{
|
||||
|
||||
OSDMap tex = (OSDMap)O;
|
||||
|
||||
Avatar A = new Avatar();
|
||||
|
||||
Primitive P = Primitive.FromOSD(O);
|
||||
|
||||
Type Prim = typeof(Primitive);
|
||||
|
||||
FieldInfo[] Fields = Prim.GetFields();
|
||||
|
||||
for (int x = 0; x < Fields.Length; x++)
|
||||
{
|
||||
Logger.Log("Field Matched in FromOSD: "+Fields[x].Name, Helpers.LogLevel.Debug);
|
||||
Fields[x].SetValue(A, Fields[x].GetValue(P));
|
||||
}
|
||||
|
||||
A.Groups = new List<UUID>();
|
||||
|
||||
foreach (OSD U in (OSDArray)tex["groups"])
|
||||
{
|
||||
A.Groups.Add(U.AsUUID());
|
||||
}
|
||||
|
||||
A.ProfileStatistics = Statistics.FromOSD(tex["profile_statistics"]);
|
||||
A.ProfileProperties = AvatarProperties.FromOSD(tex["profile_properties"]);
|
||||
A.ProfileInterests = Interests.FromOSD(tex["profile_interest"]);
|
||||
A.ControlFlags = (AgentManager.ControlFlags)tex["control_flags"].AsInteger();
|
||||
|
||||
OSDArray vp = (OSDArray)tex["visual_parameters"];
|
||||
A.VisualParameters = new byte[vp.Count];
|
||||
|
||||
for (int i = 0; i < vp.Count; i++)
|
||||
{
|
||||
A.VisualParameters[i] = (byte)vp[i].AsInteger();
|
||||
}
|
||||
|
||||
// *********************From Code Above *******************************
|
||||
/*if (NameValues[i].Name == "FirstName" && NameValues[i].Type == NameValue.ValueType.String)
|
||||
firstName = (string)NameValues[i].Value;
|
||||
else if (NameValues[i].Name == "LastName" && NameValues[i].Type == NameValue.ValueType.String)
|
||||
lastName = (string)NameValues[i].Value;*/
|
||||
// ********************************************************************
|
||||
|
||||
A.NameValues = new NameValue[3];
|
||||
|
||||
NameValue First = new NameValue();
|
||||
First.Name = "FirstName";
|
||||
First.Type = NameValue.ValueType.String;
|
||||
First.Value = tex["first_name"].AsString();
|
||||
|
||||
NameValue Last = new NameValue();
|
||||
Last.Name = "LastName";
|
||||
Last.Type = NameValue.ValueType.String;
|
||||
Last.Value = tex["last_name"].AsString();
|
||||
|
||||
// ***************From Code Above***************
|
||||
// if (NameValues[i].Name == "Title" && NameValues[i].Type == NameValue.ValueType.String)
|
||||
// *********************************************
|
||||
|
||||
NameValue Group = new NameValue();
|
||||
Group.Name = "Title";
|
||||
Group.Type = NameValue.ValueType.String;
|
||||
Group.Value = tex["group_name"].AsString();
|
||||
|
||||
|
||||
|
||||
A.NameValues[0] = First;
|
||||
A.NameValues[1] = Last;
|
||||
A.NameValues[2] = Group;
|
||||
|
||||
return A;
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public Avatar()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,559 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
using System;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads in a byte array of an Animation Asset created by the SecondLife(tm) client.
|
||||
/// </summary>
|
||||
public class BinBVHAnimationReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Rotation Keyframe count (used internally)
|
||||
/// </summary>
|
||||
private int rotationkeys;
|
||||
|
||||
/// <summary>
|
||||
/// Position Keyframe count (used internally)
|
||||
/// </summary>
|
||||
private int positionkeys;
|
||||
|
||||
public UInt16 unknown0; // Always 1
|
||||
public UInt16 unknown1; // Always 0
|
||||
|
||||
/// <summary>
|
||||
/// Animation Priority
|
||||
/// </summary>
|
||||
public int Priority;
|
||||
|
||||
/// <summary>
|
||||
/// The animation length in seconds.
|
||||
/// </summary>
|
||||
public Single Length;
|
||||
|
||||
/// <summary>
|
||||
/// Expression set in the client. Null if [None] is selected
|
||||
/// </summary>
|
||||
public string ExpressionName; // "" (null)
|
||||
|
||||
/// <summary>
|
||||
/// The time in seconds to start the animation
|
||||
/// </summary>
|
||||
public Single InPoint;
|
||||
|
||||
/// <summary>
|
||||
/// The time in seconds to end the animation
|
||||
/// </summary>
|
||||
public Single OutPoint;
|
||||
|
||||
/// <summary>
|
||||
/// Loop the animation
|
||||
/// </summary>
|
||||
public bool Loop;
|
||||
|
||||
/// <summary>
|
||||
/// Meta data. Ease in Seconds.
|
||||
/// </summary>
|
||||
public Single EaseInTime;
|
||||
|
||||
/// <summary>
|
||||
/// Meta data. Ease out seconds.
|
||||
/// </summary>
|
||||
public Single EaseOutTime;
|
||||
|
||||
/// <summary>
|
||||
/// Meta Data for the Hand Pose
|
||||
/// </summary>
|
||||
public uint HandPose;
|
||||
|
||||
/// <summary>
|
||||
/// Number of joints defined in the animation
|
||||
/// </summary>
|
||||
public uint JointCount;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Contains an array of joints
|
||||
/// </summary>
|
||||
public binBVHJoint[] joints;
|
||||
|
||||
/// <summary>
|
||||
/// Searialize an animation asset into it's joints/keyframes/meta data
|
||||
/// </summary>
|
||||
/// <param name="animationdata"></param>
|
||||
public BinBVHAnimationReader(byte[] animationdata)
|
||||
{
|
||||
int i = 0;
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
unknown0 = Utils.BytesToUInt16(EndianSwap(animationdata, i, 2)); i += 2; // Always 1
|
||||
unknown1 = Utils.BytesToUInt16(EndianSwap(animationdata, i, 2)); i += 2; // Always 0
|
||||
Priority = Utils.BytesToInt(EndianSwap(animationdata, i, 4)); i += 4;
|
||||
Length = Utils.BytesToFloat(EndianSwap(animationdata, i, 4), 0); i += 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
unknown0 = Utils.BytesToUInt16(animationdata, i); i += 2; // Always 1
|
||||
unknown1 = Utils.BytesToUInt16(animationdata, i); i += 2; // Always 0
|
||||
Priority = Utils.BytesToInt(animationdata, i); i += 4;
|
||||
Length = Utils.BytesToFloat(animationdata, i); i += 4;
|
||||
}
|
||||
ExpressionName = ReadBytesUntilNull(animationdata, ref i);
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
InPoint = Utils.BytesToFloat(EndianSwap(animationdata, i, 4), 0); i += 4;
|
||||
OutPoint = Utils.BytesToFloat(EndianSwap(animationdata, i, 4), 0); i += 4;
|
||||
Loop = (Utils.BytesToInt(EndianSwap(animationdata, i, 4)) != 0); i += 4;
|
||||
EaseInTime = Utils.BytesToFloat(EndianSwap(animationdata, i, 4), 0); i += 4;
|
||||
EaseOutTime = Utils.BytesToFloat(EndianSwap(animationdata, i, 4), 0); i += 4;
|
||||
HandPose = Utils.BytesToUInt(EndianSwap(animationdata, i, 4)); i += 4; // Handpose?
|
||||
|
||||
JointCount = Utils.BytesToUInt(animationdata, i); i += 4; // Get Joint count
|
||||
}
|
||||
else
|
||||
{
|
||||
InPoint = Utils.BytesToFloat(animationdata, i); i += 4;
|
||||
OutPoint = Utils.BytesToFloat(animationdata, i); i += 4;
|
||||
Loop = (Utils.BytesToInt(animationdata, i) != 0); i += 4;
|
||||
EaseInTime = Utils.BytesToFloat(animationdata, i); i += 4;
|
||||
EaseOutTime = Utils.BytesToFloat(animationdata, i); i += 4;
|
||||
HandPose = Utils.BytesToUInt(animationdata, i); i += 4; // Handpose?
|
||||
|
||||
JointCount = Utils.BytesToUInt(animationdata, i); i += 4; // Get Joint count
|
||||
}
|
||||
joints = new binBVHJoint[JointCount];
|
||||
|
||||
// deserialize the number of joints in the animation.
|
||||
// Joints are variable length blocks of binary data consisting of joint data and keyframes
|
||||
for (int iter = 0; iter < JointCount; iter++)
|
||||
{
|
||||
binBVHJoint joint = readJoint(animationdata, ref i);
|
||||
joints[iter] = joint;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] EndianSwap(byte[] arr, int offset, int len)
|
||||
{
|
||||
byte[] bendian = new byte[offset + len];
|
||||
Buffer.BlockCopy(arr, offset, bendian, 0, len);
|
||||
Array.Reverse(bendian);
|
||||
return bendian;
|
||||
}
|
||||
/// <summary>
|
||||
/// Variable length strings seem to be null terminated in the animation asset.. but..
|
||||
/// use with caution, home grown.
|
||||
/// advances the index.
|
||||
/// </summary>
|
||||
/// <param name="data">The animation asset byte array</param>
|
||||
/// <param name="i">The offset to start reading</param>
|
||||
/// <returns>a string</returns>
|
||||
public string ReadBytesUntilNull(byte[] data, ref int i)
|
||||
{
|
||||
char nterm = '\0'; // Null terminator
|
||||
int endpos = i;
|
||||
int startpos = i;
|
||||
|
||||
// Find the null character
|
||||
for (int j = i; j < data.Length; j++)
|
||||
{
|
||||
char spot = Convert.ToChar(data[j]);
|
||||
if (spot == nterm)
|
||||
{
|
||||
endpos = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we got to the end, then it's a zero length string
|
||||
if (i == endpos)
|
||||
{
|
||||
// advance the 1 null character
|
||||
i++;
|
||||
return string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We found the end of the string
|
||||
// append the bytes from the beginning of the string to the end of the string
|
||||
// advance i
|
||||
byte[] interm = new byte[endpos - i];
|
||||
for (; i < endpos; i++)
|
||||
{
|
||||
interm[i - startpos] = data[i];
|
||||
}
|
||||
i++; // advance past the null character
|
||||
|
||||
return Utils.BytesToString(interm);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read in a Joint from an animation asset byte array
|
||||
/// Variable length Joint fields, yay!
|
||||
/// Advances the index
|
||||
/// </summary>
|
||||
/// <param name="data">animation asset byte array</param>
|
||||
/// <param name="i">Byte Offset of the start of the joint</param>
|
||||
/// <returns>The Joint data serialized into the binBVHJoint structure</returns>
|
||||
public binBVHJoint readJoint(byte[] data, ref int i)
|
||||
{
|
||||
|
||||
binBVHJointKey[] positions;
|
||||
binBVHJointKey[] rotations;
|
||||
|
||||
binBVHJoint pJoint = new binBVHJoint();
|
||||
|
||||
/*
|
||||
109
|
||||
84
|
||||
111
|
||||
114
|
||||
114
|
||||
111
|
||||
0 <--- Null terminator
|
||||
*/
|
||||
|
||||
pJoint.Name = ReadBytesUntilNull(data, ref i); // Joint name
|
||||
|
||||
/*
|
||||
2 <- Priority Revisited
|
||||
0
|
||||
0
|
||||
0
|
||||
*/
|
||||
|
||||
/*
|
||||
5 <-- 5 keyframes
|
||||
0
|
||||
0
|
||||
0
|
||||
... 5 Keyframe data blocks
|
||||
*/
|
||||
|
||||
/*
|
||||
2 <-- 2 keyframes
|
||||
0
|
||||
0
|
||||
0
|
||||
.. 2 Keyframe data blocks
|
||||
*/
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
pJoint.Priority = Utils.BytesToInt(EndianSwap(data, i, 4)); i += 4; // Joint Priority override?
|
||||
rotationkeys = Utils.BytesToInt(EndianSwap(data, i, 4)); i += 4; // How many rotation keyframes
|
||||
}
|
||||
else
|
||||
{
|
||||
pJoint.Priority = Utils.BytesToInt(data, i); i += 4; // Joint Priority override?
|
||||
rotationkeys = Utils.BytesToInt(data, i); i += 4; // How many rotation keyframes
|
||||
}
|
||||
|
||||
// Sanity check how many rotation keys there are
|
||||
if (rotationkeys < 0 || rotationkeys > 10000)
|
||||
{
|
||||
rotationkeys = 0;
|
||||
}
|
||||
|
||||
rotations = readKeys(data, ref i, rotationkeys, -1.0f, 1.0f);
|
||||
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
positionkeys = Utils.BytesToInt(EndianSwap(data, i, 4)); i += 4; // How many position keyframes
|
||||
}
|
||||
else
|
||||
{
|
||||
positionkeys = Utils.BytesToInt(data, i); i += 4; // How many position keyframes
|
||||
}
|
||||
|
||||
// Sanity check how many positions keys there are
|
||||
if (positionkeys < 0 || positionkeys > 10000)
|
||||
{
|
||||
positionkeys = 0;
|
||||
}
|
||||
|
||||
// Read in position keyframes
|
||||
positions = readKeys(data, ref i, positionkeys, -0.5f, 1.5f);
|
||||
|
||||
pJoint.rotationkeys = rotations;
|
||||
pJoint.positionkeys = positions;
|
||||
|
||||
return pJoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read Keyframes of a certain type
|
||||
/// advance i
|
||||
/// </summary>
|
||||
/// <param name="data">Animation Byte array</param>
|
||||
/// <param name="i">Offset in the Byte Array. Will be advanced</param>
|
||||
/// <param name="keycount">Number of Keyframes</param>
|
||||
/// <param name="min">Scaling Min to pass to the Uint16ToFloat method</param>
|
||||
/// <param name="max">Scaling Max to pass to the Uint16ToFloat method</param>
|
||||
/// <returns></returns>
|
||||
public binBVHJointKey[] readKeys(byte[] data, ref int i, int keycount, float min, float max)
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
|
||||
/*
|
||||
17 255 <-- Time Code
|
||||
17 255 <-- Time Code
|
||||
255 255 <-- X
|
||||
127 127 <-- X
|
||||
255 255 <-- Y
|
||||
127 127 <-- Y
|
||||
213 213 <-- Z
|
||||
142 142 <---Z
|
||||
|
||||
*/
|
||||
|
||||
binBVHJointKey[] m_keys = new binBVHJointKey[keycount];
|
||||
for (int j = 0; j < keycount; j++)
|
||||
{
|
||||
binBVHJointKey pJKey = new binBVHJointKey();
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
pJKey.time = Utils.UInt16ToFloat(EndianSwap(data, i, 2), 0, InPoint, OutPoint); i += 2;
|
||||
x = Utils.UInt16ToFloat(EndianSwap(data, i, 2), 0, min, max); i += 2;
|
||||
y = Utils.UInt16ToFloat(EndianSwap(data, i, 2), 0, min, max); i += 2;
|
||||
z = Utils.UInt16ToFloat(EndianSwap(data, i, 2), 0, min, max); i += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
pJKey.time = Utils.UInt16ToFloat(data, i, InPoint, OutPoint); i += 2;
|
||||
x = Utils.UInt16ToFloat(data, i, min, max); i += 2;
|
||||
y = Utils.UInt16ToFloat(data, i, min, max); i += 2;
|
||||
z = Utils.UInt16ToFloat(data, i, min, max); i += 2;
|
||||
}
|
||||
pJKey.key_element = new Vector3(x, y, z);
|
||||
m_keys[j] = pJKey;
|
||||
}
|
||||
return m_keys;
|
||||
}
|
||||
|
||||
public bool Equals(BinBVHAnimationReader other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return other.Loop.Equals(Loop) && other.OutPoint == OutPoint && other.InPoint == InPoint && other.Length == Length && other.HandPose == HandPose && other.JointCount == JointCount && Equals(other.joints, joints) && other.EaseInTime == EaseInTime && other.EaseOutTime == EaseOutTime && other.Priority == Priority && other.unknown1 == unknown1 && other.unknown0 == unknown0 && other.positionkeys == positionkeys && other.rotationkeys == rotationkeys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.
|
||||
/// </param><exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.
|
||||
/// </exception><filterpriority>2</filterpriority>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != typeof(BinBVHAnimationReader)) return false;
|
||||
return Equals((BinBVHAnimationReader)obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serves as a hash function for a particular type.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for the current <see cref="T:System.Object"/>.
|
||||
/// </returns>
|
||||
/// <filterpriority>2</filterpriority>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int result = Loop.GetHashCode();
|
||||
result = (result * 397) ^ OutPoint.GetHashCode();
|
||||
result = (result * 397) ^ InPoint.GetHashCode();
|
||||
result = (result * 397) ^ Length.GetHashCode();
|
||||
result = (result * 397) ^ HandPose.GetHashCode();
|
||||
result = (result * 397) ^ JointCount.GetHashCode();
|
||||
result = (result * 397) ^ (joints != null ? joints.GetHashCode() : 0);
|
||||
result = (result * 397) ^ EaseInTime.GetHashCode();
|
||||
result = (result * 397) ^ EaseOutTime.GetHashCode();
|
||||
result = (result * 397) ^ Priority;
|
||||
result = (result * 397) ^ unknown1.GetHashCode();
|
||||
result = (result * 397) ^ unknown0.GetHashCode();
|
||||
result = (result * 397) ^ positionkeys;
|
||||
result = (result * 397) ^ rotationkeys;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Equals(binBVHJoint[] arr1, binBVHJoint[] arr2)
|
||||
{
|
||||
if (arr1.Length == arr2.Length)
|
||||
{
|
||||
for (int i = 0; i < arr1.Length; i++)
|
||||
if (!arr1[i].Equals(arr2[i]))
|
||||
return false;
|
||||
/* not same*/
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A Joint and it's associated meta data and keyframes
|
||||
/// </summary>
|
||||
public struct binBVHJoint
|
||||
{
|
||||
public static bool Equals(binBVHJointKey[] arr1, binBVHJointKey[] arr2)
|
||||
{
|
||||
if (arr1.Length == arr2.Length)
|
||||
{
|
||||
for (int i = 0; i < arr1.Length; i++)
|
||||
if (!Equals(arr1[i], arr2[i]))
|
||||
return false;
|
||||
/* not same*/
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static bool Equals(binBVHJointKey arr1, binBVHJointKey arr2)
|
||||
{
|
||||
return (arr1.time == arr2.time && arr1.key_element == arr2.key_element);
|
||||
}
|
||||
|
||||
public bool Equals(binBVHJoint other)
|
||||
{
|
||||
return other.Priority == Priority && Equals(other.rotationkeys, rotationkeys) && Equals(other.Name, Name) && Equals(other.positionkeys, positionkeys);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this instance and a specified object are equal.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="obj">Another object to compare to.
|
||||
/// </param><filterpriority>2</filterpriority>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (obj.GetType() != typeof(binBVHJoint)) return false;
|
||||
return Equals((binBVHJoint)obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A 32-bit signed integer that is the hash code for this instance.
|
||||
/// </returns>
|
||||
/// <filterpriority>2</filterpriority>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int result = Priority;
|
||||
result = (result * 397) ^ (rotationkeys != null ? rotationkeys.GetHashCode() : 0);
|
||||
result = (result * 397) ^ (Name != null ? Name.GetHashCode() : 0);
|
||||
result = (result * 397) ^ (positionkeys != null ? positionkeys.GetHashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(binBVHJoint left, binBVHJoint right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(binBVHJoint left, binBVHJoint right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of the Joint. Matches the avatar_skeleton.xml in client distros
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Joint Animation Override? Was the same as the Priority in testing..
|
||||
/// </summary>
|
||||
public int Priority;
|
||||
|
||||
/// <summary>
|
||||
/// Array of Rotation Keyframes in order from earliest to latest
|
||||
/// </summary>
|
||||
public binBVHJointKey[] rotationkeys;
|
||||
|
||||
/// <summary>
|
||||
/// Array of Position Keyframes in order from earliest to latest
|
||||
/// This seems to only be for the Pelvis?
|
||||
/// </summary>
|
||||
public binBVHJointKey[] positionkeys;
|
||||
|
||||
/// <summary>
|
||||
/// Custom application data that can be attached to a joint
|
||||
/// </summary>
|
||||
public object Tag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A Joint Keyframe. This is either a position or a rotation.
|
||||
/// </summary>
|
||||
public struct binBVHJointKey
|
||||
{
|
||||
// Time in seconds for this keyframe.
|
||||
public float time;
|
||||
|
||||
/// <summary>
|
||||
/// Either a Vector3 position or a Vector3 Euler rotation
|
||||
/// </summary>
|
||||
public Vector3 key_element;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Poses set in the animation metadata for the hands.
|
||||
/// </summary>
|
||||
public enum HandPose : uint
|
||||
{
|
||||
Spread = 0,
|
||||
Relaxed = 1,
|
||||
Point_Both = 2,
|
||||
Fist = 3,
|
||||
Relaxed_Left = 4,
|
||||
Point_Left = 5,
|
||||
Fist_Left = 6,
|
||||
Relaxed_Right = 7,
|
||||
Point_Right = 8,
|
||||
Fist_Right = 9,
|
||||
Salute_Right = 10,
|
||||
Typing = 11,
|
||||
Peace_Right = 12
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper around a byte array that allows bit to be packed and unpacked
|
||||
/// one at a time or by a variable amount. Useful for very tightly packed
|
||||
/// data like LayerData packets
|
||||
/// </summary>
|
||||
public class BitPack
|
||||
{
|
||||
/// <summary></summary>
|
||||
public byte[] Data;
|
||||
|
||||
/// <summary></summary>
|
||||
public int BytePos
|
||||
{
|
||||
get
|
||||
{
|
||||
if (bytePos != 0 && bitPos == 0)
|
||||
return bytePos - 1;
|
||||
else
|
||||
return bytePos;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary></summary>
|
||||
public int BitPos { get { return bitPos; } }
|
||||
|
||||
|
||||
private const int MAX_BITS = 8;
|
||||
private static readonly byte[] ON = new byte[] { 1 };
|
||||
private static readonly byte[] OFF = new byte[] { 0 };
|
||||
|
||||
private int bytePos;
|
||||
private int bitPos;
|
||||
private bool weAreBigEndian = !BitConverter.IsLittleEndian;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor, initialize the bit packer / bit unpacker
|
||||
/// with a byte array and starting position
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array to pack bits in to or unpack from</param>
|
||||
/// <param name="pos">Starting position in the byte array</param>
|
||||
public BitPack(byte[] data, int pos)
|
||||
{
|
||||
Data = data;
|
||||
bytePos = pos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pack a floating point value in to the data
|
||||
/// </summary>
|
||||
/// <param name="data">Floating point value to pack</param>
|
||||
public void PackFloat(float data)
|
||||
{
|
||||
byte[] input = BitConverter.GetBytes(data);
|
||||
if (weAreBigEndian) Array.Reverse(input);
|
||||
PackBitArray(input, 32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pack part or all of an integer in to the data
|
||||
/// </summary>
|
||||
/// <param name="data">Integer containing the data to pack</param>
|
||||
/// <param name="totalCount">Number of bits of the integer to pack</param>
|
||||
public void PackBits(int data, int totalCount)
|
||||
{
|
||||
byte[] input = BitConverter.GetBytes(data);
|
||||
if (weAreBigEndian) Array.Reverse(input);
|
||||
PackBitArray(input, totalCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pack part or all of an unsigned integer in to the data
|
||||
/// </summary>
|
||||
/// <param name="data">Unsigned integer containing the data to pack</param>
|
||||
/// <param name="totalCount">Number of bits of the integer to pack</param>
|
||||
public void PackBits(uint data, int totalCount)
|
||||
{
|
||||
byte[] input = BitConverter.GetBytes(data);
|
||||
if (weAreBigEndian) Array.Reverse(input);
|
||||
PackBitArray(input, totalCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pack a single bit in to the data
|
||||
/// </summary>
|
||||
/// <param name="bit">Bit to pack</param>
|
||||
public void PackBit(bool bit)
|
||||
{
|
||||
if (bit)
|
||||
PackBitArray(ON, 1);
|
||||
else
|
||||
PackBitArray(OFF, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="isSigned"></param>
|
||||
/// <param name="intBits"></param>
|
||||
/// <param name="fracBits"></param>
|
||||
public void PackFixed(float data, bool isSigned, int intBits, int fracBits)
|
||||
{
|
||||
int unsignedBits = intBits + fracBits;
|
||||
int totalBits = unsignedBits;
|
||||
int min, max;
|
||||
|
||||
if (isSigned)
|
||||
{
|
||||
totalBits++;
|
||||
min = 1 << intBits;
|
||||
min *= -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
min = 0;
|
||||
}
|
||||
|
||||
max = 1 << intBits;
|
||||
|
||||
float fixedVal = Utils.Clamp(data, (float)min, (float)max);
|
||||
if (isSigned) fixedVal += max;
|
||||
fixedVal *= 1 << fracBits;
|
||||
|
||||
if (totalBits <= 8)
|
||||
PackBits((uint)fixedVal, 8);
|
||||
else if (totalBits <= 16)
|
||||
PackBits((uint)fixedVal, 16);
|
||||
else if (totalBits <= 31)
|
||||
PackBits((uint)fixedVal, 32);
|
||||
else
|
||||
throw new Exception("Can't use fixed point packing for " + totalBits);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public void PackUUID(UUID data)
|
||||
{
|
||||
byte[] bytes = data.GetBytes();
|
||||
|
||||
// Not sure if our PackBitArray function can handle 128-bit byte
|
||||
//arrays, so using this for now
|
||||
for (int i = 0; i < 16; i++)
|
||||
PackBits(bytes[i], 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public void PackColor(Color4 data)
|
||||
{
|
||||
byte[] bytes = data.GetBytes();
|
||||
PackBitArray(bytes, 32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacking a floating point value from the data
|
||||
/// </summary>
|
||||
/// <returns>Unpacked floating point value</returns>
|
||||
public float UnpackFloat()
|
||||
{
|
||||
byte[] output = UnpackBitsArray(32);
|
||||
|
||||
if (weAreBigEndian) Array.Reverse(output);
|
||||
return BitConverter.ToSingle(output, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpack a variable number of bits from the data in to integer format
|
||||
/// </summary>
|
||||
/// <param name="totalCount">Number of bits to unpack</param>
|
||||
/// <returns>An integer containing the unpacked bits</returns>
|
||||
/// <remarks>This function is only useful up to 32 bits</remarks>
|
||||
public int UnpackBits(int totalCount)
|
||||
{
|
||||
byte[] output = UnpackBitsArray(totalCount);
|
||||
|
||||
if (weAreBigEndian) Array.Reverse(output);
|
||||
return BitConverter.ToInt32(output, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpack a variable number of bits from the data in to unsigned
|
||||
/// integer format
|
||||
/// </summary>
|
||||
/// <param name="totalCount">Number of bits to unpack</param>
|
||||
/// <returns>An unsigned integer containing the unpacked bits</returns>
|
||||
/// <remarks>This function is only useful up to 32 bits</remarks>
|
||||
public uint UnpackUBits(int totalCount)
|
||||
{
|
||||
byte[] output = UnpackBitsArray(totalCount);
|
||||
|
||||
if (weAreBigEndian) Array.Reverse(output);
|
||||
return BitConverter.ToUInt32(output, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpack a 16-bit signed integer
|
||||
/// </summary>
|
||||
/// <returns>16-bit signed integer</returns>
|
||||
public short UnpackShort()
|
||||
{
|
||||
return (short)UnpackBits(16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpack a 16-bit unsigned integer
|
||||
/// </summary>
|
||||
/// <returns>16-bit unsigned integer</returns>
|
||||
public ushort UnpackUShort()
|
||||
{
|
||||
return (ushort)UnpackUBits(16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpack a 32-bit signed integer
|
||||
/// </summary>
|
||||
/// <returns>32-bit signed integer</returns>
|
||||
public int UnpackInt()
|
||||
{
|
||||
return UnpackBits(32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpack a 32-bit unsigned integer
|
||||
/// </summary>
|
||||
/// <returns>32-bit unsigned integer</returns>
|
||||
public uint UnpackUInt()
|
||||
{
|
||||
return UnpackUBits(32);
|
||||
}
|
||||
|
||||
public byte UnpackByte()
|
||||
{
|
||||
byte[] output = UnpackBitsArray(8);
|
||||
return output[0];
|
||||
}
|
||||
|
||||
public float UnpackFixed(bool signed, int intBits, int fracBits)
|
||||
{
|
||||
int minVal;
|
||||
int maxVal;
|
||||
int unsignedBits = intBits + fracBits;
|
||||
int totalBits = unsignedBits;
|
||||
float fixedVal;
|
||||
|
||||
if (signed)
|
||||
{
|
||||
totalBits++;
|
||||
|
||||
minVal = 1 << intBits;
|
||||
minVal *= -1;
|
||||
}
|
||||
maxVal = 1 << intBits;
|
||||
|
||||
if (totalBits <= 8)
|
||||
fixedVal = (float)UnpackByte();
|
||||
else if (totalBits <= 16)
|
||||
fixedVal = (float)UnpackUBits(16);
|
||||
else if (totalBits <= 31)
|
||||
fixedVal = (float)UnpackUBits(32);
|
||||
else
|
||||
return 0.0f;
|
||||
|
||||
fixedVal /= (float)(1 << fracBits);
|
||||
|
||||
if (signed) fixedVal -= (float)maxVal;
|
||||
|
||||
return fixedVal;
|
||||
}
|
||||
|
||||
public string UnpackString(int size)
|
||||
{
|
||||
if (bitPos != 0 || bytePos + size > Data.Length) throw new IndexOutOfRangeException();
|
||||
|
||||
string str = System.Text.UTF8Encoding.UTF8.GetString(Data, bytePos, size);
|
||||
bytePos += size;
|
||||
return str;
|
||||
}
|
||||
|
||||
public UUID UnpackUUID()
|
||||
{
|
||||
if (bitPos != 0) throw new IndexOutOfRangeException();
|
||||
|
||||
UUID val = new UUID(Data, bytePos);
|
||||
bytePos += 16;
|
||||
return val;
|
||||
}
|
||||
|
||||
private void PackBitArray(byte[] data, int totalCount)
|
||||
{
|
||||
int count = 0;
|
||||
int curBytePos = 0;
|
||||
int curBitPos = 0;
|
||||
|
||||
while (totalCount > 0)
|
||||
{
|
||||
if (totalCount > MAX_BITS)
|
||||
{
|
||||
count = MAX_BITS;
|
||||
totalCount -= MAX_BITS;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = totalCount;
|
||||
totalCount = 0;
|
||||
}
|
||||
|
||||
while (count > 0)
|
||||
{
|
||||
byte curBit = (byte)(0x80 >> bitPos);
|
||||
|
||||
if ((data[curBytePos] & (0x01 << (count - 1))) != 0)
|
||||
Data[bytePos] |= curBit;
|
||||
else
|
||||
Data[bytePos] &= (byte)~curBit;
|
||||
|
||||
--count;
|
||||
++bitPos;
|
||||
++curBitPos;
|
||||
|
||||
if (bitPos >= MAX_BITS)
|
||||
{
|
||||
bitPos = 0;
|
||||
++bytePos;
|
||||
}
|
||||
if (curBitPos >= MAX_BITS)
|
||||
{
|
||||
curBitPos = 0;
|
||||
++curBytePos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] UnpackBitsArray(int totalCount)
|
||||
{
|
||||
int count = 0;
|
||||
byte[] output = new byte[4];
|
||||
int curBytePos = 0;
|
||||
int curBitPos = 0;
|
||||
|
||||
while (totalCount > 0)
|
||||
{
|
||||
if (totalCount > MAX_BITS)
|
||||
{
|
||||
count = MAX_BITS;
|
||||
totalCount -= MAX_BITS;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = totalCount;
|
||||
totalCount = 0;
|
||||
}
|
||||
|
||||
while (count > 0)
|
||||
{
|
||||
// Shift the previous bits
|
||||
output[curBytePos] <<= 1;
|
||||
|
||||
// Grab one bit
|
||||
if ((Data[bytePos] & (0x80 >> bitPos++)) != 0)
|
||||
++output[curBytePos];
|
||||
|
||||
--count;
|
||||
++curBitPos;
|
||||
|
||||
if (bitPos >= MAX_BITS)
|
||||
{
|
||||
bitPos = 0;
|
||||
++bytePos;
|
||||
}
|
||||
if (curBitPos >= MAX_BITS)
|
||||
{
|
||||
curBitPos = 0;
|
||||
++curBytePos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace OpenMetaverse.Http
|
||||
{
|
||||
public class TrustAllCertificatePolicy : ICertificatePolicy
|
||||
{
|
||||
public bool CheckValidationResult(ServicePoint sp, X509Certificate cert, WebRequest req, int problem)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TrustAllCertificateHandler(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CapsBase
|
||||
{
|
||||
public delegate void OpenWriteEventHandler(HttpWebRequest request);
|
||||
public delegate void DownloadProgressEventHandler(HttpWebRequest request, HttpWebResponse response, int bytesReceived, int totalBytesToReceive);
|
||||
public delegate void RequestCompletedEventHandler(HttpWebRequest request, HttpWebResponse response, byte[] responseData, Exception error);
|
||||
|
||||
static CapsBase()
|
||||
{
|
||||
ServicePointManager.CertificatePolicy = new TrustAllCertificatePolicy();
|
||||
// Even though this will compile on Mono 2.4, it throws a runtime exception
|
||||
//ServicePointManager.ServerCertificateValidationCallback = TrustAllCertificatePolicy.TrustAllCertificateHandler;
|
||||
}
|
||||
|
||||
private class RequestState
|
||||
{
|
||||
public HttpWebRequest Request;
|
||||
public byte[] UploadData;
|
||||
public int MillisecondsTimeout;
|
||||
public OpenWriteEventHandler OpenWriteCallback;
|
||||
public DownloadProgressEventHandler DownloadProgressCallback;
|
||||
public RequestCompletedEventHandler CompletedCallback;
|
||||
|
||||
public RequestState(HttpWebRequest request, byte[] uploadData, int millisecondsTimeout, OpenWriteEventHandler openWriteCallback,
|
||||
DownloadProgressEventHandler downloadProgressCallback, RequestCompletedEventHandler completedCallback)
|
||||
{
|
||||
Request = request;
|
||||
UploadData = uploadData;
|
||||
MillisecondsTimeout = millisecondsTimeout;
|
||||
OpenWriteCallback = openWriteCallback;
|
||||
DownloadProgressCallback = downloadProgressCallback;
|
||||
CompletedCallback = completedCallback;
|
||||
}
|
||||
}
|
||||
|
||||
public static HttpWebRequest UploadDataAsync(Uri address, X509Certificate2 clientCert, string contentType, byte[] data,
|
||||
int millisecondsTimeout, OpenWriteEventHandler openWriteCallback, DownloadProgressEventHandler downloadProgressCallback,
|
||||
RequestCompletedEventHandler completedCallback)
|
||||
{
|
||||
// Create the request
|
||||
HttpWebRequest request = SetupRequest(address, clientCert);
|
||||
request.ContentLength = data.Length;
|
||||
if (!String.IsNullOrEmpty(contentType))
|
||||
request.ContentType = contentType;
|
||||
request.Method = "POST";
|
||||
|
||||
// Create an object to hold all of the state for this request
|
||||
RequestState state = new RequestState(request, data, millisecondsTimeout, openWriteCallback,
|
||||
downloadProgressCallback, completedCallback);
|
||||
|
||||
// Start the request for a stream to upload to
|
||||
IAsyncResult result = request.BeginGetRequestStream(OpenWrite, state);
|
||||
// Register a timeout for the request
|
||||
ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, TimeoutCallback, state, millisecondsTimeout, true);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
public static HttpWebRequest DownloadStringAsync(Uri address, X509Certificate2 clientCert, int millisecondsTimeout,
|
||||
DownloadProgressEventHandler downloadProgressCallback, RequestCompletedEventHandler completedCallback)
|
||||
{
|
||||
// Create the request
|
||||
HttpWebRequest request = SetupRequest(address, clientCert);
|
||||
request.Method = "GET";
|
||||
DownloadDataAsync(request, millisecondsTimeout, downloadProgressCallback, completedCallback);
|
||||
return request;
|
||||
}
|
||||
|
||||
public static void DownloadDataAsync(HttpWebRequest request, int millisecondsTimeout,
|
||||
DownloadProgressEventHandler downloadProgressCallback, RequestCompletedEventHandler completedCallback)
|
||||
{
|
||||
// Create an object to hold all of the state for this request
|
||||
RequestState state = new RequestState(request, null, millisecondsTimeout, null, downloadProgressCallback,
|
||||
completedCallback);
|
||||
|
||||
// Start the request for the remote server response
|
||||
IAsyncResult result = request.BeginGetResponse(GetResponse, state);
|
||||
// Register a timeout for the request
|
||||
ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, TimeoutCallback, state, millisecondsTimeout, true);
|
||||
}
|
||||
|
||||
static HttpWebRequest SetupRequest(Uri address, X509Certificate2 clientCert)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException("address");
|
||||
|
||||
// Create the request
|
||||
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(address);
|
||||
|
||||
// Add the client certificate to the request if one was given
|
||||
if (clientCert != null)
|
||||
request.ClientCertificates.Add(clientCert);
|
||||
|
||||
// Leave idle connections to this endpoint open for up to 60 seconds
|
||||
request.ServicePoint.MaxIdleTime = 1000 * 60;
|
||||
// Disable stupid Expect-100: Continue header
|
||||
request.ServicePoint.Expect100Continue = false;
|
||||
// Crank up the max number of connections per endpoint
|
||||
// We set this manually here instead of in ServicePointManager to avoid intereference with callers.
|
||||
if (request.ServicePoint.ConnectionLimit < Settings.MAX_HTTP_CONNECTIONS)
|
||||
{
|
||||
Logger.Log(
|
||||
string.Format(
|
||||
"In CapsBase.SetupRequest() setting conn limit for {0}:{1} to {2}",
|
||||
address.Host, address.Port, Settings.MAX_HTTP_CONNECTIONS), Helpers.LogLevel.Debug);
|
||||
request.ServicePoint.ConnectionLimit = Settings.MAX_HTTP_CONNECTIONS;
|
||||
}
|
||||
// Caps requests are never sent as trickles of data, so Nagle's
|
||||
// coalescing algorithm won't help us
|
||||
request.ServicePoint.UseNagleAlgorithm = false;
|
||||
// If not on mono, set accept-encoding header that allows response compression
|
||||
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
||||
return request;
|
||||
}
|
||||
|
||||
static void OpenWrite(IAsyncResult ar)
|
||||
{
|
||||
RequestState state = (RequestState)ar.AsyncState;
|
||||
|
||||
try
|
||||
{
|
||||
// Get the stream to write our upload to
|
||||
using (Stream uploadStream = state.Request.EndGetRequestStream(ar))
|
||||
{
|
||||
// Fire the callback for successfully opening the stream
|
||||
if (state.OpenWriteCallback != null)
|
||||
state.OpenWriteCallback(state.Request);
|
||||
|
||||
// Write our data to the upload stream
|
||||
uploadStream.Write(state.UploadData, 0, state.UploadData.Length);
|
||||
}
|
||||
|
||||
// Start the request for the remote server response
|
||||
IAsyncResult result = state.Request.BeginGetResponse(GetResponse, state);
|
||||
// Register a timeout for the request
|
||||
ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, TimeoutCallback, state,
|
||||
state.MillisecondsTimeout, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//Logger.Log.Debug("CapsBase.OpenWrite(): " + ex.Message);
|
||||
if (state.CompletedCallback != null)
|
||||
state.CompletedCallback(state.Request, null, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
static void GetResponse(IAsyncResult ar)
|
||||
{
|
||||
RequestState state = (RequestState)ar.AsyncState;
|
||||
HttpWebResponse response = null;
|
||||
byte[] responseData = null;
|
||||
Exception error = null;
|
||||
|
||||
try
|
||||
{
|
||||
using (response = (HttpWebResponse)state.Request.EndGetResponse(ar))
|
||||
{
|
||||
// Get the stream for downloading the response
|
||||
using (Stream responseStream = response.GetResponseStream())
|
||||
{
|
||||
#region Read the response
|
||||
|
||||
// If Content-Length is set we create a buffer of the exact size, otherwise
|
||||
// a MemoryStream is used to receive the response
|
||||
bool nolength = (response.ContentLength <= 0) || (Type.GetType("Mono.Runtime") != null);
|
||||
int size = (nolength) ? 8192 : (int)response.ContentLength;
|
||||
MemoryStream ms = (nolength) ? new MemoryStream() : null;
|
||||
byte[] buffer = new byte[size];
|
||||
|
||||
int bytesRead = 0;
|
||||
int offset = 0;
|
||||
int totalBytesRead = 0;
|
||||
int totalSize = nolength ? 0 : size;
|
||||
|
||||
while ((bytesRead = responseStream.Read(buffer, offset, size)) != 0)
|
||||
{
|
||||
totalBytesRead += bytesRead;
|
||||
|
||||
if (nolength)
|
||||
{
|
||||
totalSize += (size - bytesRead);
|
||||
ms.Write(buffer, 0, bytesRead);
|
||||
}
|
||||
else
|
||||
{
|
||||
offset += bytesRead;
|
||||
size -= bytesRead;
|
||||
}
|
||||
|
||||
// Fire the download progress callback for each chunk of received data
|
||||
if (state.DownloadProgressCallback != null)
|
||||
state.DownloadProgressCallback(state.Request, response, totalBytesRead, totalSize);
|
||||
}
|
||||
|
||||
if (nolength)
|
||||
{
|
||||
responseData = ms.ToArray();
|
||||
ms.Close();
|
||||
ms.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
responseData = buffer;
|
||||
}
|
||||
|
||||
#endregion Read the response
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Logger.DebugLog("CapsBase.GetResponse(): " + ex.Message);
|
||||
error = ex;
|
||||
}
|
||||
|
||||
if (state.CompletedCallback != null)
|
||||
state.CompletedCallback(state.Request, response, responseData, error);
|
||||
}
|
||||
|
||||
static void TimeoutCallback(object state, bool timedOut)
|
||||
{
|
||||
if (timedOut)
|
||||
{
|
||||
RequestState requestState = state as RequestState;
|
||||
//Logger.Log.Debug("CapsBase.TimeoutCallback(): Request to " + requestState.Request.RequestUri +
|
||||
// " timed out after " + requestState.MillisecondsTimeout + " milliseconds");
|
||||
if (requestState != null && requestState.Request != null)
|
||||
requestState.Request.Abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using OpenMetaverse.StructuredData;
|
||||
|
||||
namespace OpenMetaverse.Http
|
||||
{
|
||||
public class CapsClient
|
||||
{
|
||||
public delegate void DownloadProgressCallback(CapsClient client, int bytesReceived, int totalBytesToReceive);
|
||||
public delegate void CompleteCallback(CapsClient client, OSD result, Exception error);
|
||||
|
||||
public event DownloadProgressCallback OnDownloadProgress;
|
||||
public event CompleteCallback OnComplete;
|
||||
|
||||
public object UserData;
|
||||
|
||||
protected Uri _Address;
|
||||
protected byte[] _PostData;
|
||||
protected X509Certificate2 _ClientCert;
|
||||
protected string _ContentType;
|
||||
protected HttpWebRequest _Request;
|
||||
protected OSD _Response;
|
||||
protected AutoResetEvent _ResponseEvent = new AutoResetEvent(false);
|
||||
|
||||
public CapsClient(Uri capability)
|
||||
: this(capability, null)
|
||||
{
|
||||
}
|
||||
|
||||
public CapsClient(Uri capability, X509Certificate2 clientCert)
|
||||
{
|
||||
_Address = capability;
|
||||
_ClientCert = clientCert;
|
||||
}
|
||||
|
||||
public void BeginGetResponse(int millisecondsTimeout)
|
||||
{
|
||||
BeginGetResponse(null, null, millisecondsTimeout);
|
||||
}
|
||||
|
||||
public void BeginGetResponse(OSD data, OSDFormat format, int millisecondsTimeout)
|
||||
{
|
||||
byte[] postData;
|
||||
string contentType;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case OSDFormat.Xml:
|
||||
postData = OSDParser.SerializeLLSDXmlBytes(data);
|
||||
contentType = "application/llsd+xml";
|
||||
break;
|
||||
case OSDFormat.Binary:
|
||||
postData = OSDParser.SerializeLLSDBinary(data);
|
||||
contentType = "application/llsd+binary";
|
||||
break;
|
||||
case OSDFormat.Json:
|
||||
default:
|
||||
postData = System.Text.Encoding.UTF8.GetBytes(OSDParser.SerializeJsonString(data));
|
||||
contentType = "application/llsd+json";
|
||||
break;
|
||||
}
|
||||
|
||||
BeginGetResponse(postData, contentType, millisecondsTimeout);
|
||||
}
|
||||
|
||||
public void BeginGetResponse(byte[] postData, string contentType, int millisecondsTimeout)
|
||||
{
|
||||
_PostData = postData;
|
||||
_ContentType = contentType;
|
||||
|
||||
if (_Request != null)
|
||||
{
|
||||
_Request.Abort();
|
||||
_Request = null;
|
||||
}
|
||||
|
||||
if (postData == null)
|
||||
{
|
||||
// GET
|
||||
//Logger.Log.Debug("[CapsClient] GET " + _Address);
|
||||
_Request = CapsBase.DownloadStringAsync(_Address, _ClientCert, millisecondsTimeout, DownloadProgressHandler,
|
||||
RequestCompletedHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
// POST
|
||||
//Logger.Log.Debug("[CapsClient] POST (" + postData.Length + " bytes) " + _Address);
|
||||
_Request = CapsBase.UploadDataAsync(_Address, _ClientCert, contentType, postData, millisecondsTimeout, null,
|
||||
DownloadProgressHandler, RequestCompletedHandler);
|
||||
}
|
||||
}
|
||||
|
||||
public OSD GetResponse(int millisecondsTimeout)
|
||||
{
|
||||
BeginGetResponse(millisecondsTimeout);
|
||||
_ResponseEvent.WaitOne(millisecondsTimeout, false);
|
||||
return _Response;
|
||||
}
|
||||
|
||||
public OSD GetResponse(OSD data, OSDFormat format, int millisecondsTimeout)
|
||||
{
|
||||
BeginGetResponse(data, format, millisecondsTimeout);
|
||||
_ResponseEvent.WaitOne(millisecondsTimeout, false);
|
||||
return _Response;
|
||||
}
|
||||
|
||||
public OSD GetResponse(byte[] postData, string contentType, int millisecondsTimeout)
|
||||
{
|
||||
BeginGetResponse(postData, contentType, millisecondsTimeout);
|
||||
_ResponseEvent.WaitOne(millisecondsTimeout, false);
|
||||
return _Response;
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
if (_Request != null)
|
||||
_Request.Abort();
|
||||
}
|
||||
|
||||
void DownloadProgressHandler(HttpWebRequest request, HttpWebResponse response, int bytesReceived, int totalBytesToReceive)
|
||||
{
|
||||
_Request = request;
|
||||
|
||||
if (OnDownloadProgress != null)
|
||||
{
|
||||
try { OnDownloadProgress(this, bytesReceived, totalBytesToReceive); }
|
||||
catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, ex); }
|
||||
}
|
||||
}
|
||||
|
||||
void RequestCompletedHandler(HttpWebRequest request, HttpWebResponse response, byte[] responseData, Exception error)
|
||||
{
|
||||
_Request = request;
|
||||
|
||||
OSD result = null;
|
||||
|
||||
if (responseData != null)
|
||||
{
|
||||
try { result = OSDParser.Deserialize(responseData); }
|
||||
catch (Exception ex) { error = ex; }
|
||||
}
|
||||
|
||||
FireCompleteCallback(result, error);
|
||||
}
|
||||
|
||||
private void FireCompleteCallback(OSD result, Exception error)
|
||||
{
|
||||
CompleteCallback callback = OnComplete;
|
||||
if (callback != null)
|
||||
{
|
||||
try { callback(this, result, error); }
|
||||
catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, ex); }
|
||||
}
|
||||
|
||||
_Response = result;
|
||||
_ResponseEvent.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using OpenMetaverse.StructuredData;
|
||||
|
||||
namespace OpenMetaverse.Http
|
||||
{
|
||||
public class EventQueueClient
|
||||
{
|
||||
/// <summary>=</summary>
|
||||
public const int REQUEST_TIMEOUT = 1000 * 120;
|
||||
|
||||
public delegate void ConnectedCallback();
|
||||
public delegate void EventCallback(string eventName, OSDMap body);
|
||||
|
||||
public ConnectedCallback OnConnected;
|
||||
public EventCallback OnEvent;
|
||||
|
||||
public bool Running { get { return _Running; } }
|
||||
|
||||
protected Uri _Address;
|
||||
protected bool _Dead;
|
||||
protected bool _Running;
|
||||
protected HttpWebRequest _Request;
|
||||
|
||||
/// <summary>Number of times we've received an unknown CAPS exception in series.</summary>
|
||||
private int _errorCount;
|
||||
/// <summary>For exponential backoff on error.</summary>
|
||||
private static Random _random = new Random();
|
||||
|
||||
public EventQueueClient(Uri eventQueueLocation)
|
||||
{
|
||||
_Address = eventQueueLocation;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_Dead = false;
|
||||
|
||||
// Create an EventQueueGet request
|
||||
OSDMap request = new OSDMap();
|
||||
request["ack"] = new OSD();
|
||||
request["done"] = OSD.FromBoolean(false);
|
||||
|
||||
byte[] postData = OSDParser.SerializeLLSDXmlBytes(request);
|
||||
|
||||
_Request = CapsBase.UploadDataAsync(_Address, null, "application/xml", postData, REQUEST_TIMEOUT, OpenWriteHandler, null, RequestCompletedHandler);
|
||||
}
|
||||
|
||||
public void Stop(bool immediate)
|
||||
{
|
||||
_Dead = true;
|
||||
|
||||
if (immediate)
|
||||
_Running = false;
|
||||
|
||||
if (_Request != null)
|
||||
_Request.Abort();
|
||||
}
|
||||
|
||||
void OpenWriteHandler(HttpWebRequest request)
|
||||
{
|
||||
_Running = true;
|
||||
_Request = request;
|
||||
|
||||
Logger.DebugLog("Capabilities event queue connected");
|
||||
|
||||
// The event queue is starting up for the first time
|
||||
if (OnConnected != null)
|
||||
{
|
||||
try { OnConnected(); }
|
||||
catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, ex); }
|
||||
}
|
||||
}
|
||||
|
||||
void RequestCompletedHandler(HttpWebRequest request, HttpWebResponse response, byte[] responseData, Exception error)
|
||||
{
|
||||
// We don't care about this request now that it has completed
|
||||
_Request = null;
|
||||
|
||||
OSDArray events = null;
|
||||
int ack = 0;
|
||||
|
||||
if (responseData != null)
|
||||
{
|
||||
_errorCount = 0;
|
||||
// Got a response
|
||||
OSDMap result = OSDParser.DeserializeLLSDXml(responseData) as OSDMap;
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
events = result["events"] as OSDArray;
|
||||
ack = result["id"].AsInteger();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("Got an unparseable response from the event queue: \"" +
|
||||
System.Text.Encoding.UTF8.GetString(responseData) + "\"", Helpers.LogLevel.Warning);
|
||||
}
|
||||
}
|
||||
else if (error != null)
|
||||
{
|
||||
#region Error handling
|
||||
|
||||
HttpStatusCode code = HttpStatusCode.OK;
|
||||
|
||||
if (error is WebException)
|
||||
{
|
||||
WebException webException = (WebException)error;
|
||||
|
||||
if (webException.Response != null)
|
||||
code = ((HttpWebResponse)webException.Response).StatusCode;
|
||||
else if (webException.Status == WebExceptionStatus.RequestCanceled)
|
||||
goto HandlingDone;
|
||||
}
|
||||
|
||||
if (error is WebException && ((WebException)error).Response != null)
|
||||
code = ((HttpWebResponse)((WebException)error).Response).StatusCode;
|
||||
|
||||
if (code == HttpStatusCode.NotFound || code == HttpStatusCode.Gone)
|
||||
{
|
||||
Logger.Log(String.Format("Closing event queue at {0} due to missing caps URI", _Address), Helpers.LogLevel.Info);
|
||||
|
||||
_Running = false;
|
||||
_Dead = true;
|
||||
}
|
||||
else if (code == HttpStatusCode.BadGateway)
|
||||
{
|
||||
// This is not good (server) protocol design, but it's normal.
|
||||
// The EventQueue server is a proxy that connects to a Squid
|
||||
// cache which will time out periodically. The EventQueue server
|
||||
// interprets this as a generic error and returns a 502 to us
|
||||
// that we ignore
|
||||
}
|
||||
else
|
||||
{
|
||||
++_errorCount;
|
||||
|
||||
// Try to log a meaningful error message
|
||||
if (code != HttpStatusCode.OK)
|
||||
{
|
||||
Logger.Log(String.Format("Unrecognized caps connection problem from {0}: {1}",
|
||||
_Address, code), Helpers.LogLevel.Warning);
|
||||
}
|
||||
else if (error.InnerException != null)
|
||||
{
|
||||
Logger.Log(String.Format("Unrecognized internal caps exception from {0}: {1}",
|
||||
_Address, error.InnerException.Message), Helpers.LogLevel.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log(String.Format("Unrecognized caps exception from {0}: {1}",
|
||||
_Address, error.Message), Helpers.LogLevel.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Error handling
|
||||
}
|
||||
else
|
||||
{
|
||||
++_errorCount;
|
||||
|
||||
Logger.Log("No response from the event queue but no reported error either", Helpers.LogLevel.Warning);
|
||||
}
|
||||
|
||||
HandlingDone:
|
||||
|
||||
#region Resume the connection
|
||||
|
||||
if (_Running)
|
||||
{
|
||||
OSDMap osdRequest = new OSDMap();
|
||||
if (ack != 0) osdRequest["ack"] = OSD.FromInteger(ack);
|
||||
else osdRequest["ack"] = new OSD();
|
||||
osdRequest["done"] = OSD.FromBoolean(_Dead);
|
||||
|
||||
byte[] postData = OSDParser.SerializeLLSDXmlBytes(osdRequest);
|
||||
|
||||
if (_errorCount > 0) // Exponentially back off, so we don't hammer the CPU
|
||||
Thread.Sleep(_random.Next(500 + (int)Math.Pow(2, _errorCount)));
|
||||
|
||||
// Resume the connection. The event handler for the connection opening
|
||||
// just sets class _Request variable to the current HttpWebRequest
|
||||
CapsBase.UploadDataAsync(_Address, null, "application/xml", postData, REQUEST_TIMEOUT,
|
||||
delegate(HttpWebRequest newRequest) { _Request = newRequest; }, null, RequestCompletedHandler);
|
||||
|
||||
// If the event queue is dead at this point, turn it off since
|
||||
// that was the last thing we want to do
|
||||
if (_Dead)
|
||||
{
|
||||
_Running = false;
|
||||
Logger.DebugLog("Sent event queue shutdown message");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Resume the connection
|
||||
|
||||
#region Handle incoming events
|
||||
|
||||
if (OnEvent != null && events != null && events.Count > 0)
|
||||
{
|
||||
// Fire callbacks for each event received
|
||||
foreach (OSDMap evt in events)
|
||||
{
|
||||
string msg = evt["message"].AsString();
|
||||
OSDMap body = (OSDMap)evt["body"];
|
||||
|
||||
try { OnEvent(msg, body); }
|
||||
catch (Exception ex) { Logger.Log(ex.Message, Helpers.LogLevel.Error, ex); }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Handle incoming events
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using OpenMetaverse.Packets;
|
||||
using OpenMetaverse.StructuredData;
|
||||
using OpenMetaverse.Interfaces;
|
||||
using OpenMetaverse.Http;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// Capabilities is the name of the bi-directional HTTP REST protocol
|
||||
/// used to communicate non real-time transactions such as teleporting or
|
||||
/// group messaging
|
||||
/// </summary>
|
||||
public partial class Caps
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggered when an event is received via the EventQueueGet
|
||||
/// capability
|
||||
/// </summary>
|
||||
/// <param name="capsKey">Event name</param>
|
||||
/// <param name="message">Decoded event data</param>
|
||||
/// <param name="simulator">The simulator that generated the event</param>
|
||||
//public delegate void EventQueueCallback(string message, StructuredData.OSD body, Simulator simulator);
|
||||
|
||||
public delegate void EventQueueCallback(string capsKey, IMessage message, Simulator simulator);
|
||||
|
||||
/// <summary>Reference to the simulator this system is connected to</summary>
|
||||
public Simulator Simulator;
|
||||
|
||||
internal string _SeedCapsURI;
|
||||
internal Dictionary<string, Uri> _Caps = new Dictionary<string, Uri>();
|
||||
|
||||
private CapsClient _SeedRequest;
|
||||
private EventQueueClient _EventQueueCap = null;
|
||||
|
||||
/// <summary>Capabilities URI this system was initialized with</summary>
|
||||
public string SeedCapsURI { get { return _SeedCapsURI; } }
|
||||
|
||||
/// <summary>Whether the capabilities event queue is connected and
|
||||
/// listening for incoming events</summary>
|
||||
public bool IsEventQueueRunning
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_EventQueueCap != null)
|
||||
return _EventQueueCap.Running;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
/// <param name="simulator"></param>
|
||||
/// <param name="seedcaps"></param>
|
||||
internal Caps(Simulator simulator, string seedcaps)
|
||||
{
|
||||
Simulator = simulator;
|
||||
_SeedCapsURI = seedcaps;
|
||||
|
||||
MakeSeedRequest();
|
||||
}
|
||||
|
||||
public void Disconnect(bool immediate)
|
||||
{
|
||||
Logger.Log(String.Format("Caps system for {0} is {1}", Simulator,
|
||||
(immediate ? "aborting" : "disconnecting")), Helpers.LogLevel.Info, Simulator.Client);
|
||||
|
||||
if (_SeedRequest != null)
|
||||
_SeedRequest.Cancel();
|
||||
|
||||
if (_EventQueueCap != null)
|
||||
_EventQueueCap.Stop(immediate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request the URI of a named capability
|
||||
/// </summary>
|
||||
/// <param name="capability">Name of the capability to request</param>
|
||||
/// <returns>The URI of the requested capability, or String.Empty if
|
||||
/// the capability does not exist</returns>
|
||||
public Uri CapabilityURI(string capability)
|
||||
{
|
||||
Uri cap;
|
||||
|
||||
if (_Caps.TryGetValue(capability, out cap))
|
||||
return cap;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private void MakeSeedRequest()
|
||||
{
|
||||
if (Simulator == null || !Simulator.Client.Network.Connected)
|
||||
return;
|
||||
|
||||
// Create a request list
|
||||
OSDArray req = new OSDArray();
|
||||
// This list can be updated by using the following command to obtain a current list of capabilities the official linden viewer supports:
|
||||
// wget -q -O - https://bitbucket.org/lindenlab/viewer-release/raw/default/indra/newview/llviewerregion.cpp | grep 'capabilityNames.append' | sed 's/^[ \t]*//;s/capabilityNames.append("/req.Add("/'
|
||||
req.Add("AgentPreferences");
|
||||
req.Add("AgentState");
|
||||
req.Add("AttachmentResources");
|
||||
req.Add("AvatarPickerSearch");
|
||||
req.Add("AvatarRenderInfo");
|
||||
req.Add("CharacterProperties");
|
||||
req.Add("ChatSessionRequest");
|
||||
req.Add("CopyInventoryFromNotecard");
|
||||
req.Add("CreateInventoryCategory");
|
||||
req.Add("DispatchRegionInfo");
|
||||
req.Add("EnvironmentSettings");
|
||||
req.Add("EstateChangeInfo");
|
||||
req.Add("EventQueueGet");
|
||||
req.Add("FacebookConnect");
|
||||
req.Add("FlickrConnect");
|
||||
req.Add("TwitterConnect");
|
||||
req.Add("FetchLib2");
|
||||
req.Add("FetchLibDescendents2");
|
||||
req.Add("FetchInventory2");
|
||||
req.Add("FetchInventoryDescendents2");
|
||||
req.Add("IncrementCOFVersion");
|
||||
req.Add("GetDisplayNames");
|
||||
req.Add("GetMesh");
|
||||
req.Add("GetMesh2");
|
||||
req.Add("GetObjectCost");
|
||||
req.Add("GetObjectPhysicsData");
|
||||
req.Add("GetTexture");
|
||||
req.Add("GroupAPIv1");
|
||||
req.Add("GroupMemberData");
|
||||
req.Add("GroupProposalBallot");
|
||||
req.Add("HomeLocation");
|
||||
req.Add("LandResources");
|
||||
req.Add("LSLSyntax");
|
||||
req.Add("MapLayer");
|
||||
req.Add("MapLayerGod");
|
||||
req.Add("MeshUploadFlag");
|
||||
req.Add("NavMeshGenerationStatus");
|
||||
req.Add("NewFileAgentInventory");
|
||||
req.Add("ObjectMedia");
|
||||
req.Add("ObjectMediaNavigate");
|
||||
req.Add("ObjectNavMeshProperties");
|
||||
req.Add("ParcelPropertiesUpdate");
|
||||
req.Add("ParcelVoiceInfoRequest");
|
||||
req.Add("ProductInfoRequest");
|
||||
req.Add("ProvisionVoiceAccountRequest");
|
||||
req.Add("RemoteParcelRequest");
|
||||
req.Add("RenderMaterials");
|
||||
req.Add("RequestTextureDownload");
|
||||
req.Add("ResourceCostSelected");
|
||||
req.Add("RetrieveNavMeshSrc");
|
||||
req.Add("SearchStatRequest");
|
||||
req.Add("SearchStatTracking");
|
||||
req.Add("SendPostcard");
|
||||
req.Add("SendUserReport");
|
||||
req.Add("SendUserReportWithScreenshot");
|
||||
req.Add("ServerReleaseNotes");
|
||||
req.Add("SetDisplayName");
|
||||
req.Add("SimConsoleAsync");
|
||||
req.Add("SimulatorFeatures");
|
||||
req.Add("StartGroupProposal");
|
||||
req.Add("TerrainNavMeshProperties");
|
||||
req.Add("TextureStats");
|
||||
req.Add("UntrustedSimulatorMessage");
|
||||
req.Add("UpdateAgentInformation");
|
||||
req.Add("UpdateAgentLanguage");
|
||||
req.Add("UpdateAvatarAppearance");
|
||||
req.Add("UpdateGestureAgentInventory");
|
||||
req.Add("UpdateGestureTaskInventory");
|
||||
req.Add("UpdateNotecardAgentInventory");
|
||||
req.Add("UpdateNotecardTaskInventory");
|
||||
req.Add("UpdateScriptAgent");
|
||||
req.Add("UpdateScriptTask");
|
||||
req.Add("UploadBakedTexture");
|
||||
req.Add("ViewerMetrics");
|
||||
req.Add("ViewerStartAuction");
|
||||
req.Add("ViewerStats");
|
||||
|
||||
_SeedRequest = new CapsClient(new Uri(_SeedCapsURI));
|
||||
_SeedRequest.OnComplete += new CapsClient.CompleteCallback(SeedRequestCompleteHandler);
|
||||
_SeedRequest.BeginGetResponse(req, OSDFormat.Xml, Simulator.Client.Settings.CAPS_TIMEOUT);
|
||||
}
|
||||
|
||||
private void SeedRequestCompleteHandler(CapsClient client, OSD result, Exception error)
|
||||
{
|
||||
if (result != null && result.Type == OSDType.Map)
|
||||
{
|
||||
OSDMap respTable = (OSDMap)result;
|
||||
|
||||
foreach (string cap in respTable.Keys)
|
||||
{
|
||||
_Caps[cap] = respTable[cap].AsUri();
|
||||
}
|
||||
|
||||
if (_Caps.ContainsKey("EventQueueGet"))
|
||||
{
|
||||
Logger.DebugLog("Starting event queue for " + Simulator.ToString(), Simulator.Client);
|
||||
|
||||
_EventQueueCap = new EventQueueClient(_Caps["EventQueueGet"]);
|
||||
_EventQueueCap.OnConnected += EventQueueConnectedHandler;
|
||||
_EventQueueCap.OnEvent += EventQueueEventHandler;
|
||||
_EventQueueCap.Start();
|
||||
}
|
||||
}
|
||||
else if (
|
||||
error != null &&
|
||||
error is WebException &&
|
||||
((WebException)error).Response != null &&
|
||||
((HttpWebResponse)((WebException)error).Response).StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
// 404 error
|
||||
Logger.Log("Seed capability returned a 404, capability system is aborting", Helpers.LogLevel.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The initial CAPS connection failed, try again
|
||||
MakeSeedRequest();
|
||||
}
|
||||
}
|
||||
|
||||
private void EventQueueConnectedHandler()
|
||||
{
|
||||
Simulator.Client.Network.RaiseConnectedEvent(Simulator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process any incoming events, check to see if we have a message created for the event,
|
||||
/// </summary>
|
||||
/// <param name="eventName"></param>
|
||||
/// <param name="body"></param>
|
||||
private void EventQueueEventHandler(string eventName, OSDMap body)
|
||||
{
|
||||
IMessage message = Messages.MessageUtils.DecodeEvent(eventName, body);
|
||||
if (message != null)
|
||||
{
|
||||
Simulator.Client.Network.CapsEvents.BeginRaiseEvent(eventName, message, Simulator);
|
||||
|
||||
#region Stats Tracking
|
||||
if (Simulator.Client.Settings.TRACK_UTILIZATION)
|
||||
{
|
||||
Simulator.Client.Stats.Update(eventName, OpenMetaverse.Stats.Type.Message, 0, body.ToString().Length);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("No Message handler exists for event " + eventName + ". Unable to decode. Will try Generic Handler next", Helpers.LogLevel.Warning);
|
||||
Logger.Log("Please report this information to http://jira.openmetaverse.org/: \n" + body, Helpers.LogLevel.Debug);
|
||||
|
||||
// try generic decoder next which takes a caps event and tries to match it to an existing packet
|
||||
if (body.Type == OSDType.Map)
|
||||
{
|
||||
OSDMap map = (OSDMap)body;
|
||||
Packet packet = Packet.BuildPacket(eventName, map);
|
||||
if (packet != null)
|
||||
{
|
||||
NetworkManager.IncomingPacket incomingPacket;
|
||||
incomingPacket.Simulator = Simulator;
|
||||
incomingPacket.Packet = packet;
|
||||
|
||||
Logger.DebugLog("Serializing " + packet.Type.ToString() + " capability with generic handler", Simulator.Client);
|
||||
|
||||
Simulator.Client.Network.PacketInbox.Enqueue(incomingPacket);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("No Packet or Message handler exists for " + eventName, Helpers.LogLevel.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using OpenMetaverse.StructuredData;
|
||||
|
||||
namespace OpenMetaverse.Packets
|
||||
{
|
||||
public abstract partial class Packet
|
||||
{
|
||||
#region Serialization/Deserialization
|
||||
|
||||
public static string ToXmlString(Packet packet)
|
||||
{
|
||||
return OSDParser.SerializeLLSDXmlString(GetLLSD(packet));
|
||||
}
|
||||
|
||||
public static OSD GetLLSD(Packet packet)
|
||||
{
|
||||
OSDMap body = new OSDMap();
|
||||
Type type = packet.GetType();
|
||||
|
||||
foreach (FieldInfo field in type.GetFields())
|
||||
{
|
||||
if (field.IsPublic)
|
||||
{
|
||||
Type blockType = field.FieldType;
|
||||
|
||||
if (blockType.IsArray)
|
||||
{
|
||||
object blockArray = field.GetValue(packet);
|
||||
Array array = (Array)blockArray;
|
||||
OSDArray blockList = new OSDArray(array.Length);
|
||||
IEnumerator ie = array.GetEnumerator();
|
||||
|
||||
while (ie.MoveNext())
|
||||
{
|
||||
object block = ie.Current;
|
||||
blockList.Add(BuildLLSDBlock(block));
|
||||
}
|
||||
|
||||
body[field.Name] = blockList;
|
||||
}
|
||||
else
|
||||
{
|
||||
object block = field.GetValue(packet);
|
||||
body[field.Name] = BuildLLSDBlock(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
public static byte[] ToBinary(Packet packet)
|
||||
{
|
||||
return OSDParser.SerializeLLSDBinary(GetLLSD(packet));
|
||||
}
|
||||
|
||||
public static Packet FromXmlString(string xml)
|
||||
{
|
||||
System.Xml.XmlTextReader reader =
|
||||
new System.Xml.XmlTextReader(new System.IO.MemoryStream(Utils.StringToBytes(xml)));
|
||||
|
||||
return FromLLSD(OSDParser.DeserializeLLSDXml(reader));
|
||||
}
|
||||
|
||||
public static Packet FromLLSD(OSD osd)
|
||||
{
|
||||
// FIXME: Need the inverse of the reflection magic above done here
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion Serialization/Deserialization
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to convert an LLSD structure to a known Packet type
|
||||
/// </summary>
|
||||
/// <param name="capsEventName">Event name, this must match an actual
|
||||
/// packet name for a Packet to be successfully built</param>
|
||||
/// <param name="body">LLSD to convert to a Packet</param>
|
||||
/// <returns>A Packet on success, otherwise null</returns>
|
||||
public static Packet BuildPacket(string capsEventName, OSDMap body)
|
||||
{
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
// Check if we have a subclass of packet with the same name as this event
|
||||
Type type = assembly.GetType("OpenMetaverse.Packets." + capsEventName + "Packet", false);
|
||||
if (type == null)
|
||||
return null;
|
||||
|
||||
Packet packet = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Create an instance of the object
|
||||
packet = (Packet)Activator.CreateInstance(type);
|
||||
|
||||
// Iterate over all of the fields in the packet class, looking for matches in the LLSD
|
||||
foreach (FieldInfo field in type.GetFields())
|
||||
{
|
||||
if (body.ContainsKey(field.Name))
|
||||
{
|
||||
Type blockType = field.FieldType;
|
||||
|
||||
if (blockType.IsArray)
|
||||
{
|
||||
OSDArray array = (OSDArray)body[field.Name];
|
||||
Type elementType = blockType.GetElementType();
|
||||
object[] blockArray = (object[])Array.CreateInstance(elementType, array.Count);
|
||||
|
||||
for (int i = 0; i < array.Count; i++)
|
||||
{
|
||||
OSDMap map = (OSDMap)array[i];
|
||||
blockArray[i] = ParseLLSDBlock(map, elementType);
|
||||
}
|
||||
|
||||
field.SetValue(packet, blockArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
OSDMap map = (OSDMap)((OSDArray)body[field.Name])[0];
|
||||
field.SetValue(packet, ParseLLSDBlock(map, blockType));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//FIXME Logger.Log(e.Message, Helpers.LogLevel.Error, e);
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static object ParseLLSDBlock(OSDMap blockData, Type blockType)
|
||||
{
|
||||
object block = Activator.CreateInstance(blockType);
|
||||
|
||||
// Iterate over each field and set the value if a match was found in the LLSD
|
||||
foreach (FieldInfo field in blockType.GetFields())
|
||||
{
|
||||
if (blockData.ContainsKey(field.Name))
|
||||
{
|
||||
Type fieldType = field.FieldType;
|
||||
|
||||
if (fieldType == typeof(ulong))
|
||||
{
|
||||
// ulongs come in as a byte array, convert it manually here
|
||||
byte[] bytes = blockData[field.Name].AsBinary();
|
||||
ulong value = Utils.BytesToUInt64(bytes);
|
||||
field.SetValue(block, value);
|
||||
}
|
||||
else if (fieldType == typeof(uint))
|
||||
{
|
||||
// uints come in as a byte array, convert it manually here
|
||||
byte[] bytes = blockData[field.Name].AsBinary();
|
||||
uint value = Utils.BytesToUInt(bytes);
|
||||
field.SetValue(block, value);
|
||||
}
|
||||
else if (fieldType == typeof(ushort))
|
||||
{
|
||||
// Just need a bit of manual typecasting love here
|
||||
field.SetValue(block, (ushort)blockData[field.Name].AsInteger());
|
||||
}
|
||||
else if (fieldType == typeof(byte))
|
||||
{
|
||||
// Just need a bit of manual typecasting love here
|
||||
field.SetValue(block, (byte)blockData[field.Name].AsInteger());
|
||||
}
|
||||
else if (fieldType == typeof(sbyte))
|
||||
{
|
||||
field.SetValue(block, (sbyte)blockData[field.Name].AsInteger());
|
||||
}
|
||||
else if (fieldType == typeof(short))
|
||||
{
|
||||
field.SetValue(block, (short)blockData[field.Name].AsInteger());
|
||||
}
|
||||
else if (fieldType == typeof(string))
|
||||
{
|
||||
field.SetValue(block, blockData[field.Name].AsString());
|
||||
}
|
||||
else if (fieldType == typeof(bool))
|
||||
{
|
||||
field.SetValue(block, blockData[field.Name].AsBoolean());
|
||||
}
|
||||
else if (fieldType == typeof(float))
|
||||
{
|
||||
field.SetValue(block, (float)blockData[field.Name].AsReal());
|
||||
}
|
||||
else if (fieldType == typeof(double))
|
||||
{
|
||||
field.SetValue(block, blockData[field.Name].AsReal());
|
||||
}
|
||||
else if (fieldType == typeof(int))
|
||||
{
|
||||
field.SetValue(block, blockData[field.Name].AsInteger());
|
||||
}
|
||||
else if (fieldType == typeof(UUID))
|
||||
{
|
||||
field.SetValue(block, blockData[field.Name].AsUUID());
|
||||
}
|
||||
else if (fieldType == typeof(Vector3))
|
||||
{
|
||||
Vector3 vec = ((OSDArray)blockData[field.Name]).AsVector3();
|
||||
field.SetValue(block, vec);
|
||||
}
|
||||
else if (fieldType == typeof(Vector4))
|
||||
{
|
||||
Vector4 vec = ((OSDArray)blockData[field.Name]).AsVector4();
|
||||
field.SetValue(block, vec);
|
||||
}
|
||||
else if (fieldType == typeof(Quaternion))
|
||||
{
|
||||
Quaternion quat = ((OSDArray)blockData[field.Name]).AsQuaternion();
|
||||
field.SetValue(block, quat);
|
||||
}
|
||||
else if (fieldType == typeof(byte[]) && blockData[field.Name].Type == OSDType.String)
|
||||
{
|
||||
field.SetValue(block, Utils.StringToBytes(blockData[field.Name]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Additional fields come as properties, Handle those as well.
|
||||
foreach (PropertyInfo property in blockType.GetProperties())
|
||||
{
|
||||
if (blockData.ContainsKey(property.Name))
|
||||
{
|
||||
OSDType proptype = blockData[property.Name].Type;
|
||||
MethodInfo set = property.GetSetMethod();
|
||||
|
||||
if (proptype.Equals(OSDType.Binary))
|
||||
{
|
||||
set.Invoke(block, new object[] { blockData[property.Name].AsBinary() });
|
||||
}
|
||||
else
|
||||
set.Invoke(block, new object[] { Utils.StringToBytes(blockData[property.Name].AsString()) });
|
||||
}
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
private static OSD BuildLLSDBlock(object block)
|
||||
{
|
||||
OSDMap map = new OSDMap();
|
||||
Type blockType = block.GetType();
|
||||
|
||||
foreach (FieldInfo field in blockType.GetFields())
|
||||
{
|
||||
if (field.IsPublic)
|
||||
map[field.Name] = OSD.FromObject(field.GetValue(block));
|
||||
}
|
||||
|
||||
foreach (PropertyInfo property in blockType.GetProperties())
|
||||
{
|
||||
if (property.Name != "Length")
|
||||
{
|
||||
map[property.Name] = OSD.FromObject(property.GetValue(block, null));
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
public class CoordinateFrame
|
||||
{
|
||||
public static readonly Vector3 X_AXIS = new Vector3(1f, 0f, 0f);
|
||||
public static readonly Vector3 Y_AXIS = new Vector3(0f, 1f, 0f);
|
||||
public static readonly Vector3 Z_AXIS = new Vector3(0f, 0f, 1f);
|
||||
|
||||
/// <summary>Origin position of this coordinate frame</summary>
|
||||
public Vector3 Origin
|
||||
{
|
||||
get { return origin; }
|
||||
set
|
||||
{
|
||||
if (!value.IsFinite())
|
||||
throw new ArgumentException("Non-finite in CoordinateFrame.Origin assignment");
|
||||
origin = value;
|
||||
}
|
||||
}
|
||||
/// <summary>X axis of this coordinate frame, or Forward/At in grid terms</summary>
|
||||
public Vector3 XAxis
|
||||
{
|
||||
get { return xAxis; }
|
||||
set
|
||||
{
|
||||
if (!value.IsFinite())
|
||||
throw new ArgumentException("Non-finite in CoordinateFrame.XAxis assignment");
|
||||
xAxis = value;
|
||||
}
|
||||
}
|
||||
/// <summary>Y axis of this coordinate frame, or Left in grid terms</summary>
|
||||
public Vector3 YAxis
|
||||
{
|
||||
get { return yAxis; }
|
||||
set
|
||||
{
|
||||
if (!value.IsFinite())
|
||||
throw new ArgumentException("Non-finite in CoordinateFrame.YAxis assignment");
|
||||
yAxis = value;
|
||||
}
|
||||
}
|
||||
/// <summary>Z axis of this coordinate frame, or Up in grid terms</summary>
|
||||
public Vector3 ZAxis
|
||||
{
|
||||
get { return zAxis; }
|
||||
set
|
||||
{
|
||||
if (!value.IsFinite())
|
||||
throw new ArgumentException("Non-finite in CoordinateFrame.ZAxis assignment");
|
||||
zAxis = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected Vector3 origin;
|
||||
protected Vector3 xAxis;
|
||||
protected Vector3 yAxis;
|
||||
protected Vector3 zAxis;
|
||||
|
||||
#region Constructors
|
||||
|
||||
public CoordinateFrame(Vector3 origin)
|
||||
{
|
||||
this.origin = origin;
|
||||
xAxis = X_AXIS;
|
||||
yAxis = Y_AXIS;
|
||||
zAxis = Z_AXIS;
|
||||
|
||||
if (!this.origin.IsFinite())
|
||||
throw new ArgumentException("Non-finite in CoordinateFrame constructor");
|
||||
}
|
||||
|
||||
public CoordinateFrame(Vector3 origin, Vector3 direction)
|
||||
{
|
||||
this.origin = origin;
|
||||
LookDirection(direction);
|
||||
|
||||
if (!IsFinite())
|
||||
throw new ArgumentException("Non-finite in CoordinateFrame constructor");
|
||||
}
|
||||
|
||||
public CoordinateFrame(Vector3 origin, Vector3 xAxis, Vector3 yAxis, Vector3 zAxis)
|
||||
{
|
||||
this.origin = origin;
|
||||
this.xAxis = xAxis;
|
||||
this.yAxis = yAxis;
|
||||
this.zAxis = zAxis;
|
||||
|
||||
if (!IsFinite())
|
||||
throw new ArgumentException("Non-finite in CoordinateFrame constructor");
|
||||
}
|
||||
|
||||
public CoordinateFrame(Vector3 origin, Matrix4 rotation)
|
||||
{
|
||||
this.origin = origin;
|
||||
xAxis = rotation.AtAxis;
|
||||
yAxis = rotation.LeftAxis;
|
||||
zAxis = rotation.UpAxis;
|
||||
|
||||
if (!IsFinite())
|
||||
throw new ArgumentException("Non-finite in CoordinateFrame constructor");
|
||||
}
|
||||
|
||||
public CoordinateFrame(Vector3 origin, Quaternion rotation)
|
||||
{
|
||||
Matrix4 m = Matrix4.CreateFromQuaternion(rotation);
|
||||
|
||||
this.origin = origin;
|
||||
xAxis = m.AtAxis;
|
||||
yAxis = m.LeftAxis;
|
||||
zAxis = m.UpAxis;
|
||||
|
||||
if (!IsFinite())
|
||||
throw new ArgumentException("Non-finite in CoordinateFrame constructor");
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void ResetAxes()
|
||||
{
|
||||
xAxis = X_AXIS;
|
||||
yAxis = Y_AXIS;
|
||||
zAxis = Z_AXIS;
|
||||
}
|
||||
|
||||
public void Rotate(float angle, Vector3 rotationAxis)
|
||||
{
|
||||
Quaternion q = Quaternion.CreateFromAxisAngle(rotationAxis, angle);
|
||||
Rotate(q);
|
||||
}
|
||||
|
||||
public void Rotate(Quaternion q)
|
||||
{
|
||||
Matrix4 m = Matrix4.CreateFromQuaternion(q);
|
||||
Rotate(m);
|
||||
}
|
||||
|
||||
public void Rotate(Matrix4 m)
|
||||
{
|
||||
xAxis = Vector3.Transform(xAxis, m);
|
||||
yAxis = Vector3.Transform(yAxis, m);
|
||||
|
||||
Orthonormalize();
|
||||
|
||||
if (!IsFinite())
|
||||
throw new Exception("Non-finite in CoordinateFrame.Rotate()");
|
||||
}
|
||||
|
||||
public void Roll(float angle)
|
||||
{
|
||||
Quaternion q = Quaternion.CreateFromAxisAngle(xAxis, angle);
|
||||
Matrix4 m = Matrix4.CreateFromQuaternion(q);
|
||||
Rotate(m);
|
||||
|
||||
if (!yAxis.IsFinite() || !zAxis.IsFinite())
|
||||
throw new Exception("Non-finite in CoordinateFrame.Roll()");
|
||||
}
|
||||
|
||||
public void Pitch(float angle)
|
||||
{
|
||||
Quaternion q = Quaternion.CreateFromAxisAngle(yAxis, angle);
|
||||
Matrix4 m = Matrix4.CreateFromQuaternion(q);
|
||||
Rotate(m);
|
||||
|
||||
if (!xAxis.IsFinite() || !zAxis.IsFinite())
|
||||
throw new Exception("Non-finite in CoordinateFrame.Pitch()");
|
||||
}
|
||||
|
||||
public void Yaw(float angle)
|
||||
{
|
||||
Quaternion q = Quaternion.CreateFromAxisAngle(zAxis, angle);
|
||||
Matrix4 m = Matrix4.CreateFromQuaternion(q);
|
||||
Rotate(m);
|
||||
|
||||
if (!xAxis.IsFinite() || !yAxis.IsFinite())
|
||||
throw new Exception("Non-finite in CoordinateFrame.Yaw()");
|
||||
}
|
||||
|
||||
public void LookDirection(Vector3 at)
|
||||
{
|
||||
LookDirection(at, Z_AXIS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="at">Looking direction, must be a normalized vector</param>
|
||||
/// <param name="upDirection">Up direction, must be a normalized vector</param>
|
||||
public void LookDirection(Vector3 at, Vector3 upDirection)
|
||||
{
|
||||
// The two parameters cannot be parallel
|
||||
Vector3 left = Vector3.Cross(upDirection, at);
|
||||
if (left == Vector3.Zero)
|
||||
{
|
||||
// Prevent left from being zero
|
||||
at.X += 0.01f;
|
||||
at.Normalize();
|
||||
left = Vector3.Cross(upDirection, at);
|
||||
}
|
||||
left.Normalize();
|
||||
|
||||
xAxis = at;
|
||||
yAxis = left;
|
||||
zAxis = Vector3.Cross(at, left);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Align the coordinate frame X and Y axis with a given rotation
|
||||
/// around the Z axis in radians
|
||||
/// </summary>
|
||||
/// <param name="heading">Absolute rotation around the Z axis in
|
||||
/// radians</param>
|
||||
public void LookDirection(double heading)
|
||||
{
|
||||
yAxis.X = (float)Math.Cos(heading);
|
||||
yAxis.Y = (float)Math.Sin(heading);
|
||||
xAxis.X = (float)-Math.Sin(heading);
|
||||
xAxis.Y = (float)Math.Cos(heading);
|
||||
}
|
||||
|
||||
public void LookAt(Vector3 origin, Vector3 target)
|
||||
{
|
||||
LookAt(origin, target, new Vector3(0f, 0f, 1f));
|
||||
}
|
||||
|
||||
public void LookAt(Vector3 origin, Vector3 target, Vector3 upDirection)
|
||||
{
|
||||
this.origin = origin;
|
||||
Vector3 at = new Vector3(target - origin);
|
||||
at.Normalize();
|
||||
|
||||
LookDirection(at, upDirection);
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
protected bool IsFinite()
|
||||
{
|
||||
if (xAxis.IsFinite() && yAxis.IsFinite() && zAxis.IsFinite())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void Orthonormalize()
|
||||
{
|
||||
// Make sure the axis are orthagonal and normalized
|
||||
xAxis.Normalize();
|
||||
yAxis -= xAxis * (xAxis * yAxis);
|
||||
yAxis.Normalize();
|
||||
zAxis = Vector3.Cross(xAxis, yAxis);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using OpenMetaverse.Http;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// Represends individual HTTP Download request
|
||||
/// </summary>
|
||||
public class DownloadRequest
|
||||
{
|
||||
/// <summary>URI of the item to fetch</summary>
|
||||
public Uri Address;
|
||||
/// <summary>Timout specified in milliseconds</summary>
|
||||
public int MillisecondsTimeout;
|
||||
/// <summary>Download progress callback</summary>
|
||||
public CapsBase.DownloadProgressEventHandler DownloadProgressCallback;
|
||||
/// <summary>Download completed callback</summary>
|
||||
public CapsBase.RequestCompletedEventHandler CompletedCallback;
|
||||
/// <summary>Accept the following content type</summary>
|
||||
public string ContentType;
|
||||
/// <summary>How many times will this request be retried</summary>
|
||||
public int Retries = 5;
|
||||
/// <summary>Current fetch attempt</summary>
|
||||
public int Attempt = 0;
|
||||
|
||||
/// <summary>Default constructor</summary>
|
||||
public DownloadRequest()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Constructor</summary>
|
||||
public DownloadRequest(Uri address, int millisecondsTimeout,
|
||||
string contentType,
|
||||
CapsBase.DownloadProgressEventHandler downloadProgressCallback,
|
||||
CapsBase.RequestCompletedEventHandler completedCallback)
|
||||
{
|
||||
this.Address = address;
|
||||
this.MillisecondsTimeout = millisecondsTimeout;
|
||||
this.DownloadProgressCallback = downloadProgressCallback;
|
||||
this.CompletedCallback = completedCallback;
|
||||
this.ContentType = contentType;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ActiveDownload
|
||||
{
|
||||
public List<CapsBase.DownloadProgressEventHandler> ProgresHadlers = new List<CapsBase.DownloadProgressEventHandler>();
|
||||
public List<CapsBase.RequestCompletedEventHandler> CompletedHandlers = new List<CapsBase.RequestCompletedEventHandler>();
|
||||
public HttpWebRequest Request;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages async HTTP downloads with a limit on maximum
|
||||
/// concurrent downloads
|
||||
/// </summary>
|
||||
public class DownloadManager
|
||||
{
|
||||
Queue<DownloadRequest> queue = new Queue<DownloadRequest>();
|
||||
Dictionary<string, ActiveDownload> activeDownloads = new Dictionary<string, ActiveDownload>();
|
||||
|
||||
X509Certificate2 m_ClientCert;
|
||||
|
||||
/// <summary>Maximum number of parallel downloads from a single endpoint</summary>
|
||||
public int ParallelDownloads { get; set; }
|
||||
|
||||
/// <summary>Client certificate</summary>
|
||||
public X509Certificate2 ClientCert
|
||||
{
|
||||
get { return m_ClientCert; }
|
||||
set { m_ClientCert = value; }
|
||||
}
|
||||
|
||||
/// <summary>Default constructor</summary>
|
||||
public DownloadManager()
|
||||
{
|
||||
ParallelDownloads = 8;
|
||||
}
|
||||
|
||||
/// <summary>Cleanup method</summary>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
lock (activeDownloads)
|
||||
{
|
||||
foreach (ActiveDownload download in activeDownloads.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (download.Request != null)
|
||||
{
|
||||
download.Request.Abort();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
activeDownloads.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Setup http download request</summary>
|
||||
protected virtual HttpWebRequest SetupRequest(Uri address, string acceptHeader)
|
||||
{
|
||||
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(address);
|
||||
request.Method = "GET";
|
||||
|
||||
if (!string.IsNullOrEmpty(acceptHeader))
|
||||
request.Accept = acceptHeader;
|
||||
|
||||
// Add the client certificate to the request if one was given
|
||||
if (m_ClientCert != null)
|
||||
request.ClientCertificates.Add(m_ClientCert);
|
||||
|
||||
// Leave idle connections to this endpoint open for up to 60 seconds
|
||||
request.ServicePoint.MaxIdleTime = 0;
|
||||
// Disable stupid Expect-100: Continue header
|
||||
request.ServicePoint.Expect100Continue = false;
|
||||
// Crank up the max number of connections per endpoint
|
||||
if (request.ServicePoint.ConnectionLimit < Settings.MAX_HTTP_CONNECTIONS)
|
||||
{
|
||||
Logger.Log(string.Format("In DownloadManager.SetupRequest() setting conn limit for {0}:{1} to {2}", address.Host, address.Port, Settings.MAX_HTTP_CONNECTIONS), Helpers.LogLevel.Debug);
|
||||
request.ServicePoint.ConnectionLimit = Settings.MAX_HTTP_CONNECTIONS;
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/// <summary>Check the queue for pending work</summary>
|
||||
private void EnqueuePending()
|
||||
{
|
||||
lock (queue)
|
||||
{
|
||||
if (queue.Count > 0)
|
||||
{
|
||||
int nr = 0;
|
||||
lock (activeDownloads)
|
||||
{
|
||||
nr = activeDownloads.Count;
|
||||
}
|
||||
|
||||
// Logger.DebugLog(nr.ToString() + " active downloads. Queued textures: " + queue.Count.ToString());
|
||||
|
||||
for (int i = nr; i < ParallelDownloads && queue.Count > 0; i++)
|
||||
{
|
||||
DownloadRequest item = queue.Dequeue();
|
||||
lock (activeDownloads)
|
||||
{
|
||||
string addr = item.Address.ToString();
|
||||
if (activeDownloads.ContainsKey(addr))
|
||||
{
|
||||
activeDownloads[addr].CompletedHandlers.Add(item.CompletedCallback);
|
||||
if (item.DownloadProgressCallback != null)
|
||||
{
|
||||
activeDownloads[addr].ProgresHadlers.Add(item.DownloadProgressCallback);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveDownload activeDownload = new ActiveDownload();
|
||||
activeDownload.CompletedHandlers.Add(item.CompletedCallback);
|
||||
if (item.DownloadProgressCallback != null)
|
||||
{
|
||||
activeDownload.ProgresHadlers.Add(item.DownloadProgressCallback);
|
||||
}
|
||||
|
||||
Logger.DebugLog("Requesting " + item.Address.ToString());
|
||||
activeDownload.Request = SetupRequest(item.Address, item.ContentType);
|
||||
CapsBase.DownloadDataAsync(
|
||||
activeDownload.Request,
|
||||
item.MillisecondsTimeout,
|
||||
(HttpWebRequest request, HttpWebResponse response, int bytesReceived, int totalBytesToReceive) =>
|
||||
{
|
||||
foreach (CapsBase.DownloadProgressEventHandler handler in activeDownload.ProgresHadlers)
|
||||
{
|
||||
handler(request, response, bytesReceived, totalBytesToReceive);
|
||||
}
|
||||
},
|
||||
(HttpWebRequest request, HttpWebResponse response, byte[] responseData, Exception error) =>
|
||||
{
|
||||
lock (activeDownloads) activeDownloads.Remove(addr);
|
||||
if (error == null || item.Attempt >= item.Retries || (error != null && error.Message.Contains("404")))
|
||||
{
|
||||
foreach (CapsBase.RequestCompletedEventHandler handler in activeDownload.CompletedHandlers)
|
||||
{
|
||||
handler(request, response, responseData, error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Attempt++;
|
||||
Logger.Log(string.Format("Texture {0} HTTP download failed, trying again retry {1}/{2}",
|
||||
item.Address, item.Attempt, item.Retries), Helpers.LogLevel.Warning);
|
||||
lock (queue) queue.Enqueue(item);
|
||||
}
|
||||
|
||||
EnqueuePending();
|
||||
}
|
||||
);
|
||||
|
||||
activeDownloads[addr] = activeDownload;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Enqueue a new HTTP download</summary>
|
||||
public void QueueDownload(DownloadRequest req)
|
||||
{
|
||||
lock (activeDownloads)
|
||||
{
|
||||
string addr = req.Address.ToString();
|
||||
if (activeDownloads.ContainsKey(addr))
|
||||
{
|
||||
activeDownloads[addr].CompletedHandlers.Add(req.CompletedCallback);
|
||||
if (req.DownloadProgressCallback != null)
|
||||
{
|
||||
activeDownloads[addr].ProgresHadlers.Add(req.DownloadProgressCallback);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lock (queue)
|
||||
{
|
||||
queue.Enqueue(req);
|
||||
}
|
||||
EnqueuePending();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using OpenMetaverse.Packets;
|
||||
using OpenMetaverse.Messages.Linden;
|
||||
using OpenMetaverse.Interfaces;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers, unregisters, and fires events generated by incoming packets
|
||||
/// </summary>
|
||||
public class PacketEventDictionary
|
||||
{
|
||||
private sealed class PacketCallback
|
||||
{
|
||||
public EventHandler<PacketReceivedEventArgs> Callback;
|
||||
public bool IsAsync;
|
||||
|
||||
public PacketCallback(EventHandler<PacketReceivedEventArgs> callback, bool isAsync)
|
||||
{
|
||||
Callback = callback;
|
||||
IsAsync = isAsync;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object that is passed to worker threads in the ThreadPool for
|
||||
/// firing packet callbacks
|
||||
/// </summary>
|
||||
private struct PacketCallbackWrapper
|
||||
{
|
||||
/// <summary>Callback to fire for this packet</summary>
|
||||
public EventHandler<PacketReceivedEventArgs> Callback;
|
||||
/// <summary>Reference to the simulator that this packet came from</summary>
|
||||
public Simulator Simulator;
|
||||
/// <summary>The packet that needs to be processed</summary>
|
||||
public Packet Packet;
|
||||
}
|
||||
|
||||
/// <summary>Reference to the GridClient object</summary>
|
||||
public GridClient Client;
|
||||
|
||||
private Dictionary<PacketType, PacketCallback> _EventTable = new Dictionary<PacketType, PacketCallback>();
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
public PacketEventDictionary(GridClient client)
|
||||
{
|
||||
Client = client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an event handler
|
||||
/// </summary>
|
||||
/// <remarks>Use PacketType.Default to fire this event on every
|
||||
/// incoming packet</remarks>
|
||||
/// <param name="packetType">Packet type to register the handler for</param>
|
||||
/// <param name="eventHandler">Callback to be fired</param>
|
||||
/// <param name="isAsync">True if this callback should be ran
|
||||
/// asynchronously, false to run it synchronous</param>
|
||||
public void RegisterEvent(PacketType packetType, EventHandler<PacketReceivedEventArgs> eventHandler, bool isAsync)
|
||||
{
|
||||
lock (_EventTable)
|
||||
{
|
||||
PacketCallback callback;
|
||||
if (_EventTable.TryGetValue(packetType, out callback))
|
||||
{
|
||||
callback.Callback += eventHandler;
|
||||
callback.IsAsync = callback.IsAsync || isAsync;
|
||||
}
|
||||
else
|
||||
{
|
||||
callback = new PacketCallback(eventHandler, isAsync);
|
||||
_EventTable[packetType] = callback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister an event handler
|
||||
/// </summary>
|
||||
/// <param name="packetType">Packet type to unregister the handler for</param>
|
||||
/// <param name="eventHandler">Callback to be unregistered</param>
|
||||
public void UnregisterEvent(PacketType packetType, EventHandler<PacketReceivedEventArgs> eventHandler)
|
||||
{
|
||||
lock (_EventTable)
|
||||
{
|
||||
PacketCallback callback;
|
||||
if (_EventTable.TryGetValue(packetType, out callback))
|
||||
{
|
||||
callback.Callback -= eventHandler;
|
||||
if (callback.Callback == null || callback.Callback.GetInvocationList().Length == 0)
|
||||
_EventTable.Remove(packetType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fire the events registered for this packet type
|
||||
/// </summary>
|
||||
/// <param name="packetType">Incoming packet type</param>
|
||||
/// <param name="packet">Incoming packet</param>
|
||||
/// <param name="simulator">Simulator this packet was received from</param>
|
||||
internal void RaiseEvent(PacketType packetType, Packet packet, Simulator simulator)
|
||||
{
|
||||
PacketCallback callback;
|
||||
|
||||
// Default handler first, if one exists
|
||||
if (_EventTable.TryGetValue(PacketType.Default, out callback) && callback.Callback != null)
|
||||
{
|
||||
if (callback.IsAsync)
|
||||
{
|
||||
PacketCallbackWrapper wrapper;
|
||||
wrapper.Callback = callback.Callback;
|
||||
wrapper.Packet = packet;
|
||||
wrapper.Simulator = simulator;
|
||||
WorkPool.QueueUserWorkItem(ThreadPoolDelegate, wrapper);
|
||||
}
|
||||
else
|
||||
{
|
||||
try { callback.Callback(this, new PacketReceivedEventArgs(packet, simulator)); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Default packet event handler: " + ex.ToString(), Helpers.LogLevel.Error, Client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_EventTable.TryGetValue(packetType, out callback) && callback.Callback != null)
|
||||
{
|
||||
if (callback.IsAsync)
|
||||
{
|
||||
PacketCallbackWrapper wrapper;
|
||||
wrapper.Callback = callback.Callback;
|
||||
wrapper.Packet = packet;
|
||||
wrapper.Simulator = simulator;
|
||||
WorkPool.QueueUserWorkItem(ThreadPoolDelegate, wrapper);
|
||||
}
|
||||
else
|
||||
{
|
||||
try { callback.Callback(this, new PacketReceivedEventArgs(packet, simulator)); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Packet event handler: " + ex.ToString(), Helpers.LogLevel.Error, Client);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (packetType != PacketType.Default && packetType != PacketType.PacketAck)
|
||||
{
|
||||
Logger.DebugLog("No handler registered for packet event " + packetType, Client);
|
||||
}
|
||||
}
|
||||
|
||||
private void ThreadPoolDelegate(Object state)
|
||||
{
|
||||
PacketCallbackWrapper wrapper = (PacketCallbackWrapper)state;
|
||||
|
||||
try
|
||||
{
|
||||
wrapper.Callback(this, new PacketReceivedEventArgs(wrapper.Packet, wrapper.Simulator));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Async Packet Event Handler: " + ex.ToString(), Helpers.LogLevel.Error, Client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers, unregisters, and fires events generated by the Capabilities
|
||||
/// event queue
|
||||
/// </summary>
|
||||
public class CapsEventDictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// Object that is passed to worker threads in the ThreadPool for
|
||||
/// firing CAPS callbacks
|
||||
/// </summary>
|
||||
private struct CapsCallbackWrapper
|
||||
{
|
||||
/// <summary>Callback to fire for this packet</summary>
|
||||
public Caps.EventQueueCallback Callback;
|
||||
/// <summary>Name of the CAPS event</summary>
|
||||
public string CapsEvent;
|
||||
/// <summary>Strongly typed decoded data</summary>
|
||||
public IMessage Message;
|
||||
/// <summary>Reference to the simulator that generated this event</summary>
|
||||
public Simulator Simulator;
|
||||
}
|
||||
|
||||
/// <summary>Reference to the GridClient object</summary>
|
||||
public GridClient Client;
|
||||
|
||||
private Dictionary<string, Caps.EventQueueCallback> _EventTable =
|
||||
new Dictionary<string, Caps.EventQueueCallback>();
|
||||
private WaitCallback _ThreadPoolCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
/// <param name="client">Reference to the GridClient object</param>
|
||||
public CapsEventDictionary(GridClient client)
|
||||
{
|
||||
Client = client;
|
||||
_ThreadPoolCallback = new WaitCallback(ThreadPoolDelegate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an new event handler for a capabilities event sent via the EventQueue
|
||||
/// </summary>
|
||||
/// <remarks>Use String.Empty to fire this event on every CAPS event</remarks>
|
||||
/// <param name="capsEvent">Capability event name to register the
|
||||
/// handler for</param>
|
||||
/// <param name="eventHandler">Callback to fire</param>
|
||||
public void RegisterEvent(string capsEvent, Caps.EventQueueCallback eventHandler)
|
||||
{
|
||||
// TODO: Should we add support for synchronous CAPS handlers?
|
||||
lock (_EventTable)
|
||||
{
|
||||
if (_EventTable.ContainsKey(capsEvent))
|
||||
_EventTable[capsEvent] += eventHandler;
|
||||
else
|
||||
_EventTable[capsEvent] = eventHandler;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister a previously registered capabilities handler
|
||||
/// </summary>
|
||||
/// <param name="capsEvent">Capability event name unregister the
|
||||
/// handler for</param>
|
||||
/// <param name="eventHandler">Callback to unregister</param>
|
||||
public void UnregisterEvent(string capsEvent, Caps.EventQueueCallback eventHandler)
|
||||
{
|
||||
lock (_EventTable)
|
||||
{
|
||||
if (_EventTable.ContainsKey(capsEvent) && _EventTable[capsEvent] != null)
|
||||
_EventTable[capsEvent] -= eventHandler;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fire the events registered for this event type synchronously
|
||||
/// </summary>
|
||||
/// <param name="capsEvent">Capability name</param>
|
||||
/// <param name="message">Decoded event body</param>
|
||||
/// <param name="simulator">Reference to the simulator that
|
||||
/// generated this event</param>
|
||||
internal void RaiseEvent(string capsEvent, IMessage message, Simulator simulator)
|
||||
{
|
||||
bool specialHandler = false;
|
||||
Caps.EventQueueCallback callback;
|
||||
|
||||
// Default handler first, if one exists
|
||||
if (_EventTable.TryGetValue(capsEvent, out callback))
|
||||
{
|
||||
if (callback != null)
|
||||
{
|
||||
try { callback(capsEvent, message, simulator); }
|
||||
catch (Exception ex) { Logger.Log("CAPS Event Handler: " + ex.ToString(), Helpers.LogLevel.Error, Client); }
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit handler next
|
||||
if (_EventTable.TryGetValue(capsEvent, out callback) && callback != null)
|
||||
{
|
||||
try { callback(capsEvent, message, simulator); }
|
||||
catch (Exception ex) { Logger.Log("CAPS Event Handler: " + ex.ToString(), Helpers.LogLevel.Error, Client); }
|
||||
|
||||
specialHandler = true;
|
||||
}
|
||||
|
||||
if (!specialHandler)
|
||||
Logger.Log("Unhandled CAPS event " + capsEvent, Helpers.LogLevel.Warning, Client);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fire the events registered for this event type asynchronously
|
||||
/// </summary>
|
||||
/// <param name="capsEvent">Capability name</param>
|
||||
/// <param name="message">Decoded event body</param>
|
||||
/// <param name="simulator">Reference to the simulator that
|
||||
/// generated this event</param>
|
||||
internal void BeginRaiseEvent(string capsEvent, IMessage message, Simulator simulator)
|
||||
{
|
||||
bool specialHandler = false;
|
||||
Caps.EventQueueCallback callback;
|
||||
|
||||
// Default handler first, if one exists
|
||||
if (_EventTable.TryGetValue(String.Empty, out callback))
|
||||
{
|
||||
if (callback != null)
|
||||
{
|
||||
callback(capsEvent, message, simulator);
|
||||
// CapsCallbackWrapper wrapper;
|
||||
// wrapper.Callback = callback;
|
||||
// wrapper.CapsEvent = capsEvent;
|
||||
// wrapper.Message = message;
|
||||
// wrapper.Simulator = simulator;
|
||||
// WorkPool.QueueUserWorkItem(_ThreadPoolCallback, wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit handler next
|
||||
if (_EventTable.TryGetValue(capsEvent, out callback) && callback != null)
|
||||
{
|
||||
callback(capsEvent, message, simulator);
|
||||
|
||||
// CapsCallbackWrapper wrapper;
|
||||
// wrapper.Callback = callback;
|
||||
// wrapper.CapsEvent = capsEvent;
|
||||
// wrapper.Message = message;
|
||||
// wrapper.Simulator = simulator;
|
||||
// WorkPool.QueueUserWorkItem(_ThreadPoolCallback, wrapper);
|
||||
|
||||
specialHandler = true;
|
||||
}
|
||||
|
||||
if (!specialHandler)
|
||||
Logger.Log("Unhandled CAPS event " + capsEvent, Helpers.LogLevel.Warning, Client);
|
||||
}
|
||||
|
||||
private void ThreadPoolDelegate(Object state)
|
||||
{
|
||||
CapsCallbackWrapper wrapper = (CapsCallbackWrapper)state;
|
||||
|
||||
try
|
||||
{
|
||||
wrapper.Callback(wrapper.CapsEvent, wrapper.Message, wrapper.Simulator);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Async CAPS Event Handler: " + ex.ToString(), Helpers.LogLevel.Error, Client);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// Main class to expose grid functionality to clients. All of the
|
||||
/// classes needed for sending and receiving data are accessible through
|
||||
/// this class.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Example minimum code required to instantiate class and
|
||||
/// // connect to a simulator.
|
||||
/// using System;
|
||||
/// using System.Collections.Generic;
|
||||
/// using System.Text;
|
||||
/// using OpenMetaverse;
|
||||
///
|
||||
/// namespace FirstBot
|
||||
/// {
|
||||
/// class Bot
|
||||
/// {
|
||||
/// public static GridClient Client;
|
||||
/// static void Main(string[] args)
|
||||
/// {
|
||||
/// Client = new GridClient(); // instantiates the GridClient class
|
||||
/// // to the global Client object
|
||||
/// // Login to Simulator
|
||||
/// Client.Network.Login("FirstName", "LastName", "Password", "FirstBot", "1.0");
|
||||
/// // Wait for a Keypress
|
||||
/// Console.ReadLine();
|
||||
/// // Logout of simulator
|
||||
/// Client.Network.Logout();
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class GridClient
|
||||
{
|
||||
/// <summary>Networking subsystem</summary>
|
||||
public NetworkManager Network;
|
||||
/// <summary>Settings class including constant values and changeable
|
||||
/// parameters for everything</summary>
|
||||
public Settings Settings;
|
||||
/// <summary>Parcel (subdivided simulator lots) subsystem</summary>
|
||||
public ParcelManager Parcels;
|
||||
/// <summary>Our own avatars subsystem</summary>
|
||||
public AgentManager Self;
|
||||
/// <summary>Other avatars subsystem</summary>
|
||||
public AvatarManager Avatars;
|
||||
/// <summary>Estate subsystem</summary>
|
||||
public EstateTools Estate;
|
||||
/// <summary>Friends list subsystem</summary>
|
||||
public FriendsManager Friends;
|
||||
/// <summary>Grid (aka simulator group) subsystem</summary>
|
||||
public GridManager Grid;
|
||||
/// <summary>Object subsystem</summary>
|
||||
public ObjectManager Objects;
|
||||
/// <summary>Group subsystem</summary>
|
||||
public GroupManager Groups;
|
||||
/// <summary>Asset subsystem</summary>
|
||||
public AssetManager Assets;
|
||||
/// <summary>Appearance subsystem</summary>
|
||||
public AppearanceManager Appearance;
|
||||
/// <summary>Inventory subsystem</summary>
|
||||
public InventoryManager Inventory;
|
||||
/// <summary>Directory searches including classifieds, people, land
|
||||
/// sales, etc</summary>
|
||||
public DirectoryManager Directory;
|
||||
/// <summary>Handles land, wind, and cloud heightmaps</summary>
|
||||
public TerrainManager Terrain;
|
||||
/// <summary>Handles sound-related networking</summary>
|
||||
public SoundManager Sound;
|
||||
/// <summary>Throttling total bandwidth usage, or allocating bandwidth
|
||||
/// for specific data stream types</summary>
|
||||
public AgentThrottle Throttle;
|
||||
|
||||
public Stats.UtilizationStatistics Stats;
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public GridClient()
|
||||
{
|
||||
// Initialise SmartThreadPool when using mono
|
||||
if (Type.GetType("Mono.Runtime") != null)
|
||||
{
|
||||
WorkPool.Init(true);
|
||||
}
|
||||
|
||||
// These are order-dependant
|
||||
Network = new NetworkManager(this);
|
||||
Settings = new Settings(this);
|
||||
Parcels = new ParcelManager(this);
|
||||
Self = new AgentManager(this);
|
||||
Avatars = new AvatarManager(this);
|
||||
Estate = new EstateTools(this);
|
||||
Friends = new FriendsManager(this);
|
||||
Grid = new GridManager(this);
|
||||
Objects = new ObjectManager(this);
|
||||
Groups = new GroupManager(this);
|
||||
Assets = new AssetManager(this);
|
||||
Appearance = new AppearanceManager(this);
|
||||
Inventory = new InventoryManager(this);
|
||||
Directory = new DirectoryManager(this);
|
||||
Terrain = new TerrainManager(this);
|
||||
Sound = new SoundManager(this);
|
||||
Throttle = new AgentThrottle(this);
|
||||
Stats = new OpenMetaverse.Stats.UtilizationStatistics();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the full name of this instance
|
||||
/// </summary>
|
||||
/// <returns>Client avatars full name</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return Self.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,933 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using OpenMetaverse.StructuredData;
|
||||
using OpenMetaverse.Http;
|
||||
using OpenMetaverse.Packets;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>
|
||||
/// Map layer request type
|
||||
/// </summary>
|
||||
public enum GridLayerType : uint
|
||||
{
|
||||
/// <summary>Objects and terrain are shown</summary>
|
||||
Objects = 0,
|
||||
/// <summary>Only the terrain is shown, no objects</summary>
|
||||
Terrain = 1,
|
||||
/// <summary>Overlay showing land for sale and for auction</summary>
|
||||
LandForSale = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of grid item, such as telehub, event, populator location, etc.
|
||||
/// </summary>
|
||||
public enum GridItemType : uint
|
||||
{
|
||||
/// <summary>Telehub</summary>
|
||||
Telehub = 1,
|
||||
/// <summary>PG rated event</summary>
|
||||
PgEvent = 2,
|
||||
/// <summary>Mature rated event</summary>
|
||||
MatureEvent = 3,
|
||||
/// <summary>Popular location</summary>
|
||||
Popular = 4,
|
||||
/// <summary>Locations of avatar groups in a region</summary>
|
||||
AgentLocations = 6,
|
||||
/// <summary>Land for sale</summary>
|
||||
LandForSale = 7,
|
||||
/// <summary>Classified ad</summary>
|
||||
Classified = 8,
|
||||
/// <summary>Adult rated event</summary>
|
||||
AdultEvent = 9,
|
||||
/// <summary>Adult land for sale</summary>
|
||||
AdultLandForSale = 10
|
||||
}
|
||||
|
||||
#endregion Enums
|
||||
|
||||
#region Structs
|
||||
|
||||
/// <summary>
|
||||
/// Information about a region on the grid map
|
||||
/// </summary>
|
||||
public struct GridRegion
|
||||
{
|
||||
/// <summary>Sim X position on World Map</summary>
|
||||
public int X;
|
||||
/// <summary>Sim Y position on World Map</summary>
|
||||
public int Y;
|
||||
/// <summary>Sim Name (NOTE: In lowercase!)</summary>
|
||||
public string Name;
|
||||
/// <summary></summary>
|
||||
public SimAccess Access;
|
||||
/// <summary>Appears to always be zero (None)</summary>
|
||||
public RegionFlags RegionFlags;
|
||||
/// <summary>Sim's defined Water Height</summary>
|
||||
public byte WaterHeight;
|
||||
/// <summary></summary>
|
||||
public byte Agents;
|
||||
/// <summary>UUID of the World Map image</summary>
|
||||
public UUID MapImageID;
|
||||
/// <summary>Unique identifier for this region, a combination of the X
|
||||
/// and Y position</summary>
|
||||
public ulong RegionHandle;
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format("{0} ({1}/{2}), Handle: {3}, MapImage: {4}, Access: {5}, Flags: {6}",
|
||||
Name, X, Y, RegionHandle, MapImageID, Access, RegionFlags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return X.GetHashCode() ^ Y.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is GridRegion)
|
||||
return Equals((GridRegion)obj);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool Equals(GridRegion region)
|
||||
{
|
||||
return (this.X == region.X && this.Y == region.Y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visual chunk of the grid map
|
||||
/// </summary>
|
||||
public struct GridLayer
|
||||
{
|
||||
public int Bottom;
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public UUID ImageID;
|
||||
|
||||
public bool ContainsRegion(int x, int y)
|
||||
{
|
||||
return (x >= Left && x <= Right && y >= Bottom && y <= Top);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Structs
|
||||
|
||||
#region Map Item Classes
|
||||
|
||||
/// <summary>
|
||||
/// Base class for Map Items
|
||||
/// </summary>
|
||||
public abstract class MapItem
|
||||
{
|
||||
/// <summary>The Global X position of the item</summary>
|
||||
public uint GlobalX;
|
||||
/// <summary>The Global Y position of the item</summary>
|
||||
public uint GlobalY;
|
||||
|
||||
/// <summary>Get the Local X position of the item</summary>
|
||||
public uint LocalX { get { return GlobalX % 256; } }
|
||||
/// <summary>Get the Local Y position of the item</summary>
|
||||
public uint LocalY { get { return GlobalY % 256; } }
|
||||
|
||||
/// <summary>Get the Handle of the region</summary>
|
||||
public ulong RegionHandle
|
||||
{
|
||||
get { return Utils.UIntsToLong((uint)(GlobalX - (GlobalX % 256)), (uint)(GlobalY - (GlobalY % 256))); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an agent or group of agents location
|
||||
/// </summary>
|
||||
public class MapAgentLocation : MapItem
|
||||
{
|
||||
public int AvatarCount;
|
||||
public string Identifier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Telehub location
|
||||
/// </summary>
|
||||
public class MapTelehub : MapItem
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a non-adult parcel of land for sale
|
||||
/// </summary>
|
||||
public class MapLandForSale : MapItem
|
||||
{
|
||||
public int Size;
|
||||
public int Price;
|
||||
public string Name;
|
||||
public UUID ID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Adult parcel of land for sale
|
||||
/// </summary>
|
||||
public class MapAdultLandForSale : MapItem
|
||||
{
|
||||
public int Size;
|
||||
public int Price;
|
||||
public string Name;
|
||||
public UUID ID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a PG Event
|
||||
/// </summary>
|
||||
public class MapPGEvent : MapItem
|
||||
{
|
||||
public DirectoryManager.EventFlags Flags; // Extra
|
||||
public DirectoryManager.EventCategories Category; // Extra2
|
||||
public string Description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Mature event
|
||||
/// </summary>
|
||||
public class MapMatureEvent : MapItem
|
||||
{
|
||||
public DirectoryManager.EventFlags Flags; // Extra
|
||||
public DirectoryManager.EventCategories Category; // Extra2
|
||||
public string Description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an Adult event
|
||||
/// </summary>
|
||||
public class MapAdultEvent : MapItem
|
||||
{
|
||||
public DirectoryManager.EventFlags Flags; // Extra
|
||||
public DirectoryManager.EventCategories Category; // Extra2
|
||||
public string Description;
|
||||
}
|
||||
#endregion Grid Item Classes
|
||||
|
||||
/// <summary>
|
||||
/// Manages grid-wide tasks such as the world map
|
||||
/// </summary>
|
||||
public class GridManager
|
||||
{
|
||||
#region Delegates
|
||||
|
||||
/// <summary>The event subscribers. null if no subcribers</summary>
|
||||
private EventHandler<CoarseLocationUpdateEventArgs> m_CoarseLocationUpdate;
|
||||
|
||||
/// <summary>Raises the CoarseLocationUpdate event</summary>
|
||||
/// <param name="e">A CoarseLocationUpdateEventArgs object containing the
|
||||
/// data sent by simulator</param>
|
||||
protected virtual void OnCoarseLocationUpdate(CoarseLocationUpdateEventArgs e)
|
||||
{
|
||||
EventHandler<CoarseLocationUpdateEventArgs> handler = m_CoarseLocationUpdate;
|
||||
if (handler != null)
|
||||
handler(this, e);
|
||||
}
|
||||
|
||||
/// <summary>Thread sync lock object</summary>
|
||||
private readonly object m_CoarseLocationUpdateLock = new object();
|
||||
|
||||
/// <summary>Raised when the simulator sends a <see cref="CoarseLocationUpdatePacket"/>
|
||||
/// containing the location of agents in the simulator</summary>
|
||||
public event EventHandler<CoarseLocationUpdateEventArgs> CoarseLocationUpdate
|
||||
{
|
||||
add { lock (m_CoarseLocationUpdateLock) { m_CoarseLocationUpdate += value; } }
|
||||
remove { lock (m_CoarseLocationUpdateLock) { m_CoarseLocationUpdate -= value; } }
|
||||
}
|
||||
|
||||
/// <summary>The event subscribers. null if no subcribers</summary>
|
||||
private EventHandler<GridRegionEventArgs> m_GridRegion;
|
||||
|
||||
/// <summary>Raises the GridRegion event</summary>
|
||||
/// <param name="e">A GridRegionEventArgs object containing the
|
||||
/// data sent by simulator</param>
|
||||
protected virtual void OnGridRegion(GridRegionEventArgs e)
|
||||
{
|
||||
EventHandler<GridRegionEventArgs> handler = m_GridRegion;
|
||||
if (handler != null)
|
||||
handler(this, e);
|
||||
}
|
||||
|
||||
/// <summary>Thread sync lock object</summary>
|
||||
private readonly object m_GridRegionLock = new object();
|
||||
|
||||
/// <summary>Raised when the simulator sends a Region Data in response to
|
||||
/// a Map request</summary>
|
||||
public event EventHandler<GridRegionEventArgs> GridRegion
|
||||
{
|
||||
add { lock (m_GridRegionLock) { m_GridRegion += value; } }
|
||||
remove { lock (m_GridRegionLock) { m_GridRegion -= value; } }
|
||||
}
|
||||
|
||||
/// <summary>The event subscribers. null if no subcribers</summary>
|
||||
private EventHandler<GridLayerEventArgs> m_GridLayer;
|
||||
|
||||
/// <summary>Raises the GridLayer event</summary>
|
||||
/// <param name="e">A GridLayerEventArgs object containing the
|
||||
/// data sent by simulator</param>
|
||||
protected virtual void OnGridLayer(GridLayerEventArgs e)
|
||||
{
|
||||
EventHandler<GridLayerEventArgs> handler = m_GridLayer;
|
||||
if (handler != null)
|
||||
handler(this, e);
|
||||
}
|
||||
|
||||
/// <summary>Thread sync lock object</summary>
|
||||
private readonly object m_GridLayerLock = new object();
|
||||
|
||||
/// <summary>Raised when the simulator sends GridLayer object containing
|
||||
/// a map tile coordinates and texture information</summary>
|
||||
public event EventHandler<GridLayerEventArgs> GridLayer
|
||||
{
|
||||
add { lock (m_GridLayerLock) { m_GridLayer += value; } }
|
||||
remove { lock (m_GridLayerLock) { m_GridLayer -= value; } }
|
||||
}
|
||||
|
||||
/// <summary>The event subscribers. null if no subcribers</summary>
|
||||
private EventHandler<GridItemsEventArgs> m_GridItems;
|
||||
|
||||
/// <summary>Raises the GridItems event</summary>
|
||||
/// <param name="e">A GridItemEventArgs object containing the
|
||||
/// data sent by simulator</param>
|
||||
protected virtual void OnGridItems(GridItemsEventArgs e)
|
||||
{
|
||||
EventHandler<GridItemsEventArgs> handler = m_GridItems;
|
||||
if (handler != null)
|
||||
handler(this, e);
|
||||
}
|
||||
|
||||
/// <summary>Thread sync lock object</summary>
|
||||
private readonly object m_GridItemsLock = new object();
|
||||
|
||||
/// <summary>Raised when the simulator sends GridItems object containing
|
||||
/// details on events, land sales at a specific location</summary>
|
||||
public event EventHandler<GridItemsEventArgs> GridItems
|
||||
{
|
||||
add { lock (m_GridItemsLock) { m_GridItems += value; } }
|
||||
remove { lock (m_GridItemsLock) { m_GridItems -= value; } }
|
||||
}
|
||||
|
||||
/// <summary>The event subscribers. null if no subcribers</summary>
|
||||
private EventHandler<RegionHandleReplyEventArgs> m_RegionHandleReply;
|
||||
|
||||
/// <summary>Raises the RegionHandleReply event</summary>
|
||||
/// <param name="e">A RegionHandleReplyEventArgs object containing the
|
||||
/// data sent by simulator</param>
|
||||
protected virtual void OnRegionHandleReply(RegionHandleReplyEventArgs e)
|
||||
{
|
||||
EventHandler<RegionHandleReplyEventArgs> handler = m_RegionHandleReply;
|
||||
if (handler != null)
|
||||
handler(this, e);
|
||||
}
|
||||
|
||||
/// <summary>Thread sync lock object</summary>
|
||||
private readonly object m_RegionHandleReplyLock = new object();
|
||||
|
||||
/// <summary>Raised in response to a Region lookup</summary>
|
||||
public event EventHandler<RegionHandleReplyEventArgs> RegionHandleReply
|
||||
{
|
||||
add { lock (m_RegionHandleReplyLock) { m_RegionHandleReply += value; } }
|
||||
remove { lock (m_RegionHandleReplyLock) { m_RegionHandleReply -= value; } }
|
||||
}
|
||||
|
||||
#endregion Delegates
|
||||
|
||||
/// <summary>Unknown</summary>
|
||||
public float SunPhase { get { return sunPhase; } }
|
||||
/// <summary>Current direction of the sun</summary>
|
||||
public Vector3 SunDirection { get { return sunDirection; } }
|
||||
/// <summary>Current angular velocity of the sun</summary>
|
||||
public Vector3 SunAngVelocity { get { return sunAngVelocity; } }
|
||||
/// <summary>Microseconds since the start of SL 4-hour day</summary>
|
||||
public ulong TimeOfDay { get { return timeOfDay; } }
|
||||
|
||||
/// <summary>A dictionary of all the regions, indexed by region name</summary>
|
||||
internal Dictionary<string, GridRegion> Regions = new Dictionary<string, GridRegion>();
|
||||
/// <summary>A dictionary of all the regions, indexed by region handle</summary>
|
||||
internal Dictionary<ulong, GridRegion> RegionsByHandle = new Dictionary<ulong, GridRegion>();
|
||||
|
||||
private GridClient Client;
|
||||
private float sunPhase;
|
||||
private Vector3 sunDirection;
|
||||
private Vector3 sunAngVelocity;
|
||||
private ulong timeOfDay;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="client">Instance of GridClient object to associate with this GridManager instance</param>
|
||||
public GridManager(GridClient client)
|
||||
{
|
||||
Client = client;
|
||||
|
||||
//Client.Network.RegisterCallback(PacketType.MapLayerReply, MapLayerReplyHandler);
|
||||
Client.Network.RegisterCallback(PacketType.MapBlockReply, MapBlockReplyHandler);
|
||||
Client.Network.RegisterCallback(PacketType.MapItemReply, MapItemReplyHandler);
|
||||
Client.Network.RegisterCallback(PacketType.SimulatorViewerTimeMessage, SimulatorViewerTimeMessageHandler);
|
||||
Client.Network.RegisterCallback(PacketType.CoarseLocationUpdate, CoarseLocationHandler, false);
|
||||
Client.Network.RegisterCallback(PacketType.RegionIDAndHandleReply, RegionHandleReplyHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="layer"></param>
|
||||
public void RequestMapLayer(GridLayerType layer)
|
||||
{
|
||||
Uri url = Client.Network.CurrentSim.Caps.CapabilityURI("MapLayer");
|
||||
|
||||
if (url != null)
|
||||
{
|
||||
OSDMap body = new OSDMap();
|
||||
body["Flags"] = OSD.FromInteger((int)layer);
|
||||
|
||||
CapsClient request = new CapsClient(url);
|
||||
request.OnComplete += new CapsClient.CompleteCallback(MapLayerResponseHandler);
|
||||
request.BeginGetResponse(body, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request a map layer
|
||||
/// </summary>
|
||||
/// <param name="regionName">The name of the region</param>
|
||||
/// <param name="layer">The type of layer</param>
|
||||
public void RequestMapRegion(string regionName, GridLayerType layer)
|
||||
{
|
||||
MapNameRequestPacket request = new MapNameRequestPacket();
|
||||
|
||||
request.AgentData.AgentID = Client.Self.AgentID;
|
||||
request.AgentData.SessionID = Client.Self.SessionID;
|
||||
request.AgentData.Flags = (uint)layer;
|
||||
request.AgentData.EstateID = 0; // Filled in on the sim
|
||||
request.AgentData.Godlike = false; // Filled in on the sim
|
||||
request.NameData.Name = Utils.StringToBytes(regionName);
|
||||
|
||||
Client.Network.SendPacket(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="layer"></param>
|
||||
/// <param name="minX"></param>
|
||||
/// <param name="minY"></param>
|
||||
/// <param name="maxX"></param>
|
||||
/// <param name="maxY"></param>
|
||||
/// <param name="returnNonExistent"></param>
|
||||
public void RequestMapBlocks(GridLayerType layer, ushort minX, ushort minY, ushort maxX, ushort maxY,
|
||||
bool returnNonExistent)
|
||||
{
|
||||
MapBlockRequestPacket request = new MapBlockRequestPacket();
|
||||
|
||||
request.AgentData.AgentID = Client.Self.AgentID;
|
||||
request.AgentData.SessionID = Client.Self.SessionID;
|
||||
request.AgentData.Flags = (uint)layer;
|
||||
request.AgentData.Flags |= (uint)(returnNonExistent ? 0x10000 : 0);
|
||||
request.AgentData.EstateID = 0; // Filled in at the simulator
|
||||
request.AgentData.Godlike = false; // Filled in at the simulator
|
||||
|
||||
request.PositionData.MinX = minX;
|
||||
request.PositionData.MinY = minY;
|
||||
request.PositionData.MaxX = maxX;
|
||||
request.PositionData.MaxY = maxY;
|
||||
|
||||
Client.Network.SendPacket(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="regionHandle"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="layer"></param>
|
||||
/// <param name="timeoutMS"></param>
|
||||
/// <returns></returns>
|
||||
public List<MapItem> MapItems(ulong regionHandle, GridItemType item, GridLayerType layer, int timeoutMS)
|
||||
{
|
||||
List<MapItem> itemList = null;
|
||||
AutoResetEvent itemsEvent = new AutoResetEvent(false);
|
||||
|
||||
EventHandler<GridItemsEventArgs> callback =
|
||||
delegate(object sender, GridItemsEventArgs e)
|
||||
{
|
||||
if (e.Type == GridItemType.AgentLocations)
|
||||
{
|
||||
itemList = e.Items;
|
||||
itemsEvent.Set();
|
||||
}
|
||||
};
|
||||
|
||||
GridItems += callback;
|
||||
|
||||
RequestMapItems(regionHandle, item, layer);
|
||||
itemsEvent.WaitOne(timeoutMS, false);
|
||||
|
||||
GridItems -= callback;
|
||||
|
||||
return itemList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="regionHandle"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="layer"></param>
|
||||
public void RequestMapItems(ulong regionHandle, GridItemType item, GridLayerType layer)
|
||||
{
|
||||
MapItemRequestPacket request = new MapItemRequestPacket();
|
||||
request.AgentData.AgentID = Client.Self.AgentID;
|
||||
request.AgentData.SessionID = Client.Self.SessionID;
|
||||
request.AgentData.Flags = (uint)layer;
|
||||
request.AgentData.Godlike = false; // Filled in on the sim
|
||||
request.AgentData.EstateID = 0; // Filled in on the sim
|
||||
|
||||
request.RequestData.ItemType = (uint)item;
|
||||
request.RequestData.RegionHandle = regionHandle;
|
||||
|
||||
Client.Network.SendPacket(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request data for all mainland (Linden managed) simulators
|
||||
/// </summary>
|
||||
public void RequestMainlandSims(GridLayerType layer)
|
||||
{
|
||||
RequestMapBlocks(layer, 0, 0, 65535, 65535, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request the region handle for the specified region UUID
|
||||
/// </summary>
|
||||
/// <param name="regionID">UUID of the region to look up</param>
|
||||
public void RequestRegionHandle(UUID regionID)
|
||||
{
|
||||
RegionHandleRequestPacket request = new RegionHandleRequestPacket();
|
||||
request.RequestBlock = new RegionHandleRequestPacket.RequestBlockBlock();
|
||||
request.RequestBlock.RegionID = regionID;
|
||||
Client.Network.SendPacket(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get grid region information using the region name, this function
|
||||
/// will block until it can find the region or gives up
|
||||
/// </summary>
|
||||
/// <param name="name">Name of sim you're looking for</param>
|
||||
/// <param name="layer">Layer that you are requesting</param>
|
||||
/// <param name="region">Will contain a GridRegion for the sim you're
|
||||
/// looking for if successful, otherwise an empty structure</param>
|
||||
/// <returns>True if the GridRegion was successfully fetched, otherwise
|
||||
/// false</returns>
|
||||
public bool GetGridRegion(string name, GridLayerType layer, out GridRegion region)
|
||||
{
|
||||
if (String.IsNullOrEmpty(name))
|
||||
{
|
||||
Logger.Log("GetGridRegion called with a null or empty region name", Helpers.LogLevel.Error, Client);
|
||||
region = new GridRegion();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Regions.ContainsKey(name))
|
||||
{
|
||||
// We already have this GridRegion structure
|
||||
region = Regions[name];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AutoResetEvent regionEvent = new AutoResetEvent(false);
|
||||
EventHandler<GridRegionEventArgs> callback =
|
||||
delegate(object sender, GridRegionEventArgs e)
|
||||
{
|
||||
if (e.Region.Name == name)
|
||||
regionEvent.Set();
|
||||
};
|
||||
GridRegion += callback;
|
||||
|
||||
RequestMapRegion(name, layer);
|
||||
regionEvent.WaitOne(Client.Settings.MAP_REQUEST_TIMEOUT, false);
|
||||
|
||||
GridRegion -= callback;
|
||||
|
||||
if (Regions.ContainsKey(name))
|
||||
{
|
||||
// The region was found after our request
|
||||
region = Regions[name];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log("Couldn't find region " + name, Helpers.LogLevel.Warning, Client);
|
||||
region = new GridRegion();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void MapLayerResponseHandler(CapsClient client, OSD result, Exception error)
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
Logger.Log("MapLayerResponseHandler error: " + error.Message + ": " + error.StackTrace, Helpers.LogLevel.Error, Client);
|
||||
return;
|
||||
}
|
||||
OSDMap body = (OSDMap)result;
|
||||
OSDArray layerData = (OSDArray)body["LayerData"];
|
||||
|
||||
if (m_GridLayer != null)
|
||||
{
|
||||
for (int i = 0; i < layerData.Count; i++)
|
||||
{
|
||||
OSDMap thisLayerData = (OSDMap)layerData[i];
|
||||
|
||||
GridLayer layer;
|
||||
layer.Bottom = thisLayerData["Bottom"].AsInteger();
|
||||
layer.Left = thisLayerData["Left"].AsInteger();
|
||||
layer.Top = thisLayerData["Top"].AsInteger();
|
||||
layer.Right = thisLayerData["Right"].AsInteger();
|
||||
layer.ImageID = thisLayerData["ImageID"].AsUUID();
|
||||
|
||||
OnGridLayer(new GridLayerEventArgs(layer));
|
||||
}
|
||||
}
|
||||
|
||||
if (body.ContainsKey("MapBlocks"))
|
||||
{
|
||||
// TODO: At one point this will become activated
|
||||
Logger.Log("Got MapBlocks through CAPS, please finish this function!", Helpers.LogLevel.Error, Client);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Process an incoming packet and raise the appropriate events</summary>
|
||||
/// <param name="sender">The sender</param>
|
||||
/// <param name="e">The EventArgs object containing the packet data</param>
|
||||
protected void MapBlockReplyHandler(object sender, PacketReceivedEventArgs e)
|
||||
{
|
||||
MapBlockReplyPacket map = (MapBlockReplyPacket)e.Packet;
|
||||
|
||||
foreach (MapBlockReplyPacket.DataBlock block in map.Data)
|
||||
{
|
||||
if (block.X != 0 || block.Y != 0)
|
||||
{
|
||||
GridRegion region;
|
||||
|
||||
region.X = block.X;
|
||||
region.Y = block.Y;
|
||||
region.Name = Utils.BytesToString(block.Name);
|
||||
// RegionFlags seems to always be zero here?
|
||||
region.RegionFlags = (RegionFlags)block.RegionFlags;
|
||||
region.WaterHeight = block.WaterHeight;
|
||||
region.Agents = block.Agents;
|
||||
region.Access = (SimAccess)block.Access;
|
||||
region.MapImageID = block.MapImageID;
|
||||
region.RegionHandle = Utils.UIntsToLong((uint)(region.X * 256), (uint)(region.Y * 256));
|
||||
|
||||
lock (Regions)
|
||||
{
|
||||
Regions[region.Name] = region;
|
||||
RegionsByHandle[region.RegionHandle] = region;
|
||||
}
|
||||
|
||||
if (m_GridRegion != null)
|
||||
{
|
||||
OnGridRegion(new GridRegionEventArgs(region));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Process an incoming packet and raise the appropriate events</summary>
|
||||
/// <param name="sender">The sender</param>
|
||||
/// <param name="e">The EventArgs object containing the packet data</param>
|
||||
protected void MapItemReplyHandler(object sender, PacketReceivedEventArgs e)
|
||||
{
|
||||
if (m_GridItems != null)
|
||||
{
|
||||
MapItemReplyPacket reply = (MapItemReplyPacket)e.Packet;
|
||||
GridItemType type = (GridItemType)reply.RequestData.ItemType;
|
||||
List<MapItem> items = new List<MapItem>();
|
||||
|
||||
for (int i = 0; i < reply.Data.Length; i++)
|
||||
{
|
||||
string name = Utils.BytesToString(reply.Data[i].Name);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case GridItemType.AgentLocations:
|
||||
MapAgentLocation location = new MapAgentLocation();
|
||||
location.GlobalX = reply.Data[i].X;
|
||||
location.GlobalY = reply.Data[i].Y;
|
||||
location.Identifier = name;
|
||||
location.AvatarCount = reply.Data[i].Extra;
|
||||
items.Add(location);
|
||||
break;
|
||||
case GridItemType.Classified:
|
||||
//FIXME:
|
||||
Logger.Log("FIXME", Helpers.LogLevel.Error, Client);
|
||||
break;
|
||||
case GridItemType.LandForSale:
|
||||
MapLandForSale landsale = new MapLandForSale();
|
||||
landsale.GlobalX = reply.Data[i].X;
|
||||
landsale.GlobalY = reply.Data[i].Y;
|
||||
landsale.ID = reply.Data[i].ID;
|
||||
landsale.Name = name;
|
||||
landsale.Size = reply.Data[i].Extra;
|
||||
landsale.Price = reply.Data[i].Extra2;
|
||||
items.Add(landsale);
|
||||
break;
|
||||
case GridItemType.MatureEvent:
|
||||
MapMatureEvent matureEvent = new MapMatureEvent();
|
||||
matureEvent.GlobalX = reply.Data[i].X;
|
||||
matureEvent.GlobalY = reply.Data[i].Y;
|
||||
matureEvent.Description = name;
|
||||
matureEvent.Flags = (DirectoryManager.EventFlags)reply.Data[i].Extra2;
|
||||
items.Add(matureEvent);
|
||||
break;
|
||||
case GridItemType.PgEvent:
|
||||
MapPGEvent PGEvent = new MapPGEvent();
|
||||
PGEvent.GlobalX = reply.Data[i].X;
|
||||
PGEvent.GlobalY = reply.Data[i].Y;
|
||||
PGEvent.Description = name;
|
||||
PGEvent.Flags = (DirectoryManager.EventFlags)reply.Data[i].Extra2;
|
||||
items.Add(PGEvent);
|
||||
break;
|
||||
case GridItemType.Popular:
|
||||
//FIXME:
|
||||
Logger.Log("FIXME", Helpers.LogLevel.Error, Client);
|
||||
break;
|
||||
case GridItemType.Telehub:
|
||||
MapTelehub teleHubItem = new MapTelehub();
|
||||
teleHubItem.GlobalX = reply.Data[i].X;
|
||||
teleHubItem.GlobalY = reply.Data[i].Y;
|
||||
items.Add(teleHubItem);
|
||||
break;
|
||||
case GridItemType.AdultLandForSale:
|
||||
MapAdultLandForSale adultLandsale = new MapAdultLandForSale();
|
||||
adultLandsale.GlobalX = reply.Data[i].X;
|
||||
adultLandsale.GlobalY = reply.Data[i].Y;
|
||||
adultLandsale.ID = reply.Data[i].ID;
|
||||
adultLandsale.Name = name;
|
||||
adultLandsale.Size = reply.Data[i].Extra;
|
||||
adultLandsale.Price = reply.Data[i].Extra2;
|
||||
items.Add(adultLandsale);
|
||||
break;
|
||||
case GridItemType.AdultEvent:
|
||||
MapAdultEvent adultEvent = new MapAdultEvent();
|
||||
adultEvent.GlobalX = reply.Data[i].X;
|
||||
adultEvent.GlobalY = reply.Data[i].Y;
|
||||
adultEvent.Description = Utils.BytesToString(reply.Data[i].Name);
|
||||
adultEvent.Flags = (DirectoryManager.EventFlags)reply.Data[i].Extra2;
|
||||
items.Add(adultEvent);
|
||||
break;
|
||||
default:
|
||||
Logger.Log("Unknown map item type " + type, Helpers.LogLevel.Warning, Client);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
OnGridItems(new GridItemsEventArgs(type, items));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Process an incoming packet and raise the appropriate events</summary>
|
||||
/// <param name="sender">The sender</param>
|
||||
/// <param name="e">The EventArgs object containing the packet data</param>
|
||||
protected void SimulatorViewerTimeMessageHandler(object sender, PacketReceivedEventArgs e)
|
||||
{
|
||||
SimulatorViewerTimeMessagePacket time = (SimulatorViewerTimeMessagePacket)e.Packet;
|
||||
|
||||
sunPhase = time.TimeInfo.SunPhase;
|
||||
sunDirection = time.TimeInfo.SunDirection;
|
||||
sunAngVelocity = time.TimeInfo.SunAngVelocity;
|
||||
timeOfDay = time.TimeInfo.UsecSinceStart;
|
||||
// TODO: Does anyone have a use for the time stuff?
|
||||
}
|
||||
|
||||
/// <summary>Process an incoming packet and raise the appropriate events</summary>
|
||||
/// <param name="sender">The sender</param>
|
||||
/// <param name="e">The EventArgs object containing the packet data</param>
|
||||
protected void CoarseLocationHandler(object sender, PacketReceivedEventArgs e)
|
||||
{
|
||||
CoarseLocationUpdatePacket coarse = (CoarseLocationUpdatePacket)e.Packet;
|
||||
|
||||
// populate a dictionary from the packet, for local use
|
||||
Dictionary<UUID, Vector3> coarseEntries = new Dictionary<UUID, Vector3>();
|
||||
for (int i = 0; i < coarse.AgentData.Length; i++)
|
||||
{
|
||||
if(coarse.Location.Length > 0)
|
||||
coarseEntries[coarse.AgentData[i].AgentID] = new Vector3((int)coarse.Location[i].X, (int)coarse.Location[i].Y, (int)coarse.Location[i].Z * 4);
|
||||
|
||||
// the friend we are tracking on radar
|
||||
if (i == coarse.Index.Prey)
|
||||
e.Simulator.preyID = coarse.AgentData[i].AgentID;
|
||||
}
|
||||
|
||||
// find stale entries (people who left the sim)
|
||||
List<UUID> removedEntries = e.Simulator.avatarPositions.FindAll(delegate(UUID findID) { return !coarseEntries.ContainsKey(findID); });
|
||||
|
||||
// anyone who was not listed in the previous update
|
||||
List<UUID> newEntries = new List<UUID>();
|
||||
|
||||
lock (e.Simulator.avatarPositions.Dictionary)
|
||||
{
|
||||
// remove stale entries
|
||||
foreach(UUID trackedID in removedEntries)
|
||||
e.Simulator.avatarPositions.Dictionary.Remove(trackedID);
|
||||
|
||||
// add or update tracked info, and record who is new
|
||||
foreach (KeyValuePair<UUID, Vector3> entry in coarseEntries)
|
||||
{
|
||||
if (!e.Simulator.avatarPositions.Dictionary.ContainsKey(entry.Key))
|
||||
newEntries.Add(entry.Key);
|
||||
|
||||
e.Simulator.avatarPositions.Dictionary[entry.Key] = entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_CoarseLocationUpdate != null)
|
||||
{
|
||||
WorkPool.QueueUserWorkItem(delegate(object o)
|
||||
{ OnCoarseLocationUpdate(new CoarseLocationUpdateEventArgs(e.Simulator, newEntries, removedEntries)); });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Process an incoming packet and raise the appropriate events</summary>
|
||||
/// <param name="sender">The sender</param>
|
||||
/// <param name="e">The EventArgs object containing the packet data</param>
|
||||
protected void RegionHandleReplyHandler(object sender, PacketReceivedEventArgs e)
|
||||
{
|
||||
if (m_RegionHandleReply != null)
|
||||
{
|
||||
RegionIDAndHandleReplyPacket reply = (RegionIDAndHandleReplyPacket)e.Packet;
|
||||
OnRegionHandleReply(new RegionHandleReplyEventArgs(reply.ReplyBlock.RegionID, reply.ReplyBlock.RegionHandle));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#region EventArgs classes
|
||||
|
||||
public class CoarseLocationUpdateEventArgs : EventArgs
|
||||
{
|
||||
private readonly Simulator m_Simulator;
|
||||
private readonly List<UUID> m_NewEntries;
|
||||
private readonly List<UUID> m_RemovedEntries;
|
||||
|
||||
public Simulator Simulator { get { return m_Simulator; } }
|
||||
public List<UUID> NewEntries { get { return m_NewEntries; } }
|
||||
public List<UUID> RemovedEntries { get { return m_RemovedEntries; } }
|
||||
|
||||
public CoarseLocationUpdateEventArgs(Simulator simulator, List<UUID> newEntries, List<UUID> removedEntries)
|
||||
{
|
||||
this.m_Simulator = simulator;
|
||||
this.m_NewEntries = newEntries;
|
||||
this.m_RemovedEntries = removedEntries;
|
||||
}
|
||||
}
|
||||
|
||||
public class GridRegionEventArgs : EventArgs
|
||||
{
|
||||
private readonly GridRegion m_Region;
|
||||
public GridRegion Region { get { return m_Region; } }
|
||||
|
||||
public GridRegionEventArgs(GridRegion region)
|
||||
{
|
||||
this.m_Region = region;
|
||||
}
|
||||
}
|
||||
|
||||
public class GridLayerEventArgs : EventArgs
|
||||
{
|
||||
private readonly GridLayer m_Layer;
|
||||
|
||||
public GridLayer Layer { get { return m_Layer; } }
|
||||
|
||||
public GridLayerEventArgs(GridLayer layer)
|
||||
{
|
||||
this.m_Layer = layer;
|
||||
}
|
||||
}
|
||||
|
||||
public class GridItemsEventArgs : EventArgs
|
||||
{
|
||||
private readonly GridItemType m_Type;
|
||||
private readonly List<MapItem> m_Items;
|
||||
|
||||
public GridItemType Type { get { return m_Type; } }
|
||||
public List<MapItem> Items { get { return m_Items; } }
|
||||
|
||||
public GridItemsEventArgs(GridItemType type, List<MapItem> items)
|
||||
{
|
||||
this.m_Type = type;
|
||||
this.m_Items = items;
|
||||
}
|
||||
}
|
||||
|
||||
public class RegionHandleReplyEventArgs : EventArgs
|
||||
{
|
||||
private readonly UUID m_RegionID;
|
||||
private readonly ulong m_RegionHandle;
|
||||
|
||||
public UUID RegionID { get { return m_RegionID; } }
|
||||
public ulong RegionHandle { get { return m_RegionHandle; } }
|
||||
|
||||
public RegionHandleReplyEventArgs(UUID regionID, ulong regionHandle)
|
||||
{
|
||||
this.m_RegionID = regionID;
|
||||
this.m_RegionHandle = regionHandle;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,615 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using OpenMetaverse.Packets;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using OpenMetaverse.StructuredData;
|
||||
using ComponentAce.Compression.Libs.zlib;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// Static helper functions and global variables
|
||||
/// </summary>
|
||||
public static class Helpers
|
||||
{
|
||||
/// <summary>This header flag signals that ACKs are appended to the packet</summary>
|
||||
public const byte MSG_APPENDED_ACKS = 0x10;
|
||||
/// <summary>This header flag signals that this packet has been sent before</summary>
|
||||
public const byte MSG_RESENT = 0x20;
|
||||
/// <summary>This header flags signals that an ACK is expected for this packet</summary>
|
||||
public const byte MSG_RELIABLE = 0x40;
|
||||
/// <summary>This header flag signals that the message is compressed using zerocoding</summary>
|
||||
public const byte MSG_ZEROCODED = 0x80;
|
||||
|
||||
/// <summary>
|
||||
/// Passed to Logger.Log() to identify the severity of a log entry
|
||||
/// </summary>
|
||||
public enum LogLevel
|
||||
{
|
||||
/// <summary>No logging information will be output</summary>
|
||||
None,
|
||||
/// <summary>Non-noisy useful information, may be helpful in
|
||||
/// debugging a problem</summary>
|
||||
Info,
|
||||
/// <summary>A non-critical error occurred. A warning will not
|
||||
/// prevent the rest of the library from operating as usual,
|
||||
/// although it may be indicative of an underlying issue</summary>
|
||||
Warning,
|
||||
/// <summary>A critical error has occurred. Generally this will
|
||||
/// be followed by the network layer shutting down, although the
|
||||
/// stability of the library after an error is uncertain</summary>
|
||||
Error,
|
||||
/// <summary>Used for internal testing, this logging level can
|
||||
/// generate very noisy (long and/or repetitive) messages. Don't
|
||||
/// pass this to the Log() function, use DebugLog() instead.
|
||||
/// </summary>
|
||||
Debug
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns></returns>
|
||||
public static short TEOffsetShort(float offset)
|
||||
{
|
||||
offset = Utils.Clamp(offset, -1.0f, 1.0f);
|
||||
offset *= 32767.0f;
|
||||
return (short)Math.Round(offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <param name="pos"></param>
|
||||
/// <returns></returns>
|
||||
public static float TEOffsetFloat(byte[] bytes, int pos)
|
||||
{
|
||||
float offset = (float)BitConverter.ToInt16(bytes, pos);
|
||||
return offset / 32767.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="rotation"></param>
|
||||
/// <returns></returns>
|
||||
public static short TERotationShort(float rotation)
|
||||
{
|
||||
const float TWO_PI = 6.283185307179586476925286766559f;
|
||||
return (short)Math.Round(((Math.IEEERemainder(rotation, TWO_PI) / TWO_PI) * 32768.0f) + 0.5f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <param name="pos"></param>
|
||||
/// <returns></returns>
|
||||
public static float TERotationFloat(byte[] bytes, int pos)
|
||||
{
|
||||
const float TWO_PI = 6.283185307179586476925286766559f;
|
||||
return ((float)(bytes[pos] | (bytes[pos + 1] << 8)) / 32768.0f) * TWO_PI;
|
||||
}
|
||||
|
||||
public static byte TEGlowByte(float glow)
|
||||
{
|
||||
return (byte)(glow * 255.0f);
|
||||
}
|
||||
|
||||
public static float TEGlowFloat(byte[] bytes, int pos)
|
||||
{
|
||||
return (float)bytes[pos] / 255.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given an X/Y location in absolute (grid-relative) terms, a region
|
||||
/// handle is returned along with the local X/Y location in that region
|
||||
/// </summary>
|
||||
/// <param name="globalX">The absolute X location, a number such as
|
||||
/// 255360.35</param>
|
||||
/// <param name="globalY">The absolute Y location, a number such as
|
||||
/// 255360.35</param>
|
||||
/// <param name="localX">The sim-local X position of the global X
|
||||
/// position, a value from 0.0 to 256.0</param>
|
||||
/// <param name="localY">The sim-local Y position of the global Y
|
||||
/// position, a value from 0.0 to 256.0</param>
|
||||
/// <returns>A 64-bit region handle that can be used to teleport to</returns>
|
||||
public static ulong GlobalPosToRegionHandle(float globalX, float globalY, out float localX, out float localY)
|
||||
{
|
||||
uint x = ((uint)globalX / 256) * 256;
|
||||
uint y = ((uint)globalY / 256) * 256;
|
||||
localX = globalX - (float)x;
|
||||
localY = globalY - (float)y;
|
||||
return Utils.UIntsToLong(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a floating point number to a terse string format used for
|
||||
/// transmitting numbers in wearable asset files
|
||||
/// </summary>
|
||||
/// <param name="val">Floating point number to convert to a string</param>
|
||||
/// <returns>A terse string representation of the input number</returns>
|
||||
public static string FloatToTerseString(float val)
|
||||
{
|
||||
string s = string.Format(Utils.EnUsCulture, "{0:.00}", val);
|
||||
|
||||
if (val == 0)
|
||||
return ".00";
|
||||
|
||||
// Trim trailing zeroes
|
||||
while (s[s.Length - 1] == '0')
|
||||
s = s.Remove(s.Length - 1, 1);
|
||||
|
||||
// Remove superfluous decimal places after the trim
|
||||
if (s[s.Length - 1] == '.')
|
||||
s = s.Remove(s.Length - 1, 1);
|
||||
// Remove leading zeroes after a negative sign
|
||||
else if (s[0] == '-' && s[1] == '0')
|
||||
s = s.Remove(1, 1);
|
||||
// Remove leading zeroes in positive numbers
|
||||
else if (s[0] == '0')
|
||||
s = s.Remove(0, 1);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a variable length field (byte array) to a string, with a
|
||||
/// field name prepended to each line of the output
|
||||
/// </summary>
|
||||
/// <remarks>If the byte array has unprintable characters in it, a
|
||||
/// hex dump will be written instead</remarks>
|
||||
/// <param name="output">The StringBuilder object to write to</param>
|
||||
/// <param name="bytes">The byte array to convert to a string</param>
|
||||
/// <param name="fieldName">A field name to prepend to each line of output</param>
|
||||
internal static void FieldToString(StringBuilder output, byte[] bytes, string fieldName)
|
||||
{
|
||||
// Check for a common case
|
||||
if (bytes.Length == 0) return;
|
||||
|
||||
bool printable = true;
|
||||
|
||||
for (int i = 0; i < bytes.Length; ++i)
|
||||
{
|
||||
// Check if there are any unprintable characters in the array
|
||||
if ((bytes[i] < 0x20 || bytes[i] > 0x7E) && bytes[i] != 0x09
|
||||
&& bytes[i] != 0x0D && bytes[i] != 0x0A && bytes[i] != 0x00)
|
||||
{
|
||||
printable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (printable)
|
||||
{
|
||||
if (fieldName.Length > 0)
|
||||
{
|
||||
output.Append(fieldName);
|
||||
output.Append(": ");
|
||||
}
|
||||
|
||||
if (bytes[bytes.Length - 1] == 0x00)
|
||||
output.Append(UTF8Encoding.UTF8.GetString(bytes, 0, bytes.Length - 1));
|
||||
else
|
||||
output.Append(UTF8Encoding.UTF8.GetString(bytes, 0, bytes.Length));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < bytes.Length; i += 16)
|
||||
{
|
||||
if (i != 0)
|
||||
output.Append('\n');
|
||||
if (fieldName.Length > 0)
|
||||
{
|
||||
output.Append(fieldName);
|
||||
output.Append(": ");
|
||||
}
|
||||
|
||||
for (int j = 0; j < 16; j++)
|
||||
{
|
||||
if ((i + j) < bytes.Length)
|
||||
output.Append(String.Format("{0:X2} ", bytes[i + j]));
|
||||
else
|
||||
output.Append(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a zerocoded byte array, used to decompress packets marked
|
||||
/// with the zerocoded flag
|
||||
/// </summary>
|
||||
/// <remarks>Any time a zero is encountered, the next byte is a count
|
||||
/// of how many zeroes to expand. One zero is encoded with 0x00 0x01,
|
||||
/// two zeroes is 0x00 0x02, three zeroes is 0x00 0x03, etc. The
|
||||
/// first four bytes are copied directly to the output buffer.
|
||||
/// </remarks>
|
||||
/// <param name="src">The byte array to decode</param>
|
||||
/// <param name="srclen">The length of the byte array to decode. This
|
||||
/// would be the length of the packet up to (but not including) any
|
||||
/// appended ACKs</param>
|
||||
/// <param name="dest">The output byte array to decode to</param>
|
||||
/// <returns>The length of the output buffer</returns>
|
||||
public static int ZeroDecode(byte[] src, int srclen, byte[] dest)
|
||||
{
|
||||
if (srclen > src.Length)
|
||||
throw new ArgumentException("srclen cannot be greater than src.Length");
|
||||
|
||||
uint zerolen = 0;
|
||||
int bodylen = 0;
|
||||
uint i = 0;
|
||||
|
||||
try
|
||||
{
|
||||
Buffer.BlockCopy(src, 0, dest, 0, 6);
|
||||
zerolen = 6;
|
||||
bodylen = srclen;
|
||||
|
||||
for (i = zerolen; i < bodylen; i++)
|
||||
{
|
||||
if (src[i] == 0x00)
|
||||
{
|
||||
for (byte j = 0; j < src[i + 1]; j++)
|
||||
{
|
||||
dest[zerolen++] = 0x00;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
dest[zerolen++] = src[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Copy appended ACKs
|
||||
for (; i < srclen; i++)
|
||||
{
|
||||
dest[zerolen++] = src[i];
|
||||
}
|
||||
|
||||
return (int)zerolen;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log(String.Format("Zerodecoding error: i={0}, srclen={1}, bodylen={2}, zerolen={3}\n{4}\n{5}",
|
||||
i, srclen, bodylen, zerolen, Utils.BytesToHexString(src, srclen, null), ex), LogLevel.Error);
|
||||
|
||||
throw new IndexOutOfRangeException(String.Format("Zerodecoding error: i={0}, srclen={1}, bodylen={2}, zerolen={3}\n{4}\n{5}",
|
||||
i, srclen, bodylen, zerolen, Utils.BytesToHexString(src, srclen, null), ex.InnerException));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode a byte array with zerocoding. Used to compress packets marked
|
||||
/// with the zerocoded flag. Any zeroes in the array are compressed down
|
||||
/// to a single zero byte followed by a count of how many zeroes to expand
|
||||
/// out. A single zero becomes 0x00 0x01, two zeroes becomes 0x00 0x02,
|
||||
/// three zeroes becomes 0x00 0x03, etc. The first four bytes are copied
|
||||
/// directly to the output buffer.
|
||||
/// </summary>
|
||||
/// <param name="src">The byte array to encode</param>
|
||||
/// <param name="srclen">The length of the byte array to encode</param>
|
||||
/// <param name="dest">The output byte array to encode to</param>
|
||||
/// <returns>The length of the output buffer</returns>
|
||||
public static int ZeroEncode(byte[] src, int srclen, byte[] dest)
|
||||
{
|
||||
uint zerolen = 0;
|
||||
byte zerocount = 0;
|
||||
|
||||
Buffer.BlockCopy(src, 0, dest, 0, 6);
|
||||
zerolen += 6;
|
||||
|
||||
int bodylen;
|
||||
if ((src[0] & MSG_APPENDED_ACKS) == 0)
|
||||
{
|
||||
bodylen = srclen;
|
||||
}
|
||||
else
|
||||
{
|
||||
bodylen = srclen - src[srclen - 1] * 4 - 1;
|
||||
}
|
||||
|
||||
uint i;
|
||||
for (i = zerolen; i < bodylen; i++)
|
||||
{
|
||||
if (src[i] == 0x00)
|
||||
{
|
||||
zerocount++;
|
||||
|
||||
if (zerocount == 0)
|
||||
{
|
||||
dest[zerolen++] = 0x00;
|
||||
dest[zerolen++] = 0xff;
|
||||
zerocount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (zerocount != 0)
|
||||
{
|
||||
dest[zerolen++] = 0x00;
|
||||
dest[zerolen++] = (byte)zerocount;
|
||||
zerocount = 0;
|
||||
}
|
||||
|
||||
dest[zerolen++] = src[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (zerocount != 0)
|
||||
{
|
||||
dest[zerolen++] = 0x00;
|
||||
dest[zerolen++] = (byte)zerocount;
|
||||
}
|
||||
|
||||
// copy appended ACKs
|
||||
for (; i < srclen; i++)
|
||||
{
|
||||
dest[zerolen++] = src[i];
|
||||
}
|
||||
|
||||
return (int)zerolen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the CRC (cyclic redundancy check) needed to upload inventory.
|
||||
/// </summary>
|
||||
/// <param name="creationDate">Creation date</param>
|
||||
/// <param name="saleType">Sale type</param>
|
||||
/// <param name="invType">Inventory type</param>
|
||||
/// <param name="type">Type</param>
|
||||
/// <param name="assetID">Asset ID</param>
|
||||
/// <param name="groupID">Group ID</param>
|
||||
/// <param name="salePrice">Sale price</param>
|
||||
/// <param name="ownerID">Owner ID</param>
|
||||
/// <param name="creatorID">Creator ID</param>
|
||||
/// <param name="itemID">Item ID</param>
|
||||
/// <param name="folderID">Folder ID</param>
|
||||
/// <param name="everyoneMask">Everyone mask (permissions)</param>
|
||||
/// <param name="flags">Flags</param>
|
||||
/// <param name="nextOwnerMask">Next owner mask (permissions)</param>
|
||||
/// <param name="groupMask">Group mask (permissions)</param>
|
||||
/// <param name="ownerMask">Owner mask (permissions)</param>
|
||||
/// <returns>The calculated CRC</returns>
|
||||
public static uint InventoryCRC(int creationDate, byte saleType, sbyte invType, sbyte type,
|
||||
UUID assetID, UUID groupID, int salePrice, UUID ownerID, UUID creatorID,
|
||||
UUID itemID, UUID folderID, uint everyoneMask, uint flags, uint nextOwnerMask,
|
||||
uint groupMask, uint ownerMask)
|
||||
{
|
||||
uint CRC = 0;
|
||||
|
||||
// IDs
|
||||
CRC += assetID.CRC(); // AssetID
|
||||
CRC += folderID.CRC(); // FolderID
|
||||
CRC += itemID.CRC(); // ItemID
|
||||
|
||||
// Permission stuff
|
||||
CRC += creatorID.CRC(); // CreatorID
|
||||
CRC += ownerID.CRC(); // OwnerID
|
||||
CRC += groupID.CRC(); // GroupID
|
||||
|
||||
// CRC += another 4 words which always seem to be zero -- unclear if this is a UUID or what
|
||||
CRC += ownerMask;
|
||||
CRC += nextOwnerMask;
|
||||
CRC += everyoneMask;
|
||||
CRC += groupMask;
|
||||
|
||||
// The rest of the CRC fields
|
||||
CRC += flags; // Flags
|
||||
CRC += (uint)invType; // InvType
|
||||
CRC += (uint)type; // Type
|
||||
CRC += (uint)creationDate; // CreationDate
|
||||
CRC += (uint)salePrice; // SalePrice
|
||||
CRC += (uint)((uint)saleType * 0x07073096); // SaleType
|
||||
|
||||
return CRC;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load a file embedded in the assembly
|
||||
/// </summary>
|
||||
/// <param name="resourceName">The filename of the resource to load</param>
|
||||
/// <returns>A Stream for the requested file, or null if the resource
|
||||
/// was not successfully loaded</returns>
|
||||
public static System.IO.Stream GetResourceStream(string resourceName)
|
||||
{
|
||||
return GetResourceStream(resourceName, "openmetaverse_data");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load a file either embedded in the assembly or found in
|
||||
/// a given search path
|
||||
/// </summary>
|
||||
/// <param name="resourceName">The filename of the resource to load</param>
|
||||
/// <param name="searchPath">An optional path that will be searched if
|
||||
/// the asset is not found embedded in the assembly</param>
|
||||
/// <returns>A Stream for the requested file, or null if the resource
|
||||
/// was not successfully loaded</returns>
|
||||
public static System.IO.Stream GetResourceStream(string resourceName, string searchPath)
|
||||
{
|
||||
if (searchPath != null)
|
||||
{
|
||||
Assembly gea = Assembly.GetEntryAssembly();
|
||||
if (gea == null) gea = typeof(Helpers).Assembly;
|
||||
string dirname = ".";
|
||||
if (gea != null && gea.Location != null)
|
||||
{
|
||||
dirname = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(gea.Location), searchPath);
|
||||
}
|
||||
|
||||
string filename = System.IO.Path.Combine(dirname, resourceName);
|
||||
try
|
||||
{
|
||||
return new System.IO.FileStream(
|
||||
filename,
|
||||
System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log(string.Format("Failed opening resource from file {0}: {1}", filename, ex.Message), LogLevel.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Reflection.Assembly a = System.Reflection.Assembly.GetExecutingAssembly();
|
||||
System.IO.Stream s = a.GetManifestResourceStream("OpenMetaverse.Resources." + resourceName);
|
||||
if (s != null) return s;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log(string.Format("Failed opening resource stream: {0}", ex.Message), LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
/// <summary>
|
||||
/// Converts a list of primitives to an object that can be serialized
|
||||
/// with the LLSD system
|
||||
/// </summary>
|
||||
/// <param name="prims">Primitives to convert to a serializable object</param>
|
||||
/// <returns>An object that can be serialized with LLSD</returns>
|
||||
public static StructuredData.OSD PrimListToOSD(List<Primitive> prims)
|
||||
{
|
||||
StructuredData.OSDMap map = new OpenMetaverse.StructuredData.OSDMap(prims.Count);
|
||||
|
||||
for (int i = 0; i < prims.Count; i++)
|
||||
map.Add(prims[i].LocalID.ToString(), prims[i].GetOSD());
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes OSD in to a list of primitives
|
||||
/// </summary>
|
||||
/// <param name="osd">Structure holding the serialized primitive list,
|
||||
/// must be of the SDMap type</param>
|
||||
/// <returns>A list of deserialized primitives</returns>
|
||||
public static List<Primitive> OSDToPrimList(StructuredData.OSD osd)
|
||||
{
|
||||
if (osd.Type != StructuredData.OSDType.Map)
|
||||
throw new ArgumentException("LLSD must be in the Map structure");
|
||||
|
||||
StructuredData.OSDMap map = (StructuredData.OSDMap)osd;
|
||||
List<Primitive> prims = new List<Primitive>(map.Count);
|
||||
|
||||
foreach (KeyValuePair<string, StructuredData.OSD> kvp in map)
|
||||
{
|
||||
Primitive prim = Primitive.FromOSD(kvp.Value);
|
||||
prim.LocalID = UInt32.Parse(kvp.Key);
|
||||
prims.Add(prim);
|
||||
}
|
||||
|
||||
return prims;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a struct or class object containing fields only into a key value separated string
|
||||
/// </summary>
|
||||
/// <param name="t">The struct object</param>
|
||||
/// <returns>A string containing the struct fields as the keys, and the field value as the value separated</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Add the following code to any struct or class containing only fields to override the ToString()
|
||||
/// // method to display the values of the passed object
|
||||
///
|
||||
/// /// <summary>Print the struct data as a string</summary>
|
||||
/// ///<returns>A string containing the field name, and field value</returns>
|
||||
///public override string ToString()
|
||||
///{
|
||||
/// return Helpers.StructToString(this);
|
||||
///}
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static string StructToString(object t)
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
Type structType = t.GetType();
|
||||
FieldInfo[] fields = structType.GetFields();
|
||||
|
||||
foreach (FieldInfo field in fields)
|
||||
{
|
||||
result.Append(field.Name + ": " + field.GetValue(t) + " ");
|
||||
}
|
||||
result.AppendLine();
|
||||
return result.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
public static void CopyStream(Stream input, Stream output)
|
||||
{
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
output.Write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] ZCompressOSD(OSD data)
|
||||
{
|
||||
byte[] ret = null;
|
||||
|
||||
using (MemoryStream outMemoryStream = new MemoryStream())
|
||||
using (ZOutputStream outZStream = new ZOutputStream(outMemoryStream, zlibConst.Z_BEST_COMPRESSION))
|
||||
using (Stream inMemoryStream = new MemoryStream(OSDParser.SerializeLLSDBinary(data, false)))
|
||||
{
|
||||
CopyStream(inMemoryStream, outZStream);
|
||||
outZStream.finish();
|
||||
ret = outMemoryStream.ToArray();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static OSD ZDecompressOSD(byte[] data)
|
||||
{
|
||||
OSD ret;
|
||||
|
||||
using (MemoryStream input = new MemoryStream(data))
|
||||
using (MemoryStream output = new MemoryStream())
|
||||
using (ZOutputStream zout = new ZOutputStream(output))
|
||||
{
|
||||
CopyStream(input, zout);
|
||||
zout.finish();
|
||||
output.Seek(0, SeekOrigin.Begin);
|
||||
ret = OSDParser.DeserializeLLSDBinary(output);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,647 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
using OpenMetaverse.Assets;
|
||||
|
||||
namespace OpenMetaverse.Imaging
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of textures that are layered on texture of each other and "baked"
|
||||
/// in to a single texture, for avatar appearances
|
||||
/// </summary>
|
||||
public class Baker
|
||||
{
|
||||
public static readonly UUID IMG_INVISIBLE = new UUID("3a367d1c-bef1-6d43-7595-e88c1e3aadb3");
|
||||
|
||||
#region Properties
|
||||
/// <summary>Final baked texture</summary>
|
||||
public AssetTexture BakedTexture { get { return bakedTexture; } }
|
||||
/// <summary>Component layers</summary>
|
||||
public List<AppearanceManager.TextureData> Textures { get { return textures; } }
|
||||
/// <summary>Width of the final baked image and scratchpad</summary>
|
||||
public int BakeWidth { get { return bakeWidth; } }
|
||||
/// <summary>Height of the final baked image and scratchpad</summary>
|
||||
public int BakeHeight { get { return bakeHeight; } }
|
||||
/// <summary>Bake type</summary>
|
||||
public BakeType BakeType { get { return bakeType; } }
|
||||
/// <summary>Is this one of the 3 skin bakes</summary>
|
||||
private bool IsSkin { get { return bakeType == BakeType.Head || bakeType == BakeType.LowerBody || bakeType == BakeType.UpperBody; } }
|
||||
#endregion
|
||||
|
||||
#region Private fields
|
||||
/// <summary>Final baked texture</summary>
|
||||
private AssetTexture bakedTexture;
|
||||
/// <summary>Component layers</summary>
|
||||
private List<AppearanceManager.TextureData> textures = new List<AppearanceManager.TextureData>();
|
||||
/// <summary>Width of the final baked image and scratchpad</summary>
|
||||
private int bakeWidth;
|
||||
/// <summary>Height of the final baked image and scratchpad</summary>
|
||||
private int bakeHeight;
|
||||
/// <summary>Bake type</summary>
|
||||
private BakeType bakeType;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
/// <param name="bakeType">Bake type</param>
|
||||
public Baker(BakeType bakeType)
|
||||
{
|
||||
this.bakeType = bakeType;
|
||||
|
||||
if (bakeType == BakeType.Eyes)
|
||||
{
|
||||
bakeWidth = 128;
|
||||
bakeHeight = 128;
|
||||
}
|
||||
else
|
||||
{
|
||||
bakeWidth = 512;
|
||||
bakeHeight = 512;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public methods
|
||||
/// <summary>
|
||||
/// Adds layer for baking
|
||||
/// </summary>
|
||||
/// <param name="tdata">TexturaData struct that contains texture and its params</param>
|
||||
public void AddTexture(AppearanceManager.TextureData tdata)
|
||||
{
|
||||
lock (textures)
|
||||
{
|
||||
textures.Add(tdata);
|
||||
}
|
||||
}
|
||||
|
||||
public void Bake()
|
||||
{
|
||||
bakedTexture = new AssetTexture(new ManagedImage(bakeWidth, bakeHeight,
|
||||
ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha | ManagedImage.ImageChannels.Bump));
|
||||
|
||||
// Base color for eye bake is white, color of layer0 for others
|
||||
if (bakeType == BakeType.Eyes)
|
||||
{
|
||||
InitBakedLayerColor(Color4.White);
|
||||
}
|
||||
else if (textures.Count > 0)
|
||||
{
|
||||
InitBakedLayerColor(textures[0].Color);
|
||||
}
|
||||
|
||||
// Do we have skin texture?
|
||||
bool SkinTexture = textures.Count > 0 && textures[0].Texture != null;
|
||||
|
||||
if (bakeType == BakeType.Head)
|
||||
{
|
||||
DrawLayer(LoadResourceLayer("head_color.tga"), false);
|
||||
AddAlpha(bakedTexture.Image, LoadResourceLayer("head_alpha.tga"));
|
||||
MultiplyLayerFromAlpha(bakedTexture.Image, LoadResourceLayer("head_skingrain.tga"));
|
||||
}
|
||||
|
||||
if (!SkinTexture && bakeType == BakeType.UpperBody)
|
||||
{
|
||||
DrawLayer(LoadResourceLayer("upperbody_color.tga"), false);
|
||||
}
|
||||
|
||||
if (!SkinTexture && bakeType == BakeType.LowerBody)
|
||||
{
|
||||
DrawLayer(LoadResourceLayer("lowerbody_color.tga"), false);
|
||||
}
|
||||
|
||||
ManagedImage alphaWearableTexture = null;
|
||||
|
||||
// Layer each texture on top of one other, applying alpha masks as we go
|
||||
for (int i = 0; i < textures.Count; i++)
|
||||
{
|
||||
// Skip if we have no texture on this layer
|
||||
if (textures[i].Texture == null) continue;
|
||||
|
||||
// Is this Alpha wearable and does it have an alpha channel?
|
||||
if (textures[i].TextureIndex >= AvatarTextureIndex.LowerAlpha &&
|
||||
textures[i].TextureIndex <= AvatarTextureIndex.HairAlpha)
|
||||
{
|
||||
if (textures[i].Texture.Image.Alpha != null)
|
||||
{
|
||||
alphaWearableTexture = textures[i].Texture.Image.Clone();
|
||||
}
|
||||
else if (textures[i].TextureID == IMG_INVISIBLE)
|
||||
{
|
||||
alphaWearableTexture = new ManagedImage(bakeWidth, bakeHeight, ManagedImage.ImageChannels.Alpha);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't draw skin and tattoo on head bake first
|
||||
// For head bake the skin and texture are drawn last, go figure
|
||||
if (bakeType == BakeType.Head && (i == 0 || i == 1)) continue;
|
||||
|
||||
ManagedImage texture = textures[i].Texture.Image.Clone();
|
||||
//File.WriteAllBytes(bakeType + "-texture-layer-" + i + ".tga", texture.ExportTGA());
|
||||
|
||||
// Resize texture to the size of baked layer
|
||||
// FIXME: if texture is smaller than the layer, don't stretch it, tile it
|
||||
if (texture.Width != bakeWidth || texture.Height != bakeHeight)
|
||||
{
|
||||
try { texture.ResizeNearestNeighbor(bakeWidth, bakeHeight); }
|
||||
catch (Exception) { continue; }
|
||||
}
|
||||
|
||||
// Special case for hair layer for the head bake
|
||||
// If we don't have skin texture, we discard hair alpha
|
||||
// and apply hair(i==2) pattern over the texture
|
||||
if (!SkinTexture && bakeType == BakeType.Head && i == 2)
|
||||
{
|
||||
if (texture.Alpha != null)
|
||||
{
|
||||
for (int j = 0; j < texture.Alpha.Length; j++) texture.Alpha[j] = (byte)255;
|
||||
}
|
||||
MultiplyLayerFromAlpha(texture, LoadResourceLayer("head_hair.tga"));
|
||||
}
|
||||
|
||||
// Aply tint and alpha masks except for skin that has a texture
|
||||
// on layer 0 which always overrides other skin settings
|
||||
if (!(IsSkin && i == 0))
|
||||
{
|
||||
ApplyTint(texture, textures[i].Color);
|
||||
|
||||
// For hair bake, we skip all alpha masks
|
||||
// and use one from the texture, for both
|
||||
// alpha and morph layers
|
||||
if (bakeType == BakeType.Hair)
|
||||
{
|
||||
if (texture.Alpha != null)
|
||||
{
|
||||
bakedTexture.Image.Bump = texture.Alpha;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = 0; j < bakedTexture.Image.Bump.Length; j++) bakedTexture.Image.Bump[j] = byte.MaxValue;
|
||||
}
|
||||
}
|
||||
// Apply parametrized alpha masks
|
||||
else if (textures[i].AlphaMasks != null && textures[i].AlphaMasks.Count > 0)
|
||||
{
|
||||
// Combined mask for the layer, fully transparent to begin with
|
||||
ManagedImage combinedMask = new ManagedImage(bakeWidth, bakeHeight, ManagedImage.ImageChannels.Alpha);
|
||||
|
||||
int addedMasks = 0;
|
||||
|
||||
// First add mask in normal blend mode
|
||||
foreach (KeyValuePair<VisualAlphaParam, float> kvp in textures[i].AlphaMasks)
|
||||
{
|
||||
if (!MaskBelongsToBake(kvp.Key.TGAFile)) continue;
|
||||
|
||||
if (kvp.Key.MultiplyBlend == false && (kvp.Value > 0f || !kvp.Key.SkipIfZero))
|
||||
{
|
||||
ApplyAlpha(combinedMask, kvp.Key, kvp.Value);
|
||||
//File.WriteAllBytes(bakeType + "-layer-" + i + "-mask-" + addedMasks + ".tga", combinedMask.ExportTGA());
|
||||
addedMasks++;
|
||||
}
|
||||
}
|
||||
|
||||
// If there were no mask in normal blend mode make aplha fully opaque
|
||||
if (addedMasks == 0) for (int l = 0; l < combinedMask.Alpha.Length; l++) combinedMask.Alpha[l] = 255;
|
||||
|
||||
// Add masks in multiply blend mode
|
||||
foreach (KeyValuePair<VisualAlphaParam, float> kvp in textures[i].AlphaMasks)
|
||||
{
|
||||
if (!MaskBelongsToBake(kvp.Key.TGAFile)) continue;
|
||||
|
||||
if (kvp.Key.MultiplyBlend == true && (kvp.Value > 0f || !kvp.Key.SkipIfZero))
|
||||
{
|
||||
ApplyAlpha(combinedMask, kvp.Key, kvp.Value);
|
||||
//File.WriteAllBytes(bakeType + "-layer-" + i + "-mask-" + addedMasks + ".tga", combinedMask.ExportTGA());
|
||||
addedMasks++;
|
||||
}
|
||||
}
|
||||
|
||||
if (addedMasks > 0)
|
||||
{
|
||||
// Apply combined alpha mask to the cloned texture
|
||||
AddAlpha(texture, combinedMask);
|
||||
}
|
||||
|
||||
// Is this layer used for morph mask? If it is, use its
|
||||
// alpha as the morth for the whole bake
|
||||
if (Textures[i].TextureIndex == AppearanceManager.MorphLayerForBakeType(bakeType))
|
||||
{
|
||||
bakedTexture.Image.Bump = texture.Alpha;
|
||||
}
|
||||
|
||||
//File.WriteAllBytes(bakeType + "-masked-texture-" + i + ".tga", texture.ExportTGA());
|
||||
}
|
||||
}
|
||||
|
||||
bool useAlpha = i == 0 && (BakeType == BakeType.Skirt || BakeType == BakeType.Hair);
|
||||
DrawLayer(texture, useAlpha);
|
||||
//File.WriteAllBytes(bakeType + "-layer-" + i + ".tga", texture.ExportTGA());
|
||||
}
|
||||
|
||||
// For head and tattoo, we add skin last
|
||||
if (IsSkin && bakeType == BakeType.Head)
|
||||
{
|
||||
ManagedImage texture;
|
||||
if (textures[0].Texture != null)
|
||||
{
|
||||
texture = textures[0].Texture.Image.Clone();
|
||||
if (texture.Width != bakeWidth || texture.Height != bakeHeight)
|
||||
{
|
||||
try { texture.ResizeNearestNeighbor(bakeWidth, bakeHeight); }
|
||||
catch (Exception) { }
|
||||
}
|
||||
DrawLayer(texture, false);
|
||||
}
|
||||
|
||||
// Add head tattoo here (if available, order-dependant)
|
||||
if (textures.Count > 1 && textures[1].Texture != null)
|
||||
{
|
||||
texture = textures[1].Texture.Image.Clone();
|
||||
if (texture.Width != bakeWidth || texture.Height != bakeHeight)
|
||||
{
|
||||
try { texture.ResizeNearestNeighbor(bakeWidth, bakeHeight); }
|
||||
catch (Exception) { }
|
||||
}
|
||||
DrawLayer(texture, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply any alpha wearable textures to make parts of the avatar disappear
|
||||
if (alphaWearableTexture != null)
|
||||
{
|
||||
AddAlpha(bakedTexture.Image, alphaWearableTexture);
|
||||
}
|
||||
|
||||
// We are done, encode asset for finalized bake
|
||||
bakedTexture.Encode();
|
||||
//File.WriteAllBytes(bakeType + ".tga", bakedTexture.Image.ExportTGA());
|
||||
}
|
||||
|
||||
private static object ResourceSync = new object();
|
||||
|
||||
public static ManagedImage LoadResourceLayer(string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Bitmap bitmap = null;
|
||||
lock (ResourceSync)
|
||||
{
|
||||
using (Stream stream = Helpers.GetResourceStream(fileName, Settings.RESOURCE_DIR))
|
||||
{
|
||||
bitmap = LoadTGAClass.LoadTGA(stream);
|
||||
}
|
||||
}
|
||||
if (bitmap == null)
|
||||
{
|
||||
Logger.Log(String.Format("Failed loading resource file: {0}", fileName), Helpers.LogLevel.Error);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
ManagedImage image = new ManagedImage(bitmap);
|
||||
bitmap.Dispose();
|
||||
return image;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log(String.Format("Failed loading resource file: {0} ({1})", fileName, e.Message),
|
||||
Helpers.LogLevel.Error, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts avatar texture index (face) to Bake type
|
||||
/// </summary>
|
||||
/// <param name="index">Face number (AvatarTextureIndex)</param>
|
||||
/// <returns>BakeType, layer to which this texture belongs to</returns>
|
||||
public static BakeType BakeTypeFor(AvatarTextureIndex index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case AvatarTextureIndex.HeadBodypaint:
|
||||
return BakeType.Head;
|
||||
|
||||
case AvatarTextureIndex.UpperBodypaint:
|
||||
case AvatarTextureIndex.UpperGloves:
|
||||
case AvatarTextureIndex.UpperUndershirt:
|
||||
case AvatarTextureIndex.UpperShirt:
|
||||
case AvatarTextureIndex.UpperJacket:
|
||||
return BakeType.UpperBody;
|
||||
|
||||
case AvatarTextureIndex.LowerBodypaint:
|
||||
case AvatarTextureIndex.LowerUnderpants:
|
||||
case AvatarTextureIndex.LowerSocks:
|
||||
case AvatarTextureIndex.LowerShoes:
|
||||
case AvatarTextureIndex.LowerPants:
|
||||
case AvatarTextureIndex.LowerJacket:
|
||||
return BakeType.LowerBody;
|
||||
|
||||
case AvatarTextureIndex.EyesIris:
|
||||
return BakeType.Eyes;
|
||||
|
||||
case AvatarTextureIndex.Skirt:
|
||||
return BakeType.Skirt;
|
||||
|
||||
case AvatarTextureIndex.Hair:
|
||||
return BakeType.Hair;
|
||||
|
||||
default:
|
||||
return BakeType.Unknown;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private layer compositing methods
|
||||
|
||||
private bool MaskBelongsToBake(string mask)
|
||||
{
|
||||
if ((bakeType == BakeType.LowerBody && mask.Contains("upper"))
|
||||
|| (bakeType == BakeType.LowerBody && mask.Contains("shirt"))
|
||||
|| (bakeType == BakeType.UpperBody && mask.Contains("lower")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool DrawLayer(ManagedImage source, bool addSourceAlpha)
|
||||
{
|
||||
if (source == null) return false;
|
||||
|
||||
bool sourceHasColor;
|
||||
bool sourceHasAlpha;
|
||||
bool sourceHasBump;
|
||||
int i = 0;
|
||||
|
||||
sourceHasColor = ((source.Channels & ManagedImage.ImageChannels.Color) != 0 &&
|
||||
source.Red != null && source.Green != null && source.Blue != null);
|
||||
sourceHasAlpha = ((source.Channels & ManagedImage.ImageChannels.Alpha) != 0 && source.Alpha != null);
|
||||
sourceHasBump = ((source.Channels & ManagedImage.ImageChannels.Bump) != 0 && source.Bump != null);
|
||||
|
||||
addSourceAlpha = (addSourceAlpha && sourceHasAlpha);
|
||||
|
||||
byte alpha = Byte.MaxValue;
|
||||
byte alphaInv = (byte)(Byte.MaxValue - alpha);
|
||||
|
||||
byte[] bakedRed = bakedTexture.Image.Red;
|
||||
byte[] bakedGreen = bakedTexture.Image.Green;
|
||||
byte[] bakedBlue = bakedTexture.Image.Blue;
|
||||
byte[] bakedAlpha = bakedTexture.Image.Alpha;
|
||||
byte[] bakedBump = bakedTexture.Image.Bump;
|
||||
|
||||
byte[] sourceRed = source.Red;
|
||||
byte[] sourceGreen = source.Green;
|
||||
byte[] sourceBlue = source.Blue;
|
||||
byte[] sourceAlpha = sourceHasAlpha ? source.Alpha : null;
|
||||
byte[] sourceBump = sourceHasBump ? source.Bump : null;
|
||||
|
||||
for (int y = 0; y < bakeHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < bakeWidth; x++)
|
||||
{
|
||||
if (sourceHasAlpha)
|
||||
{
|
||||
alpha = sourceAlpha[i];
|
||||
alphaInv = (byte)(Byte.MaxValue - alpha);
|
||||
}
|
||||
|
||||
if (sourceHasColor)
|
||||
{
|
||||
bakedRed[i] = (byte)((bakedRed[i] * alphaInv + sourceRed[i] * alpha) >> 8);
|
||||
bakedGreen[i] = (byte)((bakedGreen[i] * alphaInv + sourceGreen[i] * alpha) >> 8);
|
||||
bakedBlue[i] = (byte)((bakedBlue[i] * alphaInv + sourceBlue[i] * alpha) >> 8);
|
||||
}
|
||||
|
||||
if (addSourceAlpha)
|
||||
{
|
||||
if (sourceAlpha[i] < bakedAlpha[i])
|
||||
{
|
||||
bakedAlpha[i] = sourceAlpha[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceHasBump)
|
||||
bakedBump[i] = sourceBump[i];
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure images exist, resize source if needed to match the destination
|
||||
/// </summary>
|
||||
/// <param name="dest">Destination image</param>
|
||||
/// <param name="src">Source image</param>
|
||||
/// <returns>Sanitization was succefull</returns>
|
||||
private bool SanitizeLayers(ManagedImage dest, ManagedImage src)
|
||||
{
|
||||
if (dest == null || src == null) return false;
|
||||
|
||||
if ((dest.Channels & ManagedImage.ImageChannels.Alpha) == 0)
|
||||
{
|
||||
dest.ConvertChannels(dest.Channels | ManagedImage.ImageChannels.Alpha);
|
||||
}
|
||||
|
||||
if (dest.Width != src.Width || dest.Height != src.Height)
|
||||
{
|
||||
try { src.ResizeNearestNeighbor(dest.Width, dest.Height); }
|
||||
catch (Exception) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void ApplyAlpha(ManagedImage dest, VisualAlphaParam param, float val)
|
||||
{
|
||||
ManagedImage src = LoadResourceLayer(param.TGAFile);
|
||||
|
||||
if (dest == null || src == null || src.Alpha == null) return;
|
||||
|
||||
if ((dest.Channels & ManagedImage.ImageChannels.Alpha) == 0)
|
||||
{
|
||||
dest.ConvertChannels(ManagedImage.ImageChannels.Alpha | dest.Channels);
|
||||
}
|
||||
|
||||
if (dest.Width != src.Width || dest.Height != src.Height)
|
||||
{
|
||||
try { src.ResizeNearestNeighbor(dest.Width, dest.Height); }
|
||||
catch (Exception) { return; }
|
||||
}
|
||||
|
||||
for (int i = 0; i < dest.Alpha.Length; i++)
|
||||
{
|
||||
byte alpha = src.Alpha[i] <= ((1 - val) * 255) ? (byte)0 : (byte)255;
|
||||
if (alpha != 255)
|
||||
{
|
||||
}
|
||||
if (param.MultiplyBlend)
|
||||
{
|
||||
dest.Alpha[i] = (byte)((dest.Alpha[i] * alpha) >> 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (alpha > dest.Alpha[i])
|
||||
{
|
||||
dest.Alpha[i] = alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddAlpha(ManagedImage dest, ManagedImage src)
|
||||
{
|
||||
if (!SanitizeLayers(dest, src)) return;
|
||||
|
||||
for (int i = 0; i < dest.Alpha.Length; i++)
|
||||
{
|
||||
if (src.Alpha[i] < dest.Alpha[i])
|
||||
{
|
||||
dest.Alpha[i] = src.Alpha[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MultiplyLayerFromAlpha(ManagedImage dest, ManagedImage src)
|
||||
{
|
||||
if (!SanitizeLayers(dest, src)) return;
|
||||
|
||||
for (int i = 0; i < dest.Red.Length; i++)
|
||||
{
|
||||
dest.Red[i] = (byte)((dest.Red[i] * src.Alpha[i]) >> 8);
|
||||
dest.Green[i] = (byte)((dest.Green[i] * src.Alpha[i]) >> 8);
|
||||
dest.Blue[i] = (byte)((dest.Blue[i] * src.Alpha[i]) >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyTint(ManagedImage dest, Color4 src)
|
||||
{
|
||||
if (dest == null) return;
|
||||
|
||||
for (int i = 0; i < dest.Red.Length; i++)
|
||||
{
|
||||
dest.Red[i] = (byte)((dest.Red[i] * ((byte)(src.R * byte.MaxValue))) >> 8);
|
||||
dest.Green[i] = (byte)((dest.Green[i] * ((byte)(src.G * byte.MaxValue))) >> 8);
|
||||
dest.Blue[i] = (byte)((dest.Blue[i] * ((byte)(src.B * byte.MaxValue))) >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills a baked layer as a solid *appearing* color. The colors are
|
||||
/// subtly dithered on a 16x16 grid to prevent the JPEG2000 stage from
|
||||
/// compressing it too far since it seems to cause upload failures if
|
||||
/// the image is a pure solid color
|
||||
/// </summary>
|
||||
/// <param name="color">Color of the base of this layer</param>
|
||||
private void InitBakedLayerColor(Color4 color)
|
||||
{
|
||||
InitBakedLayerColor(color.R, color.G, color.B);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills a baked layer as a solid *appearing* color. The colors are
|
||||
/// subtly dithered on a 16x16 grid to prevent the JPEG2000 stage from
|
||||
/// compressing it too far since it seems to cause upload failures if
|
||||
/// the image is a pure solid color
|
||||
/// </summary>
|
||||
/// <param name="r">Red value</param>
|
||||
/// <param name="g">Green value</param>
|
||||
/// <param name="b">Blue value</param>
|
||||
private void InitBakedLayerColor(float r, float g, float b)
|
||||
{
|
||||
byte rByte = Utils.FloatToByte(r, 0f, 1f);
|
||||
byte gByte = Utils.FloatToByte(g, 0f, 1f);
|
||||
byte bByte = Utils.FloatToByte(b, 0f, 1f);
|
||||
|
||||
byte rAlt, gAlt, bAlt;
|
||||
|
||||
rAlt = rByte;
|
||||
gAlt = gByte;
|
||||
bAlt = bByte;
|
||||
|
||||
if (rByte < Byte.MaxValue)
|
||||
rAlt++;
|
||||
else rAlt--;
|
||||
|
||||
if (gByte < Byte.MaxValue)
|
||||
gAlt++;
|
||||
else gAlt--;
|
||||
|
||||
if (bByte < Byte.MaxValue)
|
||||
bAlt++;
|
||||
else bAlt--;
|
||||
|
||||
int i = 0;
|
||||
|
||||
byte[] red = bakedTexture.Image.Red;
|
||||
byte[] green = bakedTexture.Image.Green;
|
||||
byte[] blue = bakedTexture.Image.Blue;
|
||||
byte[] alpha = bakedTexture.Image.Alpha;
|
||||
byte[] bump = bakedTexture.Image.Bump;
|
||||
|
||||
for (int y = 0; y < bakeHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < bakeWidth; x++)
|
||||
{
|
||||
if (((x ^ y) & 0x10) == 0)
|
||||
{
|
||||
red[i] = rAlt;
|
||||
green[i] = gByte;
|
||||
blue[i] = bByte;
|
||||
alpha[i] = Byte.MaxValue;
|
||||
bump[i] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
red[i] = rByte;
|
||||
green[i] = gAlt;
|
||||
blue[i] = bAlt;
|
||||
alpha[i] = Byte.MaxValue;
|
||||
bump[i] = 0;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace OpenMetaverse.Imaging
|
||||
{
|
||||
public class ManagedImage
|
||||
{
|
||||
[Flags]
|
||||
public enum ImageChannels
|
||||
{
|
||||
Gray = 1,
|
||||
Color = 2,
|
||||
Alpha = 4,
|
||||
Bump = 8
|
||||
};
|
||||
|
||||
public enum ImageResizeAlgorithm
|
||||
{
|
||||
NearestNeighbor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Image width
|
||||
/// </summary>
|
||||
public int Width;
|
||||
|
||||
/// <summary>
|
||||
/// Image height
|
||||
/// </summary>
|
||||
public int Height;
|
||||
|
||||
/// <summary>
|
||||
/// Image channel flags
|
||||
/// </summary>
|
||||
public ImageChannels Channels;
|
||||
|
||||
/// <summary>
|
||||
/// Red channel data
|
||||
/// </summary>
|
||||
public byte[] Red;
|
||||
|
||||
/// <summary>
|
||||
/// Green channel data
|
||||
/// </summary>
|
||||
public byte[] Green;
|
||||
|
||||
/// <summary>
|
||||
/// Blue channel data
|
||||
/// </summary>
|
||||
public byte[] Blue;
|
||||
|
||||
/// <summary>
|
||||
/// Alpha channel data
|
||||
/// </summary>
|
||||
public byte[] Alpha;
|
||||
|
||||
/// <summary>
|
||||
/// Bump channel data
|
||||
/// </summary>
|
||||
public byte[] Bump;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new blank image
|
||||
/// </summary>
|
||||
/// <param name="width">width</param>
|
||||
/// <param name="height">height</param>
|
||||
/// <param name="channels">channel flags</param>
|
||||
public ManagedImage(int width, int height, ImageChannels channels)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
Channels = channels;
|
||||
|
||||
int n = width * height;
|
||||
|
||||
if ((channels & ImageChannels.Gray) != 0)
|
||||
{
|
||||
Red = new byte[n];
|
||||
}
|
||||
else if ((channels & ImageChannels.Color) != 0)
|
||||
{
|
||||
Red = new byte[n];
|
||||
Green = new byte[n];
|
||||
Blue = new byte[n];
|
||||
}
|
||||
|
||||
if ((channels & ImageChannels.Alpha) != 0)
|
||||
Alpha = new byte[n];
|
||||
|
||||
if ((channels & ImageChannels.Bump) != 0)
|
||||
Bump = new byte[n];
|
||||
}
|
||||
|
||||
#if !NO_UNSAFE
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bitmap"></param>
|
||||
public ManagedImage(System.Drawing.Bitmap bitmap)
|
||||
{
|
||||
Width = bitmap.Width;
|
||||
Height = bitmap.Height;
|
||||
|
||||
int pixelCount = Width * Height;
|
||||
|
||||
if (bitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppArgb)
|
||||
{
|
||||
Channels = ImageChannels.Alpha | ImageChannels.Color;
|
||||
Red = new byte[pixelCount];
|
||||
Green = new byte[pixelCount];
|
||||
Blue = new byte[pixelCount];
|
||||
Alpha = new byte[pixelCount];
|
||||
|
||||
System.Drawing.Imaging.BitmapData bd = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, Width, Height),
|
||||
System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
|
||||
unsafe
|
||||
{
|
||||
byte* pixel = (byte*)bd.Scan0;
|
||||
|
||||
for (int i = 0; i < pixelCount; i++)
|
||||
{
|
||||
// GDI+ gives us BGRA and we need to turn that in to RGBA
|
||||
Blue[i] = *(pixel++);
|
||||
Green[i] = *(pixel++);
|
||||
Red[i] = *(pixel++);
|
||||
Alpha[i] = *(pixel++);
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.UnlockBits(bd);
|
||||
}
|
||||
else if (bitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format16bppGrayScale)
|
||||
{
|
||||
Channels = ImageChannels.Gray;
|
||||
Red = new byte[pixelCount];
|
||||
|
||||
throw new NotImplementedException("16bpp grayscale image support is incomplete");
|
||||
}
|
||||
else if (bitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb)
|
||||
{
|
||||
Channels = ImageChannels.Color;
|
||||
Red = new byte[pixelCount];
|
||||
Green = new byte[pixelCount];
|
||||
Blue = new byte[pixelCount];
|
||||
|
||||
System.Drawing.Imaging.BitmapData bd = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, Width, Height),
|
||||
System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
|
||||
|
||||
unsafe
|
||||
{
|
||||
byte* pixel = (byte*)bd.Scan0;
|
||||
|
||||
for (int i = 0; i < pixelCount; i++)
|
||||
{
|
||||
// GDI+ gives us BGR and we need to turn that in to RGB
|
||||
Blue[i] = *(pixel++);
|
||||
Green[i] = *(pixel++);
|
||||
Red[i] = *(pixel++);
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.UnlockBits(bd);
|
||||
}
|
||||
else if (bitmap.PixelFormat == System.Drawing.Imaging.PixelFormat.Format32bppRgb)
|
||||
{
|
||||
Channels = ImageChannels.Color;
|
||||
Red = new byte[pixelCount];
|
||||
Green = new byte[pixelCount];
|
||||
Blue = new byte[pixelCount];
|
||||
|
||||
System.Drawing.Imaging.BitmapData bd = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, Width, Height),
|
||||
System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
|
||||
|
||||
unsafe
|
||||
{
|
||||
byte* pixel = (byte*)bd.Scan0;
|
||||
|
||||
for (int i = 0; i < pixelCount; i++)
|
||||
{
|
||||
// GDI+ gives us BGR and we need to turn that in to RGB
|
||||
Blue[i] = *(pixel++);
|
||||
Green[i] = *(pixel++);
|
||||
Red[i] = *(pixel++);
|
||||
pixel++; // Skip over the empty byte where the Alpha info would normally be
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.UnlockBits(bd);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Unrecognized pixel format: " + bitmap.PixelFormat.ToString());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Convert the channels in the image. Channels are created or destroyed as required.
|
||||
/// </summary>
|
||||
/// <param name="channels">new channel flags</param>
|
||||
public void ConvertChannels(ImageChannels channels)
|
||||
{
|
||||
if (Channels == channels)
|
||||
return;
|
||||
|
||||
int n = Width * Height;
|
||||
ImageChannels add = Channels ^ channels & channels;
|
||||
ImageChannels del = Channels ^ channels & Channels;
|
||||
|
||||
if ((add & ImageChannels.Color) != 0)
|
||||
{
|
||||
Red = new byte[n];
|
||||
Green = new byte[n];
|
||||
Blue = new byte[n];
|
||||
}
|
||||
else if ((del & ImageChannels.Color) != 0)
|
||||
{
|
||||
Red = null;
|
||||
Green = null;
|
||||
Blue = null;
|
||||
}
|
||||
|
||||
if ((add & ImageChannels.Alpha) != 0)
|
||||
{
|
||||
Alpha = new byte[n];
|
||||
FillArray(Alpha, 255);
|
||||
}
|
||||
else if ((del & ImageChannels.Alpha) != 0)
|
||||
Alpha = null;
|
||||
|
||||
if ((add & ImageChannels.Bump) != 0)
|
||||
Bump = new byte[n];
|
||||
else if ((del & ImageChannels.Bump) != 0)
|
||||
Bump = null;
|
||||
|
||||
Channels = channels;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resize or stretch the image using nearest neighbor (ugly) resampling
|
||||
/// </summary>
|
||||
/// <param name="width">new width</param>
|
||||
/// <param name="height">new height</param>
|
||||
public void ResizeNearestNeighbor(int width, int height)
|
||||
{
|
||||
if (width == Width && height == Height)
|
||||
return;
|
||||
|
||||
byte[]
|
||||
red = null,
|
||||
green = null,
|
||||
blue = null,
|
||||
alpha = null,
|
||||
bump = null;
|
||||
int n = width * height;
|
||||
int di = 0, si;
|
||||
|
||||
if (Red != null) red = new byte[n];
|
||||
if (Green != null) green = new byte[n];
|
||||
if (Blue != null) blue = new byte[n];
|
||||
if (Alpha != null) alpha = new byte[n];
|
||||
if (Bump != null) bump = new byte[n];
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
si = (y * Height / height) * Width + (x * Width / width);
|
||||
if (Red != null) red[di] = Red[si];
|
||||
if (Green != null) green[di] = Green[si];
|
||||
if (Blue != null) blue[di] = Blue[si];
|
||||
if (Alpha != null) alpha[di] = Alpha[si];
|
||||
if (Bump != null) bump[di] = Bump[si];
|
||||
di++;
|
||||
}
|
||||
}
|
||||
|
||||
Width = width;
|
||||
Height = height;
|
||||
Red = red;
|
||||
Green = green;
|
||||
Blue = blue;
|
||||
Alpha = alpha;
|
||||
Bump = bump;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a byte array containing 32-bit RGBA data with a bottom-left
|
||||
/// origin, suitable for feeding directly into OpenGL
|
||||
/// </summary>
|
||||
/// <returns>A byte array containing raw texture data</returns>
|
||||
public byte[] ExportRaw()
|
||||
{
|
||||
byte[] raw = new byte[Width * Height * 4];
|
||||
|
||||
if ((Channels & ImageChannels.Alpha) != 0)
|
||||
{
|
||||
if ((Channels & ImageChannels.Color) != 0)
|
||||
{
|
||||
// RGBA
|
||||
for (int h = 0; h < Height; h++)
|
||||
{
|
||||
for (int w = 0; w < Width; w++)
|
||||
{
|
||||
int pos = (Height - 1 - h) * Width + w;
|
||||
int srcPos = h * Width + w;
|
||||
|
||||
raw[pos * 4 + 0] = Red[srcPos];
|
||||
raw[pos * 4 + 1] = Green[srcPos];
|
||||
raw[pos * 4 + 2] = Blue[srcPos];
|
||||
raw[pos * 4 + 3] = Alpha[srcPos];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Alpha only
|
||||
for (int h = 0; h < Height; h++)
|
||||
{
|
||||
for (int w = 0; w < Width; w++)
|
||||
{
|
||||
int pos = (Height - 1 - h) * Width + w;
|
||||
int srcPos = h * Width + w;
|
||||
|
||||
raw[pos * 4 + 0] = Alpha[srcPos];
|
||||
raw[pos * 4 + 1] = Alpha[srcPos];
|
||||
raw[pos * 4 + 2] = Alpha[srcPos];
|
||||
raw[pos * 4 + 3] = Byte.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RGB
|
||||
for (int h = 0; h < Height; h++)
|
||||
{
|
||||
for (int w = 0; w < Width; w++)
|
||||
{
|
||||
int pos = (Height - 1 - h) * Width + w;
|
||||
int srcPos = h * Width + w;
|
||||
|
||||
raw[pos * 4 + 0] = Red[srcPos];
|
||||
raw[pos * 4 + 1] = Green[srcPos];
|
||||
raw[pos * 4 + 2] = Blue[srcPos];
|
||||
raw[pos * 4 + 3] = Byte.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a byte array containing 32-bit RGBA data with a bottom-left
|
||||
/// origin, suitable for feeding directly into OpenGL
|
||||
/// </summary>
|
||||
/// <returns>A byte array containing raw texture data</returns>
|
||||
public System.Drawing.Bitmap ExportBitmap()
|
||||
{
|
||||
byte[] raw = new byte[Width * Height * 4];
|
||||
|
||||
if ((Channels & ImageChannels.Alpha) != 0)
|
||||
{
|
||||
if ((Channels & ImageChannels.Color) != 0)
|
||||
{
|
||||
// RGBA
|
||||
for (int pos = 0; pos < Height * Width; pos++)
|
||||
{
|
||||
raw[pos * 4 + 0] = Blue[pos];
|
||||
raw[pos * 4 + 1] = Green[pos];
|
||||
raw[pos * 4 + 2] = Red[pos];
|
||||
raw[pos * 4 + 3] = Alpha[pos];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Alpha only
|
||||
for (int pos = 0; pos < Height * Width; pos++)
|
||||
{
|
||||
raw[pos * 4 + 0] = Alpha[pos];
|
||||
raw[pos * 4 + 1] = Alpha[pos];
|
||||
raw[pos * 4 + 2] = Alpha[pos];
|
||||
raw[pos * 4 + 3] = Byte.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RGB
|
||||
for (int pos = 0; pos < Height * Width; pos++)
|
||||
{
|
||||
raw[pos * 4 + 0] = Blue[pos];
|
||||
raw[pos * 4 + 1] = Green[pos];
|
||||
raw[pos * 4 + 2] = Red[pos];
|
||||
raw[pos * 4 + 3] = Byte.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
System.Drawing.Bitmap b = new System.Drawing.Bitmap(
|
||||
Width,
|
||||
Height,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
|
||||
System.Drawing.Imaging.BitmapData bd = b.LockBits(new System.Drawing.Rectangle(0, 0, b.Width, b.Height),
|
||||
System.Drawing.Imaging.ImageLockMode.WriteOnly,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
|
||||
System.Runtime.InteropServices.Marshal.Copy(raw, 0, bd.Scan0, Width * Height * 4);
|
||||
|
||||
b.UnlockBits(bd);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
public byte[] ExportTGA()
|
||||
{
|
||||
byte[] tga = new byte[Width * Height * ((Channels & ImageChannels.Alpha) == 0 ? 3 : 4) + 32];
|
||||
int di = 0;
|
||||
tga[di++] = 0; // idlength
|
||||
tga[di++] = 0; // colormaptype = 0: no colormap
|
||||
tga[di++] = 2; // image type = 2: uncompressed RGB
|
||||
tga[di++] = 0; // color map spec is five zeroes for no color map
|
||||
tga[di++] = 0; // color map spec is five zeroes for no color map
|
||||
tga[di++] = 0; // color map spec is five zeroes for no color map
|
||||
tga[di++] = 0; // color map spec is five zeroes for no color map
|
||||
tga[di++] = 0; // color map spec is five zeroes for no color map
|
||||
tga[di++] = 0; // x origin = two bytes
|
||||
tga[di++] = 0; // x origin = two bytes
|
||||
tga[di++] = 0; // y origin = two bytes
|
||||
tga[di++] = 0; // y origin = two bytes
|
||||
tga[di++] = (byte)(Width & 0xFF); // width - low byte
|
||||
tga[di++] = (byte)(Width >> 8); // width - hi byte
|
||||
tga[di++] = (byte)(Height & 0xFF); // height - low byte
|
||||
tga[di++] = (byte)(Height >> 8); // height - hi byte
|
||||
tga[di++] = (byte)((Channels & ImageChannels.Alpha) == 0 ? 24 : 32); // 24/32 bits per pixel
|
||||
tga[di++] = (byte)((Channels & ImageChannels.Alpha) == 0 ? 32 : 40); // image descriptor byte
|
||||
|
||||
int n = Width * Height;
|
||||
|
||||
if ((Channels & ImageChannels.Alpha) != 0)
|
||||
{
|
||||
if ((Channels & ImageChannels.Color) != 0)
|
||||
{
|
||||
// RGBA
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
tga[di++] = Blue[i];
|
||||
tga[di++] = Green[i];
|
||||
tga[di++] = Red[i];
|
||||
tga[di++] = Alpha[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Alpha only
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
tga[di++] = Alpha[i];
|
||||
tga[di++] = Alpha[i];
|
||||
tga[di++] = Alpha[i];
|
||||
tga[di++] = Byte.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RGB
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
tga[di++] = Blue[i];
|
||||
tga[di++] = Green[i];
|
||||
tga[di++] = Red[i];
|
||||
}
|
||||
}
|
||||
|
||||
return tga;
|
||||
}
|
||||
|
||||
private static void FillArray(byte[] array, byte value)
|
||||
{
|
||||
if (array != null)
|
||||
{
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
array[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
FillArray(Red, 0);
|
||||
FillArray(Green, 0);
|
||||
FillArray(Blue, 0);
|
||||
FillArray(Alpha, 0);
|
||||
FillArray(Bump, 0);
|
||||
}
|
||||
|
||||
public ManagedImage Clone()
|
||||
{
|
||||
ManagedImage image = new ManagedImage(Width, Height, Channels);
|
||||
if (Red != null) image.Red = (byte[])Red.Clone();
|
||||
if (Green != null) image.Green = (byte[])Green.Clone();
|
||||
if (Blue != null) image.Blue = (byte[])Blue.Clone();
|
||||
if (Alpha != null) image.Alpha = (byte[])Alpha.Clone();
|
||||
if (Bump != null) image.Bump = (byte[])Bump.Clone();
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,590 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OpenMetaverse.Imaging
|
||||
{
|
||||
#if !NO_UNSAFE
|
||||
/// <summary>
|
||||
/// A Wrapper around openjpeg to encode and decode images to and from byte arrays
|
||||
/// </summary>
|
||||
public class OpenJPEG
|
||||
{
|
||||
/// <summary>TGA Header size</summary>
|
||||
public const int TGA_HEADER_SIZE = 32;
|
||||
|
||||
#region JPEG2000 Structs
|
||||
|
||||
/// <summary>
|
||||
/// Defines the beginning and ending file positions of a layer in an
|
||||
/// LRCP-progression JPEG2000 file
|
||||
/// </summary>
|
||||
[System.Diagnostics.DebuggerDisplay("Start = {Start} End = {End} Size = {End - Start}")]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct J2KLayerInfo
|
||||
{
|
||||
public int Start;
|
||||
public int End;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This structure is used to marshal both encoded and decoded images.
|
||||
/// MUST MATCH THE STRUCT IN dotnet.h!
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
private struct MarshalledImage
|
||||
{
|
||||
public IntPtr encoded; // encoded image data
|
||||
public int length; // encoded image length
|
||||
public int dummy; // padding for 64-bit alignment
|
||||
|
||||
public IntPtr decoded; // decoded image, contiguous components
|
||||
|
||||
public int width; // width of decoded image
|
||||
public int height; // height of decoded image
|
||||
public int layers; // layer count
|
||||
public int resolutions; // resolution count
|
||||
public int components; // component count
|
||||
public int packet_count; // packet count
|
||||
public IntPtr packets; // pointer to the packets array
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about a single packet in a JPEG2000 stream
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
private struct MarshalledPacket
|
||||
{
|
||||
/// <summary>Packet start position</summary>
|
||||
public int start_pos;
|
||||
/// <summary>Packet header end position</summary>
|
||||
public int end_ph_pos;
|
||||
/// <summary>Packet end position</summary>
|
||||
public int end_pos;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format("start_pos: {0} end_ph_pos: {1} end_pos: {2}",
|
||||
start_pos, end_ph_pos, end_pos);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion JPEG2000 Structs
|
||||
|
||||
#region Unmanaged Function Declarations
|
||||
|
||||
|
||||
// allocate encoded buffer based on length field
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetAllocEncoded(ref MarshalledImage image);
|
||||
|
||||
// allocate decoded buffer based on width and height fields
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetAllocDecoded(ref MarshalledImage image);
|
||||
|
||||
// free buffers
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetFree(ref MarshalledImage image);
|
||||
|
||||
// encode raw to jpeg2000
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetEncode(ref MarshalledImage image, bool lossless);
|
||||
|
||||
// decode jpeg2000 to raw
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetDecode(ref MarshalledImage image);
|
||||
|
||||
// decode jpeg2000 to raw, get jpeg2000 file info
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetDecodeWithInfo(ref MarshalledImage image);
|
||||
|
||||
// invoke 64 bit openjpeg calls
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet-x86_64.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetAllocEncoded64(ref MarshalledImage image);
|
||||
|
||||
// allocate decoded buffer based on width and height fields
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet-x86_64.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetAllocDecoded64(ref MarshalledImage image);
|
||||
|
||||
// free buffers
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet-x86_64.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetFree64(ref MarshalledImage image);
|
||||
|
||||
// encode raw to jpeg2000
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet-x86_64.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetEncode64(ref MarshalledImage image, bool lossless);
|
||||
|
||||
// decode jpeg2000 to raw
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet-x86_64.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetDecode64(ref MarshalledImage image);
|
||||
|
||||
// decode jpeg2000 to raw, get jpeg2000 file info
|
||||
[System.Security.SuppressUnmanagedCodeSecurity]
|
||||
[DllImport("openjpeg-dotnet-x86_64.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool DotNetDecodeWithInfo64(ref MarshalledImage image);
|
||||
#endregion Unmanaged Function Declarations
|
||||
|
||||
/// <summary>OpenJPEG is not threadsafe, so this object is used to lock
|
||||
/// during calls into unmanaged code</summary>
|
||||
private static object OpenJPEGLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Encode a <seealso cref="ManagedImage"/> object into a byte array
|
||||
/// </summary>
|
||||
/// <param name="image">The <seealso cref="ManagedImage"/> object to encode</param>
|
||||
/// <param name="lossless">true to enable lossless conversion, only useful for small images ie: sculptmaps</param>
|
||||
/// <returns>A byte array containing the encoded Image object</returns>
|
||||
public static byte[] Encode(ManagedImage image, bool lossless)
|
||||
{
|
||||
if ((image.Channels & ManagedImage.ImageChannels.Color) == 0 ||
|
||||
((image.Channels & ManagedImage.ImageChannels.Bump) != 0 && (image.Channels & ManagedImage.ImageChannels.Alpha) == 0))
|
||||
throw new ArgumentException("JPEG2000 encoding is not supported for this channel combination");
|
||||
|
||||
byte[] encoded = null;
|
||||
MarshalledImage marshalled = new MarshalledImage();
|
||||
|
||||
// allocate and copy to input buffer
|
||||
marshalled.width = image.Width;
|
||||
marshalled.height = image.Height;
|
||||
marshalled.components = 3;
|
||||
if ((image.Channels & ManagedImage.ImageChannels.Alpha) != 0) marshalled.components++;
|
||||
if ((image.Channels & ManagedImage.ImageChannels.Bump) != 0) marshalled.components++;
|
||||
|
||||
lock (OpenJPEGLock)
|
||||
{
|
||||
|
||||
bool allocSuccess = (IntPtr.Size == 8) ? DotNetAllocDecoded64(ref marshalled) : DotNetAllocDecoded(ref marshalled);
|
||||
|
||||
if (!allocSuccess)
|
||||
throw new Exception("DotNetAllocDecoded failed");
|
||||
|
||||
int n = image.Width * image.Height;
|
||||
|
||||
if ((image.Channels & ManagedImage.ImageChannels.Color) != 0)
|
||||
{
|
||||
Marshal.Copy(image.Red, 0, marshalled.decoded, n);
|
||||
Marshal.Copy(image.Green, 0, (IntPtr)(marshalled.decoded.ToInt64() + n), n);
|
||||
Marshal.Copy(image.Blue, 0, (IntPtr)(marshalled.decoded.ToInt64() + n * 2), n);
|
||||
}
|
||||
|
||||
if ((image.Channels & ManagedImage.ImageChannels.Alpha) != 0) Marshal.Copy(image.Alpha, 0, (IntPtr)(marshalled.decoded.ToInt64() + n * 3), n);
|
||||
if ((image.Channels & ManagedImage.ImageChannels.Bump) != 0) Marshal.Copy(image.Bump, 0, (IntPtr)(marshalled.decoded.ToInt64() + n * 4), n);
|
||||
|
||||
// codec will allocate output buffer
|
||||
bool encodeSuccess = (IntPtr.Size == 8) ? DotNetEncode64(ref marshalled, lossless) : DotNetEncode(ref marshalled, lossless);
|
||||
if (!encodeSuccess)
|
||||
throw new Exception("DotNetEncode failed");
|
||||
|
||||
// copy output buffer
|
||||
encoded = new byte[marshalled.length];
|
||||
Marshal.Copy(marshalled.encoded, encoded, 0, marshalled.length);
|
||||
|
||||
// free buffers
|
||||
if (IntPtr.Size == 8)
|
||||
DotNetFree64(ref marshalled);
|
||||
else
|
||||
DotNetFree(ref marshalled);
|
||||
}
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode a <seealso cref="ManagedImage"/> object into a byte array
|
||||
/// </summary>
|
||||
/// <param name="image">The <seealso cref="ManagedImage"/> object to encode</param>
|
||||
/// <returns>a byte array of the encoded image</returns>
|
||||
public static byte[] Encode(ManagedImage image)
|
||||
{
|
||||
return Encode(image, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode JPEG2000 data to an <seealso cref="System.Drawing.Image"/> and
|
||||
/// <seealso cref="ManagedImage"/>
|
||||
/// </summary>
|
||||
/// <param name="encoded">JPEG2000 encoded data</param>
|
||||
/// <param name="managedImage">ManagedImage object to decode to</param>
|
||||
/// <param name="image">Image object to decode to</param>
|
||||
/// <returns>True if the decode succeeds, otherwise false</returns>
|
||||
public static bool DecodeToImage(byte[] encoded, out ManagedImage managedImage, out Image image)
|
||||
{
|
||||
managedImage = null;
|
||||
image = null;
|
||||
|
||||
if (DecodeToImage(encoded, out managedImage))
|
||||
{
|
||||
try
|
||||
{
|
||||
image = managedImage.ExportBitmap();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Failed to export and load TGA data from decoded image", Helpers.LogLevel.Error, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="encoded"></param>
|
||||
/// <param name="managedImage"></param>
|
||||
/// <returns></returns>
|
||||
public static bool DecodeToImage(byte[] encoded, out ManagedImage managedImage)
|
||||
{
|
||||
MarshalledImage marshalled = new MarshalledImage();
|
||||
|
||||
// Allocate and copy to input buffer
|
||||
marshalled.length = encoded.Length;
|
||||
|
||||
lock (OpenJPEGLock)
|
||||
{
|
||||
if (IntPtr.Size == 8)
|
||||
DotNetAllocEncoded64(ref marshalled);
|
||||
else
|
||||
DotNetAllocEncoded(ref marshalled);
|
||||
|
||||
Marshal.Copy(encoded, 0, marshalled.encoded, encoded.Length);
|
||||
|
||||
// Codec will allocate output buffer
|
||||
if (IntPtr.Size == 8)
|
||||
DotNetDecode64(ref marshalled);
|
||||
else
|
||||
DotNetDecode(ref marshalled);
|
||||
|
||||
int n = marshalled.width * marshalled.height;
|
||||
|
||||
switch (marshalled.components)
|
||||
{
|
||||
case 1: // Grayscale
|
||||
managedImage = new ManagedImage(marshalled.width, marshalled.height,
|
||||
ManagedImage.ImageChannels.Color);
|
||||
Marshal.Copy(marshalled.decoded, managedImage.Red, 0, n);
|
||||
Buffer.BlockCopy(managedImage.Red, 0, managedImage.Green, 0, n);
|
||||
Buffer.BlockCopy(managedImage.Red, 0, managedImage.Blue, 0, n);
|
||||
break;
|
||||
|
||||
case 2: // Grayscale + alpha
|
||||
managedImage = new ManagedImage(marshalled.width, marshalled.height,
|
||||
ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha);
|
||||
Marshal.Copy(marshalled.decoded, managedImage.Red, 0, n);
|
||||
Buffer.BlockCopy(managedImage.Red, 0, managedImage.Green, 0, n);
|
||||
Buffer.BlockCopy(managedImage.Red, 0, managedImage.Blue, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)n), managedImage.Alpha, 0, n);
|
||||
break;
|
||||
|
||||
case 3: // RGB
|
||||
managedImage = new ManagedImage(marshalled.width, marshalled.height,
|
||||
ManagedImage.ImageChannels.Color);
|
||||
Marshal.Copy(marshalled.decoded, managedImage.Red, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)n), managedImage.Green, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)(n * 2)), managedImage.Blue, 0, n);
|
||||
break;
|
||||
|
||||
case 4: // RGBA
|
||||
managedImage = new ManagedImage(marshalled.width, marshalled.height,
|
||||
ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha);
|
||||
Marshal.Copy(marshalled.decoded, managedImage.Red, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)n), managedImage.Green, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)(n * 2)), managedImage.Blue, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)(n * 3)), managedImage.Alpha, 0, n);
|
||||
break;
|
||||
|
||||
case 5: // RGBAB
|
||||
managedImage = new ManagedImage(marshalled.width, marshalled.height,
|
||||
ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha | ManagedImage.ImageChannels.Bump);
|
||||
Marshal.Copy(marshalled.decoded, managedImage.Red, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)n), managedImage.Green, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)(n * 2)), managedImage.Blue, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)(n * 3)), managedImage.Alpha, 0, n);
|
||||
Marshal.Copy((IntPtr)(marshalled.decoded.ToInt64() + (long)(n * 4)), managedImage.Bump, 0, n);
|
||||
break;
|
||||
|
||||
default:
|
||||
Logger.Log("Decoded image with unhandled number of components: " + marshalled.components,
|
||||
Helpers.LogLevel.Error);
|
||||
|
||||
if (IntPtr.Size == 8)
|
||||
DotNetFree64(ref marshalled);
|
||||
else
|
||||
DotNetFree(ref marshalled);
|
||||
|
||||
managedImage = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IntPtr.Size == 8)
|
||||
DotNetFree64(ref marshalled);
|
||||
else
|
||||
DotNetFree(ref marshalled);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="encoded"></param>
|
||||
/// <param name="layerInfo"></param>
|
||||
/// <param name="components"></param>
|
||||
/// <returns></returns>
|
||||
public static bool DecodeLayerBoundaries(byte[] encoded, out J2KLayerInfo[] layerInfo, out int components)
|
||||
{
|
||||
bool success = false;
|
||||
layerInfo = null;
|
||||
components = 0;
|
||||
MarshalledImage marshalled = new MarshalledImage();
|
||||
|
||||
// Allocate and copy to input buffer
|
||||
marshalled.length = encoded.Length;
|
||||
|
||||
lock (OpenJPEGLock)
|
||||
{
|
||||
if (IntPtr.Size == 8)
|
||||
DotNetAllocEncoded64(ref marshalled);
|
||||
else
|
||||
DotNetAllocEncoded(ref marshalled);
|
||||
|
||||
Marshal.Copy(encoded, 0, marshalled.encoded, encoded.Length);
|
||||
|
||||
// Run the decode
|
||||
bool decodeSuccess = (IntPtr.Size == 8) ? DotNetDecodeWithInfo64(ref marshalled) : DotNetDecodeWithInfo(ref marshalled);
|
||||
if (decodeSuccess)
|
||||
{
|
||||
components = marshalled.components;
|
||||
|
||||
// Sanity check
|
||||
if (marshalled.layers * marshalled.resolutions * marshalled.components == marshalled.packet_count)
|
||||
{
|
||||
// Manually marshal the array of opj_packet_info structs
|
||||
MarshalledPacket[] packets = new MarshalledPacket[marshalled.packet_count];
|
||||
int offset = 0;
|
||||
|
||||
for (int i = 0; i < marshalled.packet_count; i++)
|
||||
{
|
||||
MarshalledPacket packet;
|
||||
packet.start_pos = Marshal.ReadInt32(marshalled.packets, offset);
|
||||
offset += 4;
|
||||
packet.end_ph_pos = Marshal.ReadInt32(marshalled.packets, offset);
|
||||
offset += 4;
|
||||
packet.end_pos = Marshal.ReadInt32(marshalled.packets, offset);
|
||||
offset += 4;
|
||||
//double distortion = (double)Marshal.ReadInt64(marshalled.packets, offset);
|
||||
offset += 8;
|
||||
|
||||
packets[i] = packet;
|
||||
}
|
||||
|
||||
layerInfo = new J2KLayerInfo[marshalled.layers];
|
||||
|
||||
for (int i = 0; i < marshalled.layers; i++)
|
||||
{
|
||||
int packetsPerLayer = marshalled.packet_count / marshalled.layers;
|
||||
MarshalledPacket startPacket = packets[packetsPerLayer * i];
|
||||
MarshalledPacket endPacket = packets[(packetsPerLayer * (i + 1)) - 1];
|
||||
layerInfo[i].Start = startPacket.start_pos;
|
||||
layerInfo[i].End = endPacket.end_pos;
|
||||
}
|
||||
|
||||
// More sanity checking
|
||||
if (layerInfo.Length == 0 || layerInfo[layerInfo.Length - 1].End <= encoded.Length - 1)
|
||||
{
|
||||
success = true;
|
||||
|
||||
for (int i = 0; i < layerInfo.Length; i++)
|
||||
{
|
||||
if (layerInfo[i].Start >= layerInfo[i].End ||
|
||||
(i > 0 && layerInfo[i].Start <= layerInfo[i - 1].End))
|
||||
{
|
||||
System.Text.StringBuilder output = new System.Text.StringBuilder(
|
||||
"Inconsistent packet data in JPEG2000 stream:\n");
|
||||
for (int j = 0; j < layerInfo.Length; j++)
|
||||
output.AppendFormat("Layer {0}: Start: {1} End: {2}\n", j, layerInfo[j].Start, layerInfo[j].End);
|
||||
Logger.DebugLog(output.ToString());
|
||||
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
for (int i = 0; i < layerInfo.Length; i++)
|
||||
{
|
||||
if (i < layerInfo.Length - 1)
|
||||
layerInfo[i].End = layerInfo[i + 1].Start - 1;
|
||||
else
|
||||
layerInfo[i].End = marshalled.length;
|
||||
}
|
||||
|
||||
Logger.DebugLog("Corrected JPEG2000 packet data");
|
||||
success = true;
|
||||
|
||||
for (int i = 0; i < layerInfo.Length; i++)
|
||||
{
|
||||
if (layerInfo[i].Start >= layerInfo[i].End ||
|
||||
(i > 0 && layerInfo[i].Start <= layerInfo[i - 1].End))
|
||||
{
|
||||
System.Text.StringBuilder output = new System.Text.StringBuilder(
|
||||
"Still inconsistent packet data in JPEG2000 stream, giving up:\n");
|
||||
for (int j = 0; j < layerInfo.Length; j++)
|
||||
output.AppendFormat("Layer {0}: Start: {1} End: {2}\n", j, layerInfo[j].Start, layerInfo[j].End);
|
||||
Logger.DebugLog(output.ToString());
|
||||
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log(String.Format(
|
||||
"Last packet end in JPEG2000 stream extends beyond the end of the file. filesize={0} layerend={1}",
|
||||
encoded.Length, layerInfo[layerInfo.Length - 1].End), Helpers.LogLevel.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log(String.Format(
|
||||
"Packet count mismatch in JPEG2000 stream. layers={0} resolutions={1} components={2} packets={3}",
|
||||
marshalled.layers, marshalled.resolutions, marshalled.components, marshalled.packet_count),
|
||||
Helpers.LogLevel.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
if (IntPtr.Size == 8)
|
||||
DotNetFree64(ref marshalled);
|
||||
else
|
||||
DotNetFree(ref marshalled);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode a <seealso cref="System.Drawing.Bitmap"/> object into a byte array
|
||||
/// </summary>
|
||||
/// <param name="bitmap">The source <seealso cref="System.Drawing.Bitmap"/> object to encode</param>
|
||||
/// <param name="lossless">true to enable lossless decoding</param>
|
||||
/// <returns>A byte array containing the source Bitmap object</returns>
|
||||
public unsafe static byte[] EncodeFromImage(Bitmap bitmap, bool lossless)
|
||||
{
|
||||
BitmapData bd;
|
||||
ManagedImage decoded;
|
||||
|
||||
int bitmapWidth = bitmap.Width;
|
||||
int bitmapHeight = bitmap.Height;
|
||||
int pixelCount = bitmapWidth * bitmapHeight;
|
||||
int i;
|
||||
|
||||
if ((bitmap.PixelFormat & PixelFormat.Alpha) != 0 || (bitmap.PixelFormat & PixelFormat.PAlpha) != 0)
|
||||
{
|
||||
// Four layers, RGBA
|
||||
decoded = new ManagedImage(bitmapWidth, bitmapHeight,
|
||||
ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha);
|
||||
bd = bitmap.LockBits(new Rectangle(0, 0, bitmapWidth, bitmapHeight),
|
||||
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
byte* pixel = (byte*)bd.Scan0;
|
||||
|
||||
for (i = 0; i < pixelCount; i++)
|
||||
{
|
||||
// GDI+ gives us BGRA and we need to turn that in to RGBA
|
||||
decoded.Blue[i] = *(pixel++);
|
||||
decoded.Green[i] = *(pixel++);
|
||||
decoded.Red[i] = *(pixel++);
|
||||
decoded.Alpha[i] = *(pixel++);
|
||||
}
|
||||
}
|
||||
else if (bitmap.PixelFormat == PixelFormat.Format16bppGrayScale)
|
||||
{
|
||||
// One layer
|
||||
decoded = new ManagedImage(bitmapWidth, bitmapHeight,
|
||||
ManagedImage.ImageChannels.Color);
|
||||
bd = bitmap.LockBits(new Rectangle(0, 0, bitmapWidth, bitmapHeight),
|
||||
ImageLockMode.ReadOnly, PixelFormat.Format16bppGrayScale);
|
||||
byte* pixel = (byte*)bd.Scan0;
|
||||
|
||||
for (i = 0; i < pixelCount; i++)
|
||||
{
|
||||
// Normalize 16-bit data down to 8-bit
|
||||
ushort origVal = (byte)(*(pixel) + (*(pixel + 1) << 8));
|
||||
byte val = (byte)(((double)origVal / (double)UInt32.MaxValue) * (double)Byte.MaxValue);
|
||||
|
||||
decoded.Red[i] = val;
|
||||
decoded.Green[i] = val;
|
||||
decoded.Blue[i] = val;
|
||||
pixel += 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Three layers, RGB
|
||||
decoded = new ManagedImage(bitmapWidth, bitmapHeight,
|
||||
ManagedImage.ImageChannels.Color);
|
||||
bd = bitmap.LockBits(new Rectangle(0, 0, bitmapWidth, bitmapHeight),
|
||||
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
|
||||
byte* pixel = (byte*)bd.Scan0;
|
||||
|
||||
for (i = 0; i < pixelCount; i++)
|
||||
{
|
||||
decoded.Blue[i] = *(pixel++);
|
||||
decoded.Green[i] = *(pixel++);
|
||||
decoded.Red[i] = *(pixel++);
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.UnlockBits(bd);
|
||||
byte[] encoded = Encode(decoded, lossless);
|
||||
return encoded;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,640 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace OpenMetaverse.Imaging
|
||||
{
|
||||
#if !NO_UNSAFE
|
||||
|
||||
/// <summary>
|
||||
/// Capability to load TGAs to Bitmap
|
||||
/// </summary>
|
||||
public class LoadTGAClass
|
||||
{
|
||||
struct tgaColorMap
|
||||
{
|
||||
public ushort FirstEntryIndex;
|
||||
public ushort Length;
|
||||
public byte EntrySize;
|
||||
|
||||
public void Read(System.IO.BinaryReader br)
|
||||
{
|
||||
FirstEntryIndex = br.ReadUInt16();
|
||||
Length = br.ReadUInt16();
|
||||
EntrySize = br.ReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
struct tgaImageSpec
|
||||
{
|
||||
public ushort XOrigin;
|
||||
public ushort YOrigin;
|
||||
public ushort Width;
|
||||
public ushort Height;
|
||||
public byte PixelDepth;
|
||||
public byte Descriptor;
|
||||
|
||||
public void Read(System.IO.BinaryReader br)
|
||||
{
|
||||
XOrigin = br.ReadUInt16();
|
||||
YOrigin = br.ReadUInt16();
|
||||
Width = br.ReadUInt16();
|
||||
Height = br.ReadUInt16();
|
||||
PixelDepth = br.ReadByte();
|
||||
Descriptor = br.ReadByte();
|
||||
}
|
||||
|
||||
public byte AlphaBits
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)(Descriptor & 0xF);
|
||||
}
|
||||
set
|
||||
{
|
||||
Descriptor = (byte)((Descriptor & ~0xF) | (value & 0xF));
|
||||
}
|
||||
}
|
||||
|
||||
public bool BottomUp
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Descriptor & 0x20) == 0x20;
|
||||
}
|
||||
set
|
||||
{
|
||||
Descriptor = (byte)((Descriptor & ~0x20) | (value ? 0x20 : 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct tgaHeader
|
||||
{
|
||||
public byte IdLength;
|
||||
public byte ColorMapType;
|
||||
public byte ImageType;
|
||||
|
||||
public tgaColorMap ColorMap;
|
||||
public tgaImageSpec ImageSpec;
|
||||
|
||||
public void Read(System.IO.BinaryReader br)
|
||||
{
|
||||
this.IdLength = br.ReadByte();
|
||||
this.ColorMapType = br.ReadByte();
|
||||
this.ImageType = br.ReadByte();
|
||||
this.ColorMap = new tgaColorMap();
|
||||
this.ImageSpec = new tgaImageSpec();
|
||||
this.ColorMap.Read(br);
|
||||
this.ImageSpec.Read(br);
|
||||
}
|
||||
|
||||
public bool RleEncoded
|
||||
{
|
||||
get
|
||||
{
|
||||
return ImageType >= 9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct tgaCD
|
||||
{
|
||||
public uint RMask, GMask, BMask, AMask;
|
||||
public byte RShift, GShift, BShift, AShift;
|
||||
public uint FinalOr;
|
||||
public bool NeedNoConvert;
|
||||
}
|
||||
|
||||
static uint UnpackColor(
|
||||
uint sourceColor, ref tgaCD cd)
|
||||
{
|
||||
if (cd.RMask == 0xFF && cd.GMask == 0xFF && cd.BMask == 0xFF)
|
||||
{
|
||||
// Special case to deal with 8-bit TGA files that we treat as alpha masks
|
||||
return sourceColor << 24;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint rpermute = (sourceColor << cd.RShift) | (sourceColor >> (32 - cd.RShift));
|
||||
uint gpermute = (sourceColor << cd.GShift) | (sourceColor >> (32 - cd.GShift));
|
||||
uint bpermute = (sourceColor << cd.BShift) | (sourceColor >> (32 - cd.BShift));
|
||||
uint apermute = (sourceColor << cd.AShift) | (sourceColor >> (32 - cd.AShift));
|
||||
uint result =
|
||||
(rpermute & cd.RMask) | (gpermute & cd.GMask)
|
||||
| (bpermute & cd.BMask) | (apermute & cd.AMask) | cd.FinalOr;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static unsafe void decodeLine(
|
||||
System.Drawing.Imaging.BitmapData b,
|
||||
int line,
|
||||
int byp,
|
||||
byte[] data,
|
||||
ref tgaCD cd)
|
||||
{
|
||||
if (cd.NeedNoConvert)
|
||||
{
|
||||
// fast copy
|
||||
uint* linep = (uint*)((byte*)b.Scan0.ToPointer() + line * b.Stride);
|
||||
fixed (byte* ptr = data)
|
||||
{
|
||||
uint* sptr = (uint*)ptr;
|
||||
for (int i = 0; i < b.Width; ++i)
|
||||
{
|
||||
linep[i] = sptr[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
byte* linep = (byte*)b.Scan0.ToPointer() + line * b.Stride;
|
||||
|
||||
uint* up = (uint*)linep;
|
||||
|
||||
int rdi = 0;
|
||||
|
||||
fixed (byte* ptr = data)
|
||||
{
|
||||
for (int i = 0; i < b.Width; ++i)
|
||||
{
|
||||
uint x = 0;
|
||||
for (int j = 0; j < byp; ++j)
|
||||
{
|
||||
x |= ((uint)ptr[rdi]) << (j << 3);
|
||||
++rdi;
|
||||
}
|
||||
up[i] = UnpackColor(x, ref cd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void decodeRle(
|
||||
System.Drawing.Imaging.BitmapData b,
|
||||
int byp, tgaCD cd, System.IO.BinaryReader br, bool bottomUp)
|
||||
{
|
||||
try
|
||||
{
|
||||
int w = b.Width;
|
||||
// make buffer larger, so in case of emergency I can decode
|
||||
// over line ends.
|
||||
byte[] linebuffer = new byte[(w + 128) * byp];
|
||||
int maxindex = w * byp;
|
||||
int index = 0;
|
||||
|
||||
for (int j = 0; j < b.Height; ++j)
|
||||
{
|
||||
while (index < maxindex)
|
||||
{
|
||||
byte blocktype = br.ReadByte();
|
||||
|
||||
int bytestoread;
|
||||
int bytestocopy;
|
||||
|
||||
if (blocktype >= 0x80)
|
||||
{
|
||||
bytestoread = byp;
|
||||
bytestocopy = byp * (blocktype - 0x80);
|
||||
}
|
||||
else
|
||||
{
|
||||
bytestoread = byp * (blocktype + 1);
|
||||
bytestocopy = 0;
|
||||
}
|
||||
|
||||
//if (index + bytestoread > maxindex)
|
||||
// throw new System.ArgumentException ("Corrupt TGA");
|
||||
|
||||
br.Read(linebuffer, index, bytestoread);
|
||||
index += bytestoread;
|
||||
|
||||
for (int i = 0; i != bytestocopy; ++i)
|
||||
{
|
||||
linebuffer[index + i] = linebuffer[index + i - bytestoread];
|
||||
}
|
||||
index += bytestocopy;
|
||||
}
|
||||
if (!bottomUp)
|
||||
decodeLine(b, b.Height - j - 1, byp, linebuffer, ref cd);
|
||||
else
|
||||
decodeLine(b, j, byp, linebuffer, ref cd);
|
||||
|
||||
if (index > maxindex)
|
||||
{
|
||||
Array.Copy(linebuffer, maxindex, linebuffer, 0, index - maxindex);
|
||||
index -= maxindex;
|
||||
}
|
||||
else
|
||||
index = 0;
|
||||
|
||||
}
|
||||
}
|
||||
catch (System.IO.EndOfStreamException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
static void decodePlain(
|
||||
System.Drawing.Imaging.BitmapData b,
|
||||
int byp, tgaCD cd, System.IO.BinaryReader br, bool bottomUp)
|
||||
{
|
||||
int w = b.Width;
|
||||
byte[] linebuffer = new byte[w * byp];
|
||||
|
||||
for (int j = 0; j < b.Height; ++j)
|
||||
{
|
||||
br.Read(linebuffer, 0, w * byp);
|
||||
|
||||
if (!bottomUp)
|
||||
decodeLine(b, b.Height - j - 1, byp, linebuffer, ref cd);
|
||||
else
|
||||
decodeLine(b, j, byp, linebuffer, ref cd);
|
||||
}
|
||||
}
|
||||
|
||||
static void decodeStandard8(
|
||||
System.Drawing.Imaging.BitmapData b,
|
||||
tgaHeader hdr,
|
||||
System.IO.BinaryReader br)
|
||||
{
|
||||
tgaCD cd = new tgaCD();
|
||||
cd.RMask = 0x000000ff;
|
||||
cd.GMask = 0x000000ff;
|
||||
cd.BMask = 0x000000ff;
|
||||
cd.AMask = 0x000000ff;
|
||||
cd.RShift = 0;
|
||||
cd.GShift = 0;
|
||||
cd.BShift = 0;
|
||||
cd.AShift = 0;
|
||||
cd.FinalOr = 0x00000000;
|
||||
if (hdr.RleEncoded)
|
||||
decodeRle(b, 1, cd, br, hdr.ImageSpec.BottomUp);
|
||||
else
|
||||
decodePlain(b, 1, cd, br, hdr.ImageSpec.BottomUp);
|
||||
}
|
||||
|
||||
static void decodeSpecial16(
|
||||
System.Drawing.Imaging.BitmapData b, tgaHeader hdr, System.IO.BinaryReader br)
|
||||
{
|
||||
// i must convert the input stream to a sequence of uint values
|
||||
// which I then unpack.
|
||||
tgaCD cd = new tgaCD();
|
||||
cd.RMask = 0x00f00000;
|
||||
cd.GMask = 0x0000f000;
|
||||
cd.BMask = 0x000000f0;
|
||||
cd.AMask = 0xf0000000;
|
||||
cd.RShift = 12;
|
||||
cd.GShift = 8;
|
||||
cd.BShift = 4;
|
||||
cd.AShift = 16;
|
||||
cd.FinalOr = 0;
|
||||
|
||||
if (hdr.RleEncoded)
|
||||
decodeRle(b, 2, cd, br, hdr.ImageSpec.BottomUp);
|
||||
else
|
||||
decodePlain(b, 2, cd, br, hdr.ImageSpec.BottomUp);
|
||||
}
|
||||
|
||||
static void decodeStandard16(
|
||||
System.Drawing.Imaging.BitmapData b,
|
||||
tgaHeader hdr,
|
||||
System.IO.BinaryReader br)
|
||||
{
|
||||
// i must convert the input stream to a sequence of uint values
|
||||
// which I then unpack.
|
||||
tgaCD cd = new tgaCD();
|
||||
cd.RMask = 0x00f80000; // from 0xF800
|
||||
cd.GMask = 0x0000fc00; // from 0x07E0
|
||||
cd.BMask = 0x000000f8; // from 0x001F
|
||||
cd.AMask = 0x00000000;
|
||||
cd.RShift = 8;
|
||||
cd.GShift = 5;
|
||||
cd.BShift = 3;
|
||||
cd.AShift = 0;
|
||||
cd.FinalOr = 0xff000000;
|
||||
|
||||
if (hdr.RleEncoded)
|
||||
decodeRle(b, 2, cd, br, hdr.ImageSpec.BottomUp);
|
||||
else
|
||||
decodePlain(b, 2, cd, br, hdr.ImageSpec.BottomUp);
|
||||
}
|
||||
|
||||
|
||||
static void decodeSpecial24(System.Drawing.Imaging.BitmapData b,
|
||||
tgaHeader hdr, System.IO.BinaryReader br)
|
||||
{
|
||||
// i must convert the input stream to a sequence of uint values
|
||||
// which I then unpack.
|
||||
tgaCD cd = new tgaCD();
|
||||
cd.RMask = 0x00f80000;
|
||||
cd.GMask = 0x0000fc00;
|
||||
cd.BMask = 0x000000f8;
|
||||
cd.AMask = 0xff000000;
|
||||
cd.RShift = 8;
|
||||
cd.GShift = 5;
|
||||
cd.BShift = 3;
|
||||
cd.AShift = 8;
|
||||
cd.FinalOr = 0;
|
||||
|
||||
if (hdr.RleEncoded)
|
||||
decodeRle(b, 3, cd, br, hdr.ImageSpec.BottomUp);
|
||||
else
|
||||
decodePlain(b, 3, cd, br, hdr.ImageSpec.BottomUp);
|
||||
}
|
||||
|
||||
static void decodeStandard24(System.Drawing.Imaging.BitmapData b,
|
||||
tgaHeader hdr, System.IO.BinaryReader br)
|
||||
{
|
||||
// i must convert the input stream to a sequence of uint values
|
||||
// which I then unpack.
|
||||
tgaCD cd = new tgaCD();
|
||||
cd.RMask = 0x00ff0000;
|
||||
cd.GMask = 0x0000ff00;
|
||||
cd.BMask = 0x000000ff;
|
||||
cd.AMask = 0x00000000;
|
||||
cd.RShift = 0;
|
||||
cd.GShift = 0;
|
||||
cd.BShift = 0;
|
||||
cd.AShift = 0;
|
||||
cd.FinalOr = 0xff000000;
|
||||
|
||||
if (hdr.RleEncoded)
|
||||
decodeRle(b, 3, cd, br, hdr.ImageSpec.BottomUp);
|
||||
else
|
||||
decodePlain(b, 3, cd, br, hdr.ImageSpec.BottomUp);
|
||||
}
|
||||
|
||||
static void decodeStandard32(System.Drawing.Imaging.BitmapData b,
|
||||
tgaHeader hdr, System.IO.BinaryReader br)
|
||||
{
|
||||
// i must convert the input stream to a sequence of uint values
|
||||
// which I then unpack.
|
||||
tgaCD cd = new tgaCD();
|
||||
cd.RMask = 0x00ff0000;
|
||||
cd.GMask = 0x0000ff00;
|
||||
cd.BMask = 0x000000ff;
|
||||
cd.AMask = 0xff000000;
|
||||
cd.RShift = 0;
|
||||
cd.GShift = 0;
|
||||
cd.BShift = 0;
|
||||
cd.AShift = 0;
|
||||
cd.FinalOr = 0x00000000;
|
||||
cd.NeedNoConvert = true;
|
||||
|
||||
if (hdr.RleEncoded)
|
||||
decodeRle(b, 4, cd, br, hdr.ImageSpec.BottomUp);
|
||||
else
|
||||
decodePlain(b, 4, cd, br, hdr.ImageSpec.BottomUp);
|
||||
}
|
||||
|
||||
|
||||
public static System.Drawing.Size GetTGASize(string filename)
|
||||
{
|
||||
System.IO.FileStream f = System.IO.File.OpenRead(filename);
|
||||
|
||||
System.IO.BinaryReader br = new System.IO.BinaryReader(f);
|
||||
|
||||
tgaHeader header = new tgaHeader();
|
||||
header.Read(br);
|
||||
br.Close();
|
||||
|
||||
return new System.Drawing.Size(header.ImageSpec.Width, header.ImageSpec.Height);
|
||||
|
||||
}
|
||||
|
||||
public static System.Drawing.Bitmap LoadTGA(System.IO.Stream source)
|
||||
{
|
||||
byte[] buffer = new byte[source.Length];
|
||||
source.Read(buffer, 0, buffer.Length);
|
||||
|
||||
System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer);
|
||||
|
||||
using (System.IO.BinaryReader br = new System.IO.BinaryReader(ms))
|
||||
{
|
||||
tgaHeader header = new tgaHeader();
|
||||
header.Read(br);
|
||||
|
||||
if (header.ImageSpec.PixelDepth != 8 &&
|
||||
header.ImageSpec.PixelDepth != 16 &&
|
||||
header.ImageSpec.PixelDepth != 24 &&
|
||||
header.ImageSpec.PixelDepth != 32)
|
||||
throw new ArgumentException("Not a supported tga file.");
|
||||
|
||||
if (header.ImageSpec.AlphaBits > 8)
|
||||
throw new ArgumentException("Not a supported tga file.");
|
||||
|
||||
if (header.ImageSpec.Width > 4096 ||
|
||||
header.ImageSpec.Height > 4096)
|
||||
throw new ArgumentException("Image too large.");
|
||||
|
||||
System.Drawing.Bitmap b;
|
||||
System.Drawing.Imaging.BitmapData bd;
|
||||
|
||||
// Create a bitmap for the image.
|
||||
// Only include an alpha layer when the image requires one.
|
||||
if (header.ImageSpec.AlphaBits > 0 ||
|
||||
header.ImageSpec.PixelDepth == 8 || // Assume 8 bit images are alpha only
|
||||
header.ImageSpec.PixelDepth == 32) // Assume 32 bit images are ARGB
|
||||
{ // Image needs an alpha layer
|
||||
b = new System.Drawing.Bitmap(
|
||||
header.ImageSpec.Width,
|
||||
header.ImageSpec.Height,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
|
||||
bd = b.LockBits(new System.Drawing.Rectangle(0, 0, b.Width, b.Height),
|
||||
System.Drawing.Imaging.ImageLockMode.WriteOnly,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
|
||||
}
|
||||
else
|
||||
{ // Image does not need an alpha layer, so do not include one.
|
||||
b = new System.Drawing.Bitmap(
|
||||
header.ImageSpec.Width,
|
||||
header.ImageSpec.Height,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppRgb);
|
||||
|
||||
bd = b.LockBits(new System.Drawing.Rectangle(0, 0, b.Width, b.Height),
|
||||
System.Drawing.Imaging.ImageLockMode.WriteOnly,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppRgb);
|
||||
}
|
||||
|
||||
switch (header.ImageSpec.PixelDepth)
|
||||
{
|
||||
case 8:
|
||||
decodeStandard8(bd, header, br);
|
||||
break;
|
||||
case 16:
|
||||
if (header.ImageSpec.AlphaBits > 0)
|
||||
decodeSpecial16(bd, header, br);
|
||||
else
|
||||
decodeStandard16(bd, header, br);
|
||||
break;
|
||||
case 24:
|
||||
if (header.ImageSpec.AlphaBits > 0)
|
||||
decodeSpecial24(bd, header, br);
|
||||
else
|
||||
decodeStandard24(bd, header, br);
|
||||
break;
|
||||
case 32:
|
||||
decodeStandard32(bd, header, br);
|
||||
break;
|
||||
default:
|
||||
b.UnlockBits(bd);
|
||||
b.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
b.UnlockBits(bd);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe ManagedImage LoadTGAImage(System.IO.Stream source)
|
||||
{
|
||||
return LoadTGAImage(source, false);
|
||||
}
|
||||
|
||||
public static unsafe ManagedImage LoadTGAImage(System.IO.Stream source, bool mask)
|
||||
{
|
||||
byte[] buffer = new byte[source.Length];
|
||||
source.Read(buffer, 0, buffer.Length);
|
||||
|
||||
System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer);
|
||||
|
||||
using (System.IO.BinaryReader br = new System.IO.BinaryReader(ms))
|
||||
{
|
||||
tgaHeader header = new tgaHeader();
|
||||
header.Read(br);
|
||||
|
||||
if (header.ImageSpec.PixelDepth != 8 &&
|
||||
header.ImageSpec.PixelDepth != 16 &&
|
||||
header.ImageSpec.PixelDepth != 24 &&
|
||||
header.ImageSpec.PixelDepth != 32)
|
||||
throw new ArgumentException("Not a supported tga file.");
|
||||
|
||||
if (header.ImageSpec.AlphaBits > 8)
|
||||
throw new ArgumentException("Not a supported tga file.");
|
||||
|
||||
if (header.ImageSpec.Width > 4096 ||
|
||||
header.ImageSpec.Height > 4096)
|
||||
throw new ArgumentException("Image too large.");
|
||||
|
||||
byte[] decoded = new byte[header.ImageSpec.Width * header.ImageSpec.Height * 4];
|
||||
System.Drawing.Imaging.BitmapData bd = new System.Drawing.Imaging.BitmapData();
|
||||
|
||||
fixed (byte* pdecoded = &decoded[0])
|
||||
{
|
||||
bd.Width = header.ImageSpec.Width;
|
||||
bd.Height = header.ImageSpec.Height;
|
||||
bd.PixelFormat = System.Drawing.Imaging.PixelFormat.Format32bppPArgb;
|
||||
bd.Stride = header.ImageSpec.Width * 4;
|
||||
bd.Scan0 = (IntPtr)pdecoded;
|
||||
|
||||
switch (header.ImageSpec.PixelDepth)
|
||||
{
|
||||
case 8:
|
||||
decodeStandard8(bd, header, br);
|
||||
break;
|
||||
case 16:
|
||||
if (header.ImageSpec.AlphaBits > 0)
|
||||
decodeSpecial16(bd, header, br);
|
||||
else
|
||||
decodeStandard16(bd, header, br);
|
||||
break;
|
||||
case 24:
|
||||
if (header.ImageSpec.AlphaBits > 0)
|
||||
decodeSpecial24(bd, header, br);
|
||||
else
|
||||
decodeStandard24(bd, header, br);
|
||||
break;
|
||||
case 32:
|
||||
decodeStandard32(bd, header, br);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
int n = header.ImageSpec.Width * header.ImageSpec.Height;
|
||||
ManagedImage image;
|
||||
|
||||
if (mask && header.ImageSpec.AlphaBits == 0 && header.ImageSpec.PixelDepth == 8)
|
||||
{
|
||||
image = new ManagedImage(header.ImageSpec.Width, header.ImageSpec.Height,
|
||||
ManagedImage.ImageChannels.Alpha);
|
||||
int p = 3;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
image.Alpha[i] = decoded[p];
|
||||
p += 4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
image = new ManagedImage(header.ImageSpec.Width, header.ImageSpec.Height,
|
||||
ManagedImage.ImageChannels.Color | ManagedImage.ImageChannels.Alpha);
|
||||
int p = 0;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
image.Blue[i] = decoded[p++];
|
||||
image.Green[i] = decoded[p++];
|
||||
image.Red[i] = decoded[p++];
|
||||
image.Alpha[i] = decoded[p++];
|
||||
}
|
||||
}
|
||||
|
||||
br.Close();
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
public static System.Drawing.Bitmap LoadTGA(string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (System.IO.FileStream f = System.IO.File.OpenRead(filename))
|
||||
{
|
||||
return LoadTGA(f);
|
||||
}
|
||||
}
|
||||
catch (System.IO.DirectoryNotFoundException)
|
||||
{
|
||||
return null; // file not found
|
||||
}
|
||||
catch (System.IO.FileNotFoundException)
|
||||
{
|
||||
return null; // file not found
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,714 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Drawing;
|
||||
using System.Xml.Serialization;
|
||||
using OpenMetaverse.ImportExport.Collada14;
|
||||
using OpenMetaverse.Rendering;
|
||||
using OpenMetaverse.Imaging;
|
||||
|
||||
namespace OpenMetaverse.ImportExport
|
||||
{
|
||||
/// <summary>
|
||||
/// Parsing Collada model files into data structures
|
||||
/// </summary>
|
||||
public class ColladaLoader
|
||||
{
|
||||
COLLADA Model;
|
||||
static XmlSerializer Serializer = null;
|
||||
List<Node> Nodes;
|
||||
List<ModelMaterial> Materials;
|
||||
Dictionary<string, string> MatSymTarget;
|
||||
string FileName;
|
||||
|
||||
class Node
|
||||
{
|
||||
public Matrix4 Transform = Matrix4.Identity;
|
||||
public string Name;
|
||||
public string ID;
|
||||
public string MeshID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses Collada document
|
||||
/// </summary>
|
||||
/// <param name="filename">Load .dae model from this file</param>
|
||||
/// <param name="loadImages">Load and decode images for uploading with model</param>
|
||||
/// <returns>A list of mesh prims that were parsed from the collada file</returns>
|
||||
public List<ModelPrim> Load(string filename, bool loadImages)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Create an instance of the XmlSerializer specifying type and namespace.
|
||||
if (Serializer == null)
|
||||
{
|
||||
Serializer = new XmlSerializer(typeof(COLLADA));
|
||||
}
|
||||
|
||||
this.FileName = filename;
|
||||
|
||||
// A FileStream is needed to read the XML document.
|
||||
FileStream fs = new FileStream(filename, FileMode.Open);
|
||||
XmlReader reader = XmlReader.Create(fs);
|
||||
Model = (COLLADA)Serializer.Deserialize(reader);
|
||||
fs.Close();
|
||||
var prims = Parse();
|
||||
if (loadImages)
|
||||
{
|
||||
LoadImages(prims);
|
||||
}
|
||||
return prims;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Failed parsing collada file: " + ex.Message, Helpers.LogLevel.Error, ex);
|
||||
return new List<ModelPrim>();
|
||||
}
|
||||
}
|
||||
|
||||
void LoadImages(List<ModelPrim> prims)
|
||||
{
|
||||
foreach (var prim in prims)
|
||||
{
|
||||
foreach (var face in prim.Faces)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(face.Material.Texture))
|
||||
{
|
||||
LoadImage(face.Material);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadImage(ModelMaterial material)
|
||||
{
|
||||
var fname = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(FileName), material.Texture);
|
||||
|
||||
try
|
||||
{
|
||||
string ext = System.IO.Path.GetExtension(material.Texture).ToLower();
|
||||
|
||||
Bitmap bitmap = null;
|
||||
|
||||
if (ext == ".jp2" || ext == ".j2c")
|
||||
{
|
||||
material.TextureData = File.ReadAllBytes(fname);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ext == ".tga")
|
||||
{
|
||||
bitmap = LoadTGAClass.LoadTGA(fname);
|
||||
}
|
||||
else
|
||||
{
|
||||
bitmap = (Bitmap)Image.FromFile(fname);
|
||||
}
|
||||
|
||||
int width = bitmap.Width;
|
||||
int height = bitmap.Height;
|
||||
|
||||
// Handle resizing to prevent excessively large images and irregular dimensions
|
||||
if (!IsPowerOfTwo((uint)width) || !IsPowerOfTwo((uint)height) || width > 1024 || height > 1024)
|
||||
{
|
||||
var origWidth = width;
|
||||
var origHieght = height;
|
||||
|
||||
width = ClosestPowerOwTwo(width);
|
||||
height = ClosestPowerOwTwo(height);
|
||||
|
||||
width = width > 1024 ? 1024 : width;
|
||||
height = height > 1024 ? 1024 : height;
|
||||
|
||||
Logger.Log("Image has irregular dimensions " + origWidth + "x" + origHieght + ". Resizing to " + width + "x" + height, Helpers.LogLevel.Info);
|
||||
|
||||
Bitmap resized = new Bitmap(width, height, bitmap.PixelFormat);
|
||||
Graphics graphics = Graphics.FromImage(resized);
|
||||
|
||||
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
|
||||
graphics.InterpolationMode =
|
||||
System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
||||
graphics.DrawImage(bitmap, 0, 0, width, height);
|
||||
|
||||
bitmap.Dispose();
|
||||
bitmap = resized;
|
||||
}
|
||||
|
||||
material.TextureData = OpenJPEG.EncodeFromImage(bitmap, false);
|
||||
|
||||
Logger.Log("Successfully encoded " + fname, Helpers.LogLevel.Info);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log("Failed loading " + fname + ": " + ex.Message, Helpers.LogLevel.Warning);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool IsPowerOfTwo(uint n)
|
||||
{
|
||||
return (n & (n - 1)) == 0 && n != 0;
|
||||
}
|
||||
|
||||
int ClosestPowerOwTwo(int n)
|
||||
{
|
||||
int res = 1;
|
||||
|
||||
while (res < n)
|
||||
{
|
||||
res <<= 1;
|
||||
}
|
||||
|
||||
return res > 1 ? res / 2 : 1;
|
||||
}
|
||||
|
||||
ModelMaterial ExtractMaterial(object diffuse)
|
||||
{
|
||||
ModelMaterial ret = new ModelMaterial();
|
||||
if (diffuse is common_color_or_texture_typeColor)
|
||||
{
|
||||
var col = (common_color_or_texture_typeColor)diffuse;
|
||||
ret.DiffuseColor = new Color4((float)col.Values[0], (float)col.Values[1], (float)col.Values[2], (float)col.Values[3]);
|
||||
}
|
||||
else if (diffuse is common_color_or_texture_typeTexture)
|
||||
{
|
||||
var tex = (common_color_or_texture_typeTexture)diffuse;
|
||||
ret.Texture = tex.texcoord;
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void ParseMaterials()
|
||||
{
|
||||
|
||||
if (Model == null) return;
|
||||
|
||||
Materials = new List<ModelMaterial>();
|
||||
|
||||
// Material -> effect mapping
|
||||
Dictionary<string, string> matEffect = new Dictionary<string, string>();
|
||||
List<ModelMaterial> tmpEffects = new List<ModelMaterial>();
|
||||
|
||||
// Image ID -> filename mapping
|
||||
Dictionary<string, string> imgMap = new Dictionary<string, string>();
|
||||
|
||||
foreach (var item in Model.Items)
|
||||
{
|
||||
if (item is library_images)
|
||||
{
|
||||
var images = (library_images)item;
|
||||
if (images.image != null)
|
||||
{
|
||||
foreach (var image in images.image)
|
||||
{
|
||||
var img = (image)image;
|
||||
string ID = img.id;
|
||||
if (img.Item is string)
|
||||
{
|
||||
imgMap[ID] = (string)img.Item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in Model.Items)
|
||||
{
|
||||
if (item is library_materials)
|
||||
{
|
||||
var materials = (library_materials)item;
|
||||
if (materials.material != null)
|
||||
{
|
||||
foreach (var material in materials.material)
|
||||
{
|
||||
var ID = material.id;
|
||||
if (material.instance_effect != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(material.instance_effect.url))
|
||||
{
|
||||
matEffect[material.instance_effect.url.Substring(1)] = ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in Model.Items)
|
||||
{
|
||||
if (item is library_effects)
|
||||
{
|
||||
var effects = (library_effects)item;
|
||||
if (effects.effect != null)
|
||||
{
|
||||
foreach (var effect in effects.effect)
|
||||
{
|
||||
string ID = effect.id;
|
||||
foreach (var effItem in effect.Items)
|
||||
{
|
||||
if (effItem is effectFx_profile_abstractProfile_COMMON)
|
||||
{
|
||||
var teq = ((effectFx_profile_abstractProfile_COMMON)effItem).technique;
|
||||
if (teq != null)
|
||||
{
|
||||
if (teq.Item is effectFx_profile_abstractProfile_COMMONTechniquePhong)
|
||||
{
|
||||
var shader = (effectFx_profile_abstractProfile_COMMONTechniquePhong)teq.Item;
|
||||
if (shader.diffuse != null)
|
||||
{
|
||||
var material = ExtractMaterial(shader.diffuse.Item);
|
||||
material.ID = ID;
|
||||
tmpEffects.Add(material);
|
||||
}
|
||||
}
|
||||
else if (teq.Item is effectFx_profile_abstractProfile_COMMONTechniqueLambert)
|
||||
{
|
||||
var shader = (effectFx_profile_abstractProfile_COMMONTechniqueLambert)teq.Item;
|
||||
if (shader.diffuse != null)
|
||||
{
|
||||
var material = ExtractMaterial(shader.diffuse.Item);
|
||||
material.ID = ID;
|
||||
tmpEffects.Add(material);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var effect in tmpEffects)
|
||||
{
|
||||
if (matEffect.ContainsKey(effect.ID))
|
||||
{
|
||||
effect.ID = matEffect[effect.ID];
|
||||
if (!string.IsNullOrEmpty(effect.Texture))
|
||||
{
|
||||
if (imgMap.ContainsKey(effect.Texture))
|
||||
{
|
||||
effect.Texture = imgMap[effect.Texture];
|
||||
}
|
||||
}
|
||||
Materials.Add(effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessNode(node node)
|
||||
{
|
||||
Node n = new Node();
|
||||
n.ID = node.id;
|
||||
|
||||
if (node.Items != null)
|
||||
// Try finding matrix
|
||||
foreach (var i in node.Items)
|
||||
{
|
||||
if (i is matrix)
|
||||
{
|
||||
var m = (matrix)i;
|
||||
for (int a = 0; a < 4; a++)
|
||||
for (int b = 0; b < 4; b++)
|
||||
{
|
||||
n.Transform[b, a] = (float)m.Values[a * 4 + b];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find geometry and material
|
||||
if (node.instance_geometry != null && node.instance_geometry.Length > 0)
|
||||
{
|
||||
var instGeom = node.instance_geometry[0];
|
||||
if (!string.IsNullOrEmpty(instGeom.url))
|
||||
{
|
||||
n.MeshID = instGeom.url.Substring(1);
|
||||
}
|
||||
if (instGeom.bind_material != null && instGeom.bind_material.technique_common != null)
|
||||
{
|
||||
foreach (var teq in instGeom.bind_material.technique_common)
|
||||
{
|
||||
var target = teq.target;
|
||||
if (!string.IsNullOrEmpty(target))
|
||||
{
|
||||
target = target.Substring(1);
|
||||
MatSymTarget[teq.symbol] = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node.Items != null && node.instance_geometry != null && node.instance_geometry.Length > 0)
|
||||
Nodes.Add(n);
|
||||
|
||||
// Recurse if the scene is hierarchical
|
||||
if (node.node1 != null)
|
||||
foreach (node nd in node.node1)
|
||||
ProcessNode(nd);
|
||||
}
|
||||
|
||||
void ParseVisualScene()
|
||||
{
|
||||
Nodes = new List<Node>();
|
||||
if (Model == null) return;
|
||||
|
||||
MatSymTarget = new Dictionary<string, string>();
|
||||
|
||||
foreach (var item in Model.Items)
|
||||
{
|
||||
if (item is library_visual_scenes)
|
||||
{
|
||||
var scene = ((library_visual_scenes)item).visual_scene[0];
|
||||
foreach (var node in scene.node)
|
||||
{
|
||||
ProcessNode(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<ModelPrim> Parse()
|
||||
{
|
||||
var Prims = new List<ModelPrim>();
|
||||
|
||||
float DEG_TO_RAD = 0.017453292519943295769236907684886f;
|
||||
|
||||
if (Model == null) return Prims;
|
||||
|
||||
Matrix4 transform = Matrix4.Identity;
|
||||
|
||||
UpAxisType upAxis = UpAxisType.Y_UP;
|
||||
|
||||
var asset = Model.asset;
|
||||
if (asset != null)
|
||||
{
|
||||
upAxis = asset.up_axis;
|
||||
if (asset.unit != null)
|
||||
{
|
||||
float meter = (float)asset.unit.meter;
|
||||
transform[0, 0] = meter;
|
||||
transform[1, 1] = meter;
|
||||
transform[2, 2] = meter;
|
||||
}
|
||||
}
|
||||
|
||||
Matrix4 rotation = Matrix4.Identity;
|
||||
|
||||
if (upAxis == UpAxisType.X_UP)
|
||||
{
|
||||
rotation = Matrix4.CreateFromEulers(0.0f, 90.0f * DEG_TO_RAD, 0.0f);
|
||||
}
|
||||
else if (upAxis == UpAxisType.Y_UP)
|
||||
{
|
||||
rotation = Matrix4.CreateFromEulers(90.0f * DEG_TO_RAD, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
rotation = rotation * transform;
|
||||
transform = rotation;
|
||||
|
||||
ParseVisualScene();
|
||||
ParseMaterials();
|
||||
|
||||
foreach (var item in Model.Items)
|
||||
{
|
||||
if (item is library_geometries)
|
||||
{
|
||||
var geometries = (library_geometries)item;
|
||||
foreach (var geo in geometries.geometry)
|
||||
{
|
||||
var mesh = geo.Item as mesh;
|
||||
if (mesh == null) continue;
|
||||
|
||||
var nodes = Nodes.FindAll(n => n.MeshID == geo.id);
|
||||
if (nodes != null)
|
||||
{
|
||||
byte[] mesh_asset = null;
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var prim = new ModelPrim();
|
||||
prim.ID = node.ID;
|
||||
Prims.Add(prim);
|
||||
Matrix4 primTransform = transform;
|
||||
primTransform = primTransform * node.Transform;
|
||||
|
||||
AddPositions(mesh, prim, primTransform);
|
||||
|
||||
foreach (var mitem in mesh.Items)
|
||||
{
|
||||
if (mitem is triangles)
|
||||
{
|
||||
AddFacesFromPolyList(Triangles2Polylist((triangles)mitem), mesh, prim, primTransform);
|
||||
}
|
||||
if (mitem is polylist)
|
||||
{
|
||||
AddFacesFromPolyList((polylist)mitem, mesh, prim, primTransform);
|
||||
}
|
||||
}
|
||||
|
||||
if (mesh_asset == null)
|
||||
{
|
||||
prim.CreateAsset(UUID.Zero);
|
||||
mesh_asset = prim.Asset;
|
||||
}
|
||||
else
|
||||
prim.Asset = mesh_asset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Prims;
|
||||
}
|
||||
|
||||
source FindSource(source[] sources, string id)
|
||||
{
|
||||
id = id.Substring(1);
|
||||
|
||||
foreach (var src in sources)
|
||||
{
|
||||
if (src.id == id)
|
||||
return src;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void AddPositions(mesh mesh, ModelPrim prim, Matrix4 transform)
|
||||
{
|
||||
prim.Positions = new List<Vector3>();
|
||||
source posSrc = FindSource(mesh.source, mesh.vertices.input[0].source);
|
||||
double[] posVals = ((float_array)posSrc.Item).Values;
|
||||
|
||||
for (int i = 0; i < posVals.Length / 3; i++)
|
||||
{
|
||||
Vector3 pos = new Vector3((float)posVals[i * 3], (float)posVals[i * 3 + 1], (float)posVals[i * 3 + 2]);
|
||||
pos = Vector3.Transform(pos, transform);
|
||||
prim.Positions.Add(pos);
|
||||
}
|
||||
|
||||
prim.BoundMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
|
||||
prim.BoundMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
|
||||
|
||||
foreach (var pos in prim.Positions)
|
||||
{
|
||||
if (pos.X > prim.BoundMax.X) prim.BoundMax.X = pos.X;
|
||||
if (pos.Y > prim.BoundMax.Y) prim.BoundMax.Y = pos.Y;
|
||||
if (pos.Z > prim.BoundMax.Z) prim.BoundMax.Z = pos.Z;
|
||||
|
||||
if (pos.X < prim.BoundMin.X) prim.BoundMin.X = pos.X;
|
||||
if (pos.Y < prim.BoundMin.Y) prim.BoundMin.Y = pos.Y;
|
||||
if (pos.Z < prim.BoundMin.Z) prim.BoundMin.Z = pos.Z;
|
||||
}
|
||||
|
||||
prim.Scale = prim.BoundMax - prim.BoundMin;
|
||||
prim.Position = prim.BoundMin + (prim.Scale / 2);
|
||||
|
||||
// Fit vertex positions into identity cube -0.5 .. 0.5
|
||||
for (int i = 0; i < prim.Positions.Count; i++)
|
||||
{
|
||||
Vector3 pos = prim.Positions[i];
|
||||
pos = new Vector3(
|
||||
prim.Scale.X == 0 ? 0 : ((pos.X - prim.BoundMin.X) / prim.Scale.X) - 0.5f,
|
||||
prim.Scale.Y == 0 ? 0 : ((pos.Y - prim.BoundMin.Y) / prim.Scale.Y) - 0.5f,
|
||||
prim.Scale.Z == 0 ? 0 : ((pos.Z - prim.BoundMin.Z) / prim.Scale.Z) - 0.5f
|
||||
);
|
||||
prim.Positions[i] = pos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int[] StrToArray(string s)
|
||||
{
|
||||
string[] vals = Regex.Split(s.Trim(), @"\s+");
|
||||
int[] ret = new int[vals.Length];
|
||||
|
||||
for (int i = 0; i < ret.Length; i++)
|
||||
{
|
||||
int.TryParse(vals[i], out ret[i]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AddFacesFromPolyList(polylist list, mesh mesh, ModelPrim prim, Matrix4 transform)
|
||||
{
|
||||
string material = list.material;
|
||||
source posSrc = null;
|
||||
source normalSrc = null;
|
||||
source uvSrc = null;
|
||||
|
||||
ulong stride = 0;
|
||||
int posOffset = -1;
|
||||
int norOffset = -1;
|
||||
int uvOffset = -1;
|
||||
|
||||
foreach (var inp in list.input)
|
||||
{
|
||||
stride = Math.Max(stride, inp.offset);
|
||||
|
||||
if (inp.semantic == "VERTEX")
|
||||
{
|
||||
posSrc = FindSource(mesh.source, mesh.vertices.input[0].source);
|
||||
posOffset = (int)inp.offset;
|
||||
}
|
||||
else if (inp.semantic == "NORMAL")
|
||||
{
|
||||
normalSrc = FindSource(mesh.source, inp.source);
|
||||
norOffset = (int)inp.offset;
|
||||
}
|
||||
else if (inp.semantic == "TEXCOORD")
|
||||
{
|
||||
uvSrc = FindSource(mesh.source, inp.source);
|
||||
uvOffset = (int)inp.offset;
|
||||
}
|
||||
}
|
||||
|
||||
stride += 1;
|
||||
|
||||
if (posSrc == null) return;
|
||||
|
||||
var vcount = StrToArray(list.vcount);
|
||||
var idx = StrToArray(list.p);
|
||||
|
||||
Vector3[] normals = null;
|
||||
if (normalSrc != null)
|
||||
{
|
||||
var norVal = ((float_array)normalSrc.Item).Values;
|
||||
normals = new Vector3[norVal.Length / 3];
|
||||
|
||||
for (int i = 0; i < normals.Length; i++)
|
||||
{
|
||||
normals[i] = new Vector3((float)norVal[i * 3 + 0], (float)norVal[i * 3 + 1], (float)norVal[i * 3 + 2]);
|
||||
normals[i] = Vector3.TransformNormal(normals[i], transform);
|
||||
normals[i].Normalize();
|
||||
}
|
||||
}
|
||||
|
||||
Vector2[] uvs = null;
|
||||
if (uvSrc != null)
|
||||
{
|
||||
var uvVal = ((float_array)uvSrc.Item).Values;
|
||||
uvs = new Vector2[uvVal.Length / 2];
|
||||
|
||||
for (int i = 0; i < uvs.Length; i++)
|
||||
{
|
||||
uvs[i] = new Vector2((float)uvVal[i * 2 + 0], (float)uvVal[i * 2 + 1]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ModelFace face = new ModelFace();
|
||||
face.MaterialID = list.material;
|
||||
if (face.MaterialID != null)
|
||||
{
|
||||
if (MatSymTarget.ContainsKey(list.material))
|
||||
{
|
||||
ModelMaterial mat = Materials.Find(m => m.ID == MatSymTarget[list.material]);
|
||||
if (mat != null)
|
||||
{
|
||||
face.Material = mat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int curIdx = 0;
|
||||
|
||||
for (int i = 0; i < vcount.Length; i++)
|
||||
{
|
||||
var nvert = vcount[i];
|
||||
if (nvert < 3 || nvert > 4)
|
||||
{
|
||||
throw new InvalidDataException("Only triangles and quads supported");
|
||||
}
|
||||
|
||||
Vertex[] verts = new Vertex[nvert];
|
||||
for (int j = 0; j < nvert; j++)
|
||||
{
|
||||
verts[j].Position = prim.Positions[idx[curIdx + posOffset + (int)stride * j]];
|
||||
|
||||
if (normals != null)
|
||||
{
|
||||
verts[j].Normal = normals[idx[curIdx + norOffset + (int)stride * j]];
|
||||
}
|
||||
|
||||
if (uvs != null)
|
||||
{
|
||||
verts[j].TexCoord = uvs[idx[curIdx + uvOffset + (int)stride * j]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (nvert == 3) // add the triangle
|
||||
{
|
||||
face.AddVertex(verts[0]);
|
||||
face.AddVertex(verts[1]);
|
||||
face.AddVertex(verts[2]);
|
||||
}
|
||||
else if (nvert == 4) // quad, add two triangles
|
||||
{
|
||||
face.AddVertex(verts[0]);
|
||||
face.AddVertex(verts[1]);
|
||||
face.AddVertex(verts[2]);
|
||||
|
||||
face.AddVertex(verts[0]);
|
||||
face.AddVertex(verts[2]);
|
||||
face.AddVertex(verts[3]);
|
||||
}
|
||||
|
||||
curIdx += (int)stride * nvert;
|
||||
}
|
||||
|
||||
prim.Faces.Add(face);
|
||||
|
||||
|
||||
}
|
||||
|
||||
polylist Triangles2Polylist(triangles triangles)
|
||||
{
|
||||
polylist poly = new polylist();
|
||||
poly.count = triangles.count;
|
||||
poly.input = triangles.input;
|
||||
poly.material = triangles.material;
|
||||
poly.name = triangles.name;
|
||||
poly.p = triangles.p;
|
||||
|
||||
string str = "3 ";
|
||||
System.Text.StringBuilder builder = new System.Text.StringBuilder(str.Length * (int)poly.count);
|
||||
for (int i = 0; i < (int)poly.count; i++) builder.Append(str);
|
||||
poly.vcount = builder.ToString();
|
||||
|
||||
return poly;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenMetaverse.Rendering;
|
||||
using OpenMetaverse.StructuredData;
|
||||
|
||||
namespace OpenMetaverse.ImportExport
|
||||
{
|
||||
|
||||
public class ModelMaterial
|
||||
{
|
||||
public string ID;
|
||||
public Color4 DiffuseColor = Color4.White;
|
||||
public string Texture;
|
||||
public byte[] TextureData;
|
||||
}
|
||||
|
||||
public class ModelFace
|
||||
{
|
||||
public List<Vertex> Vertices = new List<Vertex>();
|
||||
public List<uint> Indices = new List<uint>();
|
||||
public string MaterialID = string.Empty;
|
||||
public ModelMaterial Material = new ModelMaterial();
|
||||
|
||||
Dictionary<Vertex, int> LookUp = new Dictionary<Vertex, int>();
|
||||
|
||||
public void AddVertex(Vertex v)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (LookUp.ContainsKey(v))
|
||||
{
|
||||
index = LookUp[v];
|
||||
}
|
||||
else
|
||||
{
|
||||
index = Vertices.Count;
|
||||
Vertices.Add(v);
|
||||
LookUp[v] = index;
|
||||
}
|
||||
|
||||
Indices.Add((uint)index);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class ModelPrim
|
||||
{
|
||||
public List<Vector3> Positions;
|
||||
public Vector3 BoundMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
|
||||
public Vector3 BoundMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
|
||||
public Vector3 Position;
|
||||
public Vector3 Scale;
|
||||
public Quaternion Rotation = Quaternion.Identity;
|
||||
public List<ModelFace> Faces = new List<ModelFace>();
|
||||
public string ID;
|
||||
public byte[] Asset;
|
||||
|
||||
public void CreateAsset(UUID creator)
|
||||
{
|
||||
OSDMap header = new OSDMap();
|
||||
header["version"] = 1;
|
||||
header["creator"] = creator;
|
||||
header["date"] = DateTime.Now;
|
||||
|
||||
OSDArray faces = new OSDArray();
|
||||
foreach (var face in Faces)
|
||||
{
|
||||
OSDMap faceMap = new OSDMap();
|
||||
|
||||
// Find UV min/max
|
||||
Vector2 uvMin = new Vector2(float.MaxValue, float.MaxValue);
|
||||
Vector2 uvMax = new Vector2(float.MinValue, float.MinValue);
|
||||
foreach (var v in face.Vertices)
|
||||
{
|
||||
if (v.TexCoord.X < uvMin.X) uvMin.X = v.TexCoord.X;
|
||||
if (v.TexCoord.Y < uvMin.Y) uvMin.Y = v.TexCoord.Y;
|
||||
|
||||
if (v.TexCoord.X > uvMax.X) uvMax.X = v.TexCoord.X;
|
||||
if (v.TexCoord.Y > uvMax.Y) uvMax.Y = v.TexCoord.Y;
|
||||
}
|
||||
OSDMap uvDomain = new OSDMap();
|
||||
uvDomain["Min"] = uvMin;
|
||||
uvDomain["Max"] = uvMax;
|
||||
faceMap["TexCoord0Domain"] = uvDomain;
|
||||
|
||||
|
||||
OSDMap positionDomain = new OSDMap();
|
||||
positionDomain["Min"] = new Vector3(-0.5f, -0.5f, -0.5f);
|
||||
positionDomain["Max"] = new Vector3(0.5f, 0.5f, 0.5f);
|
||||
faceMap["PositionDomain"] = positionDomain;
|
||||
|
||||
List<byte> posBytes = new List<byte>(face.Vertices.Count * sizeof(UInt16) * 3);
|
||||
List<byte> norBytes = new List<byte>(face.Vertices.Count * sizeof(UInt16) * 3);
|
||||
List<byte> uvBytes = new List<byte>(face.Vertices.Count * sizeof(UInt16) * 2);
|
||||
|
||||
foreach (var v in face.Vertices)
|
||||
{
|
||||
posBytes.AddRange(Utils.UInt16ToBytes(Utils.FloatToUInt16(v.Position.X, -0.5f, 0.5f)));
|
||||
posBytes.AddRange(Utils.UInt16ToBytes(Utils.FloatToUInt16(v.Position.Y, -0.5f, 0.5f)));
|
||||
posBytes.AddRange(Utils.UInt16ToBytes(Utils.FloatToUInt16(v.Position.Z, -0.5f, 0.5f)));
|
||||
|
||||
norBytes.AddRange(Utils.UInt16ToBytes(Utils.FloatToUInt16(v.Normal.X, -1f, 1f)));
|
||||
norBytes.AddRange(Utils.UInt16ToBytes(Utils.FloatToUInt16(v.Normal.Y, -1f, 1f)));
|
||||
norBytes.AddRange(Utils.UInt16ToBytes(Utils.FloatToUInt16(v.Normal.Z, -1f, 1f)));
|
||||
|
||||
uvBytes.AddRange(Utils.UInt16ToBytes(Utils.FloatToUInt16(v.TexCoord.X, uvMin.X, uvMax.X)));
|
||||
uvBytes.AddRange(Utils.UInt16ToBytes(Utils.FloatToUInt16(v.TexCoord.Y, uvMin.Y, uvMax.Y)));
|
||||
}
|
||||
|
||||
faceMap["Position"] = posBytes.ToArray();
|
||||
faceMap["Normal"] = norBytes.ToArray();
|
||||
faceMap["TexCoord0"] = uvBytes.ToArray();
|
||||
|
||||
List<byte> indexBytes = new List<byte>(face.Indices.Count * sizeof(UInt16));
|
||||
foreach (var t in face.Indices)
|
||||
{
|
||||
indexBytes.AddRange(Utils.UInt16ToBytes((ushort)t));
|
||||
}
|
||||
faceMap["TriangleList"] = indexBytes.ToArray();
|
||||
|
||||
faces.Add(faceMap);
|
||||
}
|
||||
|
||||
byte[] physicStubBytes = Helpers.ZCompressOSD(PhysicsStub());
|
||||
|
||||
byte[] meshBytes = Helpers.ZCompressOSD(faces);
|
||||
int n = 0;
|
||||
|
||||
OSDMap lodParms = new OSDMap();
|
||||
lodParms["offset"] = n;
|
||||
lodParms["size"] = meshBytes.Length;
|
||||
header["high_lod"] = lodParms;
|
||||
n += meshBytes.Length;
|
||||
|
||||
lodParms = new OSDMap();
|
||||
lodParms["offset"] = n;
|
||||
lodParms["size"] = physicStubBytes.Length;
|
||||
header["physics_convex"] = lodParms;
|
||||
n += physicStubBytes.Length;
|
||||
|
||||
byte[] headerBytes = OSDParser.SerializeLLSDBinary(header, false);
|
||||
n += headerBytes.Length;
|
||||
|
||||
Asset = new byte[n];
|
||||
|
||||
int offset = 0;
|
||||
Buffer.BlockCopy(headerBytes, 0, Asset, offset, headerBytes.Length);
|
||||
offset += headerBytes.Length;
|
||||
|
||||
Buffer.BlockCopy(meshBytes, 0, Asset, offset, meshBytes.Length);
|
||||
offset += meshBytes.Length;
|
||||
|
||||
Buffer.BlockCopy(physicStubBytes, 0, Asset, offset, physicStubBytes.Length);
|
||||
offset += physicStubBytes.Length;
|
||||
|
||||
}
|
||||
|
||||
public static OSD PhysicsStub()
|
||||
{
|
||||
OSDMap ret = new OSDMap();
|
||||
ret["Max"] = new Vector3(0.5f, 0.5f, 0.5f);
|
||||
ret["Min"] = new Vector3(-0.5f, -0.5f, -0.5f);
|
||||
ret["BoundingVerts"] = new byte[] { 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 127, 0, 0, 255, 255, 255, 127, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 127, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255 };
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
using OpenMetaverse.StructuredData;
|
||||
using OpenMetaverse.Http;
|
||||
|
||||
namespace OpenMetaverse.ImportExport
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements mesh upload communications with the simulator
|
||||
/// </summary>
|
||||
public class ModelUploader
|
||||
{
|
||||
/// <summary>
|
||||
/// Inlcude stub convex hull physics, required for uploading to Second Life
|
||||
/// </summary>
|
||||
public bool IncludePhysicsStub;
|
||||
|
||||
/// <summary>
|
||||
/// Use the same mesh used for geometry as the physical mesh upload
|
||||
/// </summary>
|
||||
public bool UseModelAsPhysics;
|
||||
|
||||
GridClient Client;
|
||||
List<ModelPrim> Prims;
|
||||
|
||||
/// <summary>
|
||||
/// Callback for mesh upload operations
|
||||
/// </summary>
|
||||
/// <param name="result">null on failure, result from server on success</param>
|
||||
public delegate void ModelUploadCallback(OSD result);
|
||||
|
||||
|
||||
string InvName, InvDescription;
|
||||
|
||||
/// <summary>
|
||||
/// Creates instance of the mesh uploader
|
||||
/// </summary>
|
||||
/// <param name="client">GridClient instance to communicate with the simulator</param>
|
||||
/// <param name="prims">List of ModelPrimitive objects to upload as a linkset</param>
|
||||
/// <param name="newInvName">Inventory name for newly uploaded object</param>
|
||||
/// <param name="newInvDesc">Inventory description for newly upload object</param>
|
||||
public ModelUploader(GridClient client, List<ModelPrim> prims, string newInvName, string newInvDesc)
|
||||
{
|
||||
this.Client = client;
|
||||
this.Prims = prims;
|
||||
this.InvName = newInvName;
|
||||
this.InvDescription = newInvDesc;
|
||||
}
|
||||
|
||||
List<byte[]> Images;
|
||||
Dictionary<string, int> ImgIndex;
|
||||
|
||||
OSD AssetResources(bool upload)
|
||||
{
|
||||
OSDArray instanceList = new OSDArray();
|
||||
List<byte[]> meshes = new List<byte[]>();
|
||||
List<byte[]> textures = new List<byte[]>();
|
||||
|
||||
foreach (var prim in Prims)
|
||||
{
|
||||
OSDMap primMap = new OSDMap();
|
||||
|
||||
OSDArray faceList = new OSDArray();
|
||||
|
||||
foreach (var face in prim.Faces)
|
||||
{
|
||||
OSDMap faceMap = new OSDMap();
|
||||
|
||||
faceMap["diffuse_color"] = face.Material.DiffuseColor;
|
||||
faceMap["fullbright"] = false;
|
||||
|
||||
if (face.Material.TextureData != null)
|
||||
{
|
||||
int index;
|
||||
if (ImgIndex.ContainsKey(face.Material.Texture))
|
||||
{
|
||||
index = ImgIndex[face.Material.Texture];
|
||||
}
|
||||
else
|
||||
{
|
||||
index = Images.Count;
|
||||
ImgIndex[face.Material.Texture] = index;
|
||||
Images.Add(face.Material.TextureData);
|
||||
}
|
||||
faceMap["image"] = index;
|
||||
faceMap["scales"] = 1.0f;
|
||||
faceMap["scalet"] = 1.0f;
|
||||
faceMap["offsets"] = 0.0f;
|
||||
faceMap["offsett"] = 0.0f;
|
||||
faceMap["imagerot"] = 0.0f;
|
||||
}
|
||||
|
||||
faceList.Add(faceMap);
|
||||
}
|
||||
|
||||
primMap["face_list"] = faceList;
|
||||
|
||||
primMap["position"] = prim.Position;
|
||||
primMap["rotation"] = prim.Rotation;
|
||||
primMap["scale"] = prim.Scale;
|
||||
|
||||
primMap["material"] = 3; // always sent as "wood" material
|
||||
primMap["physics_shape_type"] = 2; // always sent as "convex hull";
|
||||
primMap["mesh"] = meshes.Count;
|
||||
meshes.Add(prim.Asset);
|
||||
|
||||
|
||||
instanceList.Add(primMap);
|
||||
}
|
||||
|
||||
OSDMap resources = new OSDMap();
|
||||
resources["instance_list"] = instanceList;
|
||||
|
||||
OSDArray meshList = new OSDArray();
|
||||
foreach (var mesh in meshes)
|
||||
{
|
||||
meshList.Add(OSD.FromBinary(mesh));
|
||||
}
|
||||
resources["mesh_list"] = meshList;
|
||||
|
||||
OSDArray textureList = new OSDArray();
|
||||
for (int i = 0; i < Images.Count; i++)
|
||||
{
|
||||
if (upload)
|
||||
{
|
||||
textureList.Add(new OSDBinary(Images[i]));
|
||||
}
|
||||
else
|
||||
{
|
||||
textureList.Add(new OSDBinary(Utils.EmptyBytes));
|
||||
}
|
||||
}
|
||||
|
||||
resources["texture_list"] = textureList;
|
||||
|
||||
resources["metric"] = "MUT_Unspecified";
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs model upload in one go, without first checking for the price
|
||||
/// </summary>
|
||||
public void Upload()
|
||||
{
|
||||
Upload(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs model upload in one go, without first checking for the price
|
||||
/// </summary>
|
||||
/// <param name="callback">Callback that will be invoke upon completion of the upload. Null is sent on request failure</param>
|
||||
public void Upload(ModelUploadCallback callback)
|
||||
{
|
||||
PrepareUpload((result =>
|
||||
{
|
||||
if (result == null && callback != null)
|
||||
{
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result is OSDMap)
|
||||
{
|
||||
var res = (OSDMap)result;
|
||||
Uri uploader = new Uri(res["uploader"]);
|
||||
PerformUpload(uploader, (contents =>
|
||||
{
|
||||
if (contents != null)
|
||||
{
|
||||
var reply = (OSDMap)contents;
|
||||
if (reply.ContainsKey("new_inventory_item") && reply.ContainsKey("new_asset"))
|
||||
{
|
||||
// Request full update on the item in order to update the local store
|
||||
Client.Inventory.RequestFetchInventory(reply["new_inventory_item"].AsUUID(), Client.Self.AgentID);
|
||||
}
|
||||
}
|
||||
if (callback != null) callback(contents);
|
||||
}));
|
||||
}
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ask server for details of cost and impact of the mesh upload
|
||||
/// </summary>
|
||||
/// <param name="callback">Callback that will be invoke upon completion of the upload. Null is sent on request failure</param>
|
||||
public void PrepareUpload(ModelUploadCallback callback)
|
||||
{
|
||||
Uri url = null;
|
||||
if (Client.Network.CurrentSim == null ||
|
||||
Client.Network.CurrentSim.Caps == null ||
|
||||
null == (url = Client.Network.CurrentSim.Caps.CapabilityURI("NewFileAgentInventory")))
|
||||
{
|
||||
Logger.Log("Cannot upload mesh, no connection or NewFileAgentInventory not available", Helpers.LogLevel.Warning);
|
||||
if (callback != null) callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
Images = new List<byte[]>();
|
||||
ImgIndex = new Dictionary<string, int>();
|
||||
|
||||
OSDMap req = new OSDMap();
|
||||
req["name"] = InvName;
|
||||
req["description"] = InvDescription;
|
||||
|
||||
req["asset_resources"] = AssetResources(false);
|
||||
req["asset_type"] = "mesh";
|
||||
req["inventory_type"] = "object";
|
||||
|
||||
req["folder_id"] = Client.Inventory.FindFolderForType(AssetType.Object);
|
||||
req["texture_folder_id"] = Client.Inventory.FindFolderForType(AssetType.Texture);
|
||||
|
||||
req["everyone_mask"] = (int)PermissionMask.All;
|
||||
req["group_mask"] = (int)PermissionMask.All; ;
|
||||
req["next_owner_mask"] = (int)PermissionMask.All;
|
||||
|
||||
CapsClient request = new CapsClient(url);
|
||||
request.OnComplete += (client, result, error) =>
|
||||
{
|
||||
if (error != null || result == null || result.Type != OSDType.Map)
|
||||
{
|
||||
Logger.Log("Mesh upload request failure", Helpers.LogLevel.Error);
|
||||
if (callback != null) callback(null);
|
||||
return;
|
||||
}
|
||||
OSDMap res = (OSDMap)result;
|
||||
|
||||
if (res["state"] != "upload")
|
||||
{
|
||||
Logger.Log("Mesh upload failure: " + res["message"], Helpers.LogLevel.Error);
|
||||
if (callback != null) callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Log("Response from mesh upload prepare:\n" + OSDParser.SerializeLLSDNotationFormatted(result), Helpers.LogLevel.Debug);
|
||||
if (callback != null) callback(result);
|
||||
};
|
||||
|
||||
request.BeginGetResponse(req, OSDFormat.Xml, 3 * 60 * 1000);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performas actual mesh and image upload
|
||||
/// </summary>
|
||||
/// <param name="uploader">Uri recieved in the upload prepare stage</param>
|
||||
/// <param name="callback">Callback that will be invoke upon completion of the upload. Null is sent on request failure</param>
|
||||
public void PerformUpload(Uri uploader, ModelUploadCallback callback)
|
||||
{
|
||||
CapsClient request = new CapsClient(uploader);
|
||||
request.OnComplete += (client, result, error) =>
|
||||
{
|
||||
if (error != null || result == null || result.Type != OSDType.Map)
|
||||
{
|
||||
Logger.Log("Mesh upload request failure", Helpers.LogLevel.Error);
|
||||
if (callback != null) callback(null);
|
||||
return;
|
||||
}
|
||||
OSDMap res = (OSDMap)result;
|
||||
Logger.Log("Response from mesh upload perform:\n" + OSDParser.SerializeLLSDNotationFormatted(result), Helpers.LogLevel.Debug);
|
||||
if (callback != null) callback(res);
|
||||
};
|
||||
|
||||
request.BeginGetResponse(AssetResources(true), OSDFormat.Xml, 60 * 1000);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using OpenMetaverse.StructuredData;
|
||||
|
||||
|
||||
namespace OpenMetaverse.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface requirements for Messaging system
|
||||
/// </summary>
|
||||
public interface IMessage
|
||||
{
|
||||
OSDMap Serialize();
|
||||
void Deserialize(OSDMap map);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenMetaverse.Rendering
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RendererNameAttribute : System.Attribute
|
||||
{
|
||||
private string _name;
|
||||
|
||||
public RendererNameAttribute(string name)
|
||||
: base()
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base for rendering plugins
|
||||
/// </summary>
|
||||
public interface IRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a basic mesh structure from a primitive
|
||||
/// </summary>
|
||||
/// <param name="prim">Primitive to generate the mesh from</param>
|
||||
/// <param name="lod">Level of detail to generate the mesh at</param>
|
||||
/// <returns>The generated mesh</returns>
|
||||
SimpleMesh GenerateSimpleMesh(Primitive prim, DetailLevel lod);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a basic mesh structure from a sculpted primitive and
|
||||
/// texture
|
||||
/// </summary>
|
||||
/// <param name="prim">Sculpted primitive to generate the mesh from</param>
|
||||
/// <param name="sculptTexture">Sculpt texture</param>
|
||||
/// <param name="lod">Level of detail to generate the mesh at</param>
|
||||
/// <returns>The generated mesh</returns>
|
||||
SimpleMesh GenerateSimpleSculptMesh(Primitive prim, Bitmap sculptTexture, DetailLevel lod);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a series of faces, each face containing a mesh and
|
||||
/// metadata
|
||||
/// </summary>
|
||||
/// <param name="prim">Primitive to generate the mesh from</param>
|
||||
/// <param name="lod">Level of detail to generate the mesh at</param>
|
||||
/// <returns>The generated mesh</returns>
|
||||
FacetedMesh GenerateFacetedMesh(Primitive prim, DetailLevel lod);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a series of faces for a sculpted prim, each face
|
||||
/// containing a mesh and metadata
|
||||
/// </summary>
|
||||
/// <param name="prim">Sculpted primitive to generate the mesh from</param>
|
||||
/// <param name="sculptTexture">Sculpt texture</param>
|
||||
/// <param name="lod">Level of detail to generate the mesh at</param>
|
||||
/// <returns>The generated mesh</returns>
|
||||
FacetedMesh GenerateFacetedSculptMesh(Primitive prim, Bitmap sculptTexture, DetailLevel lod);
|
||||
|
||||
/// <summary>
|
||||
/// Apply texture coordinate modifications from a
|
||||
/// <seealso cref="TextureEntryFace"/> to a list of vertices
|
||||
/// </summary>
|
||||
/// <param name="vertices">Vertex list to modify texture coordinates for</param>
|
||||
/// <param name="center">Center-point of the face</param>
|
||||
/// <param name="teFace">Face texture parameters</param>
|
||||
/// <param name="primScale">Scale of the prim</param>
|
||||
void TransformTexCoords (List<Vertex> vertices, Vector3 center, Primitive.TextureEntryFace teFace, Vector3 primScale);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// The InternalDictionary class is used through the library for storing key/value pairs.
|
||||
/// It is intended to be a replacement for the generic Dictionary class and should
|
||||
/// be used in its place. It contains several methods for allowing access to the data from
|
||||
/// outside the library that are read only and thread safe.
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">Key <see langword="Tkey"/></typeparam>
|
||||
/// <typeparam name="TValue">Value <see langword="TValue"/></typeparam>
|
||||
public class InternalDictionary<TKey, TValue>
|
||||
{
|
||||
/// <summary>Internal dictionary that this class wraps around. Do not
|
||||
/// modify or enumerate the contents of this dictionary without locking
|
||||
/// on this member</summary>
|
||||
internal Dictionary<TKey, TValue> Dictionary;
|
||||
|
||||
public Dictionary<TKey,TValue> Copy()
|
||||
{
|
||||
lock (Dictionary)
|
||||
return new Dictionary<TKey, TValue>(Dictionary);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of Key/Value pairs contained in the <seealso cref="T:InternalDictionary"/>
|
||||
/// </summary>
|
||||
public int Count { get { return Dictionary.Count; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <seealso cref="T:InternalDictionary"/> Class
|
||||
/// with the specified key/value, has the default initial capacity.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // initialize a new InternalDictionary named testDict with a string as the key and an int as the value.
|
||||
/// public InternalDictionary<string, int> testDict = new InternalDictionary<string, int>();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public InternalDictionary()
|
||||
{
|
||||
Dictionary = new Dictionary<TKey, TValue>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <seealso cref="T:InternalDictionary"/> Class
|
||||
/// with the specified key/value, has its initial valies copied from the specified
|
||||
/// <seealso cref="T:System.Collections.Generic.Dictionary"/>
|
||||
/// </summary>
|
||||
/// <param name="dictionary"><seealso cref="T:System.Collections.Generic.Dictionary"/>
|
||||
/// to copy initial values from</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // initialize a new InternalDictionary named testAvName with a UUID as the key and an string as the value.
|
||||
/// // populates with copied values from example KeyNameCache Dictionary.
|
||||
///
|
||||
/// // create source dictionary
|
||||
/// Dictionary<UUID, string> KeyNameCache = new Dictionary<UUID, string>();
|
||||
/// KeyNameCache.Add("8300f94a-7970-7810-cf2c-fc9aa6cdda24", "Jack Avatar");
|
||||
/// KeyNameCache.Add("27ba1e40-13f7-0708-3e98-5819d780bd62", "Jill Avatar");
|
||||
///
|
||||
/// // Initialize new dictionary.
|
||||
/// public InternalDictionary<UUID, string> testAvName = new InternalDictionary<UUID, string>(KeyNameCache);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public InternalDictionary(IDictionary<TKey, TValue> dictionary)
|
||||
{
|
||||
Dictionary = new Dictionary<TKey, TValue>(dictionary);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <seealso cref="T:OpenMetaverse.InternalDictionary"/> Class
|
||||
/// with the specified key/value, With its initial capacity specified.
|
||||
/// </summary>
|
||||
/// <param name="capacity">Initial size of dictionary</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // initialize a new InternalDictionary named testDict with a string as the key and an int as the value,
|
||||
/// // initially allocated room for 10 entries.
|
||||
/// public InternalDictionary<string, int> testDict = new InternalDictionary<string, int>(10);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public InternalDictionary(int capacity)
|
||||
{
|
||||
Dictionary = new Dictionary<TKey, TValue>(capacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get entry from <seealso cref="T:OpenMetaverse.InternalDictionary"/> with specified key
|
||||
/// </summary>
|
||||
/// <param name="key">Key to use for lookup</param>
|
||||
/// <param name="value">Value returned</param>
|
||||
/// <returns><see langword="true"/> if specified key exists, <see langword="false"/> if not found</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // find your avatar using the Simulator.ObjectsAvatars InternalDictionary:
|
||||
/// Avatar av;
|
||||
/// if (Client.Network.CurrentSim.ObjectsAvatars.TryGetValue(Client.Self.AgentID, out av))
|
||||
/// Console.WriteLine("Found Avatar {0}", av.Name);
|
||||
/// </code>
|
||||
/// <seealso cref="Simulator.ObjectsAvatars"/>
|
||||
/// </example>
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
lock (Dictionary)
|
||||
{
|
||||
return Dictionary.TryGetValue(key, out value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the specified match.
|
||||
/// </summary>
|
||||
/// <param name="match">The match.</param>
|
||||
/// <returns>Matched value</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // use a delegate to find a prim in the ObjectsPrimitives InternalDictionary
|
||||
/// // with the ID 95683496
|
||||
/// uint findID = 95683496;
|
||||
/// Primitive findPrim = sim.ObjectsPrimitives.Find(
|
||||
/// delegate(Primitive prim) { return prim.ID == findID; });
|
||||
/// </code>
|
||||
/// </example>
|
||||
public TValue Find(Predicate<TValue> match)
|
||||
{
|
||||
lock (Dictionary)
|
||||
{
|
||||
foreach (TValue value in Dictionary.Values)
|
||||
{
|
||||
if (match(value))
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return default(TValue);
|
||||
}
|
||||
|
||||
/// <summary>Find All items in an <seealso cref="T:InternalDictionary"/></summary>
|
||||
/// <param name="match">return matching items.</param>
|
||||
/// <returns>a <seealso cref="T:System.Collections.Generic.List"/> containing found items.</returns>
|
||||
/// <example>
|
||||
/// Find All prims within 20 meters and store them in a List
|
||||
/// <code>
|
||||
/// int radius = 20;
|
||||
/// List<Primitive> prims = Client.Network.CurrentSim.ObjectsPrimitives.FindAll(
|
||||
/// delegate(Primitive prim) {
|
||||
/// Vector3 pos = prim.Position;
|
||||
/// return ((prim.ParentID == 0) && (pos != Vector3.Zero) && (Vector3.Distance(pos, location) < radius));
|
||||
/// }
|
||||
/// );
|
||||
///</code>
|
||||
///</example>
|
||||
public List<TValue> FindAll(Predicate<TValue> match)
|
||||
{
|
||||
List<TValue> found = new List<TValue>();
|
||||
lock (Dictionary)
|
||||
{
|
||||
foreach (KeyValuePair<TKey, TValue> kvp in Dictionary)
|
||||
{
|
||||
if (match(kvp.Value))
|
||||
found.Add(kvp.Value);
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>Find All items in an <seealso cref="T:InternalDictionary"/></summary>
|
||||
/// <param name="match">return matching keys.</param>
|
||||
/// <returns>a <seealso cref="T:System.Collections.Generic.List"/> containing found keys.</returns>
|
||||
/// <example>
|
||||
/// Find All keys which also exist in another dictionary
|
||||
/// <code>
|
||||
/// List<UUID> matches = myDict.FindAll(
|
||||
/// delegate(UUID id) {
|
||||
/// return myOtherDict.ContainsKey(id);
|
||||
/// }
|
||||
/// );
|
||||
///</code>
|
||||
///</example>
|
||||
public List<TKey> FindAll(Predicate<TKey> match)
|
||||
{
|
||||
List<TKey> found = new List<TKey>();
|
||||
lock (Dictionary)
|
||||
{
|
||||
foreach (KeyValuePair<TKey, TValue> kvp in Dictionary)
|
||||
{
|
||||
if (match(kvp.Key))
|
||||
found.Add(kvp.Key);
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>Perform an <seealso cref="T:System.Action"/> on each entry in an <seealso cref="T:OpenMetaverse.InternalDictionary"/></summary>
|
||||
/// <param name="action"><seealso cref="T:System.Action"/> to perform</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Iterates over the ObjectsPrimitives InternalDictionary and prints out some information.
|
||||
/// Client.Network.CurrentSim.ObjectsPrimitives.ForEach(
|
||||
/// delegate(Primitive prim)
|
||||
/// {
|
||||
/// if (prim.Text != null)
|
||||
/// {
|
||||
/// Console.WriteLine("NAME={0} ID = {1} TEXT = '{2}'",
|
||||
/// prim.PropertiesFamily.Name, prim.ID, prim.Text);
|
||||
/// }
|
||||
/// });
|
||||
///</code>
|
||||
///</example>
|
||||
public void ForEach(Action<TValue> action)
|
||||
{
|
||||
lock (Dictionary)
|
||||
{
|
||||
foreach (TValue value in Dictionary.Values)
|
||||
{
|
||||
action(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Perform an <seealso cref="T:System.Action"/> on each key of an <seealso cref="T:OpenMetaverse.InternalDictionary"/></summary>
|
||||
/// <param name="action"><seealso cref="T:System.Action"/> to perform</param>
|
||||
public void ForEach(Action<TKey> action)
|
||||
{
|
||||
lock (Dictionary)
|
||||
{
|
||||
foreach (TKey key in Dictionary.Keys)
|
||||
{
|
||||
action(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform an <seealso cref="T:System.Action"/> on each KeyValuePair of an <seealso cref="T:OpenMetaverse.InternalDictionary"/>
|
||||
/// </summary>
|
||||
/// <param name="action"><seealso cref="T:System.Action"/> to perform</param>
|
||||
public void ForEach(Action<KeyValuePair<TKey, TValue>> action)
|
||||
{
|
||||
lock (Dictionary)
|
||||
{
|
||||
foreach (KeyValuePair<TKey, TValue> entry in Dictionary)
|
||||
{
|
||||
action(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Check if Key exists in Dictionary</summary>
|
||||
/// <param name="key">Key to check for</param>
|
||||
/// <returns><see langword="true"/> if found, <see langword="false"/> otherwise</returns>
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
return Dictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>Check if Value exists in Dictionary</summary>
|
||||
/// <param name="value">Value to check for</param>
|
||||
/// <returns><see langword="true"/> if found, <see langword="false"/> otherwise</returns>
|
||||
public bool ContainsValue(TValue value)
|
||||
{
|
||||
return Dictionary.ContainsValue(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified key to the dictionary, dictionary locking is not performed,
|
||||
/// <see cref="SafeAdd"/>
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="value">The value</param>
|
||||
internal void Add(TKey key, TValue value)
|
||||
{
|
||||
lock (Dictionary)
|
||||
Dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified key, dictionary locking is not performed
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <returns><see langword="true"/> if successful, <see langword="false"/> otherwise</returns>
|
||||
internal bool Remove(TKey key)
|
||||
{
|
||||
lock (Dictionary)
|
||||
return Dictionary.Remove(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indexer for the dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <returns>The value</returns>
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (Dictionary)
|
||||
return Dictionary[key];
|
||||
}
|
||||
internal set
|
||||
{
|
||||
lock (Dictionary)
|
||||
Dictionary[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,580 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception class to identify inventory exceptions
|
||||
/// </summary>
|
||||
public class InventoryException : Exception
|
||||
{
|
||||
public InventoryException(string message)
|
||||
: base(message) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for maintaining inventory structure. Inventory constructs nodes
|
||||
/// and manages node children as is necessary to maintain a coherant hirarchy.
|
||||
/// Other classes should not manipulate or create InventoryNodes explicitly. When
|
||||
/// A node's parent changes (when a folder is moved, for example) simply pass
|
||||
/// Inventory the updated InventoryFolder and it will make the appropriate changes
|
||||
/// to its internal representation.
|
||||
/// </summary>
|
||||
public class Inventory
|
||||
{
|
||||
|
||||
/// <summary>The event subscribers, null of no subscribers</summary>
|
||||
private EventHandler<InventoryObjectUpdatedEventArgs> m_InventoryObjectUpdated;
|
||||
|
||||
///<summary>Raises the InventoryObjectUpdated Event</summary>
|
||||
/// <param name="e">A InventoryObjectUpdatedEventArgs object containing
|
||||
/// the data sent from the simulator</param>
|
||||
protected virtual void OnInventoryObjectUpdated(InventoryObjectUpdatedEventArgs e)
|
||||
{
|
||||
EventHandler<InventoryObjectUpdatedEventArgs> handler = m_InventoryObjectUpdated;
|
||||
if (handler != null)
|
||||
handler(this, e);
|
||||
}
|
||||
|
||||
/// <summary>Thread sync lock object</summary>
|
||||
private readonly object m_InventoryObjectUpdatedLock = new object();
|
||||
|
||||
/// <summary>Raised when the simulator sends us data containing
|
||||
/// ...</summary>
|
||||
public event EventHandler<InventoryObjectUpdatedEventArgs> InventoryObjectUpdated
|
||||
{
|
||||
add { lock (m_InventoryObjectUpdatedLock) { m_InventoryObjectUpdated += value; } }
|
||||
remove { lock (m_InventoryObjectUpdatedLock) { m_InventoryObjectUpdated -= value; } }
|
||||
}
|
||||
|
||||
/// <summary>The event subscribers, null of no subscribers</summary>
|
||||
private EventHandler<InventoryObjectRemovedEventArgs> m_InventoryObjectRemoved;
|
||||
|
||||
///<summary>Raises the InventoryObjectRemoved Event</summary>
|
||||
/// <param name="e">A InventoryObjectRemovedEventArgs object containing
|
||||
/// the data sent from the simulator</param>
|
||||
protected virtual void OnInventoryObjectRemoved(InventoryObjectRemovedEventArgs e)
|
||||
{
|
||||
EventHandler<InventoryObjectRemovedEventArgs> handler = m_InventoryObjectRemoved;
|
||||
if (handler != null)
|
||||
handler(this, e);
|
||||
}
|
||||
|
||||
/// <summary>Thread sync lock object</summary>
|
||||
private readonly object m_InventoryObjectRemovedLock = new object();
|
||||
|
||||
/// <summary>Raised when the simulator sends us data containing
|
||||
/// ...</summary>
|
||||
public event EventHandler<InventoryObjectRemovedEventArgs> InventoryObjectRemoved
|
||||
{
|
||||
add { lock (m_InventoryObjectRemovedLock) { m_InventoryObjectRemoved += value; } }
|
||||
remove { lock (m_InventoryObjectRemovedLock) { m_InventoryObjectRemoved -= value; } }
|
||||
}
|
||||
|
||||
/// <summary>The event subscribers, null of no subscribers</summary>
|
||||
private EventHandler<InventoryObjectAddedEventArgs> m_InventoryObjectAdded;
|
||||
|
||||
///<summary>Raises the InventoryObjectAdded Event</summary>
|
||||
/// <param name="e">A InventoryObjectAddedEventArgs object containing
|
||||
/// the data sent from the simulator</param>
|
||||
protected virtual void OnInventoryObjectAdded(InventoryObjectAddedEventArgs e)
|
||||
{
|
||||
EventHandler<InventoryObjectAddedEventArgs> handler = m_InventoryObjectAdded;
|
||||
if (handler != null)
|
||||
handler(this, e);
|
||||
}
|
||||
|
||||
/// <summary>Thread sync lock object</summary>
|
||||
private readonly object m_InventoryObjectAddedLock = new object();
|
||||
|
||||
/// <summary>Raised when the simulator sends us data containing
|
||||
/// ...</summary>
|
||||
public event EventHandler<InventoryObjectAddedEventArgs> InventoryObjectAdded
|
||||
{
|
||||
add { lock (m_InventoryObjectAddedLock) { m_InventoryObjectAdded += value; } }
|
||||
remove { lock (m_InventoryObjectAddedLock) { m_InventoryObjectAdded -= value; } }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The root folder of this avatars inventory
|
||||
/// </summary>
|
||||
public InventoryFolder RootFolder
|
||||
{
|
||||
get { return RootNode.Data as InventoryFolder; }
|
||||
set
|
||||
{
|
||||
UpdateNodeFor(value);
|
||||
_RootNode = Items[value.UUID];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default shared library folder
|
||||
/// </summary>
|
||||
public InventoryFolder LibraryFolder
|
||||
{
|
||||
get { return LibraryRootNode.Data as InventoryFolder; }
|
||||
set
|
||||
{
|
||||
UpdateNodeFor(value);
|
||||
_LibraryRootNode = Items[value.UUID];
|
||||
}
|
||||
}
|
||||
|
||||
private InventoryNode _LibraryRootNode;
|
||||
private InventoryNode _RootNode;
|
||||
|
||||
/// <summary>
|
||||
/// The root node of the avatars inventory
|
||||
/// </summary>
|
||||
public InventoryNode RootNode
|
||||
{
|
||||
get { return _RootNode; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The root node of the default shared library
|
||||
/// </summary>
|
||||
public InventoryNode LibraryRootNode
|
||||
{
|
||||
get { return _LibraryRootNode; }
|
||||
}
|
||||
|
||||
public UUID Owner {
|
||||
get { return _Owner; }
|
||||
}
|
||||
|
||||
private UUID _Owner;
|
||||
|
||||
private GridClient Client;
|
||||
//private InventoryManager Manager;
|
||||
public Dictionary<UUID, InventoryNode> Items = new Dictionary<UUID, InventoryNode>();
|
||||
|
||||
public Inventory(GridClient client, InventoryManager manager)
|
||||
: this(client, manager, client.Self.AgentID) { }
|
||||
|
||||
public Inventory(GridClient client, InventoryManager manager, UUID owner)
|
||||
{
|
||||
Client = client;
|
||||
//Manager = manager;
|
||||
_Owner = owner;
|
||||
if (owner == UUID.Zero)
|
||||
Logger.Log("Inventory owned by nobody!", Helpers.LogLevel.Warning, Client);
|
||||
Items = new Dictionary<UUID, InventoryNode>();
|
||||
}
|
||||
|
||||
public List<InventoryBase> GetContents(InventoryFolder folder)
|
||||
{
|
||||
return GetContents(folder.UUID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the contents of the specified folder
|
||||
/// </summary>
|
||||
/// <param name="folder">A folder's UUID</param>
|
||||
/// <returns>The contents of the folder corresponding to <code>folder</code></returns>
|
||||
/// <exception cref="InventoryException">When <code>folder</code> does not exist in the inventory</exception>
|
||||
public List<InventoryBase> GetContents(UUID folder)
|
||||
{
|
||||
InventoryNode folderNode;
|
||||
if (!Items.TryGetValue(folder, out folderNode))
|
||||
throw new InventoryException("Unknown folder: " + folder);
|
||||
lock (folderNode.Nodes.SyncRoot)
|
||||
{
|
||||
List<InventoryBase> contents = new List<InventoryBase>(folderNode.Nodes.Count);
|
||||
foreach (InventoryNode node in folderNode.Nodes.Values)
|
||||
{
|
||||
contents.Add(node.Data);
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the state of the InventoryNode and inventory data structure that
|
||||
/// is responsible for the InventoryObject. If the item was previously not added to inventory,
|
||||
/// it adds the item, and updates structure accordingly. If it was, it updates the
|
||||
/// InventoryNode, changing the parent node if <code>item.parentUUID</code> does
|
||||
/// not match <code>node.Parent.Data.UUID</code>.
|
||||
///
|
||||
/// You can not set the inventory root folder using this method
|
||||
/// </summary>
|
||||
/// <param name="item">The InventoryObject to store</param>
|
||||
public void UpdateNodeFor(InventoryBase item)
|
||||
{
|
||||
lock (Items)
|
||||
{
|
||||
InventoryNode itemParent = null;
|
||||
if (item.ParentUUID != UUID.Zero && !Items.TryGetValue(item.ParentUUID, out itemParent))
|
||||
{
|
||||
// OK, we have no data on the parent, let's create a fake one.
|
||||
InventoryFolder fakeParent = new InventoryFolder(item.ParentUUID);
|
||||
fakeParent.DescendentCount = 1; // Dear god, please forgive me.
|
||||
itemParent = new InventoryNode(fakeParent);
|
||||
Items[item.ParentUUID] = itemParent;
|
||||
// Unfortunately, this breaks the nice unified tree
|
||||
// while we're waiting for the parent's data to come in.
|
||||
// As soon as we get the parent, the tree repairs itself.
|
||||
//Logger.DebugLog("Attempting to update inventory child of " +
|
||||
// item.ParentUUID.ToString() + " when we have no local reference to that folder", Client);
|
||||
|
||||
if (Client.Settings.FETCH_MISSING_INVENTORY)
|
||||
{
|
||||
// Fetch the parent
|
||||
List<UUID> fetchreq = new List<UUID>(1);
|
||||
fetchreq.Add(item.ParentUUID);
|
||||
}
|
||||
}
|
||||
|
||||
InventoryNode itemNode;
|
||||
if (Items.TryGetValue(item.UUID, out itemNode)) // We're updating.
|
||||
{
|
||||
InventoryNode oldParent = itemNode.Parent;
|
||||
// Handle parent change
|
||||
if (oldParent == null || itemParent == null || itemParent.Data.UUID != oldParent.Data.UUID)
|
||||
{
|
||||
if (oldParent != null)
|
||||
{
|
||||
lock (oldParent.Nodes.SyncRoot)
|
||||
oldParent.Nodes.Remove(item.UUID);
|
||||
}
|
||||
if (itemParent != null)
|
||||
{
|
||||
lock (itemParent.Nodes.SyncRoot)
|
||||
itemParent.Nodes[item.UUID] = itemNode;
|
||||
}
|
||||
}
|
||||
|
||||
itemNode.Parent = itemParent;
|
||||
|
||||
if (m_InventoryObjectUpdated != null)
|
||||
{
|
||||
OnInventoryObjectUpdated(new InventoryObjectUpdatedEventArgs(itemNode.Data, item));
|
||||
}
|
||||
|
||||
itemNode.Data = item;
|
||||
}
|
||||
else // We're adding.
|
||||
{
|
||||
itemNode = new InventoryNode(item, itemParent);
|
||||
Items.Add(item.UUID, itemNode);
|
||||
if (m_InventoryObjectAdded != null)
|
||||
{
|
||||
OnInventoryObjectAdded(new InventoryObjectAddedEventArgs(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public InventoryNode GetNodeFor(UUID uuid)
|
||||
{
|
||||
return Items[uuid];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the InventoryObject and all related node data from Inventory.
|
||||
/// </summary>
|
||||
/// <param name="item">The InventoryObject to remove.</param>
|
||||
public void RemoveNodeFor(InventoryBase item)
|
||||
{
|
||||
lock (Items)
|
||||
{
|
||||
InventoryNode node;
|
||||
if (Items.TryGetValue(item.UUID, out node))
|
||||
{
|
||||
if (node.Parent != null)
|
||||
lock (node.Parent.Nodes.SyncRoot)
|
||||
node.Parent.Nodes.Remove(item.UUID);
|
||||
Items.Remove(item.UUID);
|
||||
if (m_InventoryObjectRemoved != null)
|
||||
{
|
||||
OnInventoryObjectRemoved(new InventoryObjectRemovedEventArgs(item));
|
||||
}
|
||||
}
|
||||
|
||||
// In case there's a new parent:
|
||||
InventoryNode newParent;
|
||||
if (Items.TryGetValue(item.ParentUUID, out newParent))
|
||||
{
|
||||
lock (newParent.Nodes.SyncRoot)
|
||||
newParent.Nodes.Remove(item.UUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to find out if Inventory contains the InventoryObject
|
||||
/// specified by <code>uuid</code>.
|
||||
/// </summary>
|
||||
/// <param name="uuid">The UUID to check.</param>
|
||||
/// <returns>true if inventory contains uuid, false otherwise</returns>
|
||||
public bool Contains(UUID uuid)
|
||||
{
|
||||
return Items.ContainsKey(uuid);
|
||||
}
|
||||
|
||||
public bool Contains(InventoryBase obj)
|
||||
{
|
||||
return Contains(obj.UUID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the current inventory structure to a cache file
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of the cache file to save to</param>
|
||||
public void SaveToDisk(string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (Stream stream = File.Open(filename, FileMode.Create))
|
||||
{
|
||||
BinaryFormatter bformatter = new BinaryFormatter();
|
||||
lock (Items)
|
||||
{
|
||||
Logger.Log("Caching " + Items.Count.ToString() + " inventory items to " + filename, Helpers.LogLevel.Info);
|
||||
foreach (KeyValuePair<UUID, InventoryNode> kvp in Items)
|
||||
{
|
||||
bformatter.Serialize(stream, kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("Error saving inventory cache to disk :"+e.Message,Helpers.LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads in inventory cache file into the inventory structure. Note only valid to call after login has been successful.
|
||||
/// </summary>
|
||||
/// <param name="filename">Name of the cache file to load</param>
|
||||
/// <returns>The number of inventory items sucessfully reconstructed into the inventory node tree</returns>
|
||||
public int RestoreFromDisk(string filename)
|
||||
{
|
||||
List<InventoryNode> nodes = new List<InventoryNode>();
|
||||
int item_count = 0;
|
||||
|
||||
try
|
||||
{
|
||||
if (!File.Exists(filename))
|
||||
return -1;
|
||||
|
||||
using (Stream stream = File.Open(filename, FileMode.Open))
|
||||
{
|
||||
BinaryFormatter bformatter = new BinaryFormatter();
|
||||
|
||||
while (stream.Position < stream.Length)
|
||||
{
|
||||
OpenMetaverse.InventoryNode node = (InventoryNode)bformatter.Deserialize(stream);
|
||||
nodes.Add(node);
|
||||
item_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("Error accessing inventory cache file :" + e.Message, Helpers.LogLevel.Error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
Logger.Log("Read " + item_count.ToString() + " items from inventory cache file", Helpers.LogLevel.Info);
|
||||
|
||||
item_count = 0;
|
||||
List<InventoryNode> del_nodes = new List<InventoryNode>(); //nodes that we have processed and will delete
|
||||
List<UUID> dirty_folders = new List<UUID>(); // Tainted folders that we will not restore items into
|
||||
|
||||
// Because we could get child nodes before parents we must itterate around and only add nodes who have
|
||||
// a parent already in the list because we must update both child and parent to link together
|
||||
// But sometimes we have seen orphin nodes due to bad/incomplete data when caching so we have an emergency abort route
|
||||
int stuck = 0;
|
||||
|
||||
while (nodes.Count != 0 && stuck<5)
|
||||
{
|
||||
foreach (InventoryNode node in nodes)
|
||||
{
|
||||
InventoryNode pnode;
|
||||
if (node.ParentID == UUID.Zero)
|
||||
{
|
||||
//We don't need the root nodes "My Inventory" etc as they will already exist for the correct
|
||||
// user of this cache.
|
||||
del_nodes.Add(node);
|
||||
item_count--;
|
||||
}
|
||||
else if(Items.TryGetValue(node.Data.UUID,out pnode))
|
||||
{
|
||||
//We already have this it must be a folder
|
||||
if (node.Data is InventoryFolder)
|
||||
{
|
||||
InventoryFolder cache_folder = (InventoryFolder)node.Data;
|
||||
InventoryFolder server_folder = (InventoryFolder)pnode.Data;
|
||||
|
||||
if (cache_folder.Version != server_folder.Version)
|
||||
{
|
||||
Logger.DebugLog("Inventory Cache/Server version mismatch on " + node.Data.Name + " " + cache_folder.Version.ToString() + " vs " + server_folder.Version.ToString());
|
||||
pnode.NeedsUpdate = true;
|
||||
dirty_folders.Add(node.Data.UUID);
|
||||
}
|
||||
else
|
||||
{
|
||||
pnode.NeedsUpdate = false;
|
||||
}
|
||||
|
||||
del_nodes.Add(node);
|
||||
}
|
||||
}
|
||||
else if (Items.TryGetValue(node.ParentID, out pnode))
|
||||
{
|
||||
if (node.Data != null)
|
||||
{
|
||||
// If node is folder, and it does not exist in skeleton, mark it as
|
||||
// dirty and don't process nodes that belong to it
|
||||
if (node.Data is InventoryFolder && !(Items.ContainsKey(node.Data.UUID)))
|
||||
{
|
||||
dirty_folders.Add(node.Data.UUID);
|
||||
}
|
||||
|
||||
//Only add new items, this is most likely to be run at login time before any inventory
|
||||
//nodes other than the root are populated. Don't add non existing folders.
|
||||
if (!Items.ContainsKey(node.Data.UUID) && !dirty_folders.Contains(pnode.Data.UUID) && !(node.Data is InventoryFolder))
|
||||
{
|
||||
Items.Add(node.Data.UUID, node);
|
||||
node.Parent = pnode; //Update this node with its parent
|
||||
pnode.Nodes.Add(node.Data.UUID, node); // Add to the parents child list
|
||||
item_count++;
|
||||
}
|
||||
}
|
||||
|
||||
del_nodes.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (del_nodes.Count == 0)
|
||||
stuck++;
|
||||
else
|
||||
stuck = 0;
|
||||
|
||||
//Clean up processed nodes this loop around.
|
||||
foreach (InventoryNode node in del_nodes)
|
||||
nodes.Remove(node);
|
||||
|
||||
del_nodes.Clear();
|
||||
}
|
||||
|
||||
Logger.Log("Reassembled " + item_count.ToString() + " items from inventory cache file", Helpers.LogLevel.Info);
|
||||
return item_count;
|
||||
}
|
||||
|
||||
#region Operators
|
||||
|
||||
/// <summary>
|
||||
/// By using the bracket operator on this class, the program can get the
|
||||
/// InventoryObject designated by the specified uuid. If the value for the corresponding
|
||||
/// UUID is null, the call is equivelant to a call to <code>RemoveNodeFor(this[uuid])</code>.
|
||||
/// If the value is non-null, it is equivelant to a call to <code>UpdateNodeFor(value)</code>,
|
||||
/// the uuid parameter is ignored.
|
||||
/// </summary>
|
||||
/// <param name="uuid">The UUID of the InventoryObject to get or set, ignored if set to non-null value.</param>
|
||||
/// <returns>The InventoryObject corresponding to <code>uuid</code>.</returns>
|
||||
public InventoryBase this[UUID uuid]
|
||||
{
|
||||
get
|
||||
{
|
||||
InventoryNode node = Items[uuid];
|
||||
return node.Data;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
// Log a warning if there is a UUID mismatch, this will cause problems
|
||||
if (value.UUID != uuid)
|
||||
Logger.Log("Inventory[uuid]: uuid " + uuid.ToString() + " is not equal to value.UUID " +
|
||||
value.UUID.ToString(), Helpers.LogLevel.Warning, Client);
|
||||
|
||||
UpdateNodeFor(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
InventoryNode node;
|
||||
if (Items.TryGetValue(uuid, out node))
|
||||
{
|
||||
RemoveNodeFor(node.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Operators
|
||||
|
||||
}
|
||||
#region EventArgs classes
|
||||
|
||||
public class InventoryObjectUpdatedEventArgs : EventArgs
|
||||
{
|
||||
private readonly InventoryBase m_OldObject;
|
||||
private readonly InventoryBase m_NewObject;
|
||||
|
||||
public InventoryBase OldObject { get { return m_OldObject; } }
|
||||
public InventoryBase NewObject { get { return m_NewObject; } }
|
||||
|
||||
public InventoryObjectUpdatedEventArgs(InventoryBase oldObject, InventoryBase newObject)
|
||||
{
|
||||
this.m_OldObject = oldObject;
|
||||
this.m_NewObject = newObject;
|
||||
}
|
||||
}
|
||||
|
||||
public class InventoryObjectRemovedEventArgs : EventArgs
|
||||
{
|
||||
private readonly InventoryBase m_Obj;
|
||||
|
||||
public InventoryBase Obj { get { return m_Obj; } }
|
||||
public InventoryObjectRemovedEventArgs(InventoryBase obj)
|
||||
{
|
||||
this.m_Obj = obj;
|
||||
}
|
||||
}
|
||||
|
||||
public class InventoryObjectAddedEventArgs : EventArgs
|
||||
{
|
||||
private readonly InventoryBase m_Obj;
|
||||
|
||||
public InventoryBase Obj { get { return m_Obj; } }
|
||||
|
||||
public InventoryObjectAddedEventArgs(InventoryBase obj)
|
||||
{
|
||||
this.m_Obj = obj;
|
||||
}
|
||||
}
|
||||
#endregion EventArgs
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
[Serializable()]
|
||||
public class InventoryNode : ISerializable
|
||||
{
|
||||
private InventoryBase data;
|
||||
private InventoryNode parent;
|
||||
private UUID parentID; //used for de-seralization
|
||||
private InventoryNodeDictionary nodes;
|
||||
private bool needsUpdate = true;
|
||||
[NonSerialized]
|
||||
private object tag;
|
||||
|
||||
/// <summary></summary>
|
||||
public InventoryBase Data
|
||||
{
|
||||
get { return data; }
|
||||
set { data = value; }
|
||||
}
|
||||
|
||||
/// <summary>User data</summary>
|
||||
public object Tag
|
||||
{
|
||||
get { return tag; }
|
||||
set { tag = value; }
|
||||
}
|
||||
|
||||
/// <summary></summary>
|
||||
public InventoryNode Parent
|
||||
{
|
||||
get { return parent; }
|
||||
set { parent = value; }
|
||||
}
|
||||
|
||||
/// <summary></summary>
|
||||
public UUID ParentID
|
||||
{
|
||||
get { return parentID; }
|
||||
}
|
||||
|
||||
/// <summary></summary>
|
||||
public InventoryNodeDictionary Nodes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (nodes == null)
|
||||
nodes = new InventoryNodeDictionary(this);
|
||||
|
||||
return nodes;
|
||||
}
|
||||
set { nodes = value; }
|
||||
}
|
||||
|
||||
|
||||
public System.DateTime ModifyTime
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Data is InventoryItem)
|
||||
{
|
||||
return ((InventoryItem)Data).CreationDate;
|
||||
}
|
||||
DateTime newest = default(DateTime);//.MinValue;
|
||||
if (Data is InventoryFolder)
|
||||
{
|
||||
foreach (var node in Nodes.Values)
|
||||
{
|
||||
var t = node.ModifyTime;
|
||||
if (t > newest) newest = t;
|
||||
}
|
||||
}
|
||||
return newest;
|
||||
}
|
||||
}
|
||||
public void Sort()
|
||||
{
|
||||
Nodes.Sort();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For inventory folder nodes specifies weather the folder needs to be
|
||||
/// refreshed from the server
|
||||
/// </summary>
|
||||
public bool NeedsUpdate
|
||||
{
|
||||
get { return needsUpdate; }
|
||||
set { needsUpdate = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public InventoryNode()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public InventoryNode(InventoryBase data)
|
||||
{
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// De-serialization constructor for the InventoryNode Class
|
||||
/// </summary>
|
||||
public InventoryNode(InventoryBase data, InventoryNode parent)
|
||||
{
|
||||
this.data = data;
|
||||
this.parent = parent;
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
// Add this node to the collection of parent nodes
|
||||
lock (parent.Nodes.SyncRoot) parent.Nodes.Add(data.UUID, this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialization handler for the InventoryNode Class
|
||||
/// </summary>
|
||||
public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
|
||||
{
|
||||
if(parent!=null)
|
||||
info.AddValue("Parent", parent.Data.UUID, typeof(UUID)); //We need to track the parent UUID for de-serialization
|
||||
else
|
||||
info.AddValue("Parent", UUID.Zero, typeof(UUID));
|
||||
|
||||
info.AddValue("Type", data.GetType(), typeof(Type));
|
||||
|
||||
data.GetObjectData(info, ctxt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// De-serialization handler for the InventoryNode Class
|
||||
/// </summary>
|
||||
public InventoryNode(SerializationInfo info, StreamingContext ctxt)
|
||||
{
|
||||
parentID = (UUID)info.GetValue("Parent", typeof(UUID));
|
||||
Type type = (Type)info.GetValue("Type", typeof(Type));
|
||||
|
||||
// Construct a new inventory object based on the Type stored in Type
|
||||
System.Reflection.ConstructorInfo ctr = type.GetConstructor(new Type[] {typeof(SerializationInfo),typeof(StreamingContext)});
|
||||
data = (InventoryBase) ctr.Invoke(new Object[] { info, ctxt });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.Data == null) return "[Empty Node]";
|
||||
return this.Data.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
public class InventoryNodeDictionary: IComparer<UUID>
|
||||
{
|
||||
protected SortedDictionary<UUID, InventoryNode> SDictionary;
|
||||
protected Dictionary<UUID, InventoryNode> Dictionary = new Dictionary<UUID, InventoryNode>();
|
||||
protected InventoryNode parent;
|
||||
protected object syncRoot = new object();
|
||||
public int Compare(UUID id1, UUID id2)
|
||||
{
|
||||
InventoryNode n1 = Get(id1);
|
||||
InventoryNode n2 = Get(id2);
|
||||
int diff = NullCompare(n1, n2);
|
||||
if (diff != 0) return diff;
|
||||
if (n1 == null) return id1.CompareTo(id2);
|
||||
DateTime t1 = n1.ModifyTime;
|
||||
DateTime t2 = n2.ModifyTime;
|
||||
diff = t1.CompareTo(t2);
|
||||
if (diff != 0) return diff;
|
||||
var d1 = n1.Data;
|
||||
var d2 = n2.Data;
|
||||
diff = NullCompare(d1, d2);
|
||||
if (diff != 0) return diff;
|
||||
if (d1 != null)
|
||||
{
|
||||
diff = NullCompare(d1.Name, d2.Name);
|
||||
if (diff != 0) return diff;
|
||||
if (d1.Name != null)
|
||||
{
|
||||
// both are not null.. due to NullCoimpare code
|
||||
diff = d1.Name.CompareTo(d2.Name);
|
||||
if (diff != 0) return diff;
|
||||
}
|
||||
}
|
||||
return id1.CompareTo(id2);
|
||||
}
|
||||
|
||||
private InventoryNode Get(UUID uuid)
|
||||
{
|
||||
InventoryNode val;
|
||||
if (Dictionary.TryGetValue(uuid, out val))
|
||||
{
|
||||
return val;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static int NullCompare(object o1, object o2)
|
||||
{
|
||||
return ReferenceEquals(o1, null).CompareTo(ReferenceEquals(o2, null));
|
||||
}
|
||||
|
||||
public InventoryNode Parent
|
||||
{
|
||||
get { return parent; }
|
||||
set { parent = value; }
|
||||
}
|
||||
|
||||
public object SyncRoot { get { return syncRoot; } }
|
||||
|
||||
public int Count { get { return Dictionary.Count; } }
|
||||
|
||||
public InventoryNodeDictionary(InventoryNode parentNode)
|
||||
{
|
||||
if (Settings.SORT_INVENTORY) SDictionary = new SortedDictionary<UUID, InventoryNode>(this);
|
||||
parent = parentNode;
|
||||
}
|
||||
|
||||
public InventoryNode this[UUID key]
|
||||
{
|
||||
get { return (InventoryNode)this.Dictionary[key]; }
|
||||
set
|
||||
{
|
||||
value.Parent = parent;
|
||||
lock (syncRoot)
|
||||
{
|
||||
Dictionary[key] = value;
|
||||
if (Settings.SORT_INVENTORY) this.SDictionary[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<UUID> Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Settings.SORT_INVENTORY) return this.SDictionary.Keys;
|
||||
return Dictionary.Keys;
|
||||
}
|
||||
}
|
||||
public ICollection<InventoryNode> Values
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Settings.SORT_INVENTORY) return this.SDictionary.Values;
|
||||
return this.Dictionary.Values;
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(UUID key, InventoryNode value)
|
||||
{
|
||||
value.Parent = parent;
|
||||
lock (syncRoot)
|
||||
{
|
||||
Dictionary[key] = value;
|
||||
if (Settings.SORT_INVENTORY) this.SDictionary.Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(UUID key)
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
this.Dictionary.Remove(key);
|
||||
if (Settings.SORT_INVENTORY) this.SDictionary.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(UUID key)
|
||||
{
|
||||
return this.Dictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
internal void Sort()
|
||||
{
|
||||
if (Settings.SORT_INVENTORY)
|
||||
{
|
||||
// TODO resort SDictionary now that more data has come?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using log4net;
|
||||
using log4net.Config;
|
||||
|
||||
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// Singleton logging class for the entire library
|
||||
/// </summary>
|
||||
public static class Logger
|
||||
{
|
||||
/// <summary>
|
||||
/// Callback used for client apps to receive log messages from
|
||||
/// the library
|
||||
/// </summary>
|
||||
/// <param name="message">Data being logged</param>
|
||||
/// <param name="level">The severity of the log entry from <seealso cref="Helpers.LogLevel"/></param>
|
||||
public delegate void LogCallback(object message, Helpers.LogLevel level);
|
||||
|
||||
/// <summary>Triggered whenever a message is logged. If this is left
|
||||
/// null, log messages will go to the console</summary>
|
||||
public static event LogCallback OnLogMessage;
|
||||
|
||||
/// <summary>log4net logging engine</summary>
|
||||
public static ILog LogInstance;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
static Logger()
|
||||
{
|
||||
LogInstance = LogManager.GetLogger("OpenMetaverse");
|
||||
|
||||
// If error level reporting isn't enabled we assume no logger is configured and initialize a default
|
||||
// ConsoleAppender
|
||||
if (!LogInstance.Logger.IsEnabledFor(log4net.Core.Level.Error))
|
||||
{
|
||||
log4net.Appender.ConsoleAppender appender = new log4net.Appender.ConsoleAppender();
|
||||
appender.Layout = new log4net.Layout.PatternLayout("%timestamp [%thread] %-5level - %message%newline");
|
||||
BasicConfigurator.Configure(appender);
|
||||
|
||||
if(Settings.LOG_LEVEL != Helpers.LogLevel.None)
|
||||
LogInstance.Info("No log configuration found, defaulting to console logging");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a log message to the logging engine
|
||||
/// </summary>
|
||||
/// <param name="message">The log message</param>
|
||||
/// <param name="level">The severity of the log entry</param>
|
||||
public static void Log(object message, Helpers.LogLevel level)
|
||||
{
|
||||
Log(message, level, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a log message to the logging engine
|
||||
/// </summary>
|
||||
/// <param name="message">The log message</param>
|
||||
/// <param name="level">The severity of the log entry</param>
|
||||
/// <param name="client">Instance of the client</param>
|
||||
public static void Log(object message, Helpers.LogLevel level, GridClient client)
|
||||
{
|
||||
Log(message, level, client, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a log message to the logging engine
|
||||
/// </summary>
|
||||
/// <param name="message">The log message</param>
|
||||
/// <param name="level">The severity of the log entry</param>
|
||||
/// <param name="exception">Exception that was raised</param>
|
||||
public static void Log(object message, Helpers.LogLevel level, Exception exception)
|
||||
{
|
||||
Log(message, level, null, exception);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a log message to the logging engine
|
||||
/// </summary>
|
||||
/// <param name="message">The log message</param>
|
||||
/// <param name="level">The severity of the log entry</param>
|
||||
/// <param name="client">Instance of the client</param>
|
||||
/// <param name="exception">Exception that was raised</param>
|
||||
public static void Log(object message, Helpers.LogLevel level, GridClient client, Exception exception)
|
||||
{
|
||||
if (client != null && client.Settings.LOG_NAMES)
|
||||
message = String.Format("<{0}>: {1}", client.Self.Name, message);
|
||||
|
||||
if (OnLogMessage != null)
|
||||
OnLogMessage(message, level);
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case Helpers.LogLevel.Debug:
|
||||
if (Settings.LOG_LEVEL == Helpers.LogLevel.Debug)
|
||||
LogInstance.Debug(message, exception);
|
||||
break;
|
||||
case Helpers.LogLevel.Info:
|
||||
if (Settings.LOG_LEVEL == Helpers.LogLevel.Debug
|
||||
|| Settings.LOG_LEVEL == Helpers.LogLevel.Info)
|
||||
LogInstance.Info(message, exception);
|
||||
break;
|
||||
case Helpers.LogLevel.Warning:
|
||||
if (Settings.LOG_LEVEL == Helpers.LogLevel.Debug
|
||||
|| Settings.LOG_LEVEL == Helpers.LogLevel.Info
|
||||
|| Settings.LOG_LEVEL == Helpers.LogLevel.Warning)
|
||||
LogInstance.Warn(message, exception);
|
||||
break;
|
||||
case Helpers.LogLevel.Error:
|
||||
if (Settings.LOG_LEVEL == Helpers.LogLevel.Debug
|
||||
|| Settings.LOG_LEVEL == Helpers.LogLevel.Info
|
||||
|| Settings.LOG_LEVEL == Helpers.LogLevel.Warning
|
||||
|| Settings.LOG_LEVEL == Helpers.LogLevel.Error)
|
||||
LogInstance.Error(message, exception);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the library is compiled with DEBUG defined, an event will be
|
||||
/// fired if an <code>OnLogMessage</code> handler is registered and the
|
||||
/// message will be sent to the logging engine
|
||||
/// </summary>
|
||||
/// <param name="message">The message to log at the DEBUG level to the
|
||||
/// current logging engine</param>
|
||||
public static void DebugLog(object message)
|
||||
{
|
||||
DebugLog(message, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the library is compiled with DEBUG defined and
|
||||
/// <code>GridClient.Settings.DEBUG</code> is true, an event will be
|
||||
/// fired if an <code>OnLogMessage</code> handler is registered and the
|
||||
/// message will be sent to the logging engine
|
||||
/// </summary>
|
||||
/// <param name="message">The message to log at the DEBUG level to the
|
||||
/// current logging engine</param>
|
||||
/// <param name="client">Instance of the client</param>
|
||||
[System.Diagnostics.Conditional("DEBUG")]
|
||||
public static void DebugLog(object message, GridClient client)
|
||||
{
|
||||
if (Settings.LOG_LEVEL == Helpers.LogLevel.Debug)
|
||||
{
|
||||
if (client != null && client.Settings.LOG_NAMES)
|
||||
message = String.Format("<{0}>: {1}", client.Self.Name, message);
|
||||
|
||||
if (OnLogMessage != null)
|
||||
OnLogMessage(message, Helpers.LogLevel.Debug);
|
||||
|
||||
LogInstance.Debug(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using OpenMetaverse.StructuredData;
|
||||
using OpenMetaverse.Interfaces;
|
||||
using OpenMetaverse.Messages.Linden;
|
||||
|
||||
namespace OpenMetaverse.Messages
|
||||
{
|
||||
|
||||
public static partial class MessageUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Return a decoded capabilities message as a strongly typed object
|
||||
/// </summary>
|
||||
/// <param name="eventName">A string containing the name of the capabilities message key</param>
|
||||
/// <param name="map">An <see cref="OSDMap"/> to decode</param>
|
||||
/// <returns>A strongly typed object containing the decoded information from the capabilities message, or null
|
||||
/// if no existing Message object exists for the specified event</returns>
|
||||
public static IMessage DecodeEvent(string eventName, OSDMap map)
|
||||
{
|
||||
IMessage message = null;
|
||||
|
||||
switch (eventName)
|
||||
{
|
||||
case "AgentGroupDataUpdate": message = new AgentGroupDataUpdateMessage(); break;
|
||||
case "AvatarGroupsReply": message = new AgentGroupDataUpdateMessage(); break; // OpenSim sends the above with the wrong? key
|
||||
case "ParcelProperties": message = new ParcelPropertiesMessage(); break;
|
||||
case "ParcelObjectOwnersReply": message = new ParcelObjectOwnersReplyMessage(); break;
|
||||
case "TeleportFinish": message = new TeleportFinishMessage(); break;
|
||||
case "EnableSimulator": message = new EnableSimulatorMessage(); break;
|
||||
case "ParcelPropertiesUpdate": message = new ParcelPropertiesUpdateMessage(); break;
|
||||
case "EstablishAgentCommunication": message = new EstablishAgentCommunicationMessage(); break;
|
||||
case "ChatterBoxInvitation": message = new ChatterBoxInvitationMessage(); break;
|
||||
case "ChatterBoxSessionEventReply": message = new ChatterboxSessionEventReplyMessage(); break;
|
||||
case "ChatterBoxSessionStartReply": message = new ChatterBoxSessionStartReplyMessage(); break;
|
||||
case "ChatterBoxSessionAgentListUpdates": message = new ChatterBoxSessionAgentListUpdatesMessage(); break;
|
||||
case "RequiredVoiceVersion": message = new RequiredVoiceVersionMessage(); break;
|
||||
case "MapLayer": message = new MapLayerMessage(); break;
|
||||
case "ChatSessionRequest": message = new ChatSessionRequestMessage(); break;
|
||||
case "CopyInventoryFromNotecard": message = new CopyInventoryFromNotecardMessage(); break;
|
||||
case "ProvisionVoiceAccountRequest": message = new ProvisionVoiceAccountRequestMessage(); break;
|
||||
case "Viewerstats": message = new ViewerStatsMessage(); break;
|
||||
case "UpdateAgentLanguage": message = new UpdateAgentLanguageMessage(); break;
|
||||
case "RemoteParcelRequest": message = new RemoteParcelRequestMessage(); break;
|
||||
case "UpdateScriptTask": message = new UpdateScriptTaskMessage(); break;
|
||||
case "UpdateScriptAgent": message = new UpdateScriptAgentMessage(); break;
|
||||
case "SendPostcard": message = new SendPostcardMessage(); break;
|
||||
case "UpdateGestureAgentInventory": message = new UpdateGestureAgentInventoryMessage(); break;
|
||||
case "UpdateNotecardAgentInventory": message = new UpdateNotecardAgentInventoryMessage(); break;
|
||||
case "LandStatReply": message = new LandStatReplyMessage(); break;
|
||||
case "ParcelVoiceInfoRequest": message = new ParcelVoiceInfoRequestMessage(); break;
|
||||
case "ViewerStats": message = new ViewerStatsMessage(); break;
|
||||
case "EventQueueGet": message = new EventQueueGetMessage(); break;
|
||||
case "CrossedRegion": message = new CrossedRegionMessage(); break;
|
||||
case "TeleportFailed": message = new TeleportFailedMessage(); break;
|
||||
case "PlacesReply": message = new PlacesReplyMessage(); break;
|
||||
case "UpdateAgentInformation": message = new UpdateAgentInformationMessage(); break;
|
||||
case "DirLandReply": message = new DirLandReplyMessage(); break;
|
||||
case "ScriptRunningReply": message = new ScriptRunningReplyMessage(); break;
|
||||
case "SearchStatRequest": message = new SearchStatRequestMessage(); break;
|
||||
case "AgentDropGroup": message = new AgentDropGroupMessage(); break;
|
||||
case "AgentStateUpdate": message = new AgentStateUpdateMessage(); break;
|
||||
case "ForceCloseChatterBoxSession": message = new ForceCloseChatterBoxSessionMessage(); break;
|
||||
case "UploadBakedTexture": message = new UploadBakedTextureMessage(); break;
|
||||
case "RegionInfo": message = new RegionInfoMessage(); break;
|
||||
case "ObjectMediaNavigate": message = new ObjectMediaNavigateMessage(); break;
|
||||
case "ObjectMedia": message = new ObjectMediaMessage(); break;
|
||||
case "AttachmentResources": message = AttachmentResourcesMessage.GetMessageHandler(map); break;
|
||||
case "LandResources": message = LandResourcesMessage.GetMessageHandler(map); break;
|
||||
case "GetDisplayNames": message = new GetDisplayNamesMessage(); break;
|
||||
case "SetDisplayName": message = new SetDisplayNameMessage(); break;
|
||||
case "SetDisplayNameReply": message = new SetDisplayNameReplyMessage(); break;
|
||||
case "DisplayNameUpdate": message = new DisplayNameUpdateMessage(); break;
|
||||
//case "ProductInfoRequest": message = new ProductInfoRequestMessage(); break;
|
||||
case "ObjectPhysicsProperties": message = new ObjectPhysicsPropertiesMessage(); break;
|
||||
case "BulkUpdateInventory": message = new BulkUpdateInventoryMessage(); break;
|
||||
case "RenderMaterials": message = new RenderMaterialsMessage(); break;
|
||||
|
||||
// Capabilities TODO:
|
||||
// DispatchRegionInfo
|
||||
// EstateChangeInfo
|
||||
// EventQueueGet
|
||||
// FetchInventoryDescendents
|
||||
// GroupProposalBallot
|
||||
// MapLayerGod
|
||||
// NewFileAgentInventory
|
||||
// RequestTextureDownload
|
||||
// SearchStatRequest
|
||||
// SearchStatTracking
|
||||
// SendUserReport
|
||||
// SendUserReportWithScreenshot
|
||||
// ServerReleaseNotes
|
||||
// StartGroupProposal
|
||||
// UpdateGestureTaskInventory
|
||||
// UpdateNotecardTaskInventory
|
||||
// ViewerStartAuction
|
||||
// UntrustedSimulatorMessage
|
||||
}
|
||||
|
||||
if (message != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
message.Deserialize(map);
|
||||
return message;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("Exception while trying to Deserialize " + eventName + ":" + e.Message + ": " + e.StackTrace, Helpers.LogLevel.Error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using OpenMetaverse.StructuredData;
|
||||
|
||||
namespace OpenMetaverse.Messages
|
||||
{
|
||||
public static partial class MessageUtils
|
||||
{
|
||||
public static IPAddress ToIP(OSD osd)
|
||||
{
|
||||
byte[] binary = osd.AsBinary();
|
||||
if (binary != null && binary.Length == 4)
|
||||
return new IPAddress(binary);
|
||||
else
|
||||
return IPAddress.Any;
|
||||
}
|
||||
|
||||
public static OSD FromIP(IPAddress address)
|
||||
{
|
||||
if (address != null && address != IPAddress.Any)
|
||||
return OSD.FromBinary(address.GetAddressBytes());
|
||||
else
|
||||
return new OSD();
|
||||
}
|
||||
|
||||
public static Dictionary<string, string> ToDictionaryString(OSD osd)
|
||||
{
|
||||
if (osd.Type == OSDType.Map)
|
||||
{
|
||||
OSDMap map = (OSDMap)osd;
|
||||
Dictionary<string, string> dict = new Dictionary<string, string>(map.Count);
|
||||
foreach (KeyValuePair<string, OSD> entry in map)
|
||||
dict.Add(entry.Key, entry.Value.AsString());
|
||||
return dict;
|
||||
}
|
||||
|
||||
return new Dictionary<string, string>(0);
|
||||
}
|
||||
|
||||
public static Dictionary<Uri, Uri> ToDictionaryUri(OSD osd)
|
||||
{
|
||||
if (osd.Type == OSDType.Map)
|
||||
{
|
||||
OSDMap map = (OSDMap)osd;
|
||||
Dictionary<Uri, Uri> dict = new Dictionary<Uri, Uri>(map.Count);
|
||||
foreach (KeyValuePair<string, OSD> entry in map)
|
||||
dict.Add(new Uri(entry.Key), entry.Value.AsUri());
|
||||
return dict;
|
||||
}
|
||||
|
||||
return new Dictionary<Uri, Uri>(0);
|
||||
}
|
||||
|
||||
public static OSDMap FromDictionaryString(Dictionary<string, string> dict)
|
||||
{
|
||||
if (dict != null)
|
||||
{
|
||||
OSDMap map = new OSDMap(dict.Count);
|
||||
foreach (KeyValuePair<string, string> entry in dict)
|
||||
map.Add(entry.Key, OSD.FromString(entry.Value));
|
||||
return map;
|
||||
}
|
||||
|
||||
return new OSDMap(0);
|
||||
}
|
||||
|
||||
public static OSDMap FromDictionaryUri(Dictionary<Uri, Uri> dict)
|
||||
{
|
||||
if (dict != null)
|
||||
{
|
||||
OSDMap map = new OSDMap(dict.Count);
|
||||
foreach (KeyValuePair<Uri, Uri> entry in dict)
|
||||
map.Add(entry.Key.ToString(), OSD.FromUri(entry.Value));
|
||||
return map;
|
||||
}
|
||||
|
||||
return new OSDMap(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
/// <summary>
|
||||
/// A Name Value pair with additional settings, used in the protocol
|
||||
/// primarily to transmit avatar names and active group in object packets
|
||||
/// </summary>
|
||||
public struct NameValue
|
||||
{
|
||||
#region Enums
|
||||
|
||||
/// <summary>Type of the value</summary>
|
||||
public enum ValueType
|
||||
{
|
||||
/// <summary>Unknown</summary>
|
||||
Unknown = -1,
|
||||
/// <summary>String value</summary>
|
||||
String,
|
||||
/// <summary></summary>
|
||||
F32,
|
||||
/// <summary></summary>
|
||||
S32,
|
||||
/// <summary></summary>
|
||||
VEC3,
|
||||
/// <summary></summary>
|
||||
U32,
|
||||
/// <summary>Deprecated</summary>
|
||||
[Obsolete]
|
||||
CAMERA,
|
||||
/// <summary>String value, but designated as an asset</summary>
|
||||
Asset,
|
||||
/// <summary></summary>
|
||||
U64
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public enum ClassType
|
||||
{
|
||||
/// <summary></summary>
|
||||
Unknown = -1,
|
||||
/// <summary></summary>
|
||||
ReadOnly,
|
||||
/// <summary></summary>
|
||||
ReadWrite,
|
||||
/// <summary></summary>
|
||||
Callback
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public enum SendtoType
|
||||
{
|
||||
/// <summary></summary>
|
||||
Unknown = -1,
|
||||
/// <summary></summary>
|
||||
Sim,
|
||||
/// <summary></summary>
|
||||
DataSim,
|
||||
/// <summary></summary>
|
||||
SimViewer,
|
||||
/// <summary></summary>
|
||||
DataSimViewer
|
||||
}
|
||||
|
||||
#endregion Enums
|
||||
|
||||
|
||||
/// <summary></summary>
|
||||
public string Name;
|
||||
/// <summary></summary>
|
||||
public ValueType Type;
|
||||
/// <summary></summary>
|
||||
public ClassType Class;
|
||||
/// <summary></summary>
|
||||
public SendtoType Sendto;
|
||||
/// <summary></summary>
|
||||
public object Value;
|
||||
|
||||
|
||||
private static readonly string[] TypeStrings = new string[]
|
||||
{
|
||||
"STRING",
|
||||
"F32",
|
||||
"S32",
|
||||
"VEC3",
|
||||
"U32",
|
||||
"ASSET",
|
||||
"U64"
|
||||
};
|
||||
private static readonly string[] ClassStrings = new string[]
|
||||
{
|
||||
"R", // Read-only
|
||||
"RW", // Read-write
|
||||
"CB" // Callback
|
||||
};
|
||||
private static readonly string[] SendtoStrings = new string[]
|
||||
{
|
||||
"S", // Sim
|
||||
"DS", // Data Sim
|
||||
"SV", // Sim Viewer
|
||||
"DSV" // Data Sim Viewer
|
||||
};
|
||||
private static readonly char[] Separators = new char[]
|
||||
{
|
||||
' ',
|
||||
'\n',
|
||||
'\t',
|
||||
'\r'
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes all the fields as parameters
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="valueType"></param>
|
||||
/// <param name="classType"></param>
|
||||
/// <param name="sendtoType"></param>
|
||||
/// <param name="value"></param>
|
||||
public NameValue(string name, ValueType valueType, ClassType classType, SendtoType sendtoType, object value)
|
||||
{
|
||||
Name = name;
|
||||
Type = valueType;
|
||||
Class = classType;
|
||||
Sendto = sendtoType;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that takes a single line from a NameValue field
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public NameValue(string data)
|
||||
{
|
||||
int i;
|
||||
|
||||
// Name
|
||||
i = data.IndexOfAny(Separators);
|
||||
if (i < 1)
|
||||
{
|
||||
Name = String.Empty;
|
||||
Type = ValueType.Unknown;
|
||||
Class = ClassType.Unknown;
|
||||
Sendto = SendtoType.Unknown;
|
||||
Value = null;
|
||||
return;
|
||||
}
|
||||
Name = data.Substring(0, i);
|
||||
data = data.Substring(i + 1);
|
||||
|
||||
// Type
|
||||
i = data.IndexOfAny(Separators);
|
||||
if (i > 0)
|
||||
{
|
||||
Type = GetValueType(data.Substring(0, i));
|
||||
data = data.Substring(i + 1);
|
||||
|
||||
// Class
|
||||
i = data.IndexOfAny(Separators);
|
||||
if (i > 0)
|
||||
{
|
||||
Class = GetClassType(data.Substring(0, i));
|
||||
data = data.Substring(i + 1);
|
||||
|
||||
// Sendto
|
||||
i = data.IndexOfAny(Separators);
|
||||
if (i > 0)
|
||||
{
|
||||
Sendto = GetSendtoType(data.Substring(0, 1));
|
||||
data = data.Substring(i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Value
|
||||
Type = ValueType.String;
|
||||
Class = ClassType.ReadOnly;
|
||||
Sendto = SendtoType.Sim;
|
||||
Value = null;
|
||||
SetValue(data);
|
||||
}
|
||||
|
||||
public static string NameValuesToString(NameValue[] values)
|
||||
{
|
||||
if (values == null || values.Length == 0)
|
||||
return String.Empty;
|
||||
|
||||
StringBuilder output = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
NameValue value = values[i];
|
||||
|
||||
if (value.Value != null)
|
||||
{
|
||||
string newLine = (i < values.Length - 1) ? "\n" : String.Empty;
|
||||
output.AppendFormat("{0} {1} {2} {3} {4}{5}", value.Name, TypeStrings[(int)value.Type],
|
||||
ClassStrings[(int)value.Class], SendtoStrings[(int)value.Sendto], value.Value.ToString(), newLine);
|
||||
}
|
||||
}
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
private void SetValue(string value)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case ValueType.Asset:
|
||||
case ValueType.String:
|
||||
Value = value;
|
||||
break;
|
||||
case ValueType.F32:
|
||||
{
|
||||
float temp;
|
||||
Utils.TryParseSingle(value, out temp);
|
||||
Value = temp;
|
||||
break;
|
||||
}
|
||||
case ValueType.S32:
|
||||
{
|
||||
int temp;
|
||||
Int32.TryParse(value, out temp);
|
||||
Value = temp;
|
||||
break;
|
||||
}
|
||||
case ValueType.U32:
|
||||
{
|
||||
uint temp;
|
||||
UInt32.TryParse(value, out temp);
|
||||
Value = temp;
|
||||
break;
|
||||
}
|
||||
case ValueType.U64:
|
||||
{
|
||||
ulong temp;
|
||||
UInt64.TryParse(value, out temp);
|
||||
Value = temp;
|
||||
break;
|
||||
}
|
||||
case ValueType.VEC3:
|
||||
{
|
||||
Vector3 temp;
|
||||
Vector3.TryParse(value, out temp);
|
||||
Value = temp;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Value = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static ValueType GetValueType(string value)
|
||||
{
|
||||
ValueType type = ValueType.Unknown;
|
||||
|
||||
for (int i = 0; i < TypeStrings.Length; i++)
|
||||
{
|
||||
if (value == TypeStrings[i])
|
||||
{
|
||||
type = (ValueType)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == ValueType.Unknown)
|
||||
type = ValueType.String;
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private static ClassType GetClassType(string value)
|
||||
{
|
||||
ClassType type = ClassType.Unknown;
|
||||
|
||||
for (int i = 0; i < ClassStrings.Length; i++)
|
||||
{
|
||||
if (value == ClassStrings[i])
|
||||
{
|
||||
type = (ClassType)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == ClassType.Unknown)
|
||||
type = ClassType.ReadOnly;
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private static SendtoType GetSendtoType(string value)
|
||||
{
|
||||
SendtoType type = SendtoType.Unknown;
|
||||
|
||||
for (int i = 0; i < SendtoStrings.Length; i++)
|
||||
{
|
||||
if (value == SendtoStrings[i])
|
||||
{
|
||||
type = (SendtoType)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == SendtoType.Unknown)
|
||||
type = SendtoType.Sim;
|
||||
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
// this class encapsulates a single packet that
|
||||
// is either sent or received by a UDP socket
|
||||
public class UDPPacketBuffer
|
||||
{
|
||||
/// <summary>Size of the byte array used to store raw packet data</summary>
|
||||
public const int BUFFER_SIZE = 4096;
|
||||
/// <summary>Raw packet data buffer</summary>
|
||||
public readonly byte[] Data;
|
||||
/// <summary>Length of the data to transmit</summary>
|
||||
public int DataLength;
|
||||
/// <summary>EndPoint of the remote host</summary>
|
||||
public EndPoint RemoteEndPoint;
|
||||
|
||||
/// <summary>
|
||||
/// Create an allocated UDP packet buffer for receiving a packet
|
||||
/// </summary>
|
||||
public UDPPacketBuffer()
|
||||
{
|
||||
Data = new byte[UDPPacketBuffer.BUFFER_SIZE];
|
||||
// Will be modified later by BeginReceiveFrom()
|
||||
RemoteEndPoint = new IPEndPoint(Settings.BIND_ADDR, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an allocated UDP packet buffer for sending a packet
|
||||
/// </summary>
|
||||
/// <param name="endPoint">EndPoint of the remote host</param>
|
||||
public UDPPacketBuffer(IPEndPoint endPoint)
|
||||
{
|
||||
Data = new byte[UDPPacketBuffer.BUFFER_SIZE];
|
||||
RemoteEndPoint = endPoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an allocated UDP packet buffer for sending a packet
|
||||
/// </summary>
|
||||
/// <param name="endPoint">EndPoint of the remote host</param>
|
||||
/// <param name="bufferSize">Size of the buffer to allocate for packet data</param>
|
||||
public UDPPacketBuffer(IPEndPoint endPoint, int bufferSize)
|
||||
{
|
||||
Data = new byte[bufferSize];
|
||||
RemoteEndPoint = endPoint;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Object pool for packet buffers. This is used to allocate memory for all
|
||||
/// incoming and outgoing packets, and zerocoding buffers for those packets
|
||||
/// </summary>
|
||||
public class PacketBufferPool : ObjectPoolBase<UDPPacketBuffer>
|
||||
{
|
||||
private IPEndPoint EndPoint;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the object pool in client mode
|
||||
/// </summary>
|
||||
/// <param name="endPoint">Server to connect to</param>
|
||||
/// <param name="itemsPerSegment"></param>
|
||||
/// <param name="minSegments"></param>
|
||||
public PacketBufferPool(IPEndPoint endPoint, int itemsPerSegment, int minSegments)
|
||||
: base()
|
||||
{
|
||||
EndPoint = endPoint;
|
||||
Initialize(itemsPerSegment, minSegments, true, 1000 * 60 * 5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the object pool in server mode
|
||||
/// </summary>
|
||||
/// <param name="itemsPerSegment"></param>
|
||||
/// <param name="minSegments"></param>
|
||||
public PacketBufferPool(int itemsPerSegment, int minSegments)
|
||||
: base()
|
||||
{
|
||||
EndPoint = null;
|
||||
Initialize(itemsPerSegment, minSegments, true, 1000 * 60 * 5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a packet buffer with EndPoint set if the buffer is in
|
||||
/// client mode, or with EndPoint set to null in server mode
|
||||
/// </summary>
|
||||
/// <returns>Initialized UDPPacketBuffer object</returns>
|
||||
protected override UDPPacketBuffer GetObjectInstance()
|
||||
{
|
||||
if (EndPoint != null)
|
||||
// Client mode
|
||||
return new UDPPacketBuffer(EndPoint);
|
||||
else
|
||||
// Server mode
|
||||
return new UDPPacketBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Pool
|
||||
{
|
||||
public static PacketBufferPool PoolInstance;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
static Pool()
|
||||
{
|
||||
PoolInstance = new PacketBufferPool(new IPEndPoint(Settings.BIND_ADDR, 0), 16, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check a packet buffer out of the pool
|
||||
/// </summary>
|
||||
/// <returns>A packet buffer object</returns>
|
||||
public static WrappedObject<UDPPacketBuffer> CheckOut()
|
||||
{
|
||||
return PoolInstance.CheckOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,529 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
public sealed class WrappedObject<T> : IDisposable where T : class
|
||||
{
|
||||
private T _instance;
|
||||
internal readonly ObjectPoolSegment<T> _owningSegment;
|
||||
internal readonly ObjectPoolBase<T> _owningObjectPool;
|
||||
private bool _disposed = false;
|
||||
|
||||
internal WrappedObject(ObjectPoolBase<T> owningPool, ObjectPoolSegment<T> ownerSegment, T activeInstance)
|
||||
{
|
||||
_owningObjectPool = owningPool;
|
||||
_owningSegment = ownerSegment;
|
||||
_instance = activeInstance;
|
||||
}
|
||||
|
||||
~WrappedObject()
|
||||
{
|
||||
#if !PocketPC
|
||||
// If the AppDomain is being unloaded, or the CLR is
|
||||
// shutting down, just exit gracefully
|
||||
if (Environment.HasShutdownStarted)
|
||||
return;
|
||||
#endif
|
||||
|
||||
// Object Resurrection in Action!
|
||||
GC.ReRegisterForFinalize(this);
|
||||
|
||||
// Return this instance back to the owning queue
|
||||
_owningObjectPool.CheckIn(_owningSegment, _instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instance of the class that has been checked out of the Object Pool.
|
||||
/// </summary>
|
||||
public T Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("WrappedObject");
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the instance back into the object pool
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_disposed = true;
|
||||
_owningObjectPool.CheckIn(_owningSegment, _instance);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ObjectPoolBase<T> : IDisposable where T : class
|
||||
{
|
||||
private int _itemsPerSegment = 32;
|
||||
private int _minimumSegmentCount = 1;
|
||||
|
||||
// A segment won't be eligible for cleanup unless it's at least this old...
|
||||
private TimeSpan _minimumAgeToCleanup = new TimeSpan(0, 5, 0);
|
||||
|
||||
// ever increasing segment counter
|
||||
private int _activeSegment = 0;
|
||||
|
||||
private bool _gc = true;
|
||||
|
||||
private volatile bool _disposed = false;
|
||||
|
||||
private Dictionary<int, ObjectPoolSegment<T>> _segments = new Dictionary<int, ObjectPoolSegment<T>>();
|
||||
private object _syncRoot = new object();
|
||||
private object _timerLock = new object();
|
||||
|
||||
// create a timer that starts in 5 minutes, and gets called every 5 minutes.
|
||||
System.Threading.Timer _timer;
|
||||
int _cleanupFrequency;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the ObjectPoolBase class. Initialize MUST be called
|
||||
/// after using this constructor.
|
||||
/// </summary>
|
||||
protected ObjectPoolBase()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the ObjectPool Base class.
|
||||
/// </summary>
|
||||
/// <param name="itemsPerSegment">The object pool is composed of segments, which
|
||||
/// are allocated whenever the size of the pool is exceeded. The number of items
|
||||
/// in a segment should be large enough that allocating a new segmeng is a rare
|
||||
/// thing. For example, on a server that will have 10k people logged in at once,
|
||||
/// the receive buffer object pool should have segment sizes of at least 1000
|
||||
/// byte arrays per segment.
|
||||
/// </param>
|
||||
/// <param name="minimumSegmentCount">The minimun number of segments that may exist.</param>
|
||||
/// <param name="gcOnPoolGrowth">Perform a full GC.Collect whenever a segment is allocated, and then again after allocation to compact the heap.</param>
|
||||
/// <param name="cleanupFrequenceMS">The frequency which segments are checked to see if they're eligible for cleanup.</param>
|
||||
protected ObjectPoolBase(int itemsPerSegment, int minimumSegmentCount, bool gcOnPoolGrowth, int cleanupFrequenceMS)
|
||||
{
|
||||
Initialize(itemsPerSegment, minimumSegmentCount, gcOnPoolGrowth, cleanupFrequenceMS);
|
||||
}
|
||||
|
||||
protected void Initialize(int itemsPerSegment, int minimumSegmentCount, bool gcOnPoolGrowth, int cleanupFrequenceMS)
|
||||
{
|
||||
_itemsPerSegment = itemsPerSegment;
|
||||
_minimumSegmentCount = minimumSegmentCount;
|
||||
_gc = gcOnPoolGrowth;
|
||||
|
||||
// force garbage collection to make sure these new long lived objects
|
||||
// cause as little fragmentation as possible
|
||||
if (_gc)
|
||||
System.GC.Collect();
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
while (_segments.Count < this.MinimumSegmentCount)
|
||||
{
|
||||
ObjectPoolSegment<T> segment = CreateSegment(false);
|
||||
_segments.Add(segment.SegmentNumber, segment);
|
||||
}
|
||||
}
|
||||
|
||||
// This forces a compact, to make sure our objects fill in any holes in the heap.
|
||||
if (_gc)
|
||||
{
|
||||
System.GC.Collect();
|
||||
}
|
||||
|
||||
_timer = new Timer(CleanupThreadCallback, null, cleanupFrequenceMS, cleanupFrequenceMS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the segment cleanup algorithm to be run. This method is intended
|
||||
/// primarly for use from the Unit Test libraries.
|
||||
/// </summary>
|
||||
internal void ForceCleanup()
|
||||
{
|
||||
CleanupThreadCallback(null);
|
||||
}
|
||||
|
||||
private void CleanupThreadCallback(object state)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (Monitor.TryEnter(_timerLock) == false)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
// If we're below, or at, or minimum segment count threshold,
|
||||
// there's no point in going any further.
|
||||
if (_segments.Count <= _minimumSegmentCount)
|
||||
return;
|
||||
|
||||
for (int i = _activeSegment; i > 0; i--)
|
||||
{
|
||||
ObjectPoolSegment<T> segment;
|
||||
if (_segments.TryGetValue(i, out segment) == true)
|
||||
{
|
||||
// For the "old" segments that were allocated at startup, this will
|
||||
// always be false, as their expiration dates are set at infinity.
|
||||
if (segment.CanBeCleanedUp())
|
||||
{
|
||||
_segments.Remove(i);
|
||||
segment.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(_timerLock);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for allocate 1 instance of an object that will be stored in a segment.
|
||||
/// </summary>
|
||||
/// <returns>An instance of whatever objec the pool is pooling.</returns>
|
||||
protected abstract T GetObjectInstance();
|
||||
|
||||
|
||||
private ObjectPoolSegment<T> CreateSegment(bool allowSegmentToBeCleanedUp)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ObjectPoolBase");
|
||||
|
||||
if (allowSegmentToBeCleanedUp)
|
||||
Logger.Log("Creating new object pool segment", Helpers.LogLevel.Info);
|
||||
|
||||
// This method is called inside a lock, so no interlocked stuff required.
|
||||
int segmentToAdd = _activeSegment;
|
||||
_activeSegment++;
|
||||
|
||||
Queue<T> buffers = new Queue<T>();
|
||||
for (int i = 1; i <= this._itemsPerSegment; i++)
|
||||
{
|
||||
T obj = GetObjectInstance();
|
||||
buffers.Enqueue(obj);
|
||||
}
|
||||
|
||||
// certain segments we don't want to ever be cleaned up (the initial segments)
|
||||
DateTime cleanupTime = (allowSegmentToBeCleanedUp) ? DateTime.Now.Add(this._minimumAgeToCleanup) : DateTime.MaxValue;
|
||||
ObjectPoolSegment<T> segment = new ObjectPoolSegment<T>(segmentToAdd, buffers, cleanupTime);
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks in an instance of T owned by the object pool. This method is only intended to be called
|
||||
/// by the <c>WrappedObject</c> class.
|
||||
/// </summary>
|
||||
/// <param name="owningSegment">The segment from which the instance is checked out.</param>
|
||||
/// <param name="instance">The instance of <c>T</c> to check back into the segment.</param>
|
||||
internal void CheckIn(ObjectPoolSegment<T> owningSegment, T instance)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
owningSegment.CheckInObject(instance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks an instance of <c>T</c> from the pool. If the pool is not sufficient to
|
||||
/// allow the checkout, a new segment is created.
|
||||
/// </summary>
|
||||
/// <returns>A <c>WrappedObject</c> around the instance of <c>T</c>. To check
|
||||
/// the instance back into the segment, be sureto dispose the WrappedObject
|
||||
/// when finished. </returns>
|
||||
public WrappedObject<T> CheckOut()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ObjectPoolBase");
|
||||
|
||||
// It's key that this CheckOut always, always, uses a pooled object
|
||||
// from the oldest available segment. This will help keep the "newer"
|
||||
// segments from being used - which in turn, makes them eligible
|
||||
// for deletion.
|
||||
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
ObjectPoolSegment<T> targetSegment = null;
|
||||
|
||||
// find the oldest segment that has items available for checkout
|
||||
for (int i = 0; i < _activeSegment; i++)
|
||||
{
|
||||
ObjectPoolSegment<T> segment;
|
||||
if (_segments.TryGetValue(i, out segment) == true)
|
||||
{
|
||||
if (segment.AvailableItems > 0)
|
||||
{
|
||||
targetSegment = segment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetSegment == null)
|
||||
{
|
||||
// We couldn't find a sigment that had any available space in it,
|
||||
// so it's time to create a new segment.
|
||||
|
||||
// Before creating the segment, do a GC to make sure the heap
|
||||
// is compacted.
|
||||
if (_gc) GC.Collect();
|
||||
|
||||
targetSegment = CreateSegment(true);
|
||||
|
||||
if (_gc) GC.Collect();
|
||||
|
||||
_segments.Add(targetSegment.SegmentNumber, targetSegment);
|
||||
}
|
||||
|
||||
WrappedObject<T> obj = new WrappedObject<T>(this, targetSegment, targetSegment.CheckOutObject());
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The total number of segments created. Intended to be used by the Unit Tests.
|
||||
/// </summary>
|
||||
public int TotalSegments
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ObjectPoolBase");
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return _segments.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of items that are in a segment. Items in a segment
|
||||
/// are all allocated at the same time, and are hopefully close to
|
||||
/// each other in the managed heap.
|
||||
/// </summary>
|
||||
public int ItemsPerSegment
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ObjectPoolBase");
|
||||
|
||||
return _itemsPerSegment;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The minimum number of segments. When segments are reclaimed,
|
||||
/// this number of segments will always be left alone. These
|
||||
/// segments are allocated at startup.
|
||||
/// </summary>
|
||||
public int MinimumSegmentCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ObjectPoolBase");
|
||||
|
||||
return _minimumSegmentCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The age a segment must be before it's eligible for cleanup.
|
||||
/// This is used to prevent thrash, and typical values are in
|
||||
/// the 5 minute range.
|
||||
/// </summary>
|
||||
public TimeSpan MinimumSegmentAgePriorToCleanup
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ObjectPoolBase");
|
||||
|
||||
return _minimumAgeToCleanup;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ObjectPoolBase");
|
||||
|
||||
_minimumAgeToCleanup = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The frequence which the cleanup thread runs. This is typically
|
||||
/// expected to be in the 5 minute range.
|
||||
/// </summary>
|
||||
public int CleanupFrequencyMilliseconds
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ObjectPoolBase");
|
||||
|
||||
return _cleanupFrequency;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException("ObjectPoolBase");
|
||||
|
||||
Interlocked.Exchange(ref _cleanupFrequency, value);
|
||||
|
||||
_timer.Change(_cleanupFrequency, _cleanupFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
Dispose(true);
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_timer.Dispose();
|
||||
_disposed = true;
|
||||
|
||||
foreach (KeyValuePair<int, ObjectPoolSegment<T>> kvp in _segments)
|
||||
{
|
||||
try
|
||||
{
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
_segments.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal class ObjectPoolSegment<T> : IDisposable where T : class
|
||||
{
|
||||
private Queue<T> _liveInstances = new Queue<T>();
|
||||
private int _segmentNumber;
|
||||
private int _originalCount;
|
||||
private bool _isDisposed = false;
|
||||
private DateTime _eligibleForDeletionAt;
|
||||
|
||||
public int SegmentNumber { get { return _segmentNumber; } }
|
||||
public int AvailableItems { get { return _liveInstances.Count; } }
|
||||
public DateTime DateEligibleForDeletion { get { return _eligibleForDeletionAt; } }
|
||||
|
||||
public ObjectPoolSegment(int segmentNumber, Queue<T> liveInstances, DateTime eligibleForDeletionAt)
|
||||
{
|
||||
_segmentNumber = segmentNumber;
|
||||
_liveInstances = liveInstances;
|
||||
_originalCount = liveInstances.Count;
|
||||
_eligibleForDeletionAt = eligibleForDeletionAt;
|
||||
}
|
||||
|
||||
public bool CanBeCleanedUp()
|
||||
{
|
||||
if (_isDisposed == true)
|
||||
throw new ObjectDisposedException("ObjectPoolSegment");
|
||||
|
||||
return ((_originalCount == _liveInstances.Count) && (DateTime.Now > _eligibleForDeletionAt));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
bool shouldDispose = (typeof(T) is IDisposable);
|
||||
while (_liveInstances.Count != 0)
|
||||
{
|
||||
T instance = _liveInstances.Dequeue();
|
||||
if (shouldDispose)
|
||||
{
|
||||
try
|
||||
{
|
||||
(instance as IDisposable).Dispose();
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void CheckInObject(T o)
|
||||
{
|
||||
if (_isDisposed == true)
|
||||
throw new ObjectDisposedException("ObjectPoolSegment");
|
||||
|
||||
_liveInstances.Enqueue(o);
|
||||
}
|
||||
|
||||
internal T CheckOutObject()
|
||||
{
|
||||
if (_isDisposed == true)
|
||||
throw new ObjectDisposedException("ObjectPoolSegment");
|
||||
|
||||
if (0 == _liveInstances.Count)
|
||||
throw new InvalidOperationException("No Objects Available for Checkout");
|
||||
|
||||
T o = _liveInstances.Dequeue();
|
||||
return o;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* Copyright (c) 2006-2014, openmetaverse.org
|
||||
* All rights reserved.
|
||||
*
|
||||
* - 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.
|
||||
* - Neither the name of the openmetaverse.org 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR 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.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace OpenMetaverse
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public enum DictionaryEventAction
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Add,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Remove,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Change
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="entry"></param>
|
||||
public delegate void DictionaryChangeCallback(DictionaryEventAction action, DictionaryEntry entry);
|
||||
|
||||
/// <summary>
|
||||
/// The ObservableDictionary class is used for storing key/value pairs. It has methods for firing
|
||||
/// events to subscribers when items are added, removed, or changed.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">Key <see langword="Tkey"/></typeparam>
|
||||
/// <typeparam name="TValue">Value <see langword="TValue"/></typeparam>
|
||||
public class ObservableDictionary<TKey, TValue>
|
||||
{
|
||||
#region Observable implementation
|
||||
/// <summary>
|
||||
/// A dictionary of callbacks to fire when specified action occurs
|
||||
/// </summary>
|
||||
private Dictionary<DictionaryEventAction, List<DictionaryChangeCallback>> Delegates;
|
||||
|
||||
/// <summary>
|
||||
/// Register a callback to be fired when an action occurs
|
||||
/// </summary>
|
||||
/// <param name="action">The action</param>
|
||||
/// <param name="callback">The callback to fire</param>
|
||||
public void AddDelegate(DictionaryEventAction action, DictionaryChangeCallback callback)
|
||||
{
|
||||
if (Delegates.ContainsKey(action))
|
||||
{
|
||||
Delegates[action].Add(callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<DictionaryChangeCallback> callbacks = new List<DictionaryChangeCallback>(1);
|
||||
callbacks.Add(callback);
|
||||
Delegates.Add(action, callbacks);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister a callback
|
||||
/// </summary>
|
||||
/// <param name="action">The action</param>
|
||||
/// <param name="callback">The callback to fire</param>
|
||||
public void RemoveDelegate(DictionaryEventAction action, DictionaryChangeCallback callback)
|
||||
{
|
||||
if (Delegates.ContainsKey(action))
|
||||
{
|
||||
if (Delegates[action].Contains(callback))
|
||||
Delegates[action].Remove(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="entry"></param>
|
||||
private void FireChangeEvent(DictionaryEventAction action, DictionaryEntry entry)
|
||||
{
|
||||
|
||||
if(Delegates.ContainsKey(action))
|
||||
{
|
||||
foreach(DictionaryChangeCallback handler in Delegates[action])
|
||||
{
|
||||
handler(action, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>Internal dictionary that this class wraps around. Do not
|
||||
/// modify or enumerate the contents of this dictionary without locking</summary>
|
||||
private Dictionary<TKey, TValue> Dictionary;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of Key/Value pairs contained in the <seealso cref="T:ObservableDictionary"/>
|
||||
/// </summary>
|
||||
public int Count { get { return Dictionary.Count; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <seealso cref="T:ObservableDictionary"/> Class
|
||||
/// with the specified key/value, has the default initial capacity.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // initialize a new ObservableDictionary named testDict with a string as the key and an int as the value.
|
||||
/// public ObservableDictionary<string, int> testDict = new ObservableDictionary<string, int>();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public ObservableDictionary()
|
||||
{
|
||||
Dictionary = new Dictionary<TKey, TValue>();
|
||||
Delegates = new Dictionary<DictionaryEventAction, List<DictionaryChangeCallback>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <seealso cref="T:OpenMetaverse.ObservableDictionary"/> Class
|
||||
/// with the specified key/value, With its initial capacity specified.
|
||||
/// </summary>
|
||||
/// <param name="capacity">Initial size of dictionary</param>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // initialize a new ObservableDictionary named testDict with a string as the key and an int as the value,
|
||||
/// // initially allocated room for 10 entries.
|
||||
/// public ObservableDictionary<string, int> testDict = new ObservableDictionary<string, int>(10);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public ObservableDictionary(int capacity)
|
||||
{
|
||||
Dictionary = new Dictionary<TKey, TValue>(capacity);
|
||||
Delegates = new Dictionary<DictionaryEventAction, List<DictionaryChangeCallback>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get entry from the <seealso cref="ObservableDictionary"/> with specified key
|
||||
/// </summary>
|
||||
/// <param name="key">Key to use for lookup</param>
|
||||
/// <param name="value">Value returned</param>
|
||||
/// <returns><see langword="true"/> if specified key exists, <see langword="false"/> if not found</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // find your avatar using the Simulator.ObjectsAvatars ObservableDictionary:
|
||||
/// Avatar av;
|
||||
/// if (Client.Network.CurrentSim.ObjectsAvatars.TryGetValue(Client.Self.AgentID, out av))
|
||||
/// Console.WriteLine("Found Avatar {0}", av.Name);
|
||||
/// </code>
|
||||
/// <seealso cref="Simulator.ObjectsAvatars"/>
|
||||
/// </example>
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return Dictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the specified match.
|
||||
/// </summary>
|
||||
/// <param name="match">The match.</param>
|
||||
/// <returns>Matched value</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // use a delegate to find a prim in the ObjectsPrimitives ObservableDictionary
|
||||
/// // with the ID 95683496
|
||||
/// uint findID = 95683496;
|
||||
/// Primitive findPrim = sim.ObjectsPrimitives.Find(
|
||||
/// delegate(Primitive prim) { return prim.ID == findID; });
|
||||
/// </code>
|
||||
/// </example>
|
||||
public TValue Find(Predicate<TValue> match)
|
||||
{
|
||||
foreach (TValue value in Dictionary.Values)
|
||||
{
|
||||
if (match(value))
|
||||
return value;
|
||||
}
|
||||
return default(TValue);
|
||||
}
|
||||
|
||||
/// <summary>Find All items in an <seealso cref="T:ObservableDictionary"/></summary>
|
||||
/// <param name="match">return matching items.</param>
|
||||
/// <returns>a <seealso cref="T:System.Collections.Generic.List"/> containing found items.</returns>
|
||||
/// <example>
|
||||
/// Find All prims within 20 meters and store them in a List
|
||||
/// <code>
|
||||
/// int radius = 20;
|
||||
/// List<Primitive> prims = Client.Network.CurrentSim.ObjectsPrimitives.FindAll(
|
||||
/// delegate(Primitive prim) {
|
||||
/// Vector3 pos = prim.Position;
|
||||
/// return ((prim.ParentID == 0) && (pos != Vector3.Zero) && (Vector3.Distance(pos, location) < radius));
|
||||
/// }
|
||||
/// );
|
||||
///</code>
|
||||
///</example>
|
||||
public List<TValue> FindAll(Predicate<TValue> match)
|
||||
{
|
||||
List<TValue> found = new List<TValue>();
|
||||
|
||||
foreach (KeyValuePair<TKey, TValue> kvp in Dictionary)
|
||||
{
|
||||
if (match(kvp.Value))
|
||||
found.Add(kvp.Value);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>Find All items in an <seealso cref="T:ObservableDictionary"/></summary>
|
||||
/// <param name="match">return matching keys.</param>
|
||||
/// <returns>a <seealso cref="T:System.Collections.Generic.List"/> containing found keys.</returns>
|
||||
/// <example>
|
||||
/// Find All keys which also exist in another dictionary
|
||||
/// <code>
|
||||
/// List<UUID> matches = myDict.FindAll(
|
||||
/// delegate(UUID id) {
|
||||
/// return myOtherDict.ContainsKey(id);
|
||||
/// }
|
||||
/// );
|
||||
///</code>
|
||||
///</example>
|
||||
public List<TKey> FindAll(Predicate<TKey> match)
|
||||
{
|
||||
List<TKey> found = new List<TKey>();
|
||||
|
||||
foreach (KeyValuePair<TKey, TValue> kvp in Dictionary)
|
||||
{
|
||||
if (match(kvp.Key))
|
||||
found.Add(kvp.Key);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>Check if Key exists in Dictionary</summary>
|
||||
/// <param name="key">Key to check for</param>
|
||||
/// <returns><see langword="true"/> if found, <see langword="false"/> otherwise</returns>
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
return Dictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>Check if Value exists in Dictionary</summary>
|
||||
/// <param name="value">Value to check for</param>
|
||||
/// <returns><see langword="true"/> if found, <see langword="false"/> otherwise</returns>
|
||||
public bool ContainsValue(TValue value)
|
||||
{
|
||||
return Dictionary.ContainsValue(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified key to the dictionary, dictionary locking is not performed,
|
||||
/// <see cref="SafeAdd"/>
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <param name="value">The value</param>
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
Dictionary.Add(key, value);
|
||||
FireChangeEvent(DictionaryEventAction.Add, new DictionaryEntry(key, value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified key, dictionary locking is not performed
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <returns><see langword="true"/> if successful, <see langword="false"/> otherwise</returns>
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
FireChangeEvent(DictionaryEventAction.Remove, new DictionaryEntry(key, Dictionary[key]));
|
||||
return Dictionary.Remove(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indexer for the dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <returns>The value</returns>
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get { return Dictionary[key]; }
|
||||
set { FireChangeEvent(DictionaryEventAction.Add, new DictionaryEntry(key, value));
|
||||
Dictionary[key] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the contents of the dictionary
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (KeyValuePair<TKey, TValue> kvp in Dictionary)
|
||||
FireChangeEvent(DictionaryEventAction.Remove, new DictionaryEntry(kvp.Key, kvp.Value));
|
||||
|
||||
Dictionary.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerator for iterating dictionary entries
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public System.Collections.IEnumerator GetEnumerator()
|
||||
{
|
||||
return Dictionary.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user