167 lines
6.4 KiB
C#
167 lines
6.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Runtime.Serialization.Formatters.Binary;
|
|
using System.Threading;
|
|
using UnityEditor.Build.CacheServer;
|
|
using UnityEditor.Build.Pipeline.Interfaces;
|
|
using UnityEditor.Build.Pipeline.Utilities.USerialize;
|
|
using UnityEngine;
|
|
using UnityEngine.Assertions;
|
|
|
|
namespace UnityEditor.Build.Pipeline.Utilities
|
|
{
|
|
class CacheServerDownloader : IDisposable
|
|
{
|
|
const string k_CachePath = "Temp/CacheServer";
|
|
|
|
IBuildCache m_Cache;
|
|
Client m_Client;
|
|
Hash128 m_GlobalHash;
|
|
DeSerializer m_Deserializer;
|
|
|
|
bool m_Disposed;
|
|
|
|
Semaphore m_Semaphore;
|
|
|
|
public CacheServerDownloader(IBuildCache cache, DeSerializer deserializer, string host, int port = 8126)
|
|
{
|
|
m_Deserializer = deserializer;
|
|
m_Cache = cache;
|
|
m_Client = new Client(host, port);
|
|
m_Client.Connect();
|
|
m_GlobalHash = new Hash128(0, 0, 0, BuildCache.k_CacheServerVersion);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (!m_Disposed)
|
|
{
|
|
m_Disposed = true;
|
|
m_Client.Close();
|
|
}
|
|
}
|
|
|
|
public void SetGlobalHash(Hash128 hash)
|
|
{
|
|
m_GlobalHash = hash;
|
|
}
|
|
|
|
string GetCachedInfoFile(CacheEntry entry)
|
|
{
|
|
string finalHash = HashingMethods.Calculate(entry.Hash, m_GlobalHash).ToString();
|
|
return string.Format("{0}/{1}_{2}.info", k_CachePath, entry.Guid.ToString(), finalHash);
|
|
}
|
|
|
|
string GetCachedArtifactsFile(CacheEntry entry)
|
|
{
|
|
string finalHash = HashingMethods.Calculate(entry.Hash, m_GlobalHash).ToString();
|
|
return string.Format("{0}/{1}_{2}.sbpGz", k_CachePath, entry.Guid.ToString(), finalHash);
|
|
}
|
|
|
|
// Called on background thread
|
|
void ThreadedDownloadFinished(object sender, DownloadFinishedEventArgs args)
|
|
{
|
|
// Only run this for Info files
|
|
if (args.DownloadItem.Type != FileType.Info)
|
|
return;
|
|
|
|
m_Semaphore.Release();
|
|
}
|
|
|
|
// We don't return from this function until all downloads are processed. So it is safe to dispose immediately after.
|
|
public void DownloadMissing(IList<CacheEntry> entries, IList<CachedInfo> cachedInfos)
|
|
{
|
|
Assert.AreEqual(entries.Count, cachedInfos.Count);
|
|
Directory.CreateDirectory(k_CachePath);
|
|
|
|
m_Semaphore = new Semaphore(0, entries.Count);
|
|
m_Client.DownloadFinished += ThreadedDownloadFinished;
|
|
|
|
// Queue up downloads for the missing or invalid local data
|
|
for (var index = 0; index < entries.Count; index++)
|
|
{
|
|
// Only download data for cachedInfos that are invalid
|
|
if (cachedInfos[index] != null)
|
|
continue;
|
|
|
|
var entry = entries[index];
|
|
|
|
string finalHash = HashingMethods.Calculate(entry.Hash, m_GlobalHash).ToHash128().ToString();
|
|
var fileId = FileId.From(entry.Guid.ToString(), finalHash);
|
|
|
|
// Download artifacts before info to ensure both are available when download for info returns
|
|
var downloadArtifact = new FileDownloadItem(fileId, FileType.Resource, GetCachedArtifactsFile(entry));
|
|
m_Client.QueueDownload(downloadArtifact);
|
|
|
|
var downloadInfo = new FileDownloadItem(fileId, FileType.Info, GetCachedInfoFile(entry));
|
|
m_Client.QueueDownload(downloadInfo);
|
|
}
|
|
|
|
// Check downloads to see if it is usable data
|
|
var formatter = new BinaryFormatter();
|
|
for (var index = 0; index < entries.Count; index++)
|
|
{
|
|
// find the next invalid cachedInfo
|
|
while (index < entries.Count && cachedInfos[index] != null)
|
|
index++;
|
|
// make sure we didn't go out of bounds looking for invalid entries
|
|
if (index >= entries.Count)
|
|
break;
|
|
|
|
// Wait for info download
|
|
m_Semaphore.WaitOne();
|
|
|
|
string tempInfoFile = GetCachedInfoFile(entries[index]);
|
|
if (!File.Exists(tempInfoFile))
|
|
continue;
|
|
|
|
try
|
|
{
|
|
CachedInfo info;
|
|
using (var fileStream = new FileStream(tempInfoFile, FileMode.Open, FileAccess.Read))
|
|
info = m_Deserializer.DeSerialize<CachedInfo>(fileStream);
|
|
|
|
if (m_Cache.HasAssetOrDependencyChanged(info))
|
|
continue;
|
|
|
|
// Not every info file will have artifacts. So just check to see if we downloaded something.
|
|
// TODO: May want to extend CachedInfo with Artifact knowledge if there is a performance benefit?
|
|
string tempArtifactFile = GetCachedArtifactsFile(entries[index]);
|
|
string tempArtifactDir = Path.ChangeExtension(tempArtifactFile, "");
|
|
if (File.Exists(tempArtifactFile) && !FileCompressor.Decompress(tempArtifactFile, tempArtifactDir))
|
|
continue;
|
|
|
|
// All valid, move downloaded data into place
|
|
cachedInfos[index] = info;
|
|
|
|
string targetInfoFile = m_Cache.GetCachedInfoFile(info.Asset);
|
|
if (File.Exists(targetInfoFile))
|
|
File.Delete(targetInfoFile);
|
|
else
|
|
Directory.CreateDirectory(Path.GetDirectoryName(targetInfoFile));
|
|
File.Move(tempInfoFile, targetInfoFile);
|
|
|
|
if (Directory.Exists(tempArtifactDir))
|
|
{
|
|
string targetArtifactDir = m_Cache.GetCachedArtifactsDirectory(info.Asset);
|
|
if (Directory.Exists(targetArtifactDir))
|
|
Directory.Delete(targetArtifactDir, true);
|
|
Directory.Move(tempArtifactDir, targetArtifactDir);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Just regenerate the artifact, logging this exception is not useful
|
|
}
|
|
}
|
|
|
|
m_Client.ResetDownloadFinishedEventHandler();
|
|
|
|
((IDisposable)m_Semaphore).Dispose();
|
|
m_Semaphore = null;
|
|
|
|
Directory.Delete(k_CachePath, true);
|
|
}
|
|
}
|
|
}
|