initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.CacheServer
|
||||
{
|
||||
/// <summary>
|
||||
/// The CacheServerUploader is responsible for uploading assets to a given Cache Server.
|
||||
/// </summary>
|
||||
public static class CacheServerUploader
|
||||
{
|
||||
private struct Transaction
|
||||
{
|
||||
public struct FileInfo
|
||||
{
|
||||
public readonly FileType type;
|
||||
public readonly string path;
|
||||
|
||||
public FileInfo(FileType type, string path)
|
||||
{
|
||||
this.type = type;
|
||||
this.path = path;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly FileId fileId;
|
||||
public readonly FileInfo[] files;
|
||||
|
||||
private Transaction(FileId fileId, FileInfo[] files)
|
||||
{
|
||||
this.fileId = fileId;
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
public static Transaction CreateForAssetPath(string assetPath)
|
||||
{
|
||||
var projectRoot = Directory.GetParent(Application.dataPath).FullName;
|
||||
|
||||
var guid = AssetDatabase.AssetPathToGUID(assetPath);
|
||||
var hash = AssetDatabase.GetAssetDependencyHash(assetPath);
|
||||
|
||||
var libPath =
|
||||
new[] { projectRoot, "Library", "metadata", guid.Substring(0, 2), guid }
|
||||
.Aggregate(string.Empty, Path.Combine);
|
||||
|
||||
if (!File.Exists(libPath))
|
||||
{
|
||||
throw new Exception("Cannot find Library representation for GUID " + guid);
|
||||
}
|
||||
|
||||
var files = new List<FileInfo>
|
||||
{
|
||||
new FileInfo(FileType.Asset, libPath)
|
||||
};
|
||||
|
||||
var infoLibPath = libPath + ".info";
|
||||
if (File.Exists(infoLibPath))
|
||||
{
|
||||
files.Add(new FileInfo(FileType.Info, infoLibPath));
|
||||
}
|
||||
|
||||
var resLibPath = libPath + ".resource";
|
||||
if (File.Exists(resLibPath))
|
||||
{
|
||||
files.Add(new FileInfo(FileType.Resource, resLibPath));
|
||||
}
|
||||
|
||||
return new Transaction(FileId.From(guid, hash.ToString()), files.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize project library with the configured Cache Server.
|
||||
/// </summary>
|
||||
public static void UploadAllFilesToCacheServer()
|
||||
{
|
||||
string host;
|
||||
int port;
|
||||
Util.ParseCacheServerIpAddress(Util.ConfigCacheServerAddress, out host, out port);
|
||||
UploadAllFilesToCacheServer(host, port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize project library folder with a remote Cache Server.
|
||||
/// </summary>
|
||||
/// <param name="host">Host name or IP or remote Cache Server</param>
|
||||
/// <param name="port">Port number for remote Cache Server</param>
|
||||
public static void UploadAllFilesToCacheServer(string host, int port)
|
||||
{
|
||||
var client = new Client(host, port);
|
||||
client.Connect();
|
||||
|
||||
var assetPaths = AssetDatabase.GetAllAssetPaths();
|
||||
var len = assetPaths.Length;
|
||||
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var path = assetPaths[i];
|
||||
if (!File.Exists(path))
|
||||
continue;
|
||||
|
||||
var progress = (float)(i + 1) / (len + 1);
|
||||
|
||||
if (EditorUtility.DisplayCancelableProgressBar("Uploading to Cache Server", path, progress)) break;
|
||||
|
||||
try
|
||||
{
|
||||
var trx = Transaction.CreateForAssetPath(path);
|
||||
client.BeginTransaction(trx.fileId);
|
||||
|
||||
foreach (var file in trx.files)
|
||||
using (var stream = new FileStream(file.path, FileMode.Open, FileAccess.Read))
|
||||
client.Upload(file.type, stream);
|
||||
|
||||
client.EndTransaction();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EditorUtility.ClearProgressBar();
|
||||
client.Close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e766efa9ccaf441728f810262e5ca359
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.CacheServer
|
||||
{
|
||||
/// <summary>
|
||||
/// The Cache Server Uploader window. This interface will upload your assets to a the given address of a Cache Server.
|
||||
/// </summary>
|
||||
public class CacheServerUploaderWindow : EditorWindow
|
||||
{
|
||||
private string m_address;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_address = Util.ConfigCacheServerAddress;
|
||||
titleContent = new GUIContent("CS Upload");
|
||||
}
|
||||
|
||||
private bool ValidateAddress()
|
||||
{
|
||||
string host;
|
||||
int port;
|
||||
Util.ParseCacheServerIpAddress(m_address, out host, out port);
|
||||
|
||||
var c = new Client(host, port);
|
||||
try
|
||||
{
|
||||
c.Connect();
|
||||
c.Close();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("Cache Server Address: ");
|
||||
m_address = GUILayout.TextField(m_address);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
if (GUILayout.Button("Upload") && EditorUtility.DisplayDialog("Upload to Cache Server",
|
||||
"This will upload all assets in your Library folder to the specified Cache Server.", "Continue", "Cancel"))
|
||||
{
|
||||
GetWindow<CacheServerUploaderWindow>().Close();
|
||||
if (!ValidateAddress())
|
||||
{
|
||||
Debug.LogError("Could not connect to Cache Server");
|
||||
return;
|
||||
}
|
||||
|
||||
string host;
|
||||
int port;
|
||||
Util.ParseCacheServerIpAddress(m_address, out host, out port);
|
||||
CacheServerUploader.UploadAllFilesToCacheServer(host, port);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Cancel"))
|
||||
{
|
||||
GetWindow<CacheServerUploaderWindow>().Close();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void OnInspectorUpdate()
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads all assets to the cache server.
|
||||
/// </summary>
|
||||
[MenuItem("Assets/Cache Server/Upload All Assets")]
|
||||
public static void UploadAllFilesToCacheServerMenuItem()
|
||||
{
|
||||
var window = GetWindow<CacheServerUploaderWindow>();
|
||||
window.ShowUtility();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c18f0df0496b5418bbb5a34c4c30d6e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,541 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using UnityEngine;
|
||||
using System.Threading;
|
||||
|
||||
namespace UnityEditor.Build.CacheServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Options for the type of a particular file.
|
||||
/// </summary>
|
||||
public enum FileType
|
||||
{
|
||||
/// <summary>
|
||||
/// Use to indicate that the file is an asset.
|
||||
/// </summary>
|
||||
Asset = 'a',
|
||||
/// <summary>
|
||||
/// Use to indicate that the file holds information for an asset/resource.
|
||||
/// </summary>
|
||||
Info = 'i',
|
||||
/// <summary>
|
||||
/// Use to indicate that the file is a resource.
|
||||
/// </summary>
|
||||
Resource = 'r'
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for the result returned by a download operation.
|
||||
/// </summary>
|
||||
public enum DownloadResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Use to indicate that the operation failed.
|
||||
/// </summary>
|
||||
Failure = 0,
|
||||
/// <summary>
|
||||
/// Use to indicate that the operation failed because it could not locate the specified file.
|
||||
/// </summary>
|
||||
FileNotFound = 1,
|
||||
/// <summary>
|
||||
/// Use to indicate that the operation succedeed.
|
||||
/// </summary>
|
||||
Success = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A GUID/Hash pair that uniquely identifies a particular file. For each FileId, the Cache Server can store a separate
|
||||
/// binary stream for each FileType.
|
||||
/// </summary>
|
||||
public struct FileId : IEqualityComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// The guid byte array.
|
||||
/// </summary>
|
||||
public readonly byte[] guid;
|
||||
|
||||
/// <summary>
|
||||
/// The hash code byte array.
|
||||
/// </summary>
|
||||
public readonly byte[] hash;
|
||||
|
||||
/// <summary>
|
||||
/// A structure used to identify a file by guid and hash code.
|
||||
/// </summary>
|
||||
/// <param name="guid">File GUID.</param>
|
||||
/// <param name="hash">File hash code.</param>
|
||||
private FileId(byte[] guid, byte[] hash)
|
||||
{
|
||||
this.guid = guid;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a FileId given a string guid and string hash code representation.
|
||||
/// </summary>
|
||||
/// <param name="guidStr">GUID string representation.</param>
|
||||
/// <param name="hashStr">Hash code string representation.</param>
|
||||
/// <returns></returns>
|
||||
public static FileId From(string guidStr, string hashStr)
|
||||
{
|
||||
if (guidStr.Length != 32)
|
||||
throw new ArgumentException("Length != 32", "guidStr");
|
||||
|
||||
if (hashStr.Length != 32)
|
||||
throw new ArgumentException("Length != 32", "hashStr");
|
||||
|
||||
return new FileId(Util.StringToGuid(guidStr), Util.StringToHash(hashStr));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a FileId given a byte array guid and byte array hash code.
|
||||
/// </summary>
|
||||
/// <param name="guid">GUID byte array.</param>
|
||||
/// <param name="hash">Hash code byte array.</param>
|
||||
/// <returns></returns>
|
||||
public static FileId From(byte[] guid, byte[] hash)
|
||||
{
|
||||
if (guid.Length != 16)
|
||||
throw new ArgumentException("Length != 32", "guid");
|
||||
|
||||
if (hash.Length != 16)
|
||||
throw new ArgumentException("Length != 32", "hash");
|
||||
|
||||
return new FileId(guid, hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check equality of two objects given their guid and hash code.
|
||||
/// </summary>
|
||||
/// <param name="x">lhs object.</param>
|
||||
/// <param name="y">rhs object.</param>
|
||||
/// <returns></returns>
|
||||
public new bool Equals(object x, object y)
|
||||
{
|
||||
var hash1 = (byte[])x;
|
||||
var hash2 = (byte[])y;
|
||||
|
||||
if (hash1.Length != hash2.Length)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < hash1.Length; i++)
|
||||
if (hash1[i] != hash2[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the hash code for a specific object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object you want the hash code for.</param>
|
||||
/// <returns></returns>
|
||||
public int GetHashCode(object obj)
|
||||
{
|
||||
var hc = 17;
|
||||
hc = hc * 23 + guid.GetHashCode();
|
||||
hc = hc * 23 + hash.GetHashCode();
|
||||
return hc;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when an upload operation is not properly isolated within a begin/end transaction
|
||||
/// </summary>
|
||||
public class TransactionIsolationException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new exception for when an upload operation is not properly isolated within a begin/end transaction.
|
||||
/// </summary>
|
||||
/// <param name="msg">The text containing information to display.</param>
|
||||
public TransactionIsolationException(string msg) : base(msg) {}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs passed to the DownloadFinished event handler
|
||||
/// </summary>
|
||||
public class DownloadFinishedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// EventArgs download result code.
|
||||
/// </summary>
|
||||
public DownloadResult Result { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The downloaded item.
|
||||
/// </summary>
|
||||
public IDownloadItem DownloadItem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The size of the downloaded item.
|
||||
/// </summary>
|
||||
public long Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of the download queue.
|
||||
/// </summary>
|
||||
public long DownloadQueueLength { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A client API for uploading and downloading files from a Cache Server
|
||||
/// </summary>
|
||||
public class Client
|
||||
{
|
||||
private enum StreamReadState
|
||||
{
|
||||
Response,
|
||||
Size,
|
||||
Id
|
||||
}
|
||||
|
||||
private const int ProtocolVersion = 254;
|
||||
private const string CmdTrxBegin = "ts";
|
||||
private const string CmdTrxEnd = "te";
|
||||
private const string CmdGet = "g";
|
||||
private const string CmdPut = "p";
|
||||
private const string CmdQuit = "q";
|
||||
|
||||
private const int ResponseLen = 2;
|
||||
private const int SizeLen = 16;
|
||||
private const int GuidLen = 16;
|
||||
private const int HashLen = 16;
|
||||
private const int IdLen = GuidLen + HashLen;
|
||||
private const int ReadBufferLen = 64 * 1024;
|
||||
|
||||
private readonly Queue<IDownloadItem> m_downloadQueue;
|
||||
private readonly TcpClient m_tcpClient;
|
||||
private readonly string m_host;
|
||||
private readonly int m_port;
|
||||
internal Stream m_stream;
|
||||
private Mutex m_mutex;
|
||||
private readonly byte[] m_streamReadBuffer;
|
||||
private int m_streamBytesRead;
|
||||
private int m_streamBytesNeeded;
|
||||
private StreamReadState m_streamReadState = StreamReadState.Response;
|
||||
private DownloadFinishedEventArgs m_nextFileCompleteEventArgs;
|
||||
private Stream m_nextWriteStream;
|
||||
private bool m_inTrx;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of items in the download queue
|
||||
/// </summary>
|
||||
public int DownloadQueueLength
|
||||
{
|
||||
get { return m_downloadQueue.Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when a queued download request finishes.
|
||||
/// </summary>
|
||||
public event EventHandler<DownloadFinishedEventArgs> DownloadFinished;
|
||||
|
||||
/// <summary>
|
||||
/// Remove all listeners from the DownloadFinished event
|
||||
/// </summary>
|
||||
public void ResetDownloadFinishedEventHandler()
|
||||
{
|
||||
DownloadFinished = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new Cache Server client
|
||||
/// </summary>
|
||||
/// <param name="host">The host name or IP of the Cache Server.</param>
|
||||
/// <param name="port">The port number of the Cache Server. Default port is 8126.</param>
|
||||
public Client(string host, int port = 8126)
|
||||
{
|
||||
m_streamReadBuffer = new byte[ReadBufferLen];
|
||||
m_downloadQueue = new Queue<IDownloadItem>();
|
||||
m_tcpClient = new TcpClient();
|
||||
m_host = host;
|
||||
m_port = port;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the Cache Server and sends a protocol version handshake.
|
||||
/// </summary>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public void Connect()
|
||||
{
|
||||
var client = m_tcpClient;
|
||||
client.Connect(m_host, m_port);
|
||||
m_stream = client.GetStream();
|
||||
m_stream.ReadTimeout = 10000;
|
||||
m_stream.WriteTimeout = 10000;
|
||||
SendVersion();
|
||||
m_mutex = new Mutex();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the Cache Server and sends a protocol version handshake. A TimeoutException is thrown if the connection cannot
|
||||
/// be established within <paramref name="timeoutMs"/> milliseconds.
|
||||
/// </summary>
|
||||
/// <param name="timeoutMs"></param>
|
||||
/// <exception cref="TimeoutException"></exception>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public void Connect(int timeoutMs)
|
||||
{
|
||||
var client = m_tcpClient;
|
||||
var op = client.BeginConnect(m_host, m_port, null, null);
|
||||
|
||||
var connected = op.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(timeoutMs));
|
||||
|
||||
if (!connected)
|
||||
throw new TimeoutException();
|
||||
|
||||
m_stream = client.GetStream();
|
||||
SendVersion();
|
||||
m_mutex = new Mutex();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin an upload transaction for an asset. Transactions in process can be interupted by calling BeginTransaction
|
||||
/// again before calling EndTransaction.
|
||||
/// </summary>
|
||||
/// <param name="fileId"></param>
|
||||
public void BeginTransaction(FileId fileId)
|
||||
{
|
||||
m_inTrx = true;
|
||||
m_stream.Write(Encoding.ASCII.GetBytes(CmdTrxBegin), 0, 2);
|
||||
m_stream.Write(fileId.guid, 0, GuidLen);
|
||||
m_stream.Write(fileId.hash, 0, HashLen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Upload from the given stream for the given FileType. Will throw an exception if not preceeded by BeginTransaction.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="readStream"></param>
|
||||
/// <exception cref="TransactionIsolationException"></exception>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public void Upload(FileType type, Stream readStream)
|
||||
{
|
||||
if (!m_inTrx)
|
||||
throw new TransactionIsolationException("Upload without BeginTransaction");
|
||||
|
||||
if (!readStream.CanRead || !readStream.CanSeek)
|
||||
throw new ArgumentException();
|
||||
|
||||
m_stream.Write(Encoding.ASCII.GetBytes(CmdPut + (char)type), 0, 2);
|
||||
m_stream.Write(Util.EncodeInt64(readStream.Length), 0, SizeLen);
|
||||
|
||||
var buf = new byte[ReadBufferLen];
|
||||
while (readStream.Position < readStream.Length - 1)
|
||||
{
|
||||
var len = readStream.Read(buf, 0, ReadBufferLen);
|
||||
m_stream.Write(buf, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commit the uploaded files to the Cache Server. Will throw an exception if not preceeded by BeginTransaction.
|
||||
/// </summary>
|
||||
/// <exception cref="TransactionIsolationException"></exception>
|
||||
public void EndTransaction()
|
||||
{
|
||||
if (!m_inTrx)
|
||||
throw new TransactionIsolationException("EndTransaction without BeginTransaction");
|
||||
|
||||
m_inTrx = false;
|
||||
m_stream.Write(Encoding.ASCII.GetBytes(CmdTrxEnd), 0, 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a download request to the Cache Server. Listen to the DownloadComplete event to read the results.
|
||||
/// </summary>
|
||||
/// <param name="downloadItem">The IDownloadItem that specifies which file to download</param>
|
||||
public void QueueDownload(IDownloadItem downloadItem)
|
||||
{
|
||||
m_stream.Write(Encoding.ASCII.GetBytes(CmdGet + (char)downloadItem.Type), 0, 2);
|
||||
m_stream.Write(downloadItem.Id.guid, 0, GuidLen);
|
||||
m_stream.Write(downloadItem.Id.hash, 0, HashLen);
|
||||
|
||||
m_mutex.WaitOne();
|
||||
m_downloadQueue.Enqueue(downloadItem);
|
||||
int count = m_downloadQueue.Count;
|
||||
m_mutex.ReleaseMutex();
|
||||
|
||||
if (count == 1)
|
||||
ReadNextDownloadResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close the connection to the Cache Server. Sends the 'quit' command and closes the network stream.
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
if (m_stream != null)
|
||||
m_stream.Write(Encoding.ASCII.GetBytes(CmdQuit), 0, 1);
|
||||
|
||||
if (m_tcpClient != null)
|
||||
m_tcpClient.Close();
|
||||
|
||||
if (m_mutex != null)
|
||||
{
|
||||
m_mutex.Dispose();
|
||||
m_mutex = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void SendVersion()
|
||||
{
|
||||
var encodedVersion = Util.EncodeInt32(ProtocolVersion, true);
|
||||
m_stream.Write(encodedVersion, 0, encodedVersion.Length);
|
||||
|
||||
var versionBuf = new byte[8];
|
||||
var pos = 0;
|
||||
while (pos < versionBuf.Length - 1)
|
||||
{
|
||||
pos += m_stream.Read(versionBuf, 0, versionBuf.Length);
|
||||
}
|
||||
|
||||
if (Util.ReadUInt32(versionBuf, 0) != ProtocolVersion)
|
||||
throw new Exception("Server version mismatch");
|
||||
}
|
||||
|
||||
private void OnDownloadFinished(DownloadFinishedEventArgs e)
|
||||
{
|
||||
m_mutex.WaitOne();
|
||||
m_downloadQueue.Dequeue();
|
||||
int count = m_downloadQueue.Count;
|
||||
m_mutex.ReleaseMutex();
|
||||
|
||||
e.DownloadQueueLength = count;
|
||||
if (DownloadFinished != null)
|
||||
DownloadFinished(this, e);
|
||||
|
||||
if (count > 0)
|
||||
ReadNextDownloadResult();
|
||||
}
|
||||
|
||||
internal void ReadNextDownloadResult()
|
||||
{
|
||||
m_streamReadState = StreamReadState.Response;
|
||||
m_streamBytesNeeded = ResponseLen;
|
||||
m_streamBytesRead = 0;
|
||||
m_nextFileCompleteEventArgs = new DownloadFinishedEventArgs { Result = DownloadResult.Failure };
|
||||
BeginReadHeader();
|
||||
}
|
||||
|
||||
private void BeginReadHeader()
|
||||
{
|
||||
m_stream.BeginRead(m_streamReadBuffer,
|
||||
m_streamBytesRead,
|
||||
m_streamBytesNeeded - m_streamBytesRead,
|
||||
EndReadHeader,
|
||||
m_stream);
|
||||
}
|
||||
|
||||
internal Action<int, byte[]> OnReadHeader;
|
||||
|
||||
private void EndReadHeader(IAsyncResult r)
|
||||
{
|
||||
var bytesRead = m_stream.EndRead(r);
|
||||
if (bytesRead <= 0) return;
|
||||
|
||||
m_streamBytesRead += bytesRead;
|
||||
|
||||
if (OnReadHeader != null)
|
||||
OnReadHeader(m_streamBytesRead, m_streamReadBuffer);
|
||||
|
||||
if (m_streamBytesRead < m_streamBytesNeeded)
|
||||
{
|
||||
BeginReadHeader();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_streamReadState)
|
||||
{
|
||||
case StreamReadState.Response:
|
||||
if (Convert.ToChar(m_streamReadBuffer[0]) == '+')
|
||||
{
|
||||
m_streamReadState = StreamReadState.Size;
|
||||
m_streamBytesNeeded = SizeLen;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nextFileCompleteEventArgs.Result = DownloadResult.FileNotFound;
|
||||
m_streamReadState = StreamReadState.Id;
|
||||
m_streamBytesNeeded = IdLen;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case StreamReadState.Size:
|
||||
m_nextFileCompleteEventArgs.Size = Util.ReadUInt64(m_streamReadBuffer, 0);
|
||||
m_streamReadState = StreamReadState.Id;
|
||||
m_streamBytesNeeded = IdLen;
|
||||
break;
|
||||
|
||||
case StreamReadState.Id:
|
||||
m_mutex.WaitOne();
|
||||
var next = m_downloadQueue.Peek();
|
||||
m_mutex.ReleaseMutex();
|
||||
m_nextFileCompleteEventArgs.DownloadItem = next;
|
||||
|
||||
var match =
|
||||
Util.ByteArraysAreEqual(next.Id.guid, 0, m_streamReadBuffer, 0, GuidLen) &&
|
||||
Util.ByteArraysAreEqual(next.Id.hash, 0, m_streamReadBuffer, GuidLen, HashLen);
|
||||
|
||||
if (!match)
|
||||
{
|
||||
Close();
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
if (m_nextFileCompleteEventArgs.Result == DownloadResult.FileNotFound)
|
||||
{
|
||||
OnDownloadFinished(m_nextFileCompleteEventArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
var size = m_nextFileCompleteEventArgs.Size;
|
||||
m_nextWriteStream = next.GetWriteStream(size);
|
||||
m_streamBytesNeeded = (int)size;
|
||||
m_streamBytesRead = 0;
|
||||
BeginReadData();
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
m_streamBytesRead = 0;
|
||||
BeginReadHeader();
|
||||
}
|
||||
|
||||
private void BeginReadData()
|
||||
{
|
||||
var len = Math.Min(ReadBufferLen, m_streamBytesNeeded - m_streamBytesRead);
|
||||
m_stream.BeginRead(m_streamReadBuffer, 0, len, EndReadData, null);
|
||||
}
|
||||
|
||||
private void EndReadData(IAsyncResult readResult)
|
||||
{
|
||||
var bytesRead = m_stream.EndRead(readResult);
|
||||
Debug.Assert(bytesRead > 0);
|
||||
m_streamBytesRead += bytesRead;
|
||||
|
||||
var writeResult = m_nextWriteStream.BeginWrite(m_streamReadBuffer, 0, bytesRead, null, null);
|
||||
m_nextWriteStream.EndWrite(writeResult);
|
||||
|
||||
if (m_streamBytesRead < m_streamBytesNeeded)
|
||||
{
|
||||
BeginReadData();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nextFileCompleteEventArgs.DownloadItem.Finish();
|
||||
m_nextFileCompleteEventArgs.Result = DownloadResult.Success;
|
||||
OnDownloadFinished(m_nextFileCompleteEventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 02aa99c7f5d8f4c77810419ebddad96d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,82 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.CacheServer
|
||||
{
|
||||
/// <summary>
|
||||
/// IDownloadItem implementation for downloading to a file specified by path.
|
||||
/// </summary>
|
||||
public class FileDownloadItem : IDownloadItem
|
||||
{
|
||||
private Stream m_writeStream;
|
||||
private string m_tmpPath;
|
||||
|
||||
/// <summary>
|
||||
/// The FileId for the FileDownloadItem. FileId consists of an assets guid and hash code.
|
||||
/// </summary>
|
||||
public FileId Id { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the FileDownloadItems desired item.
|
||||
/// </summary>
|
||||
public FileType Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// File path where downloaded file data is saved. Data is first written to a temporary file location, and moved
|
||||
/// into place when the Finish() method is called by the Cache Server Client.
|
||||
/// </summary>
|
||||
public string FilePath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the write stream for a given FileDownloadItem. If one does not exist, it will be created.
|
||||
/// </summary>
|
||||
/// <param name="size"></param>
|
||||
/// <returns></returns>
|
||||
public Stream GetWriteStream(long size)
|
||||
{
|
||||
if (m_writeStream == null)
|
||||
{
|
||||
m_tmpPath = Path.GetTempFileName();
|
||||
m_writeStream = new FileStream(m_tmpPath, FileMode.Create, FileAccess.Write);
|
||||
}
|
||||
|
||||
return m_writeStream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new FileDownloadItem
|
||||
/// </summary>
|
||||
/// <param name="fileId">The FileId of the desired item.</param>
|
||||
/// <param name="fileType">The FileType of the desired item.</param>
|
||||
/// <param name="path">The path of the desired item.</param>
|
||||
public FileDownloadItem(FileId fileId, FileType fileType, string path)
|
||||
{
|
||||
Id = fileId;
|
||||
Type = fileType;
|
||||
FilePath = path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose the FileDownloadItems write stream and move the data from the temporary path to its final destination.
|
||||
/// </summary>
|
||||
public void Finish()
|
||||
{
|
||||
if (m_writeStream == null)
|
||||
return;
|
||||
|
||||
m_writeStream.Dispose();
|
||||
try
|
||||
{
|
||||
if (File.Exists(FilePath))
|
||||
File.Delete(FilePath);
|
||||
|
||||
File.Move(m_tmpPath, FilePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e253c6be7d9134114a9b533965c2b45f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,33 @@
|
|||
using System.IO;
|
||||
|
||||
namespace UnityEditor.Build.CacheServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single file download request from a Cache Server.
|
||||
/// </summary>
|
||||
public interface IDownloadItem
|
||||
{
|
||||
/// <summary>
|
||||
/// the FileId (guid/hash pair) of the file to download
|
||||
/// </summary>
|
||||
FileId Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// the FileType for the given FileId to download
|
||||
/// </summary>
|
||||
FileType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides a writable stream for saving downloaded file bytes
|
||||
/// </summary>
|
||||
/// <param name="size">Size of file to download</param>
|
||||
/// <returns>A writable stream</returns>
|
||||
Stream GetWriteStream(long size);
|
||||
|
||||
/// <summary>
|
||||
/// Method called when a download is finished. Used to finalize and cleanup a single file download. e.g. to move
|
||||
/// a temporary file into place.
|
||||
/// </summary>
|
||||
void Finish();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 31314932afa7e4ce3ab642bfbc2dd8ff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "UnityEditor.CacheServer",
|
||||
"references": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": []
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 27fb11404301449859aa664f4cb020e0
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,163 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
[assembly: InternalsVisibleTo("UnityEditor.CacheServerTests")]
|
||||
|
||||
namespace UnityEditor.Build.CacheServer
|
||||
{
|
||||
internal static class Util
|
||||
{
|
||||
private const string IpAddressKey = "CacheServerIPAddress";
|
||||
|
||||
private static int ReverseByte(int b)
|
||||
{
|
||||
return ((b & 0x0F) << 4) | ((b >> 4) & 0x0F);
|
||||
}
|
||||
|
||||
private static byte[] StringToByteArray(string input, bool asGuid)
|
||||
{
|
||||
var bytes = new byte[input.Length / 2];
|
||||
for (var i = 0; i < input.Length; i += 2)
|
||||
{
|
||||
var b = Convert.ToByte(input.Substring(i, 2), 16);
|
||||
bytes[i / 2] = asGuid ? (byte)ReverseByte(b) : b;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a hex string to a byte array that represents an Asset Hash
|
||||
/// </summary>
|
||||
/// <param name="hashStr">32 character hex string</param>
|
||||
/// <returns>byte array</returns>
|
||||
public static byte[] StringToHash(string hashStr)
|
||||
{
|
||||
Debug.Assert(hashStr.Length == 32);
|
||||
return StringToByteArray(hashStr, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a hex string to a byte array that represents an Asset GUID
|
||||
/// </summary>
|
||||
/// <param name="guidStr">32 character hex string</param>
|
||||
/// <returns>byte array</returns>
|
||||
public static byte[] StringToGuid(string guidStr)
|
||||
{
|
||||
Debug.Assert(guidStr.Length == 32);
|
||||
return StringToByteArray(guidStr, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse an ascii byte array at <paramref name="index"/>start as an int value
|
||||
/// </summary>
|
||||
/// <param name="bytes">byte array</param>
|
||||
/// <param name="index">offset</param>
|
||||
/// <returns></returns>
|
||||
public static int ReadUInt32(byte[] bytes, int index)
|
||||
{
|
||||
Debug.Assert(bytes.Length + index >= 8);
|
||||
return Int32.Parse(Encoding.ASCII.GetString(bytes, index, 8), NumberStyles.HexNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode an integer as an ascii byte array
|
||||
/// </summary>
|
||||
/// <param name="input">integer</param>
|
||||
/// <param name="minLength">true ensure the byte array is as short as possible; false to pad to 8 bytes</param>
|
||||
/// <returns></returns>
|
||||
public static byte[] EncodeInt32(int input, bool minLength = false)
|
||||
{
|
||||
return Encoding.ASCII.GetBytes(input.ToString(minLength ? "X" : "X8"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a subset of an ascii byte array as a long value
|
||||
/// </summary>
|
||||
/// <param name="bytes">byte array</param>
|
||||
/// <param name="index">offset within <paramref name="bytes"/> to read from</param>
|
||||
/// <returns></returns>
|
||||
public static long ReadUInt64(byte[] bytes, int index)
|
||||
{
|
||||
Debug.Assert(bytes.Length + index >= 16);
|
||||
return Int64.Parse(Encoding.ASCII.GetString(bytes, index, 16), NumberStyles.HexNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode a long value into an ascii byte array
|
||||
/// </summary>
|
||||
/// <param name="input">long value</param>
|
||||
/// <returns></returns>
|
||||
public static byte[] EncodeInt64(long input)
|
||||
{
|
||||
return Encoding.ASCII.GetBytes(input.ToString("X16"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two byte arrays for value equality
|
||||
/// </summary>
|
||||
/// <param name="ar1">first array</param>
|
||||
/// <param name="ar2">second array</param>
|
||||
/// <returns></returns>
|
||||
public static bool ByteArraysAreEqual(byte[] ar1, byte[] ar2)
|
||||
{
|
||||
return ar1.Length == ar2.Length && ByteArraysAreEqual(ar1, 0, ar2, 0, ar1.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two byte arrays for value equality at specific offsets and length
|
||||
/// </summary>
|
||||
/// <param name="ar1">first array</param>
|
||||
/// <param name="start1">offset within first array</param>
|
||||
/// <param name="ar2">second array</param>
|
||||
/// <param name="start2">offset within second array</param>
|
||||
/// <param name="count">number of bytes to compare</param>
|
||||
/// <returns></returns>
|
||||
public static bool ByteArraysAreEqual(byte[] ar1, int start1, byte[] ar2, int start2, int count)
|
||||
{
|
||||
Debug.Assert(start1 >= 0 && start2 >= 0 && count >= 0);
|
||||
if (start1 + count > ar1.Length)
|
||||
return false;
|
||||
|
||||
if (start2 + count > ar2.Length)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
if (ar1[start1 + i] != ar2[start2 + i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the configured cache server address for the Unity Editor
|
||||
/// </summary>
|
||||
public static string ConfigCacheServerAddress
|
||||
{
|
||||
get { return EditorPrefs.GetString(IpAddressKey); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse an address string in the format of 'address:port' to a string address and integer port number
|
||||
/// </summary>
|
||||
/// <param name="address">combined address string</param>
|
||||
/// <param name="host">address part</param>
|
||||
/// <param name="port">port part</param>
|
||||
public static void ParseCacheServerIpAddress(string address, out string host, out int port)
|
||||
{
|
||||
host = null;
|
||||
port = 8126;
|
||||
|
||||
var parts = address.Split(':');
|
||||
|
||||
if (parts.Length > 0)
|
||||
host = parts[0];
|
||||
|
||||
if (parts.Length > 1)
|
||||
port = int.Parse(parts[1]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a0c7d049419ae46f19ead59b45d622fb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue