NetworkManager C# Magic Foo

This afternoon JP asked if Banshee should not fetch CD metadata through MusicBrainz if there wasn’t an available network connection; Nat and I also discussed related ideas a few months ago, and I felt like taking a few hours to diverge from the normal code base this evening, so it was perfect timing to implement a nice, simple piece of “functional candy.”

I ended up writing some quick NetworkManager C# “bindings” using dbus-sharp and the NetworkManager DBus API. They’re mostly complete: no dialup support and no wireless network creation support. The manager, device, and network objects are supported, with only a select few method implementations missing on each. Signals work, though I’m pretty pissed at System.Reflection.EventInfo.GetRaiseMethod() (see NetworkManager.Manager.InvokeEvent(string) for juicy details in Manager.cs).

For those interested in viewing the code or even using it in your C# programs, it is checked in.

In Banshee there is a new class, Banshee.Base.NetworkDetect, which has an event to notify other classes that a connection is either up or down, and has a basic Connected property. If NetworkManager can’t be found, it’s always assumed that a connection is available. There should probably be some kind of non-NetworkManager fall back, but it’s not really that important. If you have NetworkManager, great, if not, oh well.

This now means that, for example, if you started Banshee and you were offline, and then inserted an audio CD, Banshee would not attempt to fetch the metadata from MusicBrainz. However, you then connect and are online, and NetworkManager fires a signal, Banshee propagates it to the audio CD object, and it fetches metadata automagically. How cute.

9 Replies to “NetworkManager C# Magic Foo”

  1. I believe iTunes does something clever that allows you to rip CDs when disconnected from the network but stores the relevant disc info with the ripped tracks so that you can later retrieve data based on the disc and add it to the ripped files. That would be a nice addition if you don’t already do this.

    The only bad thing about this functionality is that it is a bit hidden, presumably as automtically changing tags was thought to be intrusive (even if they’re all track 1, track 2) but some halfway house where it subtly lets you know better info is now available could be good.

  2. This is such a nice example how an extra engineering effort makes a difference. I’m sure a lot of hackers would favor a query dialog or a status toggle button. Thanks Arron for promoting a “Just Works(TM)” principle.

  3. Isn’t this kind of overkill? I’d think that just silently failing on a MusicBrainz query would be enough. That being said, the NetworkManager C# library is definitely worth something to the community, and the NM signal firing back to Banshee to update the metadata is _very_ cool.

  4. Overkill, no, I don’t think so. As you said, it’s useful to have NM notify the application when a connection is available. If it were just for MusicBrainz, then maybe it would be, but this code will come in handy for AudioScrobbler and other Internet-based features in the future, where a connection is the central role of the feature.


  5. Ideally, GetRaiseMethod would work, and for other compilers it may. You can still invoke the event via reflection…I do it as follows:

    void RaiseEvent( string eventName, params object[] args )
    Type type = GetType( );

    EventInfo eventInfo = type.GetEvent( eventName );
    MethodInfo methodInfo = eventInfo.GetRaiseMethod( );
    if ( methodInfo != null )
    methodInfo.Invoke( this, args );
    FieldInfo fieldInfo = type.GetField( eventName, BindingFlags.Instance | BindingFlags.NonPublic );
    if ( fieldInfo != null )
    Delegate del = fieldInfo.GetValue( this ) as Delegate;
    if ( del != null )
    del.DynamicInvoke( args );

  6. Ryan, I don’t see how this would work. The event handler will not appear in the Fields array, so GetField returns null also.

  7. First: thanks for the inspiration.

    Second: sorry! My code isn’t c#, because i prefer vb … but it should be easy to translate. :)

    Third: My code solves the “raise a foreign event” problem for each control and it childs.

    Public Sub RaiseControlEvent(ByVal EventName As String, ByVal Target As Control, ByVal e As EventArgs)

    ‘This will work for all events declared in the type “control”
    Dim ControlType As Type
    Dim EventKey As Object
    Dim EventKeyField As System.Reflection.FieldInfo
    Dim EventHandler As System.EventHandler
    Dim EventHandlerList As System.ComponentModel.EventHandlerList

    ControlType = GetType(Control)

    ‘The baseclass “Control” defines a shared variable with a key-object
    ‘for each event. The name of this variables begins with “Event” and
    ‘is followed by the eventname.
    ‘So … we need that key:
    EventKeyField = ControlType.GetField(“Event” & EventName, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Static)
    EventKey = EventKeyField.GetValue(Nothing) ‘static call doesn’t need a target. :)

    ‘Now we need access to the EventHandlerList of the control.
    ‘This list contains all active EventHandlers:
    Dim EventsProperty As System.Reflection.PropertyInfo = ControlType.GetProperty(“Events”, Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)
    EventHandlerList = EventsProperty.GetValue(Target, Nothing)

    ‘Weaponed with the key it is no further magic, to get the right eventhandler:
    EventHandler = EventHandlerList.Item(EventKey)

    ‘and last, but not least: invoke it!
    ‘(“target” means “sender” in the event handler routine … feel free to modify it after your needs).
    If Not EventHandler Is Nothing Then EventHandler.Invoke(Target, Nothing)

    End Sub

    (The Sub isn’t optimized for understanding and not for speed. Feel free, to speed it up.)

    Thanks so far,

  8. *boah* that formating is a dream … :-(

    one little error slipped in:

    If Not EventHandler Is Nothing Then
    EventHandler.Invoke(Target, Nothing)

    Replace “Nothing” with “e” :)

  9. Torsten, your code relies that the string “Event” prefixes all event field names which isn’t always the case. If you take a look at the ComboBox class for example, it prefixes event field names with “event_” (parenthetically, you should use the IgnoreCase BindingFlag since the casing isn’t always consistent).

Comments are closed.