using System; using System.Threading; using UnityEditor; using Codice.Client.BaseCommands.EventTracking; using Codice.Client.Common.Connection; using Codice.Client.Common.Threading; using Codice.CM.Common; using Codice.LogWrapper; using PlasticPipe; using Unity.PlasticSCM.Editor.UI; namespace Unity.PlasticSCM.Editor { internal class PlasticConnectionMonitor : HandleCredsAliasAndServerCert.IHostUnreachableExceptionListener { internal bool IsTryingReconnection { get { return mIsTryingReconnection; } } internal bool IsConnected { get { return PlasticPlugin.IsUnitTesting || mIsConnected; } } internal void CheckConnection() { mIsTryingReconnection = true; mResetEvent.Set(); } internal void SetAsConnected() { mIsConnected = true; } internal void Stop() { mIsMonitoringServerConnection = false; mResetEvent.Set(); } internal void SetRepositorySpecForEventTracking(RepositorySpec repSpec) { mRepSpecForEventTracking = repSpec; } internal void OnConnectionError(Exception ex, string server) { if (!mIsConnected) { mLog.WarnFormat("A network exception happened while the plugin was offline!"); ExceptionsHandler.LogException("PlasticConnectionMonitor", ex); return; } mLog.Debug("A network exception will cause the Plugin to go offline"); ExceptionsHandler.LogException("PlasticConnectionMonitor", ex); OnConnectionLost(server); } void HandleCredsAliasAndServerCert.IHostUnreachableExceptionListener.OnHostUnreachableException( Exception ex, PlasticServer plasticServer) { OnConnectionError(ex, plasticServer.OriginalUrl); } void StartMonitoring(string server) { mIsMonitoringServerConnection = true; Thread thread = new Thread(MonitorServerConnection); thread.IsBackground = true; thread.Start(server); } void MonitorServerConnection(object obj) { string server = (string)obj; while (true) { if (!mIsMonitoringServerConnection) break; try { bool isConnected; mResetEvent.Reset(); isConnected = HasConnectionToServer(server); mIsTryingReconnection = false; if (isConnected) { OnConnectionRestored(); break; } EditorDispatcher.Dispatch(() => { PlasticWindow window = GetPlasticWindowIfOpened(); if (window != null) window.Repaint(); }); mResetEvent.WaitOne(CONNECTION_POLL_TIME_MS); } catch (Exception ex) { mLog.Error("Error checking network connectivity", ex); mLog.DebugFormat("Stacktrace: {0}", ex.StackTrace); } } } void OnConnectionLost(string server) { TrackConnectionLostEvent(mRepSpecForEventTracking); mIsConnected = false; EditorDispatcher.Dispatch(() => { PlasticPlugin.Disable(); StartMonitoring(server); PlasticWindow window = GetPlasticWindowIfOpened(); if (window != null) window.Repaint(); }); } void OnConnectionRestored() { TrackConnectionRestoredEvent(mRepSpecForEventTracking); mIsConnected = true; EditorDispatcher.Dispatch(() => { PlasticPlugin.Enable(); PlasticWindow window = GetPlasticWindowIfOpened(); if (window != null) window.RefreshWorkspaceUI(); }); } static bool HasConnectionToServer(string server) { try { mLog.DebugFormat("Checking connection to {0}...", server); return PlasticGui.Plastic.API.CheckServerConnection(server); } catch (Exception ex) { mLog.DebugFormat("Checking connection to {0} failed: {1}", server, ex.Message); return false; } } static void TrackConnectionLostEvent(RepositorySpec repSpec) { if (repSpec == null) return; TrackFeatureUseEvent.For( repSpec, TrackFeatureUseEvent.Features.UnityPackage.DisableAutomatically); } static void TrackConnectionRestoredEvent(RepositorySpec repSpec) { if (repSpec == null) return; TrackFeatureUseEvent.For( repSpec, TrackFeatureUseEvent.Features.UnityPackage.EnableAutomatically); } static PlasticWindow GetPlasticWindowIfOpened() { if (!EditorWindow.HasOpenInstances()) return null; return EditorWindow.GetWindow(null, false); } RepositorySpec mRepSpecForEventTracking; volatile bool mIsMonitoringServerConnection; volatile bool mIsTryingReconnection; volatile bool mIsConnected = true; ManualResetEvent mResetEvent = new ManualResetEvent(false); const int CONNECTION_POLL_TIME_MS = 30000; static readonly ILog mLog = LogManager.GetLogger("PlasticConnectionMonitor"); } }