using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.IO; using Mono.Unix; using Mono.Unix.Native; using Tomboy.Sync; namespace Tomboy { public class AddinManager { readonly string tomboy_conf_dir; /// /// Key = TypeExtensionNode.Id /// Dictionary app_addins; /// /// Dictionary> note_addins; /// /// Key = TypeExtensionNode.Id /// Dictionary> note_addin_infos; public event System.EventHandler ApplicationAddinListChanged; public AddinManager (string tomboy_conf_dir) { this.tomboy_conf_dir = tomboy_conf_dir; app_addins = new Dictionary (); note_addins = new Dictionary> (); note_addin_infos = new Dictionary> (); InitializeMonoAddins (); } void InitializeMonoAddins () { Logger.Info ("Initializing Mono.Addins"); string addins_dir = Path.Combine (tomboy_conf_dir, "addins"); if (!Directory.Exists (addins_dir)) Directory.CreateDirectory (addins_dir); // Make sure a Tomboy.addins file exists string addins_file = Path.Combine (addins_dir, "Tomboy.addins"); if (!File.Exists (addins_file)) { StreamWriter sw = File.CreateText (addins_file); string addins_file_contents = String.Format ( "\n\n" + "\n" + "\t{0}\n" + "\n", Defines.SYS_ADDINS_DIR); sw.Write (addins_file_contents); sw.Close (); } Mono.Addins.AddinManager.AddinLoaded += OnAddinLoaded; Mono.Addins.AddinManager.AddinUnloaded += OnAddinUnloaded; Mono.Addins.AddinManager.Initialize (tomboy_conf_dir); Mono.Addins.AddinManager.Registry.Rebuild (null); Mono.Addins.AddinManager.AddExtensionNodeHandler ("/Tomboy/ApplicationAddins", OnApplicationAddinExtensionChanged); // NOTE: A SyncServiceAddin is a specialization of an ApplicationAddin Mono.Addins.AddinManager.AddExtensionNodeHandler ("/Tomboy/SyncServiceAddins", OnApplicationAddinExtensionChanged); Mono.Addins.AddinManager.AddExtensionNodeHandler ("/Tomboy/NoteAddins", OnNoteAddinExtensionChanged); } void OnAddinLoaded (object sender, Mono.Addins.AddinEventArgs args) { Logger.Debug ("AddinManager.OnAddinLoaded: {0}", args.AddinId); Mono.Addins.Addin addin = Mono.Addins.AddinManager.Registry.GetAddin (args.AddinId); Logger.Debug ("\t Name: {0}", addin.Name); Logger.Debug ("\tDescription: {0}", addin.Description.Description); Logger.Debug ("\t Namespace: {0}", addin.Namespace); Logger.Debug ("\t Enabled: {0}", addin.Enabled); Logger.Debug ("\t File: {0}", addin.AddinFile); } void OnAddinUnloaded (object sender, Mono.Addins.AddinEventArgs args) { Logger.Debug ("AddinManager.OnAddinUnloaded: {0}", args.AddinId); } void OnApplicationAddinExtensionChanged (object sender, Mono.Addins.ExtensionNodeEventArgs args) { Mono.Addins.TypeExtensionNode type_node = args.ExtensionNode as Mono.Addins.TypeExtensionNode; ApplicationAddin addin; if (args.Change == Mono.Addins.ExtensionChange.Add) { // Load NoteAddins if (Tomboy.DefaultNoteManager == null) { return; // too early -- YUCK! Bad hack } addin = type_node.GetInstance ( typeof (ApplicationAddin)) as ApplicationAddin; if (addin != null) { if (addin.Initialized == false) { try { addin.Initialize (); app_addins [type_node.Id] = addin; } catch (Exception e) { Logger.Debug ("Error initializing app addin {0}: {1}\n{2}", addin.GetType ().ToString (), e.Message, e.StackTrace); } } } } else { if (app_addins.ContainsKey (type_node.Id)) { addin = app_addins [type_node.Id]; try { addin.Shutdown (); } catch (Exception e1) { Logger.Warn ("Error shutting down app addin {0}: {1}\n{2}", addin.GetType ().ToString (), e1.Message, e1.StackTrace); } finally { app_addins.Remove (type_node.Id); } try { addin.Dispose (); } catch (Exception e1) { Logger.Warn ("Error disposing app addin: {0} - {1}", addin.GetType ().ToString (), e1.Message); } } } if (ApplicationAddinListChanged != null) ApplicationAddinListChanged (sender, args); } void OnNoteAddinExtensionChanged (object sender, Mono.Addins.ExtensionNodeEventArgs args) { if (args.Change == Mono.Addins.ExtensionChange.Add) OnNoteAddinEnabled (args); else OnNoteAddinDisabled (args); } void OnNoteAddinEnabled (Mono.Addins.ExtensionNodeEventArgs args) { // Load NoteAddins if (Tomboy.DefaultNoteManager == null) { return; // too early -- YUCK! Bad hack } foreach (Note note in Tomboy.DefaultNoteManager.Notes) { // Create a new NoteAddin Mono.Addins.TypeExtensionNode type_node = args.ExtensionNode as Mono.Addins.TypeExtensionNode; try { NoteAddin n_addin = type_node.CreateInstance () as NoteAddin; // Keep track of the addins added to each note AttachAddin (type_node.Id, note, n_addin); } catch (Exception e) { Logger.Debug ("Couldn't create a NoteAddin instance: {0}", e.Message); } } } void OnNoteAddinDisabled (Mono.Addins.ExtensionNodeEventArgs args) { Mono.Addins.TypeExtensionNode type_node = args.ExtensionNode as Mono.Addins.TypeExtensionNode; OnDisabledAddin (type_node.Id); } public void LoadAddinsForNote (Note note) { Mono.Addins.ExtensionNodeList list = Mono.Addins.AddinManager.GetExtensionNodes ("/Tomboy/NoteAddins"); foreach (Mono.Addins.ExtensionNode node in list) { Mono.Addins.TypeExtensionNode type_node = node as Mono.Addins.TypeExtensionNode; try { NoteAddin n_addin = type_node.CreateInstance () as NoteAddin; // Keep track of the addins added to each note AttachAddin (type_node.Id, note, n_addin); } catch (Exception e) { Logger.Debug ("Couldn't create a NoteAddin instance: {0}", e.Message); } } // Make sure we remove addins when a note is deleted note.Manager.NoteDeleted += OnNoteDeleted; } /// /// Returns an array of ApplicationAddin objects /// public ApplicationAddin [] GetApplicationAddins () { ApplicationAddin [] app_addins; try { app_addins = (ApplicationAddin []) Mono.Addins.AddinManager.GetExtensionObjects ( "/Tomboy/ApplicationAddins", typeof (ApplicationAddin), true); } catch (Exception e) { Logger.Debug ("No ApplicationAddins found: {0}", e.Message); app_addins = new ApplicationAddin [0]; } return app_addins; } /// /// Returns an array of NoteAddin objects that tomboy /// currently knows about. /// public NoteAddin [] GetNoteAddins () { NoteAddin [] addins; try { addins = (NoteAddin []) Mono.Addins.AddinManager.GetExtensionObjects ( "/Tomboy/NoteAddins", typeof (NoteAddin)); } catch (Exception e) { Logger.Debug ("No NoteAddins found: {0}", e.Message); addins = new NoteAddin [0]; } return addins; } /// /// Returns an array of SyncServiceAddin objects /// public SyncServiceAddin [] GetSyncServiceAddins () { SyncServiceAddin [] addins; try { addins = (SyncServiceAddin []) Mono.Addins.AddinManager.GetExtensionObjects ( "/Tomboy/SyncServiceAddins", typeof (SyncServiceAddin)); } catch (Exception e) { Logger.Debug ("No SyncServiceAddins found: {0}", e.Message); addins = new SyncServiceAddin [0]; } return addins; } /// /// Add the addin to the note and save off a reference to the addin that /// will be used when the note is deleted. /// public List GetAllAddins () { List addins = new List (); Mono.Addins.Addin [] addinsArray = Mono.Addins.AddinManager.Registry.GetAddins (); if (addinsArray != null) { // It just so happens that the NoteAddins that are part of // Tomboy.exe (from Watchers.cs) are not returned by the // above GetAddins () call, so we don't have to do anything // to exclude them here. addins = new List (addinsArray); } return addins; } /// /// Call NoteAddin.Shutdown () and NoteAddin.Dispose () on every /// NoteAddin that's attached to the deleted Note. /// void OnNoteDeleted (object sender, Note deleted) { if (note_addins.ContainsKey (deleted) == false) return; OnDeletedNote (deleted); } void AttachAddin (string ext_node_id, Note note, NoteAddin addin) { if (ext_node_id == null || note == null || addin == null) throw new ArgumentNullException ("Cannot pass in a null parameter to AttachAddin"); // Loading the addin to the note try { addin.Initialize (note); } catch (Exception e) { Logger.Warn ("Error initializing addin: {0}: {1}", addin.GetType ().ToString (), e.Message); // TODO: Would be nice to figure out how to just disable // the addin altogether if it's failing to initialize so // it doesn't keep causing problems. return; } NoteAddinInfo info = new NoteAddinInfo (ext_node_id, note, addin); List note_addin_list; List ext_node_addin_list; if (note_addins.ContainsKey (note)) note_addin_list = note_addins [note]; else { note_addin_list = new List (); note_addins [note] = note_addin_list; } if (note_addin_infos.ContainsKey (ext_node_id)) ext_node_addin_list = note_addin_infos [ext_node_id]; else { ext_node_addin_list = new List (); note_addin_infos [ext_node_id] = ext_node_addin_list; } note_addin_list.Add (info); ext_node_addin_list.Add (info); } void OnDisabledAddin (string ext_node_id) { if (ext_node_id == null) throw new ArgumentNullException ( "Cannot call OnDisabledAddin with null parameters"); Logger.Debug ("OnDisabledAddin: {0}", ext_node_id); // Remove and shut down all the addins if (note_addin_infos.ContainsKey (ext_node_id) == false) throw new ArgumentException ( "Cannot call OnDisabledAddin with an invalid Mono.Addins.ExtensionNode.Id"); List addin_info_list = note_addin_infos [ext_node_id]; foreach (NoteAddinInfo info in addin_info_list) { try { info.Addin.Shutdown (); } catch (Exception e) { Logger.Warn ("Error shutting down addin: {0} - {1}", info.Addin.GetType ().ToString (), e.Message); } try { info.Addin.Dispose (); } catch (Exception e1) { Logger.Warn ("Error disposing addin: {0} - {1}", info.Addin.GetType ().ToString (), e1.Message); } // Remove the addin from the Note if (note_addins.ContainsKey (info.Note)) { List note_addin_list = note_addins [info.Note]; note_addin_list.Remove (info); } } note_addin_infos.Remove (ext_node_id); } void OnDeletedNote (Note note) { if (note == null) throw new ArgumentNullException ( "Cannot call OnDeletedNote with null parameters"); if (note_addins.ContainsKey (note) == false) throw new ArgumentException ( "Cannot call OnDeletedNote with an invalid Note"); List note_addin_list = note_addins [note]; foreach (NoteAddinInfo info in note_addin_list) { try { info.Addin.Shutdown (); } catch (Exception e) { Logger.Warn ("Error shutting down addin: {0} - {1}", info.Addin.GetType ().ToString (), e.Message); } try { info.Addin.Dispose (); } catch (Exception e1) { Logger.Warn ("Error disposing addin: {0} - {1}", info.Addin.GetType ().ToString (), e1.Message); } if (note_addin_infos.ContainsKey (info.ExtensionNodeId)) { List addin_info_list = note_addin_infos [info.ExtensionNodeId]; addin_info_list.Remove (info); } } note_addins.Remove (note); } public bool IsAddinConfigurable (Mono.Addins.Addin addin) { object o = GetAddinPrefFactory (addin); if (o == null) return false; return true; } public Gtk.Widget CreateAddinPreferenceWidget (Mono.Addins.Addin addin) { AddinPreferenceFactory factory = GetAddinPrefFactory (addin); if (factory == null) return null; return factory.CreatePreferenceWidget (); } AddinPreferenceFactory GetAddinPrefFactory (Mono.Addins.Addin addin) { Mono.Addins.ExtensionNode node = Mono.Addins.AddinManager.GetExtensionNode ("/Tomboy/AddinPreferences"); if (node != null) { Mono.Addins.ExtensionNodeList child_nodes = node.ChildNodes; if (child_nodes != null) { foreach (Mono.Addins.ExtensionNode child_node in child_nodes) { if (addin.Id.StartsWith (child_node.Addin.Id)) { AddinPreferenceFactory factory = ((Mono.Addins.TypeExtensionNode)child_node).GetInstance () as AddinPreferenceFactory; return factory; } } } } return null; } } // TODO: Add this back in so that we can know when Addins leak memory at // disable/shutdown. class AddinReference : WeakReference { readonly string description; public AddinReference (object addin, string description) : base (addin) { this.description = description; } public string Description { get { return description; } } } class NoteAddinInfo { readonly string extension_node_id; readonly Note note; readonly NoteAddin note_addin; public NoteAddinInfo (string node_id, Note note, NoteAddin addin) { this.extension_node_id = node_id; this.note = note; this.note_addin = addin; } public string ExtensionNodeId { get { return extension_node_id; } } public Note Note { get { return note; } } public NoteAddin Addin { get { return note_addin; } } } }