using System;
using System.Collections.Generic;
using System.Threading;
using System.IO;
using libsecondlife;
using libsecondlife.Packets;
namespace libsecondlife
{
#region Enums
///
/// The different types of assets in Second Life
///
public enum AssetType : sbyte
{
/// Unknown asset type
Unknown = -1,
/// Texture asset, stores in JPEG2000 J2C stream format
Texture = 0,
/// Sound asset
Sound = 1,
/// Calling card for another avatar
CallingCard = 2,
/// Link to a location in world
Landmark = 3,
/// Legacy script asset, you should never see one of these
[Obsolete]
Script = 4,
/// Collection of textures and parameters that can be
/// worn by an avatar
Clothing = 5,
/// Primitive that can contain textures, sounds,
/// scripts and more
Object = 6,
/// Notecard asset
Notecard = 7,
/// Holds a collection of inventory items
Folder = 8,
/// Root inventory folder
RootFolder = 9,
/// Linden scripting language script
LSLText = 10,
/// LSO bytecode for a script
LSLBytecode = 11,
/// Uncompressed TGA texture
TextureTGA = 12,
/// Collection of textures and shape parameters that can
/// be worn
Bodypart = 13,
/// Trash folder
TrashFolder = 14,
/// Snapshot folder
SnapshotFolder = 15,
/// Lost and found folder
LostAndFoundFolder = 16,
/// Uncompressed sound
SoundWAV = 17,
/// Uncompressed TGA non-square image, not to be used as a
/// texture
ImageTGA = 18,
/// Compressed JPEG non-square image, not to be used as a
/// texture
ImageJPEG = 19,
/// Animation
Animation = 20,
/// Sequence of animations, sounds, chat, and pauses
Gesture = 21,
/// Simstate file
Simstate = 22,
}
///
///
///
public enum StatusCode
{
/// OK
OK = 0,
/// Transfer completed
Done = 1,
///
Skip = 2,
///
Abort = 3,
/// Unknown error occurred
Error = -1,
/// Equivalent to a 404 error
UnknownSource = -2,
/// Client does not have permission for that resource
InsufficientPermissiosn = -3,
/// Unknown status
Unknown = -4
}
///
///
///
public enum ChannelType : int
{
///
Unknown = 0,
/// Unknown
Misc = 1,
/// Virtually all asset transfers use this channel
Asset = 2
}
///
///
///
public enum SourceType : int
{
///
Unknown = 0,
/// Arbitrary system files off the server
[Obsolete]
File = 1,
/// Asset from the asset server
Asset = 2,
/// Inventory item
SimInventoryItem = 3,
///
SimEstate = 4
}
///
///
///
public enum TargetType : int
{
///
Unknown = 0,
///
File,
///
VFile
}
///
///
///
public enum ImageType : byte
{
///
Normal = 0,
///
Baked = 1
}
#endregion Enums
#region Transfer Classes
///
///
///
public class Transfer
{
public LLUUID ID;
public int Size;
public byte[] AssetData = new byte[0];
public int Transferred;
public bool Success;
public AssetType AssetType;
internal int transferStart;
/// Number of milliseconds passed since this transfer was
/// initialized
public int TransferTime { get { return Environment.TickCount - transferStart; } }
public Transfer()
{
AssetData = new byte[0];
transferStart = Environment.TickCount;
}
}
///
///
///
public class AssetDownload : Transfer
{
public LLUUID AssetID;
public ChannelType Channel;
public SourceType Source;
public TargetType Target;
public StatusCode Status;
public float Priority;
public Simulator Simulator;
internal AutoResetEvent HeaderReceivedEvent = new AutoResetEvent(false);
}
public class XferDownload : Transfer
{
public ulong XferID;
public LLUUID VFileID;
public AssetType Type;
public uint PacketNum;
public string Filename = String.Empty;
}
///
///
///
public class ImageDownload : Transfer
{
public ushort PacketCount;
public int Codec;
public bool NotFound;
public Simulator Simulator;
internal int InitialDataSize;
internal AutoResetEvent HeaderReceivedEvent = new AutoResetEvent(false);
}
///
///
///
public class AssetUpload : Transfer
{
public LLUUID AssetID;
public AssetType Type;
public ulong XferID;
public uint PacketNum;
}
public class ImageRequest
{
public ImageRequest(LLUUID imageid, ImageType type, float priority, int discardLevel)
{
ImageID = imageid;
Type = type;
Priority = priority;
DiscardLevel = discardLevel;
}
public LLUUID ImageID;
public ImageType Type;
public float Priority;
public int DiscardLevel;
}
#endregion Transfer Classes
///
///
///
public class AssetManager
{
#region Delegates
///
///
///
///
public delegate void AssetReceivedCallback(AssetDownload transfer, Asset asset);
///
///
///
///
public delegate void XferReceivedCallback(XferDownload xfer);
///
///
///
///
public delegate void ImageReceivedCallback(ImageDownload image, AssetTexture asset);
///
///
///
public delegate void ImageReceiveProgressCallback(LLUUID image, int recieved, int total);
///
///
///
///
public delegate void AssetUploadedCallback(AssetUpload upload);
///
///
///
///
public delegate void UploadProgressCallback(AssetUpload upload);
#endregion Delegates
#region Events
///
public event AssetReceivedCallback OnAssetReceived;
///
public event XferReceivedCallback OnXferReceived;
///
public event ImageReceivedCallback OnImageReceived;
///
public event ImageReceiveProgressCallback OnImageReceiveProgress;
///
public event AssetUploadedCallback OnAssetUploaded;
///
public event UploadProgressCallback OnUploadProgress;
#endregion Events
private SecondLife Client;
private Dictionary Transfers = new Dictionary();
private AssetUpload PendingUpload;
private AutoResetEvent PendingUploadEvent = new AutoResetEvent(true);
private TextureCache Cache;
///
/// Default constructor
///
/// A reference to the SecondLife client object
public AssetManager(SecondLife client)
{
Client = client;
Cache = new TextureCache(client);
// Transfer packets for downloading large assets
Client.Network.RegisterCallback(PacketType.TransferInfo, new NetworkManager.PacketCallback(TransferInfoHandler));
Client.Network.RegisterCallback(PacketType.TransferPacket, new NetworkManager.PacketCallback(TransferPacketHandler));
// Image downloading packets
Client.Network.RegisterCallback(PacketType.ImageData, new NetworkManager.PacketCallback(ImageDataHandler));
Client.Network.RegisterCallback(PacketType.ImagePacket, new NetworkManager.PacketCallback(ImagePacketHandler));
Client.Network.RegisterCallback(PacketType.ImageNotInDatabase, new NetworkManager.PacketCallback(ImageNotInDatabaseHandler));
// Xfer packets for uploading large assets
Client.Network.RegisterCallback(PacketType.RequestXfer, new NetworkManager.PacketCallback(RequestXferHandler));
Client.Network.RegisterCallback(PacketType.ConfirmXferPacket, new NetworkManager.PacketCallback(ConfirmXferPacketHandler));
Client.Network.RegisterCallback(PacketType.AssetUploadComplete, new NetworkManager.PacketCallback(AssetUploadCompleteHandler));
// Xfer packet for downloading misc assets
Client.Network.RegisterCallback(PacketType.SendXferPacket, new NetworkManager.PacketCallback(SendXferPacketHandler));
}
///
/// Request an asset download
///
/// Asset UUID
/// Asset type, must be correct for the transfer to succeed
/// Whether to give this transfer an elevated priority
/// The transaction ID generated for this transfer
public LLUUID RequestAsset(LLUUID assetID, AssetType type, bool priority)
{
AssetDownload transfer = new AssetDownload();
transfer.ID = LLUUID.Random();
transfer.AssetID = assetID;
//transfer.AssetType = type; // Set in TransferInfoHandler.
transfer.Priority = 100.0f + (priority ? 1.0f : 0.0f);
transfer.Channel = ChannelType.Asset;
transfer.Source = SourceType.Asset;
transfer.Simulator = Client.Network.CurrentSim;
// Add this transfer to the dictionary
lock (Transfers) Transfers[transfer.ID] = transfer;
// Build the request packet and send it
TransferRequestPacket request = new TransferRequestPacket();
request.TransferInfo.ChannelType = (int)transfer.Channel;
request.TransferInfo.Priority = transfer.Priority;
request.TransferInfo.SourceType = (int)transfer.Source;
request.TransferInfo.TransferID = transfer.ID;
byte[] paramField = new byte[20];
Array.Copy(assetID.GetBytes(), 0, paramField, 0, 16);
Array.Copy(Helpers.IntToBytes((int)type), 0, paramField, 16, 4);
request.TransferInfo.Params = paramField;
Client.Network.SendPacket(request, transfer.Simulator);
return transfer.ID;
}
///
/// Request an asset download through the almost deprecated Xfer system
///
/// Filename of the asset to request
/// Whether or not to delete the asset
/// off the server after it is retrieved
/// Use large transfer packets or not
/// UUID of the file to request, if filename is
/// left empty
/// Asset type of vFileID, or
/// AssetType.Unknown if filename is not empty
///
public ulong RequestAssetXfer(string filename, bool deleteOnCompletion, bool useBigPackets, LLUUID vFileID, AssetType vFileType)
{
LLUUID uuid = LLUUID.Random();
ulong id = uuid.GetULong();
XferDownload transfer = new XferDownload();
transfer.XferID = id;
transfer.ID = new LLUUID(id); // Our dictionary tracks transfers with LLUUIDs, so convert the ulong back
transfer.Filename = filename;
transfer.VFileID = vFileID;
transfer.AssetType = vFileType;
// Add this transfer to the dictionary
lock (Transfers) Transfers[transfer.ID] = transfer;
RequestXferPacket request = new RequestXferPacket();
request.XferID.ID = id;
request.XferID.Filename = Helpers.StringToField(filename);
request.XferID.FilePath = 4; // "Cache". This is a horrible thing that hardcodes a file path enumeration in to the
// protocol. For asset downloads we should only ever need this value
request.XferID.DeleteOnCompletion = deleteOnCompletion;
request.XferID.UseBigPackets = useBigPackets;
request.XferID.VFileID = vFileID;
request.XferID.VFileType = (short)vFileType;
Client.Network.SendPacket(request);
return id;
}
///
///
///
/// Use LLUUID.Zero if you do not have the
/// asset ID but have all the necessary permissions
/// The item ID of this asset in the inventory
/// Use LLUUID.Zero if you are not requesting an
/// asset from an object inventory
/// The owner of this asset
/// Asset type
/// Whether to prioritize this asset download or not
public LLUUID RequestInventoryAsset(LLUUID assetID, LLUUID itemID, LLUUID taskID, LLUUID ownerID, AssetType type, bool priority)
{
AssetDownload transfer = new AssetDownload();
transfer.ID = LLUUID.Random();
transfer.AssetID = assetID;
//transfer.AssetType = type; // Set in TransferInfoHandler.
transfer.Priority = 100.0f + (priority ? 1.0f : 0.0f);
transfer.Channel = ChannelType.Asset;
transfer.Source = SourceType.SimInventoryItem;
transfer.Simulator = Client.Network.CurrentSim;
// Add this transfer to the dictionary
lock (Transfers) Transfers[transfer.ID] = transfer;
// Build the request packet and send it
TransferRequestPacket request = new TransferRequestPacket();
request.TransferInfo.ChannelType = (int)transfer.Channel;
request.TransferInfo.Priority = transfer.Priority;
request.TransferInfo.SourceType = (int)transfer.Source;
request.TransferInfo.TransferID = transfer.ID;
byte[] paramField = new byte[100];
Buffer.BlockCopy(Client.Self.AgentID.GetBytes(), 0, paramField, 0, 16);
Buffer.BlockCopy(Client.Self.SessionID.GetBytes(), 0, paramField, 16, 16);
Buffer.BlockCopy(ownerID.GetBytes(), 0, paramField, 32, 16);
Buffer.BlockCopy(taskID.GetBytes(), 0, paramField, 48, 16);
Buffer.BlockCopy(itemID.GetBytes(), 0, paramField, 64, 16);
Buffer.BlockCopy(assetID.GetBytes(), 0, paramField, 80, 16);
Buffer.BlockCopy(Helpers.IntToBytes((int)type), 0, paramField, 96, 4);
request.TransferInfo.Params = paramField;
Client.Network.SendPacket(request, transfer.Simulator);
return transfer.ID;
}
public LLUUID RequestInventoryAsset(InventoryItem item, bool priority)
{
return RequestInventoryAsset(item.AssetUUID, item.UUID, LLUUID.Zero, item.OwnerID, item.AssetType, priority);
}
public void RequestEstateAsset()
{
throw new Exception("This function is not implemented yet!");
}
///
/// Initiate an image download. This is an asynchronous function
///
/// The image to download
/// Type of the image to download, either a baked
/// avatar texture or a normal texture
public void RequestImage(LLUUID imageID, ImageType type)
{
RequestImage(imageID, type, 1013000.0f, 0);
}
///
/// Initiate an image download. This is an asynchronous function
///
/// The image to download
/// Type of the image to download, either a baked
/// avatar texture or a normal texture
/// Priority level of the download. Default is
/// 1,013,000.0f
/// Number of quality layers to discard
/// Sending a priority of 0, and a discardlevel of -1 aborts
/// download
public void RequestImage(LLUUID imageID, ImageType type, float priority, int discardLevel)
{
if (Cache.HasImage(imageID)) {
ImageDownload transfer = Cache.GetCachedImage(imageID);
if (null != transfer) {
if (null != OnImageReceived) {
OnImageReceived(transfer, new AssetTexture(transfer.AssetData));
}
return;
}
}
// allows aborting of download
if (Transfers.ContainsKey(imageID) && priority.Equals(0) && discardLevel.Equals(-1))
Transfers.Remove(imageID);
if (!Transfers.ContainsKey(imageID) && !priority.Equals(0) && !discardLevel.Equals(-1))
{
ImageDownload transfer = new ImageDownload();
//transfer.AssetType = AssetType.Texture // Handled in ImageDataHandler.
transfer.ID = imageID;
transfer.Simulator = Client.Network.CurrentSim;
// Add this transfer to the dictionary
lock (Transfers) Transfers[transfer.ID] = transfer;
// Build and send the request packet
RequestImagePacket request = new RequestImagePacket();
request.AgentData.AgentID = Client.Self.AgentID;
request.AgentData.SessionID = Client.Self.SessionID;
request.RequestImage = new RequestImagePacket.RequestImageBlock[1];
request.RequestImage[0] = new RequestImagePacket.RequestImageBlock();
request.RequestImage[0].DiscardLevel = (sbyte)discardLevel;
request.RequestImage[0].DownloadPriority = priority;
request.RequestImage[0].Packet = 0;
request.RequestImage[0].Image = imageID;
request.RequestImage[0].Type = (byte)type;
Client.Network.SendPacket(request, transfer.Simulator);
}
else
{
Client.Log("RequestImage() called for an image we are already downloading, ignoring",
Helpers.LogLevel.Info);
}
}
///
/// Requests multiple Images
///
/// List of requested images
public void RequestImages(List Images)
{
for (int iri = 0; iri < Images.Count; iri++)
{
if (Transfers.ContainsKey(Images[iri].ImageID)) {
Images.RemoveAt(iri);
}
if (Cache.HasImage(Images[iri].ImageID)) {
ImageDownload transfer = Cache.GetCachedImage(Images[iri].ImageID);
if (null != transfer) {
if (null != OnImageReceived) {
OnImageReceived(transfer, new AssetTexture(transfer.AssetData));
}
Images.RemoveAt(iri);
}
}
}
if (Images.Count > 0)
{
// Build and send the request packet
RequestImagePacket request = new RequestImagePacket();
request.AgentData.AgentID = Client.Self.AgentID;
request.AgentData.SessionID = Client.Self.SessionID;
request.RequestImage = new RequestImagePacket.RequestImageBlock[Images.Count];
for (int iru = 0; iru < Images.Count; ++iru)
{
ImageDownload transfer = new ImageDownload();
//transfer.AssetType = AssetType.Texture // Handled in ImageDataHandler.
transfer.ID = Images[iru].ImageID;
transfer.Simulator = Client.Network.CurrentSim;
// Add this transfer to the dictionary
lock (Transfers) Transfers[transfer.ID] = transfer;
request.RequestImage[iru] = new RequestImagePacket.RequestImageBlock();
request.RequestImage[iru].DiscardLevel = (sbyte)Images[iru].DiscardLevel;
request.RequestImage[iru].DownloadPriority = Images[iru].Priority;
request.RequestImage[iru].Packet = 0;
request.RequestImage[iru].Image = Images[iru].ImageID;
request.RequestImage[iru].Type = (byte)Images[iru].Type;
}
Client.Network.SendPacket(request, Client.Network.CurrentSim);
}
else
{
Client.Log("RequestImages() called for an image(s) we are already downloading or an empty list, ignoring",
Helpers.LogLevel.Info);
}
}
public LLUUID RequestUpload(Asset asset, bool tempFile, bool storeLocal, bool isPriority)
{
if (asset.AssetData == null)
throw new ArgumentException("Can't upload an asset with no data (did you forget to call Encode?)");
LLUUID assetID;
LLUUID transferID = RequestUpload(out assetID, asset.AssetType, asset.AssetData, tempFile, storeLocal, isPriority);
asset.AssetID = assetID;
return transferID;
}
public LLUUID RequestUpload(AssetType type, byte[] data, bool tempFile, bool storeLocal, bool isPriority)
{
LLUUID assetID;
return RequestUpload(out assetID, type, data, tempFile, storeLocal, isPriority);
}
///
/// Initiate an asset upload
///
/// The ID this asset will have if the
/// upload succeeds
/// Asset type to upload this data as
/// Raw asset data to upload
/// Whether this is a temporary file or not
/// Whether to store this asset on the local
/// simulator or the grid-wide asset server
/// Give this upload a higher priority
/// The transaction ID of this transfer
public LLUUID RequestUpload(out LLUUID assetID, AssetType type, byte[] data, bool tempFile, bool storeLocal, bool isPriority)
{
AssetUpload upload = new AssetUpload();
upload.AssetData = data;
upload.AssetType = type;
upload.ID = LLUUID.Random();
assetID = LLUUID.Combine(upload.ID, Client.Self.SecureSessionID);
upload.AssetID = assetID;
upload.Size = data.Length;
upload.XferID = 0;
// Build and send the upload packet
AssetUploadRequestPacket request = new AssetUploadRequestPacket();
request.AssetBlock.StoreLocal = storeLocal;
request.AssetBlock.Tempfile = tempFile;
request.AssetBlock.TransactionID = upload.ID;
request.AssetBlock.Type = (sbyte)type;
if (data.Length + 100 < Settings.MAX_PACKET_SIZE)
{
Client.Log(
String.Format("Beginning asset upload [Single Packet], ID: {0}, AssetID: {1}, Size: {2}",
upload.ID.ToString(), upload.AssetID.ToString(), upload.Size),
Helpers.LogLevel.Info);
// The whole asset will fit in this packet, makes things easy
request.AssetBlock.AssetData = data;
upload.Transferred = data.Length;
}
else
{
Client.Log(
String.Format("Beginning asset upload [Multiple Packets], ID: {0}, AssetID: {1}, Size: {2}",
upload.ID.ToString(), upload.AssetID.ToString(), upload.Size),
Helpers.LogLevel.Info);
// Asset is too big, send in multiple packets
request.AssetBlock.AssetData = new byte[0];
}
//Client.DebugLog(request.ToString());
/*
// Add this upload to the Transfers dictionary using the assetID as the key.
// Once the simulator assigns an actual identifier for this upload it will be
// removed from Transfers and reinserted with the proper identifier
lock (Transfers) Transfers[upload.AssetID] = upload;
*/
// Wait for the previous upload to receive a RequestXferPacket
if (PendingUploadEvent.WaitOne(10000, false))
{
PendingUpload = upload;
Client.Network.SendPacket(request);
return upload.ID;
}
else
throw new Exception("Timeout waiting for previous asset upload to begin");
}
#region Helpers
private Asset CreateAssetWrapper(AssetType type)
{
Asset asset;
switch (type)
{
case AssetType.Notecard:
asset = new AssetNotecard();
break;
case AssetType.LSLText:
asset = new AssetScriptText();
break;
case AssetType.LSLBytecode:
asset = new AssetScriptBinary();
break;
case AssetType.Texture:
asset = new AssetTexture();
break;
case AssetType.Object:
asset = new AssetPrim();
break;
case AssetType.Clothing:
asset = new AssetClothing();
break;
case AssetType.Bodypart:
asset = new AssetBodypart();
break;
default:
Client.Log("Unimplemented asset type: " + type, Helpers.LogLevel.Error);
return null;
}
return asset;
}
private Asset WrapAsset(AssetDownload download)
{
Asset asset = CreateAssetWrapper(download.AssetType);
asset.AssetID = download.AssetID;
asset.AssetData = download.AssetData;
return asset;
}
private void SendNextUploadPacket(AssetUpload upload)
{
SendXferPacketPacket send = new SendXferPacketPacket();
send.XferID.ID = upload.XferID;
send.XferID.Packet = upload.PacketNum++;
if (send.XferID.Packet == 0)
{
// The first packet reserves the first four bytes of the data for the
// total length of the asset and appends 1000 bytes of data after that
send.DataPacket.Data = new byte[1004];
Buffer.BlockCopy(Helpers.IntToBytes(upload.Size), 0, send.DataPacket.Data, 0, 4);
Buffer.BlockCopy(upload.AssetData, 0, send.DataPacket.Data, 4, 1000);
upload.Transferred += 1000;
lock (Transfers)
{
Transfers.Remove(upload.AssetID);
Transfers[upload.ID] = upload;
}
}
else if ((send.XferID.Packet + 1) * 1000 < upload.Size)
{
// This packet is somewhere in the middle of the transfer, or a perfectly
// aligned packet at the end of the transfer
send.DataPacket.Data = new byte[1000];
Buffer.BlockCopy(upload.AssetData, upload.Transferred, send.DataPacket.Data, 0, 1000);
upload.Transferred += 1000;
}
else
{
// Special handler for the last packet which will be less than 1000 bytes
int lastlen = upload.Size - ((int)send.XferID.Packet * 1000);
send.DataPacket.Data = new byte[lastlen];
Buffer.BlockCopy(upload.AssetData, (int)send.XferID.Packet * 1000, send.DataPacket.Data, 0, lastlen);
send.XferID.Packet |= (uint)0x80000000; // This signals the final packet
upload.Transferred += lastlen;
}
Client.Network.SendPacket(send);
}
private void SendConfirmXferPacket(ulong xferID, uint packetNum)
{
ConfirmXferPacketPacket confirm = new ConfirmXferPacketPacket();
confirm.XferID.ID = xferID;
confirm.XferID.Packet = packetNum;
Client.Network.SendPacket(confirm);
}
#endregion Helpers
#region Transfer Callbacks
private void TransferInfoHandler(Packet packet, Simulator simulator)
{
if (OnAssetReceived != null)
{
TransferInfoPacket info = (TransferInfoPacket)packet;
Transfer transfer;
AssetDownload download;
if (Transfers.TryGetValue(info.TransferInfo.TransferID, out transfer))
{
download = (AssetDownload)transfer;
download.Channel = (ChannelType)info.TransferInfo.ChannelType;
download.Status = (StatusCode)info.TransferInfo.Status;
download.Target = (TargetType)info.TransferInfo.TargetType;
download.Size = info.TransferInfo.Size;
// TODO: Once we support mid-transfer status checking and aborting this
// will need to become smarter
if (download.Status != StatusCode.OK)
{
Client.Log("Transfer failed with status code " + download.Status, Helpers.LogLevel.Warning);
lock (Transfers) Transfers.Remove(download.ID);
// No data could have been received before the TransferInfo packet
download.AssetData = null;
// Fire the event with our transfer that contains Success = false;
try { OnAssetReceived(download, null); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
else
{
download.AssetData = new byte[download.Size];
if (download.Source == SourceType.Asset && info.TransferInfo.Params.Length == 20)
{
download.AssetID = new LLUUID(info.TransferInfo.Params, 0);
download.AssetType = (AssetType)(sbyte)info.TransferInfo.Params[16];
//Client.DebugLog(String.Format("TransferInfo packet received. AssetID: {0} Type: {1}",
// transfer.AssetID, type));
}
else if (download.Source == SourceType.SimInventoryItem && info.TransferInfo.Params.Length == 100)
{
// TODO: Can we use these?
LLUUID agentID = new LLUUID(info.TransferInfo.Params, 0);
LLUUID sessionID = new LLUUID(info.TransferInfo.Params, 16);
LLUUID ownerID = new LLUUID(info.TransferInfo.Params, 32);
LLUUID taskID = new LLUUID(info.TransferInfo.Params, 48);
LLUUID itemID = new LLUUID(info.TransferInfo.Params, 64);
download.AssetID = new LLUUID(info.TransferInfo.Params, 80);
download.AssetType = (AssetType)(sbyte)info.TransferInfo.Params[96];
//Client.DebugLog(String.Format("TransferInfo packet received. AgentID: {0} SessionID: {1} " +
// "OwnerID: {2} TaskID: {3} ItemID: {4} AssetID: {5} Type: {6}", agentID, sessionID,
// ownerID, taskID, itemID, transfer.AssetID, type));
}
else
{
Client.Log("Received a TransferInfo packet with a SourceType of " + download.Source.ToString() +
" and a Params field length of " + info.TransferInfo.Params.Length,
Helpers.LogLevel.Warning);
}
}
}
else
{
Client.Log("Received a TransferInfo packet for an asset we didn't request, TransferID: " +
info.TransferInfo.TransferID, Helpers.LogLevel.Warning);
}
}
}
private void TransferPacketHandler(Packet packet, Simulator simulator)
{
TransferPacketPacket asset = (TransferPacketPacket)packet;
Transfer transfer;
AssetDownload download;
if (Transfers.TryGetValue(asset.TransferData.TransferID, out transfer))
{
download = (AssetDownload)transfer;
if (download.Size == 0)
{
Client.DebugLog("TransferPacket received ahead of the transfer header, blocking...");
// We haven't received the header yet, block until it's received or times out
download.HeaderReceivedEvent.WaitOne(1000 * 5, false);
if (download.Size == 0)
{
Client.Log("Timed out while waiting for the asset header to download for " +
download.ID.ToString(), Helpers.LogLevel.Warning);
// Abort the transfer
TransferAbortPacket abort = new TransferAbortPacket();
abort.TransferInfo.ChannelType = (int)download.Channel;
abort.TransferInfo.TransferID = download.ID;
Client.Network.SendPacket(abort, download.Simulator);
download.Success = false;
lock (Transfers) Transfers.Remove(download.ID);
// Fire the event with our transfer that contains Success = false
if (OnAssetReceived != null)
{
try { OnAssetReceived(download, null); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
return;
}
}
// This assumes that every transfer packet except the last one is exactly 1000 bytes,
// hopefully that is a safe assumption to make
Buffer.BlockCopy(asset.TransferData.Data, 0, download.AssetData, 1000 * asset.TransferData.Packet,
asset.TransferData.Data.Length);
download.Transferred += asset.TransferData.Data.Length;
//Client.DebugLog(String.Format("Transfer packet {0}, received {1}/{2}/{3} bytes for asset {4}",
// asset.TransferData.Packet, asset.TransferData.Data.Length, transfer.Transferred, transfer.Size,
// transfer.AssetID.ToString()));
// Check if we downloaded the full asset
if (download.Transferred >= download.Size)
{
Client.DebugLog("Transfer for asset " + download.AssetID.ToString() + " completed");
download.Success = true;
lock (Transfers) Transfers.Remove(download.ID);
if (OnAssetReceived != null)
{
try { OnAssetReceived(download, WrapAsset(download)); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
}
}
#endregion Transfer Callbacks
#region Xfer Callbacks
private void RequestXferHandler(Packet packet, Simulator simulator)
{
if (PendingUpload == null)
Client.Log("Received a RequestXferPacket for an unknown asset upload", Helpers.LogLevel.Warning);
else
{
AssetUpload upload = PendingUpload;
PendingUpload = null;
PendingUploadEvent.Set();
RequestXferPacket request = (RequestXferPacket)packet;
upload.XferID = request.XferID.ID;
upload.Type = (AssetType)request.XferID.VFileType;
LLUUID transferID = new LLUUID(upload.XferID);
Transfers[transferID] = upload;
// Send the first packet containing actual asset data
SendNextUploadPacket(upload);
}
}
private void ConfirmXferPacketHandler(Packet packet, Simulator simulator)
{
ConfirmXferPacketPacket confirm = (ConfirmXferPacketPacket)packet;
// Building a new UUID every time an ACK is received for an upload is a horrible
// thing, but this whole Xfer system is horrible
LLUUID transferID = new LLUUID(confirm.XferID.ID);
Transfer transfer;
AssetUpload upload = null;
if (Transfers.TryGetValue(transferID, out transfer))
{
upload = (AssetUpload)transfer;
//Client.DebugLog(String.Format("ACK for upload {0} of asset type {1} ({2}/{3})",
// upload.AssetID.ToString(), upload.Type, upload.Transferred, upload.Size));
if (OnUploadProgress != null)
{
try { OnUploadProgress(upload); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
if (upload.Transferred < upload.Size)
SendNextUploadPacket(upload);
}
}
private void AssetUploadCompleteHandler(Packet packet, Simulator simulator)
{
AssetUploadCompletePacket complete = (AssetUploadCompletePacket)packet;
if (OnAssetUploaded != null)
{
bool found = false;
KeyValuePair foundTransfer = new KeyValuePair();
// Xfer system sucks really really bad. Where is the damn XferID?
lock (Transfers)
{
foreach (KeyValuePair transfer in Transfers)
{
if (transfer.Value.GetType() == typeof(AssetUpload))
{
AssetUpload upload = (AssetUpload)transfer.Value;
if ((upload).AssetID == complete.AssetBlock.UUID)
{
found = true;
foundTransfer = transfer;
upload.Success = complete.AssetBlock.Success;
upload.Type = (AssetType)complete.AssetBlock.Type;
found = true;
break;
}
}
}
}
if (found)
{
lock (Transfers) Transfers.Remove(foundTransfer.Key);
try { OnAssetUploaded((AssetUpload)foundTransfer.Value); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
}
private void SendXferPacketHandler(Packet packet, Simulator simulator)
{
SendXferPacketPacket xfer = (SendXferPacketPacket)packet;
// Lame ulong to LLUUID conversion, please go away Xfer system
LLUUID transferID = new LLUUID(xfer.XferID.ID);
Transfer transfer;
XferDownload download = null;
if (Transfers.TryGetValue(transferID, out transfer))
{
download = (XferDownload)transfer;
// Apply a mask to get rid of the "end of transfer" bit
uint packetNum = xfer.XferID.Packet & 0x0FFFFFFF;
// Check for out of order packets, possibly indicating a resend
if (packetNum != download.PacketNum)
{
if (packetNum == download.PacketNum - 1)
{
Client.DebugLog("Resending Xfer download confirmation for packet " + packetNum);
SendConfirmXferPacket(download.XferID, packetNum);
}
else
{
Client.Log("Out of order Xfer packet in a download, got " + packetNum + " expecting " + download.PacketNum,
Helpers.LogLevel.Warning);
}
return;
}
if (packetNum == 0)
{
// This is the first packet received in the download, the first four bytes are a network order size integer
download.Size = (int)Helpers.BytesToUIntBig(xfer.DataPacket.Data);
download.AssetData = new byte[download.Size];
Buffer.BlockCopy(xfer.DataPacket.Data, 4, download.AssetData, 0, xfer.DataPacket.Data.Length - 4);
download.Transferred += xfer.DataPacket.Data.Length - 4;
}
else
{
Buffer.BlockCopy(xfer.DataPacket.Data, 0, download.AssetData, 1000 * (int)packetNum, xfer.DataPacket.Data.Length);
download.Transferred += xfer.DataPacket.Data.Length;
}
// Increment the packet number to the packet we are expecting next
download.PacketNum++;
// Confirm receiving this packet
SendConfirmXferPacket(download.XferID, packetNum);
if ((xfer.XferID.Packet & 0x80000000) != 0)
{
// This is the last packet in the transfer
if (!String.IsNullOrEmpty(download.Filename))
Client.DebugLog("Xfer download for asset " + download.Filename + " completed");
else
Client.DebugLog("Xfer download for asset " + download.VFileID.ToString() + " completed");
download.Success = true;
lock (Transfers) Transfers.Remove(download.ID);
if (OnXferReceived != null)
{
try { OnXferReceived(download); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
}
}
#endregion Xfer Callbacks
#region Image Callbacks
///
/// Handles the Image Data packet which includes the ID and Size of the image,
/// along with the first block of data for the image. If the image is small enough
/// there will be no additional packets
///
private void ImageDataHandler(Packet packet, Simulator simulator)
{
ImageDataPacket data = (ImageDataPacket)packet;
ImageDownload transfer = null;
lock (Transfers)
{
if (Transfers.ContainsKey(data.ImageID.ID))
{
transfer = (ImageDownload)Transfers[data.ImageID.ID];
//Client.DebugLog("Received first " + data.ImageData.Data.Length + " bytes for image " +
// data.ImageID.ID.ToString());
if (OnImageReceiveProgress != null) {
OnImageReceiveProgress(data.ImageID.ID, data.ImageData.Data.Length, transfer.Size);
}
transfer.Codec = data.ImageID.Codec;
transfer.PacketCount = data.ImageID.Packets;
transfer.Size = (int)data.ImageID.Size;
transfer.AssetData = new byte[transfer.Size];
transfer.AssetType = AssetType.Texture;
Buffer.BlockCopy(data.ImageData.Data, 0, transfer.AssetData, 0, data.ImageData.Data.Length);
transfer.InitialDataSize = data.ImageData.Data.Length;
transfer.Transferred += data.ImageData.Data.Length;
// Check if we downloaded the full image
if (transfer.Transferred >= transfer.Size)
{
Transfers.Remove(transfer.ID);
transfer.Success = true;
Cache.SaveImageToCache(transfer.ID, transfer.AssetData);
}
}
}
if (transfer != null)
{
transfer.HeaderReceivedEvent.Set();
if (OnImageReceived != null && transfer.Transferred >= transfer.Size)
{
try { OnImageReceived(transfer, new AssetTexture(transfer.AssetData)); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
}
///
/// Handles the remaining Image data that did not fit in the initial ImageData packet
///
private void ImagePacketHandler(Packet packet, Simulator simulator)
{
ImagePacketPacket image = (ImagePacketPacket)packet;
ImageDownload transfer = null;
lock (Transfers)
{
if (Transfers.ContainsKey(image.ImageID.ID))
{
transfer = (ImageDownload)Transfers[image.ImageID.ID];
if (transfer.Size == 0)
{
// We haven't received the header yet, block until it's received or times out
transfer.HeaderReceivedEvent.WaitOne(1000 * 5, false);
if (transfer.Size == 0)
{
Client.Log("Timed out while waiting for the image header to download for " +
transfer.ID.ToString(), Helpers.LogLevel.Warning);
transfer.Success = false;
Transfers.Remove(transfer.ID);
goto Callback;
}
}
// The header is downloaded, we can insert this data in to the proper position
Array.Copy(image.ImageData.Data, 0, transfer.AssetData, transfer.InitialDataSize +
(1000 * (image.ImageID.Packet - 1)), image.ImageData.Data.Length);
transfer.Transferred += image.ImageData.Data.Length;
if (OnImageReceiveProgress != null) {
OnImageReceiveProgress(image.ImageID.ID, transfer.Transferred, transfer.Size);
}
//Client.DebugLog("Received " + image.ImageData.Data.Length + "/" + transfer.Transferred +
// "/" + transfer.Size + " bytes for image " + image.ImageID.ID.ToString());
// Check if we downloaded the full image
if (transfer.Transferred >= transfer.Size)
{
Cache.SaveImageToCache(transfer.ID, transfer.AssetData);
transfer.Success = true;
Transfers.Remove(transfer.ID);
}
}
}
Callback:
if (transfer != null && OnImageReceived != null && (transfer.Transferred >= transfer.Size || transfer.Size == 0))
{
try { OnImageReceived(transfer, new AssetTexture(transfer.AssetData)); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
///
/// The requested image does not exist on the asset server
///
private void ImageNotInDatabaseHandler(Packet packet, Simulator simulator)
{
ImageNotInDatabasePacket notin = (ImageNotInDatabasePacket)packet;
ImageDownload transfer = null;
lock (Transfers)
{
if (Transfers.ContainsKey(notin.ImageID.ID))
{
transfer = (ImageDownload)Transfers[notin.ImageID.ID];
transfer.NotFound = true;
Transfers.Remove(transfer.ID);
}
}
// Fire the event with our transfer that contains Success = false;
if (transfer != null && OnImageReceived != null)
{
try { OnImageReceived(transfer, null); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
}
}
#endregion Image Callbacks
}
#region Texture Cache
///
/// Class that handles the local image cache
///
public class TextureCache
{
private SecondLife Client;
///
/// Default constructor
///
/// A reference to the SecondLife client object
public TextureCache(SecondLife client)
{
Client = client;
}
///
/// Return bytes read from the local image cache, null if it does not exist
///
/// LLUUID of the image we want to get
/// Raw bytes of the image, or null on failure
public byte[] GetCachedImageBytes(LLUUID imageID)
{
if (!Operational()) {
return null;
}
try {
Client.Log("Reading " + FileName(imageID) + " from texture cache.", Helpers.LogLevel.Debug);
byte[] data = File.ReadAllBytes(FileName(imageID));
return data;
} catch (Exception ex) {
Client.Log("Failed reading image from cache (" + ex.Message + ")", Helpers.LogLevel.Warning);
return null;
}
}
///
/// Returns ImageDownload object of the
/// image from the local image cache, null if it does not exist
///
/// LLUUID of the image we want to get
/// ImageDownload object containing the image, or null on failure
public ImageDownload GetCachedImage(LLUUID imageID)
{
if (!Operational()) {
return null;
}
byte[] imageData = GetCachedImageBytes(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;
}
///
/// Constructs a file name of the cached image
///
/// LLUUID of the image
/// String with the file name of the cahced image
private string FileName(LLUUID imageID)
{
return Client.Settings.TEXTURE_CACHE_DIR + "/" + imageID.ToString();
}
///
/// Saves an image to the local cache
///
/// LLUUID of the image
/// Raw bytes the image consists of
/// Weather the operation was successfull
public bool SaveImageToCache(LLUUID imageID, byte[] imageData)
{
if (!Operational()) {
return false;
}
try {
Client.Log("Saving " + FileName(imageID) + " to texture cache.", Helpers.LogLevel.Debug);
if (!Directory.Exists(Client.Settings.TEXTURE_CACHE_DIR)) {
Directory.CreateDirectory(Client.Settings.TEXTURE_CACHE_DIR);
}
File.WriteAllBytes(FileName(imageID), imageData);
} catch (Exception ex) {
Client.Log("Failed saving image to cache (" + ex.Message + ")", Helpers.LogLevel.Warning);
return false;
}
return true;
}
///
/// Checks if the image exists in the local cache
///
/// LLUUID of the image
public bool HasImage(LLUUID imageID)
{
if (!Operational()) {
return false;
}
return File.Exists(FileName(imageID));
}
///
/// Checks weather caching is enabled
///
private bool Operational()
{
return Client.Settings.USE_TEXTURE_CACHE;
}
}
#endregion
}