提交 57f8693e authored 作者: Mitch Capper's avatar Mitch Capper

Initial support for plugin control (enable/disable, options, etc) also…

Initial support for plugin control (enable/disable, options, etc)  also consolidated common plugin code to PluginManagerBase.cs and made all plugins inherit from IPlugin.
上级 8492f4a1
......@@ -23,8 +23,8 @@ namespace FSClient {
Call.CallStateChanged += CallStateChangedHandler;
Call.ActiveCallChanged += ActiveCallChanged;
init_us();
DelayedFunction.DelayedCall("LoadContactManager", initContactManager, 1000);
init_us();
}
public bool fully_loaded;
......@@ -42,6 +42,13 @@ namespace FSClient {
}
}
private void initContactManager() {
if (Properties.Settings.Default.ContactPlugins != null)
contact_plugin_manager = ContactPluginManager.GetPluginManager(Properties.Settings.Default.ContactPlugins);
else
contact_plugin_manager = new ContactPluginManager();
contact_plugin_manager.LoadPlugins();
}
private void init_us() {
if (is_inited)
......@@ -75,6 +82,12 @@ namespace FSClient {
else
sofia = new Sofia();
if (Properties.Settings.Default.HeadsetPlugins != null)
headset_plugin_manager = HeadsetPluginManager.GetPluginManager(Properties.Settings.Default.HeadsetPlugins);
else
headset_plugin_manager = new HeadsetPluginManager();
headset_plugin_manager.LoadPlugins();
if (Properties.Settings.Default.EventSocket != null)
event_socket = Properties.Settings.Default.EventSocket.GetEventSocket();
else
......@@ -114,10 +127,6 @@ namespace FSClient {
#endif
}
private void initContactManager() {
contact_plugin_manager = new ContactPluginManager();
}
#region Text Input
......@@ -340,7 +349,9 @@ namespace FSClient {
Properties.Settings.Default.UseNumberOnlyInput = UseNumberOnlyInput;
Properties.Settings.Default.Sofia = new SettingsSofia(sofia);
Properties.Settings.Default.EventSocket = new SettingsEventSocket(event_socket);
Properties.Settings.Default.ContactPlugins = contact_plugin_manager.GetSettings();
Properties.Settings.Default.HeadsetPlugins = headset_plugin_manager.GetSettings();
Properties.Settings.Default.EventSocket = new SettingsEventSocket(event_socket);
Properties.Settings.Default.Save();
} catch (Exception e) {//if there is an error doing saving lets skip saving any settings to avoid overriding something else
MessageBox.Show("Error saving settings out: " + e.Message + "\n" + e.StackTrace);
......
......@@ -114,10 +114,12 @@
<Compile Include="InputBox.xaml.cs">
<DependentUpon>InputBox.xaml</DependentUpon>
</Compile>
<Compile Include="IPlugin.cs" />
<Compile Include="ObservableClass.cs" />
<Compile Include="Options.xaml.cs">
<DependentUpon>Options.xaml</DependentUpon>
</Compile>
<Compile Include="PluginManagerBase.cs" />
<Compile Include="SimpleContactPluginBase.cs" />
<Compile Include="Sofia.cs" />
<Compile Include="Utils.cs" />
......
......@@ -2,100 +2,127 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using FSClient.Controls;
namespace FSClient {
public class ContactPluginManager : IDisposable {
private bool IsTypeOf(Type to_check, Type of) {
if (to_check == null)
return false;
if (to_check == of)
return true;
return IsTypeOf(to_check.BaseType, of);
}
public static IEnumerable<MenuItem> ContactMenuItems { get; set; }
private List<IContactPlugin> plugins = new List<IContactPlugin>();
public void RegisterPlugin(IContactPlugin plugin) {
if (plugins.Count > 0)
throw new Exception("Can only handle one contact plugin at a time right now and the current one is: " + plugins[0].ProviderName());
try {
plugins.Add(plugin);
plugin.Initialize();
Application.Current.Dispatcher.BeginInvoke((Action)ContactInit);
public class ContactPluginManager : PluginManagerBase {
} catch (Exception e) {
HandleError(plugin, e);
}
public static ContactPluginManager GetPluginManager(SettingsPluginDataCollection settings){
ContactPluginManager manager = new ContactPluginManager();
manager.LoadSettings(settings);
return manager;
}
private void ContactInit(){
IContactPlugin plugin = plugins[0];
ContactMenuItems = plugin.ContactRightClickMenu();
OurAutoCompleteBox box = Broker.get_instance().GetContactSearchBox();
if (plugin.HandleSearchBox(box))
box.Visibility = Visibility.Visible;
public static IEnumerable<MenuItem> ContactMenuItems { get; set; }
private List<ContactPluginData> plugins = new List<ContactPluginData>();
private ContactPluginData active_plugin;
private class ContactPluginData : PluginData {
public ContactPluginData(PluginData data) : base(data){
}
public ContactPluginData(): base(){}
public IContactPlugin contact_plugin {
get { return plugin as IContactPlugin; }
set { plugin = value; }
}
}
public override IEnumerable<PluginData> GetPlugins() {
return plugins;
}
protected override void SetPlugins(IEnumerable<PluginData> plugins){
if (this.plugins.Count > 0)
throw new Exception("Cannot call SetPlugins if plugins have already been loaded");
foreach (PluginData data in plugins)
this.plugins.Add(new ContactPluginData(data));
}
private void ContactInit(){
ContactMenuItems = active_plugin.contact_plugin.ContactRightClickMenu();
OurAutoCompleteBox box = Broker.get_instance().GetContactSearchBox();
if (active_plugin.contact_plugin.HandleSearchBox(box))
box.Visibility = Visibility.Visible;
active_plugin.state = PluginData.PluginDataState.LOADED;
}
private void HandleError(IContactPlugin plugin, Exception e) {
Utils.PluginLog("Contact Plugin Manager", "Plugin \"" + plugin.ProviderName() + "\" had an error Due to: " + e.Message);
private void HandleError(ContactPluginData plugin, Exception e) {
Utils.PluginLog(PluginManagerName(), "Plugin \"" + plugin.contact_plugin.ProviderName() + "\" had an error Due to: " + e.Message);
plugin.state = PluginData.PluginDataState.DISABLED_ERROR;
active_plugin = null;
}
public ContactPluginManager() {
public ContactPluginManager(){
OurAutoCompleteBox box = Broker.get_instance().GetContactSearchBox();
Application.Current.Dispatcher.BeginInvoke((Action) (() => {
box.Visibility = Visibility.Collapsed;
}));
Application.Current.Dispatcher.BeginInvoke((Action)(() => {
box.Visibility = Visibility.Collapsed;
}));
}
private bool no_plugins_in_config;
public override void LoadPlugins(){
no_plugins_in_config = plugins.Count == 0;
LoadActualPlugins(typeof (IContactPlugin), plugins);
if (active_plugin != null){
Call.calls.CollectionChanged += calls_CollectionChanged;
Call.CallRightClickMenuShowing += calls_RightClickMenuShowing;
}
}
protected override void HandlePluginLoadException(PluginData data, Exception e){
base.HandlePluginLoadException(data, e);
active_plugin = null;
}
protected override void HandlePluginLoadReflectionException(PluginData data, ReflectionTypeLoadException ex) {
base.HandlePluginLoadReflectionException(data, ex);
active_plugin = null;
}
public override string PluginManagerName(){
return "Contact Plugin Manager";
}
protected override void PluginLoadAddPlugin(PluginData plugin){
plugins.Add(plugin as ContactPluginData);
}
String plugin_dir = Utils.plugins_dir();
string[] dlls;
protected override void PluginLoadRegisterPlugin(PluginData plugin){
ContactPluginData data = plugin as ContactPluginData;
if (active_plugin != null)
throw new Exception("Can only handle one contact plugin at a time right now and the current one is: " + active_plugin.plugin.ProviderName());
try {
dlls = Directory.GetFileSystemEntries(plugin_dir, "*.dll");
} catch (DirectoryNotFoundException) {
return;
}
foreach (String dll in dlls) {
try {
Assembly asm = Assembly.LoadFrom(dll);
if (asm == null)
continue;
foreach (Type type in asm.GetTypes()) {
if (type.IsAbstract)
continue;
if (!IsTypeOf(type, typeof(IContactPlugin)))
continue;
RegisterPlugin(asm.CreateInstance(type.FullName) as IContactPlugin);
}
} catch (ReflectionTypeLoadException ex) {
String err = "Error creating contact plugin from dll \"" + dll + "\" due to a loader exception, make sure you have the headset runtime/api/sdk installed error was:\n";
foreach (Exception e in ex.LoaderExceptions)
err += e.Message + "\n";
Utils.PluginLog("Contact Plugin Manager", err);
} catch (Exception e) {
Utils.PluginLog("Contact Plugin Manager", "Error creating contact plugin from dll \"" + dll + "\" of: " + e.Message);
}
data.contact_plugin.Initialize();
Application.Current.Dispatcher.BeginInvoke((Action)ContactInit);
active_plugin = data;
} catch (Exception e) {
HandleError(data, e);
}
Call.calls.CollectionChanged += calls_CollectionChanged;
Call.CallRightClickMenuShowing += calls_RightClickMenuShowing;
}
}
protected override PluginData NewPluginData(String dll){
bool enabled = false;
if (no_plugins_in_config && dll == "SimpleXmlContactPlugin.dll")
enabled = true;
return new ContactPluginData(){enabled = enabled};
}
private void calls_RightClickMenuShowing(object sender, Call.CallRightClickEventArgs e){
if (plugins.Count == 0)
if (active_plugin == null)
return;
plugins[0].CallRightClickMenu(e.call, e.menu);
active_plugin.contact_plugin.CallRightClickMenu(e.call, e.menu);
}
private void calls_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { //We want to background resolve things incase a plugin does something that takes awhile
if (e.NewItems == null || plugins.Count != 1)
if (e.NewItems == null || active_plugin == null)
return;
bgresolve_worker_init();
lock (pending_bg_queue.SyncRoot) {
......@@ -129,7 +156,7 @@ namespace FSClient {
private void bgResolveWorker_DoWork(object sender, DoWorkEventArgs e) {
Call c = (Call)e.Argument;
plugins[0].ResolveNumber(c.other_party_number, alias => { if (c.other_party_name == c.other_party_number) c.other_party_name = alias; });
active_plugin.contact_plugin.ResolveNumber(c.other_party_number, alias => { if (c.other_party_name == c.other_party_number) c.other_party_name = alias; });
}
private static Queue pending_bg_queue = new Queue();
......@@ -142,15 +169,16 @@ namespace FSClient {
}
private bool disposed;
public void Dispose(){
public override void Dispose(){
if (!disposed){
disposed = true;
foreach (IContactPlugin plugin in plugins){
try{
plugin.Terminate();
foreach (ContactPluginData plugin in plugins){
try{
if (plugin.contact_plugin != null)
plugin.contact_plugin.Terminate();
}
catch (Exception e){
Utils.PluginLog("Contact Plugin Manager", "Error terminating a plugin: " + e.Message);
catch (Exception e){
Utils.PluginLog(PluginManagerName(), "Error terminating a plugin: " + e.Message);
}
}
}
......@@ -158,16 +186,12 @@ namespace FSClient {
GC.SuppressFinalize(this);
}
}
public abstract class IContactPlugin {
public abstract class IContactPlugin : IPlugin {
public delegate void NumberResolved(String DisplayName);
public abstract void ResolveNumber(String number, NumberResolved on_resolved);
public abstract void CallRightClickMenu(Call call, ContextMenu menu);
public abstract IEnumerable<MenuItem> ContactRightClickMenu();
public abstract void Initialize();
public abstract void Terminate();
public abstract string ProviderName();
public abstract bool HandleSearchBox(OurAutoCompleteBox box);
public abstract bool HandleSearchBox(OurAutoCompleteBox box);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FSClient {
public abstract class IPlugin {
public abstract string ProviderName();
public abstract void Initialize();
public abstract void Terminate();
public virtual bool ShowOptionsButton() {
return false;
}
public virtual void EditOptions() { }
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Serialization;
namespace FSClient {
[XmlRoot("SettingsPluginDataCollection")]
public class SettingsPluginDataCollection {
public SettingsPluginData[] data { get; set; }
[XmlRoot("SettingsPluginData")]
public class SettingsPluginData {
public string dll { get; set; }
public bool enabled { get; set; }
public SettingsPluginData() { }
public SettingsPluginData(PluginManagerBase.PluginData data) {
dll = data.dll;
enabled = data.enabled;
}
public PluginManagerBase.PluginData GetPluginData(){
return new PluginManagerBase.PluginData(){dll = dll, enabled = enabled};
}
}
public SettingsPluginDataCollection() {
}
public SettingsPluginDataCollection(IEnumerable<PluginManagerBase.PluginData> plugins) {
data = (from p in plugins select new SettingsPluginData(p)).ToArray();
}
}
public abstract class PluginManagerBase : IDisposable {
public class PluginData{
public string dll { get; set; }
public bool enabled { get; set; }
public enum PluginDataState { MISSING, SKIPPED, ERROR_LOADING, LOADED, DISABLED_ERROR };
public PluginDataState state;
public string last_error;
[System.Xml.Serialization.XmlIgnoreAttribute]
public virtual IPlugin plugin { get; set; }
public PluginData(){
enabled = true;
state = PluginDataState.MISSING;
}
public PluginData(PluginData data){
dll = data.dll;
enabled = data.enabled;
state = data.state;
plugin = data.plugin;
last_error = data.last_error;
}
}
protected virtual void LoadSettings(SettingsPluginDataCollection settings){
SetPlugins(settings.data.Select(settings_data => settings_data.GetPluginData()));
}
public abstract void LoadPlugins();
protected void LoadActualPlugins(Type plugin_type, IEnumerable<PluginData> plugins){
String plugin_dir = Utils.plugins_dir();
string[] dlls;
try {
dlls = Directory.GetFileSystemEntries(plugin_dir, "*.dll");
}
catch (DirectoryNotFoundException) {
return;
}
foreach (String full_dll in dlls){
FileInfo file_info = new FileInfo(full_dll);
String dll = file_info.Name;
PluginData data = (from p in plugins where p.dll == dll select p).SingleOrDefault();
bool add_to_list = false;
if (data == null) {
add_to_list = true;
data = NewPluginData(dll);
data.dll = dll;
}
if (data.enabled == false && !add_to_list) { //if not yet on the list we have to continue to see if we should be on the list
data.state = PluginData.PluginDataState.SKIPPED;
continue;
}
try {
Assembly asm = Assembly.LoadFrom(full_dll);
if (asm == null)
continue;
foreach (Type type in asm.GetTypes()) {
if (type.IsAbstract)
continue;
if (!IsTypeOf(type, plugin_type))
continue;
if (add_to_list)
PluginLoadAddPlugin(data);
if (data.enabled == false) {
data.state = PluginData.PluginDataState.SKIPPED;
continue;
}
data.plugin = asm.CreateInstance(type.FullName) as IPlugin;
PluginLoadRegisterPlugin(data);
}
}
catch (ReflectionTypeLoadException ex){
HandlePluginLoadReflectionException(data, ex);
}
catch (Exception e){
HandlePluginLoadException(data, e);
}
}
}
public abstract string PluginManagerName();
protected abstract void PluginLoadAddPlugin(PluginData plugin);
protected abstract void PluginLoadRegisterPlugin(PluginData plugin);
protected abstract PluginData NewPluginData(String dll);
protected virtual void HandlePluginLoadReflectionException(PluginData data, ReflectionTypeLoadException ex) {
String err = "Error creating plugin from dll \"" + data.dll + "\" due to a loader exception error was:\n";
foreach (Exception e in ex.LoaderExceptions)
err += e.Message + "\n";
data.last_error = err;
data.state = PluginData.PluginDataState.ERROR_LOADING;
Utils.PluginLog(PluginManagerName(), err);
}
protected virtual void HandlePluginLoadException(PluginData data, Exception e){
String err = "Error creating plugin from dll \"" + data.dll + "\" of: " + e.Message;
data.last_error = err;
data.state = PluginData.PluginDataState.ERROR_LOADING;
Utils.PluginLog(PluginManagerName(), err);
}
protected bool IsTypeOf(Type to_check, Type of) {
if (to_check == null)
return false;
if (to_check == of)
return true;
return IsTypeOf(to_check.BaseType, of);
}
public virtual SettingsPluginDataCollection GetSettings(){
return new SettingsPluginDataCollection(GetPlugins());
}
public abstract IEnumerable<PluginData> GetPlugins();
protected abstract void SetPlugins(IEnumerable<PluginData> plugins);
public virtual void SetPluginEnabled(bool enabled, PluginData plugin){
plugin.enabled = false;
}
public abstract void Dispose();
}
}
......@@ -187,5 +187,25 @@ namespace FSClient.Properties {
this["EventSocket"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::FSClient.SettingsPluginDataCollection HeadsetPlugins {
get {
return ((global::FSClient.SettingsPluginDataCollection)(this["HeadsetPlugins"]));
}
set {
this["HeadsetPlugins"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::FSClient.SettingsPluginDataCollection ContactPlugins {
get {
return ((global::FSClient.SettingsPluginDataCollection)(this["ContactPlugins"]));
}
set {
this["ContactPlugins"] = value;
}
}
}
}
\ No newline at end of file
......@@ -44,5 +44,11 @@
<Setting Name="EventSocket" Type="FSClient.SettingsEventSocket" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="HeadsetPlugins" Type="FSClient.SettingsPluginDataCollection" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="ContactPlugins" Type="FSClient.SettingsPluginDataCollection" Scope="User">
<Value Profile="(Default)" />
</Setting>
</Settings>
</SettingsFile>
\ No newline at end of file
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论