using System; using System.Collections.Generic; using System.ComponentModel; using System.Net; using System.ServiceProcess; using System.Threading; using Tesla.Net; namespace TeslaConsole; public class PodInfo : Component { private delegate void ConnectWorkerEventHandler(AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate); private delegate void LaunchAppWorkerEventHandler(Guid appId, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate); private delegate void KillAppWorkerEventHandler(Guid appId, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate); private delegate void ShutdownWorkerEventHandler(bool restart, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate); private delegate void InstallProductWorkerEventHandler(string uncPath, SendOrPostCallback progressCallback, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate); private delegate void UninstallProductWorkerEventHandler(AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate); private delegate void ServiceWorkerEventHandler(string machineName, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate); private const int ManagePort = 53290; private static Dictionary mPods = new Dictionary(); private AsyncDelegates connectDelegates; private SendOrPostCallback onLaunchAppCompletedDelegate; private SendOrPostCallback launchAppCompletionMethodDelegate; private SendOrPostCallback onKillAppCompletedDelegate; private SendOrPostCallback killAppCompletionMethodDelegate; private AsyncDelegates installProductDelegates; private AsyncDelegates uninstallProductDelegates; private AsyncDelegates serviceGetDelegates; private AsyncDelegates serviceStartDelegates; private AsyncDelegates serviceStopDelegates; private Pod mPod; private IPodManagerConnection mConnection = new PodManagerConnection(); private object mConnectSyncRoot = new object(); private AsyncOperation mConnectAsyncOp; private Exception mLastConnectionException; private Cached mAllApps = new Cached(TimeSpan.FromMinutes(30.0)); private Cached> mRunningApps = new Cached>(TimeSpan.FromSeconds(60.0)); private float mVolume = float.NaN; private readonly List> rConnectActions = new List>(); private IContainer components; public Pod Pod => mPod; public bool IsConnecting => mConnectAsyncOp != null; public bool IsConnected { get { if (!IsConnecting && mConnection != null) { return mConnection.IsOpen; } return false; } } public Exception LastConnectionException => mLastConnectionException; public LaunchData[] AllApplications { get { if (mAllApps.IsExpired && mConnection.IsOpen) { try { mAllApps.Item = mConnection.GetInstalledApps(); } catch { mConnection.Close(); mAllApps.Expire(); return new LaunchData[0]; } } return mAllApps.Item; } } public LaunchedAppData[] RunningApplications { get { if (!mConnection.IsOpen) { return new LaunchedAppData[0]; } if (mRunningApps.IsExpired) { try { lock (mRunningApps.SyncRoot) { mRunningApps.Item = new List(mConnection.GetLaunchedApps()); } } catch { mConnection.Close(); lock (mRunningApps.SyncRoot) { mRunningApps.Expire(); } return new LaunchedAppData[0]; } } if (mRunningApps.Item == null) { return null; } return mRunningApps.Item.ToArray(); } } public float Volume { get { if (float.IsNaN(mVolume) && mConnection.IsOpen) { try { mVolume = mConnection.VolumeLevel; } catch { mConnection.Close(); mVolume = float.NaN; } } return mVolume; } set { if (mConnection.IsOpen) { mVolume = value; try { mConnection.VolumeLevel = value; } catch { mConnection.Close(); mVolume = float.NaN; } } } } public bool IsServiceRunning { get { try { ServiceController serviceController = new ServiceController("TeslaLauncherService", mPod.IPAddress.ToString()); return serviceController.Status == ServiceControllerStatus.Running; } catch { return false; } } } public event EventHandler ConnectCompleted; public event EventHandler LaunchApplicationCompleted; public event EventHandler KillApplicationCompleted; public event EventHandler InstallProductProgress; public event EventHandler InstallProductCompleted; public event EventHandler UninstallProductCompleted; public event EventHandler ServiceStatusGetCompleted; public event EventHandler ServiceStartCompleted; public event EventHandler ServiceStopCompleted; public PodInfo() { InitializeComponent(); InitializeDelegates(); } public PodInfo(IContainer container) { container.Add(this); InitializeComponent(); InitializeDelegates(); } public PodInfo(Pod pod) : this(pod, register: false) { } public PodInfo(Pod pod, bool register) : this() { mPod = pod; if (register) { RegisterPod(this); } } protected virtual void InitializeDelegates() { connectDelegates = new AsyncDelegates(ConnectCompletedInternal, ConnectCompletionMethod); onLaunchAppCompletedDelegate = LaunchAppCompleted; launchAppCompletionMethodDelegate = LaunchAppCompletionMethod; onKillAppCompletedDelegate = KillAppCompleted; killAppCompletionMethodDelegate = KillAppCompletionMethod; installProductDelegates = new AsyncDelegates(InstallProductReportProgress, InstallProductInternalCompleted, InstallProductCompletionMethod); uninstallProductDelegates = new AsyncDelegates(UninstallProductInternalCompleted, UninstallProductCompletionMethod); serviceGetDelegates = new AsyncDelegates(ServiceStatusGetCompletedInternal, ServiceStatusGetCompletionMethod); serviceStartDelegates = new AsyncDelegates(ServiceStartCompletedInternal, ServiceStartCompletionMethod); serviceStopDelegates = new AsyncDelegates(ServiceStopCompletedInternal, ServiceStopCompletionMethod); } public static void RegisterPod(PodInfo pod) { mPods[pod.Pod.ID] = pod; } public static PodInfo GetPod(Guid id) { PodInfo value = null; if (mPods.TryGetValue(id, out value)) { return value; } return null; } public bool EnqueueConnectAction(Action action) { if (IsConnected) { action(this); return true; } rConnectActions.Add(action); return false; } public void Disconnect() { if (mConnectAsyncOp != null) { CancelConnectAsync(); } else if (IsConnected) { mConnection.Close(); } mLastConnectionException = null; mAllApps.Expire(); mRunningApps.Expire(); mVolume = float.NaN; } public void Refresh() { if (!IsConnected) { throw new InvalidOperationException("Cannot refresh pod info while disconnected."); } try { FullUpdateData fullUpdateData = mConnection.FullUpdate(); mAllApps.Item = fullUpdateData.InstalledApps; lock (mRunningApps.SyncRoot) { mRunningApps.Item = new List(fullUpdateData.LaunchedApps); } mVolume = fullUpdateData.VolumeLevel; } catch { mConnection.Close(); mAllApps.Expire(); lock (mRunningApps.SyncRoot) { mRunningApps.Expire(); } mVolume = float.NaN; } } public bool IsAppRunning(Guid appId) { LaunchedAppData[] runningApplications = RunningApplications; return Array.Exists(runningApplications, (LaunchedAppData app) => app.LaunchKey == appId); } public void AddApp(LaunchData appData) { if (!IsConnected) { throw new InvalidOperationException("Application cannot be added while disconnected."); } try { mConnection.InstallApp(appData); } catch (Exception ex) { Program.LogException(ex, "Add Application Failed."); mConnection.Close(); } } public void Shutdown(bool restart) { try { mConnection.Shutdown(restart); } catch (Exception ex) { Program.LogExceptionAndShowDialog(ex, "Remote Shutdown Failed.", "Remote Shutdown Failed."); } } public void ClearStore() { mConnection.ClearStore(); } private void ConnectCompletedInternal(object operationState) { AsyncCompletedEventArgs e = operationState as AsyncCompletedEventArgs; OnConnectCompleted(e); } protected void OnConnectCompleted(AsyncCompletedEventArgs e) { if (IsConnected) { foreach (Action rConnectAction in rConnectActions) { rConnectAction(this); } rConnectActions.Clear(); } if (this.ConnectCompleted != null) { this.ConnectCompleted(this, e); } } private void ConnectCompletionMethod(object completionState) { AsyncControlState asyncControlState = completionState as AsyncControlState; AsyncCompletedEventArgs asyncCompletedEventArgs = new AsyncCompletedEventArgs(asyncControlState.ex, cancelled: false, asyncControlState.asyncOp.UserSuppliedState); mLastConnectionException = asyncCompletedEventArgs.Error; lock (mConnectSyncRoot) { if (mConnectAsyncOp != null) { mConnectAsyncOp.PostOperationCompleted(connectDelegates.OnCompleted, asyncCompletedEventArgs); mConnectAsyncOp = null; } } } private void ConnectWorker(AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) { Exception ex = null; mAllApps.Expire(); mRunningApps.Expire(); mVolume = float.NaN; try { if (!mConnection.IsOpen) { if (mPod.IPAddress == IPAddress.None) { throw new ArgumentException("Pod IP address cannot be empty.", "IPAddress"); } if (mPod.Key == null || mPod.Key.Length == 0) { throw new ArgumentException("Pod encryption key cannot be empty.", "Key"); } mConnection.Open(new IPEndPoint(mPod.IPAddress, 53290), mPod.Key); } } catch (Exception ex2) { ex = ex2; } AsyncControlState state = new AsyncControlState(ex, asyncOp); completionMethodDelegate(state); } public void ConnectAsync(object userState) { AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); lock (mConnectSyncRoot) { if (mConnectAsyncOp != null) { throw new InvalidOperationException("Already connecting. Cannot connect again."); } mConnectAsyncOp = asyncOp; } ThreadPool.QueueUserWorkItem(delegate { ConnectWorker(asyncOp, connectDelegates.CompletionMethod); }); } public void CancelConnectAsync() { lock (mConnectSyncRoot) { if (mConnectAsyncOp != null) { AsyncCompletedEventArgs arg = new AsyncCompletedEventArgs(null, cancelled: true, mConnectAsyncOp.UserSuppliedState); mConnectAsyncOp.PostOperationCompleted(connectDelegates.OnCompleted, arg); mConnectAsyncOp = null; } } } private void LaunchAppCompleted(object operationState) { ApplicationCompletedEventArgs e = operationState as ApplicationCompletedEventArgs; OnLaunchApplicationCompleted(e); } protected void OnLaunchApplicationCompleted(ApplicationCompletedEventArgs e) { if (this.LaunchApplicationCompleted != null) { this.LaunchApplicationCompleted(this, e); } } private void LaunchAppCompletionMethod(object completionState) { AsyncControlState asyncControlState = completionState as AsyncControlState; ApplicationCompletedEventArgs arg = new ApplicationCompletedEventArgs(asyncControlState.state, asyncControlState.ex, canceled: false, asyncControlState.asyncOp.UserSuppliedState); asyncControlState.asyncOp.PostOperationCompleted(onLaunchAppCompletedDelegate, arg); } private void LaunchAppWorker(Guid appId, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) { Exception ex = null; try { int processId = mConnection.LaunchApp(appId); lock (mRunningApps.SyncRoot) { if (mRunningApps.Item == null) { mRunningApps.Item = new List(); } mRunningApps.Item.Add(new LaunchedAppData { ProcessId = processId, LaunchKey = appId }); } } catch (Exception ex2) { ex = ex2; } completionMethodDelegate(new AsyncControlState(appId, ex, asyncOp)); } public void LaunchApplicationAsync(Guid appId, object userState) { if (!IsConnected) { throw new InvalidOperationException("Cannot launch application while disconnected."); } AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); ThreadPool.QueueUserWorkItem(delegate { LaunchAppWorker(appId, asyncOp, launchAppCompletionMethodDelegate); }); } private void KillAppCompleted(object operationState) { ApplicationCompletedEventArgs e = operationState as ApplicationCompletedEventArgs; OnKillApplicationCompleted(e); } protected void OnKillApplicationCompleted(ApplicationCompletedEventArgs e) { if (this.KillApplicationCompleted != null) { this.KillApplicationCompleted(this, e); } } private void KillAppCompletionMethod(object completionState) { AsyncControlState asyncControlState = completionState as AsyncControlState; ApplicationCompletedEventArgs arg = new ApplicationCompletedEventArgs(asyncControlState.state, asyncControlState.ex, canceled: false, asyncControlState.asyncOp.UserSuppliedState); asyncControlState.asyncOp.PostOperationCompleted(onKillAppCompletedDelegate, arg); } private void KillAppWorker(Guid appId, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) { Exception ex = null; try { mConnection.KillAllOfType(appId); lock (mRunningApps.SyncRoot) { if (mRunningApps.Item != null) { for (int num = mRunningApps.Item.Count - 1; num >= 0; num--) { if (mRunningApps.Item[num].LaunchKey == appId) { mRunningApps.Item.RemoveAt(num); } } } } } catch (Exception ex2) { ex = ex2; } AsyncControlState state = new AsyncControlState(appId, ex, asyncOp); completionMethodDelegate(state); } public void KillApplicationAsync(Guid appId, object userState) { if (!IsConnected) { throw new InvalidOperationException("Cannot kill application while disconnected."); } AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); ThreadPool.QueueUserWorkItem(delegate { KillAppWorker(appId, asyncOp, killAppCompletionMethodDelegate); }); } private void InstallProductInternalCompleted(object operationState) { InstallProductCompletedEventArgs e = operationState as InstallProductCompletedEventArgs; OnInstallProductCompleted(e); } private void InstallProductReportProgress(object state) { InstallProductProgressEventArgs e = state as InstallProductProgressEventArgs; OnInstallProductProgress(e); } protected void OnInstallProductCompleted(InstallProductCompletedEventArgs e) { if (this.InstallProductCompleted != null) { this.InstallProductCompleted(this, e); } } protected void OnInstallProductProgress(InstallProductProgressEventArgs e) { if (this.InstallProductProgress != null) { this.InstallProductProgress(this, e); } } private void InstallProductCompletionMethod(object completionState) { AsyncControlState asyncControlState = completionState as AsyncControlState; InstallProductCompletedEventArgs arg = new InstallProductCompletedEventArgs(asyncControlState.state, asyncControlState.ex, canceled: false, asyncControlState.asyncOp.UserSuppliedState); asyncControlState.asyncOp.PostOperationCompleted(installProductDelegates.OnCompleted, arg); } private void InstallProductWorker(string filePath, SendOrPostCallback progressCallback, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) { Exception ex = null; try { for (int i = 0; i < 3; i++) { if (!mConnection.IsOpen) { mConnection.Open(new IPEndPoint(mPod.IPAddress, 53290), mPod.Key); } Guid guid = mConnection.InstallProduct(filePath); if (guid == Guid.Empty) { throw new Exception($"'{filePath}' is an invalid install file."); } OutOfBandProgress outOfBandProgress; do { Thread.Sleep(250); outOfBandProgress = mConnection.GetOutOfBandProgress(guid); InstallProductProgressEventArgs arg = new InstallProductProgressEventArgs(outOfBandProgress.Status, outOfBandProgress.PercentComplete, asyncOp.UserSuppliedState); asyncOp.Post(installProductDelegates.OnProgressReport, arg); if (progressCallback != null) { asyncOp.Post(progressCallback, arg); } } while (!outOfBandProgress.IsCompleted); if (outOfBandProgress.PercentComplete == 99) { mAllApps.Expire(); break; } } } catch (Exception ex2) { Program.LogException(ex2, "Installation Failed"); ex = ex2; } AsyncControlState state = new AsyncControlState(filePath, ex, asyncOp); completionMethodDelegate(state); } public void InstallProductAsync(string uncPath, object userState) { InstallProductAsync(uncPath, userState, null); } public void InstallProductAsync(string uncPath, object userState, Action progressCallback) { if (!IsConnected) { throw new InvalidOperationException("Cannot install application while disconnected."); } AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); ThreadPool.QueueUserWorkItem(delegate { InstallProductWorker(uncPath, delegate(object e) { progressCallback((InstallProductProgressEventArgs)e); }, asyncOp, installProductDelegates.CompletionMethod); }); } private void UninstallProductInternalCompleted(object operationState) { AsyncCompletedEventArgs e = operationState as AsyncCompletedEventArgs; OnUninstallProductCompleted(e); } protected void OnUninstallProductCompleted(AsyncCompletedEventArgs e) { if (this.UninstallProductCompleted != null) { this.UninstallProductCompleted(this, e); } } private void UninstallProductCompletionMethod(object completionState) { AsyncControlState asyncControlState = completionState as AsyncControlState; AsyncCompletedEventArgs arg = new AsyncCompletedEventArgs(asyncControlState.ex, cancelled: false, asyncControlState.asyncOp.UserSuppliedState); asyncControlState.asyncOp.PostOperationCompleted(uninstallProductDelegates.OnCompleted, arg); } private void UninstallProductWorker(Guid launchKey, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) { Exception ex = null; try { if (!mConnection.IsOpen) { mConnection.Open(new IPEndPoint(mPod.IPAddress, 53290), mPod.Key); } mConnection.UninstallApp(launchKey); mAllApps.Expire(); } catch (Exception ex2) { Program.LogException(ex2, "Uninstallation Failed"); ex = ex2; } AsyncControlState state = new AsyncControlState(ex, asyncOp); completionMethodDelegate(state); } public void UninstallProductAsync(Guid launchKey, object userState) { if (!IsConnected) { throw new InvalidOperationException("Cannot uninstall application while disconnected."); } AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); ThreadPool.QueueUserWorkItem(delegate { UninstallProductWorker(launchKey, asyncOp, uninstallProductDelegates.CompletionMethod); }); } private void ServiceStatusGetCompletedInternal(object operationState) { ServiceStatusEventArgs e = operationState as ServiceStatusEventArgs; OnServiceStatusGetCompleted(e); } protected void OnServiceStatusGetCompleted(ServiceStatusEventArgs e) { if (this.ServiceStatusGetCompleted != null) { this.ServiceStatusGetCompleted(this, e); } } private void ServiceStatusGetCompletionMethod(object completionState) { AsyncControlState asyncControlState = completionState as AsyncControlState; ServiceStatusEventArgs arg = new ServiceStatusEventArgs(asyncControlState.state, asyncControlState.ex, canceled: false, asyncControlState.asyncOp.UserSuppliedState); asyncControlState.asyncOp.PostOperationCompleted(serviceGetDelegates.OnCompleted, arg); } private void ServiceStatusWorker(string machineName, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) { Exception ex = null; ServiceControllerStatus state = ServiceControllerStatus.Stopped; try { if (IPAddress.TryParse(machineName, out var address)) { IPHostEntry hostEntry = Dns.GetHostEntry(address); machineName = hostEntry.HostName; } using ServiceController serviceController = new ServiceController("TeslaLauncherService", machineName); state = serviceController.Status; } catch (Exception ex2) { ex = ex2; } AsyncControlState state2 = new AsyncControlState(state, ex, asyncOp); completionMethodDelegate(state2); } public void GetServiceStatusAsync(string machineName, object userState) { AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); ThreadPool.QueueUserWorkItem(delegate { ServiceStatusWorker(machineName, asyncOp, serviceGetDelegates.CompletionMethod); }); } private void ServiceStartCompletedInternal(object operationState) { ServiceStatusEventArgs e = operationState as ServiceStatusEventArgs; OnServiceStartCompleted(e); } protected void OnServiceStartCompleted(ServiceStatusEventArgs e) { if (this.ServiceStartCompleted != null) { this.ServiceStartCompleted(this, e); } } private void ServiceStartCompletionMethod(object completionState) { AsyncControlState asyncControlState = completionState as AsyncControlState; ServiceStatusEventArgs arg = new ServiceStatusEventArgs(asyncControlState.state, asyncControlState.ex, canceled: false, asyncControlState.asyncOp.UserSuppliedState); asyncControlState.asyncOp.PostOperationCompleted(serviceGetDelegates.OnCompleted, arg); } private void ServiceStartWorker(string machineName, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) { Exception ex = null; try { if (IPAddress.TryParse(machineName, out var address)) { IPHostEntry hostEntry = Dns.GetHostEntry(address); machineName = hostEntry.HostName; } using ServiceController serviceController = new ServiceController("TeslaLauncherService", machineName); serviceController.Start(); serviceController.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30.0)); } catch (Exception ex2) { ex = ex2; } AsyncControlState state = new AsyncControlState(ex, asyncOp); completionMethodDelegate(state); } public void ServiceStartAsync(string machineName, object userState) { AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); ThreadPool.QueueUserWorkItem(delegate { ServiceStartWorker(machineName, asyncOp, serviceStartDelegates.CompletionMethod); }); } private void ServiceStopCompletedInternal(object operationState) { ServiceStatusEventArgs e = operationState as ServiceStatusEventArgs; OnServiceStopCompleted(e); } protected void OnServiceStopCompleted(ServiceStatusEventArgs e) { if (this.ServiceStopCompleted != null) { this.ServiceStopCompleted(this, e); } } private void ServiceStopCompletionMethod(object completionState) { AsyncControlState asyncControlState = completionState as AsyncControlState; ServiceStatusEventArgs arg = new ServiceStatusEventArgs(asyncControlState.state, asyncControlState.ex, canceled: false, asyncControlState.asyncOp.UserSuppliedState); asyncControlState.asyncOp.PostOperationCompleted(serviceGetDelegates.OnCompleted, arg); } private void ServiceStopWorker(string machineName, AsyncOperation asyncOp, SendOrPostCallback completionMethodDelegate) { Exception ex = null; try { if (IPAddress.TryParse(machineName, out var address)) { IPHostEntry hostEntry = Dns.GetHostEntry(address); machineName = hostEntry.HostName; } using ServiceController serviceController = new ServiceController("TeslaLauncherService", machineName); serviceController.Stop(); serviceController.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30.0)); } catch (Exception ex2) { ex = ex2; } AsyncControlState state = new AsyncControlState(ex, asyncOp); completionMethodDelegate(state); } public void ServiceStopAsync(string machineName, object userState) { AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(userState); ThreadPool.QueueUserWorkItem(delegate { ServiceStopWorker(machineName, asyncOp, serviceStopDelegates.CompletionMethod); }); } protected override void Dispose(bool disposing) { if (disposing && components != null) { components.Dispose(); } base.Dispose(disposing); } private void InitializeComponent() { components = new Container(); } }