using JetBrains.Annotations;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Xml.Serialization;
namespace Microsoft.Win32.TaskScheduler
{
/// Provides the methods that are used to add to, remove from, and get the triggers of a task.
[XmlRoot("Triggers", Namespace = TaskDefinition.tns, IsNullable = false)]
public sealed class TriggerCollection : IList, IDisposable, IXmlSerializable, IList, INotifyCollectionChanged, INotifyPropertyChanged
{
private const string IndexerName = "Item[]";
private readonly V2Interop.ITriggerCollection v2Coll;
private bool inV2set;
private V1Interop.ITask v1Task;
private V2Interop.ITaskDefinition v2Def;
internal TriggerCollection([NotNull] V1Interop.ITask iTask) => v1Task = iTask;
internal TriggerCollection([NotNull] V2Interop.ITaskDefinition iTaskDef)
{
v2Def = iTaskDef;
v2Coll = v2Def.Triggers;
}
/// Occurs when a collection changes.
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// Occurs when a property value changes.
public event PropertyChangedEventHandler PropertyChanged;
/// Gets the number of triggers in the collection.
public int Count => v2Coll?.Count ?? v1Task.GetTriggerCount();
bool IList.IsFixedSize => false;
bool ICollection.IsReadOnly => false;
bool IList.IsReadOnly => false;
bool ICollection.IsSynchronized => false;
object ICollection.SyncRoot => this;
/// Gets or sets a specified trigger from the collection.
/// The .
/// The id ( ) of the trigger to be retrieved.
/// Specialized instance.
///
///
///
/// Mismatching Id for trigger and lookup.
public Trigger this[[NotNull] string triggerId]
{
get
{
if (string.IsNullOrEmpty(triggerId))
throw new ArgumentNullException(nameof(triggerId));
foreach (var t in this)
if (string.Equals(t.Id, triggerId))
return t;
throw new ArgumentOutOfRangeException(nameof(triggerId));
}
set
{
if (value == null)
throw new NullReferenceException();
if (string.IsNullOrEmpty(triggerId))
throw new ArgumentNullException(nameof(triggerId));
if (triggerId != value.Id)
throw new InvalidOperationException("Mismatching Id for trigger and lookup.");
var index = IndexOf(triggerId);
if (index >= 0)
{
var orig = this[index].Clone();
inV2set = true;
try
{
RemoveAt(index);
Insert(index, value);
}
finally
{
inV2set = true;
}
OnNotifyPropertyChanged(IndexerName);
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, orig, index));
}
else
Add(value);
}
}
/// Gets a specified trigger from the collection.
/// The index of the trigger to be retrieved.
/// Specialized instance.
public Trigger this[int index]
{
get
{
if (v2Coll != null)
return Trigger.CreateTrigger(v2Coll[++index], v2Def);
return Trigger.CreateTrigger(v1Task.GetTrigger((ushort)index));
}
set
{
if (index < 0 || Count <= index)
throw new ArgumentOutOfRangeException(nameof(index), index, @"Index is not a valid index in the TriggerCollection");
var orig = this[index].Clone();
inV2set = true;
try
{
Insert(index, value);
RemoveAt(index + 1);
}
finally
{
inV2set = false;
}
OnNotifyPropertyChanged(IndexerName);
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, orig, index));
}
}
object IList.this[int index]
{
get => this[index];
set => this[index] = (Trigger)value;
}
/*///
/// Add an unbound to the task. derivative to
/// add to the task. Bound trigger. unboundTrigger
/// is null.
public Trigger Add([NotNull] Trigger unboundTrigger)
{
if (unboundTrigger == null)
throw new ArgumentNullException(nameof(unboundTrigger));
if (v2Def != null)
unboundTrigger.Bind(v2Def);
else
unboundTrigger.Bind(v1Task);
return unboundTrigger;
}*/
/// Add an unbound to the task.
/// A type derived from .
/// derivative to add to the task.
/// Bound trigger.
/// unboundTrigger is null.
public TTrigger Add([NotNull] TTrigger unboundTrigger) where TTrigger : Trigger
{
if (unboundTrigger == null)
throw new ArgumentNullException(nameof(unboundTrigger));
if (v2Def != null)
unboundTrigger.Bind(v2Def);
else
unboundTrigger.Bind(v1Task);
OnNotifyPropertyChanged(nameof(Count));
OnNotifyPropertyChanged(IndexerName);
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, unboundTrigger));
return unboundTrigger;
}
/// Add a new trigger to the collections of triggers for the task.
/// The type of trigger to create.
/// A instance of the specified type.
public Trigger AddNew(TaskTriggerType taskTriggerType)
{
if (v1Task != null)
return Trigger.CreateTrigger(v1Task.CreateTrigger(out _), Trigger.ConvertToV1TriggerType(taskTriggerType));
return Trigger.CreateTrigger(v2Coll.Create(taskTriggerType), v2Def);
}
/// Adds a collection of unbound triggers to the end of the .
///
/// The triggers to be added to the end of the . The collection itself cannot be null and
/// cannot contain null elements.
///
/// is null.
public void AddRange([NotNull] IEnumerable triggers)
{
if (triggers == null)
throw new ArgumentNullException(nameof(triggers));
foreach (var item in triggers)
Add(item);
}
/// Clears all triggers from the task.
public void Clear()
{
if (v2Coll != null)
v2Coll.Clear();
else
{
inV2set = true;
try
{
for (var i = Count - 1; i >= 0; i--)
RemoveAt(i);
}
finally
{
inV2set = false;
}
}
OnNotifyPropertyChanged(nameof(Count));
OnNotifyPropertyChanged(IndexerName);
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// Determines whether the contains a specific value.
/// The object to locate in the .
/// true if is found in the ; otherwise, false.
public bool Contains([NotNull] Trigger item) => Find(a => a.Equals(item)) != null;
/// Determines whether the specified trigger type is contained in this collection.
/// Type of the trigger.
/// true if the specified trigger type is contained in this collection; otherwise, false.
public bool ContainsType(Type triggerType) => Find(a => a.GetType() == triggerType) != null;
///
/// Copies the elements of the to an , starting at a particular index.
///
///
/// The one-dimensional that is the destination of the elements copied from . The
/// must have zero-based indexing.
///
/// The zero-based index in at which copying begins.
public void CopyTo(Trigger[] array, int arrayIndex) => CopyTo(0, array, arrayIndex, Count);
///
/// Copies the elements of the to a array, starting at a particular array index.
///
/// The zero-based index in the source at which copying begins.
///
/// The array that is the destination of the elements copied from . The array must have zero-based indexing.
///
/// The zero-based index in array at which copying begins.
/// The number of elements to copy.
/// is null.
/// is less than 0.
///
/// The number of elements in the source is greater than the available space from to the end of the destination .
///
public void CopyTo(int index, Trigger[] array, int arrayIndex, int count)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
if (arrayIndex < 0)
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
if (count < 0 || count > (Count - index))
throw new ArgumentOutOfRangeException(nameof(count));
if ((Count - index) > (array.Length - arrayIndex))
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
for (var i = 0; i < count; i++)
array[arrayIndex + i] = (Trigger)this[index + i].Clone();
}
/// Releases all resources used by this class.
public void Dispose()
{
if (v2Coll != null) Marshal.ReleaseComObject(v2Coll);
v2Def = null;
v1Task = null;
}
///
/// Searches for an that matches the conditions defined by the specified predicate, and returns the first
/// occurrence within the entire collection.
///
///
/// The delegate that defines the conditions of the to search for.
///
///
/// The first that matches the conditions defined by the specified predicate, if found; otherwise, null.
///
public Trigger Find([NotNull] Predicate match)
{
if (match == null)
throw new ArgumentNullException(nameof(match));
foreach (var item in this)
if (match(item)) return item;
return null;
}
///
/// Searches for an that matches the conditions defined by the specified predicate, and returns the zero-based
/// index of the first occurrence within the collection that starts at the specified index and contains the specified number of elements.
///
/// The zero-based starting index of the search.
/// The number of elements in the collection to search.
/// The delegate that defines the conditions of the element to search for.
///
/// The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, –1.
///
public int FindIndexOf(int startIndex, int count, [NotNull] Predicate match)
{
if (startIndex < 0 || startIndex >= Count)
throw new ArgumentOutOfRangeException(nameof(startIndex));
if (startIndex + count > Count)
throw new ArgumentOutOfRangeException(nameof(count));
if (match == null)
throw new ArgumentNullException(nameof(match));
for (var i = startIndex; i < startIndex + count; i++)
if (match(this[i])) return i;
return -1;
}
///
/// Searches for an that matches the conditions defined by the specified predicate, and returns the zero-based
/// index of the first occurrence within the collection.
///
/// The delegate that defines the conditions of the element to search for.
///
/// The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, –1.
///
public int FindIndexOf([NotNull] Predicate match) => FindIndexOf(0, Count, match);
/// Gets the collection enumerator for this collection.
/// The for this collection.
public IEnumerator GetEnumerator()
{
if (v1Task != null)
return new V1TriggerEnumerator(v1Task);
return new ComEnumerator(() => v2Coll.Count, i => v2Coll[i], o => Trigger.CreateTrigger(o, v2Def));
}
/// Determines the index of a specific item in the .
/// The object to locate in the .
/// The index of if found in the list; otherwise, -1.
public int IndexOf([NotNull] Trigger item) => FindIndexOf(a => a.Equals(item));
/// Determines the index of a specific item in the .
/// The id ( ) of the trigger to be retrieved.
/// The index of if found in the list; otherwise, -1.
public int IndexOf([NotNull] string triggerId)
{
if (string.IsNullOrEmpty(triggerId))
throw new ArgumentNullException(triggerId);
return FindIndexOf(a => string.Equals(a.Id, triggerId));
}
/// Inserts an trigger at the specified index.
/// The zero-based index at which trigger should be inserted.
/// The trigger to insert into the list.
public void Insert(int index, [NotNull] Trigger trigger)
{
if (trigger == null)
throw new ArgumentNullException(nameof(trigger));
if (index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
var pushItems = new Trigger[Count - index];
CopyTo(index, pushItems, 0, Count - index);
for (var j = Count - 1; j >= index; j--)
RemoveAt(j);
Add(trigger);
foreach (var t in pushItems)
Add(t);
}
/// Removes the first occurrence of a specific object from the .
/// The object to remove from the .
///
/// true if was successfully removed from the ; otherwise, false. This method
/// also returns false if is not found in the original .
///
public bool Remove([NotNull] Trigger item)
{
var idx = IndexOf(item);
if (idx != -1)
{
try
{
RemoveAt(idx);
return true;
}
catch { }
}
return false;
}
/// Removes the trigger at a specified index.
/// Index of trigger to remove.
/// Index out of range.
public void RemoveAt(int index)
{
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index), index, @"Failed to remove Trigger. Index out of range.");
var item = this[index].Clone();
if (v2Coll != null)
v2Coll.Remove(++index);
else
v1Task.DeleteTrigger((ushort)index); //Remove the trigger from the Task Scheduler
if (!inV2set)
{
OnNotifyPropertyChanged(nameof(Count));
OnNotifyPropertyChanged(IndexerName);
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
}
}
/// Copies the elements of the to a new array.
/// An array containing copies of the elements of the .
public Trigger[] ToArray()
{
var ret = new Trigger[Count];
CopyTo(ret, 0);
return ret;
}
/// Returns a that represents the triggers in this collection.
/// A that represents the triggers in this collection.
public override string ToString()
{
if (Count == 1)
return this[0].ToString();
if (Count > 1)
return Properties.Resources.MultipleTriggers;
return string.Empty;
}
void ICollection.Add(Trigger item) => Add(item);
int IList.Add(object value)
{
Add((Trigger)value);
return Count - 1;
}
bool IList.Contains(object value) => Contains((Trigger)value);
void ICollection.CopyTo(Array array, int index)
{
if (array != null && array.Rank != 1)
throw new RankException("Multi-dimensional arrays are not supported.");
var src = new Trigger[Count];
CopyTo(src, 0);
Array.Copy(src, 0, array, index, Count);
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null;
int IList.IndexOf(object value) => IndexOf((Trigger)value);
void IList.Insert(int index, object value) => Insert(index, (Trigger)value);
void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
{
reader.ReadStartElement(XmlSerializationHelper.GetElementName(this), TaskDefinition.tns);
while (reader.MoveToContent() == System.Xml.XmlNodeType.Element)
{
switch (reader.LocalName)
{
case "BootTrigger":
XmlSerializationHelper.ReadObject(reader, AddNew(TaskTriggerType.Boot));
break;
case "IdleTrigger":
XmlSerializationHelper.ReadObject(reader, AddNew(TaskTriggerType.Idle));
break;
case "TimeTrigger":
XmlSerializationHelper.ReadObject(reader, AddNew(TaskTriggerType.Time));
break;
case "LogonTrigger":
XmlSerializationHelper.ReadObject(reader, AddNew(TaskTriggerType.Logon));
break;
case "CalendarTrigger":
Add(CalendarTrigger.GetTriggerFromXml(reader));
break;
default:
reader.Skip();
break;
}
}
reader.ReadEndElement();
}
void IList.Remove(object value) => Remove((Trigger)value);
void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
{
foreach (var t in this)
XmlSerializationHelper.WriteObject(writer, t);
}
internal void Bind()
{
foreach (var t in this)
t.SetV1TriggerData();
}
/// Called when a property has changed to notify any attached elements.
/// Name of the property.
private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private sealed class V1TriggerEnumerator : IEnumerator
{
private short curItem = -1;
private V1Interop.ITask iTask;
internal V1TriggerEnumerator(V1Interop.ITask task) => iTask = task;
public Trigger Current => Trigger.CreateTrigger(iTask.GetTrigger((ushort)curItem));
object IEnumerator.Current => Current;
/// Releases all resources used by this class.
public void Dispose() => iTask = null;
public bool MoveNext() => (++curItem < iTask.GetTriggerCount());
public void Reset() => curItem = -1;
}
}
}