using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using static Vanara.PInvoke.Ole32;
using static Vanara.PInvoke.PortableDeviceApi;
namespace Vanara.Extensions;
/// Extension methods for classes in Vanara.PInvoke.PortableDeviceApi.
public static class PortableDeviceExtensions
{
private const BindingFlags BindStPub = BindingFlags.Static | BindingFlags.Public;
private static Dictionary> gReversePKLookup = new();
/// Extracts command results from an for a documented .
/// The results from a call to IPortableDevice.SendCommand.
/// The command's PROPERTYKEY.
/// The type in which is defined, if not other than .
/// A dictionary containing the result values.
/// Supplied PROPERTYKEY is not a recognized WPD command.
public static IReadOnlyDictionary ExtractResults(this IPortableDeviceValues results, in PROPERTYKEY cmd, Type? parentType = null)
{
if (!cmd.TryGetCommandInfo(out _, out _, out var rAttrs, parentType))
throw new InvalidOperationException("Supplied PROPERTYKEY is not a recognized WPD command.");
var ret = new Dictionary();
foreach (var a in rAttrs)
{
PROPVARIANT pv = new();
try { pv = results.GetValue(a.Property); } catch { }
ret.Add(a.Property, pv.Value);
}
return ret;
}
/// Sends a command to the device and retrieves the results synchronously.
/// The portable device.
/// The command's PROPERTYKEY.
/// The PROPERTYKEY of the result value to return.
/// The value returned in .
public static object? SendCommand(this IPortableDevice device, in PROPERTYKEY cmd, in PROPERTYKEY pkResult) =>
device.SendCommand(cmd).GetValue(pkResult).Value;
/// Sends a command to the device and retrieves the results synchronously.
/// The portable device.
/// The command's PROPERTYKEY.
///
/// An action that can optionally be called to manipulate the instance passed to .
///
/// The instance returned by .
public static IPortableDeviceValues SendCommand(this IPortableDevice device, in PROPERTYKEY cmd, Action? addParams = null)
{
IPortableDeviceValues cmdParams = new();
cmdParams.SetCommandPKey(cmd);
addParams?.Invoke(cmdParams);
var cmdResults = device.SendCommand(0, cmdParams);
cmdResults.GetErrorValue(WPD_PROPERTY_COMMON_HRESULT).ThrowIfFailed();
return cmdResults;
}
/// Sends a command to the device and retrieves the results synchronously.
/// The portable device.
/// The command's PROPERTYKEY.
///
/// A list of / tuples representing the property keys and their related values to add
/// to .
///
/// The instance returned by .
public static IPortableDeviceValues SendCommand(this IPortableDevice device, in PROPERTYKEY cmd, params (PROPERTYKEY key, object value)[] addParams) =>
SendCommand(device, cmd, v =>
{
if (addParams is not null)
foreach ((PROPERTYKEY key, object value) in addParams)
v.SetValue(key, new PROPVARIANT(value));
});
/// Adds a new enumeration value or overwrites an existing one.
/// The type of the enumeration value.
/// The instance.
/// A PROPERTYKEY that specifies the item to create or overwrite.
/// The enum value.
///
/// If an existing value has the same key that is specified by the key parameter, it overwrites the existing value without any
/// warning. The existing key memory is released appropriately.
///
public static void SetEnumValue(this IPortableDeviceValues vals, in PROPERTYKEY key, T enumVal) where T : Enum, IConvertible =>
vals.SetValue(key, new PROPVARIANT(Convert.ChangeType(enumVal, Enum.GetUnderlyingType(typeof(T)))));
/// Tries to get the command information associated with a provided Command PROPERTYKEY.
/// The of the WPD command.
/// The instance with detail about the command.
///
/// An array of instances representing valid parameters for .
///
///
/// An array of instances representing valid results for .
///
/// The type in which is defined, if not other than .
///
/// if is a valid command in ; otherwise.
///
///
/// The reflection based lookup is cached so that subsequent lookups are accelerated. As such, expect a slower response the first
/// time this method is called with each unique .
///
public static bool TryGetCommandInfo(this PROPERTYKEY key, [NotNullWhen(true)] out WPDCommandAttribute? cmd, [NotNullWhen(true)] out WPDCommandParamAttribute[]? param, [NotNullWhen(true)] out WPDCommandResultAttribute[]? result, Type? parentType = null)
{
var pi = GetPI(key, parentType);
parentType ??= typeof(Vanara.PInvoke.PortableDeviceApi);
if (pi is not null)
{
cmd = pi.GetCustomAttribute();
if (cmd is not null)
{
param = pi.GetCustomAttributes()?.ToArray() ?? new WPDCommandParamAttribute[0];
result = pi.GetCustomAttributes()?.ToArray() ?? new WPDCommandResultAttribute[0];
return true;
}
}
cmd = null;
param = null;
result = null;
return false;
}
internal static PROPERTYKEY? GetKeyFromName(string keyName, Type? parentType = null)
{
parentType ??= typeof(Vanara.PInvoke.PortableDeviceApi);
var kv = GetDict(parentType).FirstOrDefault(kv => kv.Value.Name == keyName);
return kv.Value is null ? null : kv.Key;
}
private static Dictionary GetDict(Type type)
{
if (!gReversePKLookup.TryGetValue(type, out var dict))
{
dict = type.GetProperties(BindStPub).ToDictionary(m => (PROPERTYKEY)m.GetValue(null, null)!);
gReversePKLookup.Add(type, dict);
}
return dict;
}
private static PropertyInfo? GetPI(in PROPERTYKEY key, Type? parentType = null)
{
parentType ??= typeof(Vanara.PInvoke.PortableDeviceApi);
return GetDict(parentType).TryGetValue(key, out var pi) ? pi : null;
}
}