603 lines
21 KiB
C#
603 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using UAF;
|
|
using UAF.Interfaces.Logging;
|
|
using System.Diagnostics;
|
|
using System.Xml.Linq;
|
|
|
|
namespace DataFileAnalyzer
|
|
{
|
|
/// <summary>
|
|
/// Manages the update of the data files.
|
|
/// </summary>
|
|
public class FileUpdater : IDisposable
|
|
{
|
|
public enum OperationPriority
|
|
{
|
|
High = 0,
|
|
Medium = 1,
|
|
Low = 2,
|
|
Count
|
|
}
|
|
|
|
// Maximum time spent in one call to the UpdateLibrary function
|
|
private static readonly long maxTimePerUpdate = 16;
|
|
// Number of file treated by the UpdateLibrary function before
|
|
// it can be interrupted
|
|
private static readonly int updateLibraryBatchSize = 100;
|
|
|
|
private readonly FileLibrary m_library;
|
|
private DataFolderWatcher m_watcher;
|
|
private ILog m_logger = new ConsoleLog();
|
|
|
|
// Operations to launch (by priority)
|
|
private List<Queue<UpdateOperation>> m_pendingOperations;
|
|
// Operations that have been executed and are waiting for the library to ask for an update (by priority)
|
|
private List<Queue<UpdateOperation>> m_waitingForUpdateOperations;
|
|
// Operations that have been treated by the update thread
|
|
private Int32 m_finishedOperationCount = 0;
|
|
|
|
// The update thread
|
|
private Thread m_thread = null;
|
|
// Mutex to lock the members of the class (bool, int, etc...)
|
|
private Mutex m_internalMutex = new Mutex(false, "DataFileAnalyzer.FileUpdater.m_internalMutex");
|
|
// Event set when the update thread has been started
|
|
private readonly AutoResetEvent m_startedSyncEvent = new AutoResetEvent(false);
|
|
// Event set when the update thread has been stopped
|
|
private readonly AutoResetEvent m_stopSyncEvent = new AutoResetEvent(false);
|
|
// Event to notify the update thread that there is new operations to treat
|
|
private readonly AutoResetEvent m_pendingOperationsEvent = new AutoResetEvent(false);
|
|
// Event to set when we exit the pause
|
|
private readonly AutoResetEvent m_unpauseEvent = new AutoResetEvent(false);
|
|
// Event to set when the update thread is paused
|
|
private readonly AutoResetEvent m_pauseSyncEvent = new AutoResetEvent(false);
|
|
// Stop as soon as possible
|
|
private bool m_exitAsked = false;
|
|
// Pause as soon as possible
|
|
private bool m_pauseAsked = false;
|
|
// The update thread is running
|
|
private bool m_running = false;
|
|
// The update thread is paused
|
|
private bool m_paused = false;
|
|
// Operations currently treated by the update thread (by priority)
|
|
private List<Queue<UpdateOperation>> m_runningOperations;
|
|
|
|
private engineWrapper m_engineWrapper = null;
|
|
private cookerWrapper m_cookerWrapper = null;
|
|
private Stopwatch m_runStopwatch = new Stopwatch();
|
|
private Stopwatch m_updateStopwatch = new Stopwatch();
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
public FileUpdater(FileLibrary _library)
|
|
{
|
|
m_pendingOperations = new List<Queue<UpdateOperation>>();
|
|
m_runningOperations = new List<Queue<UpdateOperation>>();
|
|
m_waitingForUpdateOperations = new List<Queue<UpdateOperation>>();
|
|
for (int priority = 0; priority < (int)OperationPriority.Count; ++priority)
|
|
{
|
|
m_pendingOperations.Add(new Queue<UpdateOperation>());
|
|
m_runningOperations.Add(new Queue<UpdateOperation>());
|
|
m_waitingForUpdateOperations.Add(new Queue<UpdateOperation>());
|
|
}
|
|
|
|
m_library = _library;
|
|
m_logger = _library.Logger;
|
|
m_watcher = new DataFolderWatcher(this);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if(m_watcher != null)
|
|
m_watcher.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The logger. Cannot be set to a null value.
|
|
/// </summary>
|
|
public ILog Logger
|
|
{
|
|
get { return m_logger; }
|
|
set
|
|
{
|
|
if (value != null)
|
|
{
|
|
m_logger = value;
|
|
m_watcher.Logger = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accessor to the FileLibrary.
|
|
/// </summary>
|
|
public FileLibrary Library { get { return m_library; } }
|
|
|
|
/// <summary>
|
|
/// The cooker wrapper
|
|
/// </summary>
|
|
public cookerWrapper CookerWrapper { get { return m_cookerWrapper; } }
|
|
|
|
/// <summary>
|
|
/// The number of update operations that have been done since Start was called
|
|
/// </summary>
|
|
public Int32 FinishedOperationsCount
|
|
{
|
|
get
|
|
{
|
|
m_internalMutex.WaitOne();
|
|
Int32 result = m_finishedOperationCount;
|
|
m_internalMutex.ReleaseMutex();
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The data folder of the library has changed
|
|
/// </summary>
|
|
public void OnDataFolderChanged(string _folder, List<string> _excludedFolders)
|
|
{
|
|
m_watcher.OnDataFolderChanged(_folder, _excludedFolders);
|
|
}
|
|
|
|
#region Start/Stop/Pause/Resume management
|
|
|
|
/// <summary>
|
|
/// Start updating.
|
|
/// </summary>
|
|
public void Start()
|
|
{
|
|
if (!Running)
|
|
{
|
|
m_thread = new Thread(MainThread);
|
|
m_thread.Name = "DataFileAnalyzer.FileUpdater.MainThread";
|
|
m_thread.Start();
|
|
m_startedSyncEvent.WaitOne();
|
|
m_running = true;
|
|
m_paused = false;
|
|
|
|
m_watcher.Start(m_library);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stop updating.
|
|
/// </summary>
|
|
public void Stop()
|
|
{
|
|
if (Running)
|
|
{
|
|
Resume();
|
|
|
|
m_watcher.Stop();
|
|
|
|
ExitAsked = true;
|
|
m_pendingOperationsEvent.Set();
|
|
m_stopSyncEvent.WaitOne();
|
|
m_exitAsked = false;
|
|
m_thread = null;
|
|
|
|
for (int priority = 0; priority < (int)OperationPriority.Count; ++priority)
|
|
{
|
|
m_pendingOperations[priority].Clear();
|
|
m_runningOperations[priority].Clear();
|
|
m_waitingForUpdateOperations[priority].Clear();
|
|
}
|
|
m_finishedOperationCount = 0;
|
|
|
|
m_runStopwatch.Reset();
|
|
m_updateStopwatch.Reset();
|
|
|
|
m_running = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pause all the threads
|
|
/// </summary>
|
|
public void Pause()
|
|
{
|
|
if (Running && !Paused)
|
|
{
|
|
PausedAsked = true;
|
|
m_pendingOperationsEvent.Set();
|
|
m_pauseSyncEvent.WaitOne();
|
|
Paused = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resume the threads
|
|
/// </summary>
|
|
public void Resume()
|
|
{
|
|
if (Running && Paused)
|
|
{
|
|
PausedAsked = false;
|
|
m_unpauseEvent.Set();
|
|
Paused = false;
|
|
}
|
|
}
|
|
|
|
#region Multithread-safe properties
|
|
|
|
/// <summary>
|
|
/// Ask for the other thread(s) to exit.
|
|
/// </summary>
|
|
protected bool ExitAsked
|
|
{
|
|
get
|
|
{
|
|
m_internalMutex.WaitOne();
|
|
bool result = m_exitAsked;
|
|
m_internalMutex.ReleaseMutex();
|
|
return result;
|
|
}
|
|
set
|
|
{
|
|
m_internalMutex.WaitOne();
|
|
m_exitAsked = value;
|
|
m_internalMutex.ReleaseMutex();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ask for a pause and wait for the threads to pause.
|
|
/// </summary>
|
|
protected bool PausedAsked
|
|
{
|
|
get
|
|
{
|
|
m_internalMutex.WaitOne();
|
|
bool result = m_pauseAsked;
|
|
m_internalMutex.ReleaseMutex();
|
|
return result;
|
|
}
|
|
set
|
|
{
|
|
m_internalMutex.WaitOne();
|
|
m_pauseAsked = value;
|
|
m_internalMutex.ReleaseMutex();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Running?
|
|
/// </summary>
|
|
protected bool Running
|
|
{
|
|
get
|
|
{
|
|
m_internalMutex.WaitOne();
|
|
bool result = m_running;
|
|
m_internalMutex.ReleaseMutex();
|
|
return result;
|
|
}
|
|
set
|
|
{
|
|
m_internalMutex.WaitOne();
|
|
m_running = value;
|
|
m_internalMutex.ReleaseMutex();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Paused?
|
|
/// </summary>
|
|
protected bool Paused
|
|
{
|
|
get
|
|
{
|
|
m_internalMutex.WaitOne();
|
|
bool result = m_paused;
|
|
m_internalMutex.ReleaseMutex();
|
|
return result;
|
|
}
|
|
set
|
|
{
|
|
m_internalMutex.WaitOne();
|
|
m_paused = value;
|
|
m_internalMutex.ReleaseMutex();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Clear the file library and cancel all the pending and running update operations.
|
|
/// </summary>
|
|
public void ClearLibrary()
|
|
{
|
|
m_logger.Message("[Updater] Canceling all operation and asking to clear the library.");
|
|
|
|
// This function is called from the watcher thread.
|
|
// We should modify the library the library and updater threads are running.
|
|
m_library.Pause();
|
|
|
|
// Clear the file library
|
|
m_library.Clear();
|
|
|
|
// Cancel all operations
|
|
lock (m_pendingOperations)
|
|
{
|
|
for (int priority = 0; priority < (int)OperationPriority.Count; ++priority)
|
|
m_pendingOperations[priority].Clear();
|
|
}
|
|
lock (m_runningOperations)
|
|
{
|
|
for (int priority = 0; priority < (int)OperationPriority.Count; ++priority)
|
|
m_runningOperations[priority].Clear();
|
|
}
|
|
lock (m_waitingForUpdateOperations)
|
|
{
|
|
for (int priority = 0; priority < (int)OperationPriority.Count; ++priority)
|
|
m_waitingForUpdateOperations[priority].Clear();
|
|
}
|
|
|
|
m_library.Resume();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Main thread of the updater.
|
|
/// </summary>
|
|
protected void MainThread()
|
|
{
|
|
m_engineWrapper = new engineWrapper("DataFileAnalyzer.UpdateThread");
|
|
m_cookerWrapper = new cookerWrapper();
|
|
m_cookerWrapper.init(PathUtils.NormalizeDataRoot(m_library.DataFolder));
|
|
m_cookerWrapper.disableCache();
|
|
|
|
// Unlock parent thread
|
|
m_startedSyncEvent.Set();
|
|
|
|
// Main loop
|
|
while(!ExitAsked)
|
|
{
|
|
m_pendingOperationsEvent.WaitOne();
|
|
|
|
if (PausedAsked)
|
|
{
|
|
m_pauseSyncEvent.Set();
|
|
m_unpauseEvent.WaitOne();
|
|
}
|
|
|
|
if (ExitAsked)
|
|
break;
|
|
|
|
lock (m_runStopwatch)
|
|
{
|
|
m_runStopwatch.Start();
|
|
}
|
|
|
|
for (int priority = 0; priority < (int)OperationPriority.Count && !ExitAsked && !PausedAsked; ++priority)
|
|
{
|
|
Queue<UpdateOperation> pendingOperations;
|
|
lock (m_pendingOperations)
|
|
{
|
|
pendingOperations = new Queue<UpdateOperation>(m_pendingOperations[priority]);
|
|
m_pendingOperations[priority].Clear();
|
|
}
|
|
|
|
lock (m_runningOperations)
|
|
{
|
|
while (pendingOperations.Count != 0)
|
|
m_runningOperations[priority].Enqueue(pendingOperations.Dequeue());
|
|
}
|
|
|
|
while (!ExitAsked && !PausedAsked)
|
|
{
|
|
UpdateOperation operation;
|
|
lock (m_runningOperations)
|
|
{
|
|
if (m_runningOperations[priority].Count != 0)
|
|
operation = m_runningOperations[priority].Dequeue();
|
|
else
|
|
break;
|
|
}
|
|
|
|
operation.Run();
|
|
|
|
lock (m_waitingForUpdateOperations)
|
|
{
|
|
m_waitingForUpdateOperations[priority].Enqueue(operation);
|
|
}
|
|
}
|
|
}
|
|
|
|
lock (m_runStopwatch)
|
|
{
|
|
m_runStopwatch.Stop();
|
|
}
|
|
}
|
|
|
|
m_cookerWrapper.destroy();
|
|
m_cookerWrapper = null;
|
|
m_engineWrapper.destroy();
|
|
m_engineWrapper = null;
|
|
|
|
// Unlock parent thread
|
|
m_stopSyncEvent.Set();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notify the updater that a data file should be fully updated.
|
|
/// <returns>The added operation or the existing operation that
|
|
/// will do the job.</returns>
|
|
/// </summary>
|
|
public UpdateOperation AskForFileUpdate(string _filePath)
|
|
{
|
|
return AskForFileUpdate(_filePath, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notify the updater that a data file should be fully updated.
|
|
/// <returns>The added operation or the existing operation that
|
|
/// will do the job.</returns>
|
|
/// </summary>
|
|
public UpdateOperation AskForFileUpdate(string _filePath, bool isRelative)
|
|
{
|
|
string dataFilePath;
|
|
if (isRelative)
|
|
dataFilePath = _filePath;
|
|
else
|
|
dataFilePath = PathUtils.MakeRelative(_filePath, Library.DataFolder);
|
|
|
|
UpdateOperation newOperation = new BasicFileUpdate(this, dataFilePath);
|
|
return AddPendingOperation(newOperation, OperationPriority.High);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notify the updater that the dependencies of a data file should be recomputed.
|
|
/// <returns>The added operation or the existing operation that
|
|
/// will do the job.</returns>
|
|
/// </summary>
|
|
public UpdateOperation AskForFileDependenciesUpdate(string _dataFilePath)
|
|
{
|
|
UpdateOperation newOperation = new DependenciesUpdateOperation(this, _dataFilePath);
|
|
return AddPendingOperation(newOperation, OperationPriority.Medium);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notify the updater that the dependencies of a data file should be recomputed.
|
|
/// <returns>The added operation or the existing operation that
|
|
/// will do the job.</returns>
|
|
/// </summary>
|
|
public UpdateOperation AskForOrphanDetectionUpdate(string _dataFilePath)
|
|
{
|
|
UpdateOperation newOperation = new OrphanDetectionOperation(this, _dataFilePath);
|
|
return AddPendingOperation(newOperation, OperationPriority.Low);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Notify the updater that Perforce information of a data file should be recomputed.
|
|
/// <returns>The added operation or the existing operation that
|
|
/// will do the job.</returns>
|
|
/// </summary>
|
|
public UpdateOperation AskForPerforceUpdate(string _dataFilePath, Ubisoft.Perforce.Data.DepotFileInfo _p4info)
|
|
{
|
|
UpdateOperation newOperation = new PerforceUpdateOperation(this, _dataFilePath, _p4info);
|
|
return AddPendingOperation(newOperation, OperationPriority.Low);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add an update operation to the pending list. Do not add the operation
|
|
/// if there is already another pending operation updating the same
|
|
/// thing.
|
|
/// <returns>The added operation or of the existing operation that
|
|
/// will do the job.</returns>
|
|
/// </summary>
|
|
protected UpdateOperation AddPendingOperation(UpdateOperation _newOperation, OperationPriority _priority)
|
|
{
|
|
lock (m_pendingOperations)
|
|
{
|
|
Queue<UpdateOperation> queue = m_pendingOperations[(int)_priority];
|
|
foreach (UpdateOperation operation in queue)
|
|
{
|
|
if (operation.CanSkip(_newOperation))
|
|
return operation;
|
|
}
|
|
queue.Enqueue(_newOperation);
|
|
m_pendingOperationsEvent.Set();
|
|
return _newOperation;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get some info about the current state (number of operations pending, running, etc...)
|
|
/// Called from the library thread.
|
|
/// </summary>
|
|
public void GetCurrentState(FileUpdaterState _state)
|
|
{
|
|
_state.Initializing = m_running && m_watcher.Initializing;
|
|
lock (m_pendingOperations)
|
|
{
|
|
for (int priority = 0; priority < (int)OperationPriority.Count; ++priority)
|
|
_state.PendingCount = m_pendingOperations[priority].Count;
|
|
}
|
|
lock (m_runningOperations)
|
|
{
|
|
for (int priority = 0; priority < (int)OperationPriority.Count; ++priority)
|
|
_state.RunningCount = m_runningOperations[priority].Count;
|
|
}
|
|
lock (m_waitingForUpdateOperations)
|
|
{
|
|
for (int priority = 0; priority < (int)OperationPriority.Count; ++priority)
|
|
_state.WaitingUpdateCount = m_waitingForUpdateOperations[priority].Count;
|
|
}
|
|
lock (m_runStopwatch)
|
|
{
|
|
_state.RunningDuration = m_runStopwatch.Elapsed;
|
|
}
|
|
_state.FinishedCount = FinishedOperationsCount;
|
|
_state.UpdateDuration = m_updateStopwatch.Elapsed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the library from the finished operations. Called from the library thread.
|
|
/// </summary>
|
|
public void UpdateLibrary()
|
|
{
|
|
// Now treat operations that are waiting for update until the time is up
|
|
Queue<UpdateOperation> operationList = new Queue<UpdateOperation>();
|
|
Int64 initialElapsed = m_updateStopwatch.ElapsedMilliseconds;
|
|
m_updateStopwatch.Start();
|
|
for (int priority = 0; priority < (int)OperationPriority.Count && m_updateStopwatch.ElapsedMilliseconds - initialElapsed < maxTimePerUpdate; ++priority)
|
|
{
|
|
while (m_updateStopwatch.ElapsedMilliseconds - initialElapsed < maxTimePerUpdate)
|
|
{
|
|
lock (m_waitingForUpdateOperations)
|
|
{
|
|
Int32 fileCount = m_waitingForUpdateOperations[priority].Count;
|
|
if (fileCount != 0)
|
|
{
|
|
int batchSize = (fileCount < updateLibraryBatchSize) ? fileCount : updateLibraryBatchSize;
|
|
for (int i = 0; i < batchSize; ++i)
|
|
operationList.Enqueue(m_waitingForUpdateOperations[priority].Dequeue());
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
foreach (UpdateOperation operation in operationList)
|
|
{
|
|
operation.UpdateLibrary();
|
|
}
|
|
|
|
m_internalMutex.WaitOne();
|
|
m_finishedOperationCount += operationList.Count;
|
|
m_internalMutex.ReleaseMutex();
|
|
|
|
operationList.Clear();
|
|
}
|
|
}
|
|
m_updateStopwatch.Stop();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load the state of the updater that was saved in a library XML
|
|
/// </summary>
|
|
/// <returns>true if no error occurred</returns>
|
|
public bool Load(XElement _libraryElement)
|
|
{
|
|
if (m_running)
|
|
return false;
|
|
return m_watcher.Load(_libraryElement);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Save the state of the updater in a library XML
|
|
/// </summary>
|
|
/// <returns>true if no error occurred</returns>
|
|
public bool Save(XElement _libraryElement)
|
|
{
|
|
return m_watcher.Save(_libraryElement);
|
|
}
|
|
}
|
|
}
|