/* Copyright (c) Citrix Systems, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.ComponentModel; using XenAPI; namespace XenAdmin.Actions.GUIActions { /// /// MeddlingActionManager handles task events, categorises tasks depending upon whether they /// are ours or not, and creates MeddlingAction instances when necessary /// public class MeddlingActionManager { #region Predicates private static readonly ITaskSpecification taskFinishedSuccessfully = new TaskHasFinishedSuccessfullySpecification(); private static readonly ITaskSpecification taskIsSuitableForMeddlingAction = new TaskIsSuitableForMeddlingActionSpecification(); private static readonly ITaskSpecification taskIsUnwanted = new TaskIsUnwantedSpecification(); #endregion private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); /// /// Tasks that we've seen, but haven't categorised yet. May only be acccesed under the DictionaryLock. /// private static readonly List UnmatchedTasks = new List(); /// /// Tasks that we've categorised as not being ours, and the corresponding MeddlingAction that we created. May /// only be accessed under the DictionaryLock. /// private static readonly Dictionary MatchedTasks = new Dictionary(); private static readonly object DictionaryLock = new object(); public static void TaskCollectionChanged(object sender, CollectionChangeEventArgs e) { lock (DictionaryLock) { Task task = (Task)e.Element; if (e.Action == CollectionChangeAction.Add) { if (!UnmatchedTasks.Contains(task.opaque_ref)) AddTaskToUnmatchedList(task); } else if (e.Action == CollectionChangeAction.Remove) { CompletelyForgetTask(task); } else { log.DebugFormat(String.Format("Unmatched action from sender -- Action: {0}; Task: {1}", e.Action, task.opaque_ref)); } } } private static void AddTaskToUnmatchedList(Task task) { task.PropertyChanged += Task_PropertyChanged; UnmatchedTasks.Add(task.opaque_ref); } private static void CompletelyForgetTask(Task task) { task.PropertyChanged -= Task_PropertyChanged; if (MatchedTasks.ContainsKey(task.opaque_ref)) { MatchedTasks[task.opaque_ref].Update(task, true); } UnmatchedTasks.Remove(task.opaque_ref); MatchedTasks.Remove(task.opaque_ref); } public static void ForceAddTask(Task task) { TaskCollectionChanged(task, new CollectionChangeEventArgs(CollectionChangeAction.Add, task)); } public static void Task_PropertyChanged(object sender, PropertyChangedEventArgs e) { Task task = (Task)sender; lock (DictionaryLock) { if (UnmatchedTasks.Contains(task.opaque_ref)) { if (taskIsUnwanted.IsSatisfiedBy(task)) { RemoveUnmatchedTask(task); } else if (taskIsSuitableForMeddlingAction.IsSatisfiedBy(task)) { CreateMeddlingActionForTask(task); } else { log.DebugFormat("Unmatched meddling task skipped -- " + task.opaque_ref); } } else if (MatchedTasks.ContainsKey(task.opaque_ref)) { if (taskFinishedSuccessfully.IsSatisfiedBy(task)) { UpdateAndDeleteTask(task); } else UpdateTask(task); } else { // else - it is a hidden (etc..) task that has called already and been removed from unmatched //(and not put into matched) - so ignore it log.DebugFormat("Uncategorised meddling task skipped -- " + task.opaque_ref); } } } private static void UpdateTask(Task task) { MatchedTasks[task.opaque_ref].Update(task, false); } private static void UpdateAndDeleteTask(Task task) { task.PropertyChanged -= Task_PropertyChanged; MatchedTasks[task.opaque_ref].Update(task, true); MatchedTasks.Remove(task.opaque_ref); } private static void CreateMeddlingActionForTask(Task task) { // If AppliesTo is set, then the client that created this task knows about our scheme for passing // info between clients. We give the client a window (AwareClientHeuristic) to set this field // before deciding that it's a non-aware client. Having decided that it's one of those two cases, // we make a MeddlingAction. MeddlingAction a = new MeddlingAction(task); UnmatchedTasks.Remove(task.opaque_ref); MatchedTasks[task.opaque_ref] = a; } private static void RemoveUnmatchedTask(Task task) { // This is one of our tasks, or it's a subtask of something else, or it's one that we // just don't care about. We're going to do no more with it. task.PropertyChanged -= Task_PropertyChanged; UnmatchedTasks.Remove(task.opaque_ref); } } }