提交 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 { ...@@ -23,8 +23,8 @@ namespace FSClient {
Call.CallStateChanged += CallStateChangedHandler; Call.CallStateChanged += CallStateChangedHandler;
Call.ActiveCallChanged += ActiveCallChanged; Call.ActiveCallChanged += ActiveCallChanged;
init_us();
DelayedFunction.DelayedCall("LoadContactManager", initContactManager, 1000); DelayedFunction.DelayedCall("LoadContactManager", initContactManager, 1000);
init_us();
} }
public bool fully_loaded; public bool fully_loaded;
...@@ -42,6 +42,13 @@ namespace FSClient { ...@@ -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() { private void init_us() {
if (is_inited) if (is_inited)
...@@ -75,6 +82,12 @@ namespace FSClient { ...@@ -75,6 +82,12 @@ namespace FSClient {
else else
sofia = new Sofia(); 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) if (Properties.Settings.Default.EventSocket != null)
event_socket = Properties.Settings.Default.EventSocket.GetEventSocket(); event_socket = Properties.Settings.Default.EventSocket.GetEventSocket();
else else
...@@ -114,10 +127,6 @@ namespace FSClient { ...@@ -114,10 +127,6 @@ namespace FSClient {
#endif #endif
} }
private void initContactManager() {
contact_plugin_manager = new ContactPluginManager();
}
#region Text Input #region Text Input
...@@ -340,7 +349,9 @@ namespace FSClient { ...@@ -340,7 +349,9 @@ namespace FSClient {
Properties.Settings.Default.UseNumberOnlyInput = UseNumberOnlyInput; Properties.Settings.Default.UseNumberOnlyInput = UseNumberOnlyInput;
Properties.Settings.Default.Sofia = new SettingsSofia(sofia); 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(); Properties.Settings.Default.Save();
} catch (Exception e) {//if there is an error doing saving lets skip saving any settings to avoid overriding something else } 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); MessageBox.Show("Error saving settings out: " + e.Message + "\n" + e.StackTrace);
......
...@@ -114,10 +114,12 @@ ...@@ -114,10 +114,12 @@
<Compile Include="InputBox.xaml.cs"> <Compile Include="InputBox.xaml.cs">
<DependentUpon>InputBox.xaml</DependentUpon> <DependentUpon>InputBox.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="IPlugin.cs" />
<Compile Include="ObservableClass.cs" /> <Compile Include="ObservableClass.cs" />
<Compile Include="Options.xaml.cs"> <Compile Include="Options.xaml.cs">
<DependentUpon>Options.xaml</DependentUpon> <DependentUpon>Options.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="PluginManagerBase.cs" />
<Compile Include="SimpleContactPluginBase.cs" /> <Compile Include="SimpleContactPluginBase.cs" />
<Compile Include="Sofia.cs" /> <Compile Include="Sofia.cs" />
<Compile Include="Utils.cs" /> <Compile Include="Utils.cs" />
......
...@@ -2,100 +2,127 @@ using System; ...@@ -2,100 +2,127 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using FSClient.Controls; using FSClient.Controls;
namespace FSClient { namespace FSClient {
public class ContactPluginManager : IDisposable { public class ContactPluginManager : PluginManagerBase {
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);
} catch (Exception e) { public static ContactPluginManager GetPluginManager(SettingsPluginDataCollection settings){
HandleError(plugin, e); ContactPluginManager manager = new ContactPluginManager();
} manager.LoadSettings(settings);
return manager;
} }
private void ContactInit(){ public static IEnumerable<MenuItem> ContactMenuItems { get; set; }
IContactPlugin plugin = plugins[0];
ContactMenuItems = plugin.ContactRightClickMenu(); private List<ContactPluginData> plugins = new List<ContactPluginData>();
OurAutoCompleteBox box = Broker.get_instance().GetContactSearchBox(); private ContactPluginData active_plugin;
if (plugin.HandleSearchBox(box)) private class ContactPluginData : PluginData {
box.Visibility = Visibility.Visible; 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) { private void HandleError(ContactPluginData plugin, Exception e) {
Utils.PluginLog("Contact Plugin Manager", "Plugin \"" + plugin.ProviderName() + "\" had an error Due to: " + e.Message); 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(); OurAutoCompleteBox box = Broker.get_instance().GetContactSearchBox();
Application.Current.Dispatcher.BeginInvoke((Action) (() => { Application.Current.Dispatcher.BeginInvoke((Action)(() => {
box.Visibility = Visibility.Collapsed; 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(); protected override void PluginLoadRegisterPlugin(PluginData plugin){
string[] dlls; 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 { try {
dlls = Directory.GetFileSystemEntries(plugin_dir, "*.dll"); data.contact_plugin.Initialize();
} catch (DirectoryNotFoundException) { Application.Current.Dispatcher.BeginInvoke((Action)ContactInit);
return; active_plugin = data;
} } catch (Exception e) {
foreach (String dll in dlls) { HandleError(data, e);
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);
}
} }
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){ private void calls_RightClickMenuShowing(object sender, Call.CallRightClickEventArgs e){
if (plugins.Count == 0) if (active_plugin == null)
return; 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 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; return;
bgresolve_worker_init(); bgresolve_worker_init();
lock (pending_bg_queue.SyncRoot) { lock (pending_bg_queue.SyncRoot) {
...@@ -129,7 +156,7 @@ namespace FSClient { ...@@ -129,7 +156,7 @@ namespace FSClient {
private void bgResolveWorker_DoWork(object sender, DoWorkEventArgs e) { private void bgResolveWorker_DoWork(object sender, DoWorkEventArgs e) {
Call c = (Call)e.Argument; 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(); private static Queue pending_bg_queue = new Queue();
...@@ -142,15 +169,16 @@ namespace FSClient { ...@@ -142,15 +169,16 @@ namespace FSClient {
} }
private bool disposed; private bool disposed;
public void Dispose(){ public override void Dispose(){
if (!disposed){ if (!disposed){
disposed = true; disposed = true;
foreach (IContactPlugin plugin in plugins){ foreach (ContactPluginData plugin in plugins){
try{ try{
plugin.Terminate(); if (plugin.contact_plugin != null)
plugin.contact_plugin.Terminate();
} }
catch (Exception e){ catch (Exception e){
Utils.PluginLog("Contact Plugin Manager", "Error terminating a plugin: " + e.Message); Utils.PluginLog(PluginManagerName(), "Error terminating a plugin: " + e.Message);
} }
} }
} }
...@@ -158,16 +186,12 @@ namespace FSClient { ...@@ -158,16 +186,12 @@ namespace FSClient {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
} }
public abstract class IContactPlugin { public abstract class IContactPlugin : IPlugin {
public delegate void NumberResolved(String DisplayName); public delegate void NumberResolved(String DisplayName);
public abstract void ResolveNumber(String number, NumberResolved on_resolved); public abstract void ResolveNumber(String number, NumberResolved on_resolved);
public abstract void CallRightClickMenu(Call call, ContextMenu menu); public abstract void CallRightClickMenu(Call call, ContextMenu menu);
public abstract IEnumerable<MenuItem> ContactRightClickMenu(); public abstract IEnumerable<MenuItem> ContactRightClickMenu();
public abstract void Initialize(); public abstract bool HandleSearchBox(OurAutoCompleteBox box);
public abstract void Terminate();
public abstract string ProviderName();
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 { ...@@ -187,5 +187,25 @@ namespace FSClient.Properties {
this["EventSocket"] = value; 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 @@ ...@@ -44,5 +44,11 @@
<Setting Name="EventSocket" Type="FSClient.SettingsEventSocket" Scope="User"> <Setting Name="EventSocket" Type="FSClient.SettingsEventSocket" Scope="User">
<Value Profile="(Default)" /> <Value Profile="(Default)" />
</Setting> </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> </Settings>
</SettingsFile> </SettingsFile>
\ No newline at end of file
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论