TIBCO Spotfire Community

Welcome to TIBCO Spotfire Community Sign in | Join | Help

Importing data from iTunes to TIBCO Spotfire

This article targets: TIBCO Spotfire 2.1 and forward

Introduction

In this article describes one way to integrate a COM component with Spotfire to extract data and perform actions in the external component. As an example we will use the Apple iTunes music player application that I’m sure that most of you are familiar with. Our goal with the integration is to be able to load the iTunes library as a data table and start playing a song in iTunes when the row that represents the song is marked in Spotfire. It is assumed here that you know how to create a Spotfire extension.

Creating the project

First create a new Spotfire extension project in Microsoft Visual Studio and add references to the Spotfire libraries. Then add a new reference to the iTunes exe file by first selecting “Add Reference”.

clip_image002

Then go to the “Browse” tab and go to the iTunes folder and select the iTunes.exe file.

clip_image002[4]

This will add the iTunesLib COM component (iTunesLib) to the references part of the solution.

 

image

COM components and multithreading

Spotfire is a multi-threaded application and it is not guaranteed which thread a component will be called in. Extensions may be called from different threads at different times. This is something that may cause problems with COM component since they may use a single threaded apartment (STA) which means that the thread that created the instance of the COM object must be the only thread that accesses the object.

To do this we create a class called ITunesController which is a singleton wrapper for the iTunes COM object that creates a specific thread where all communication with the COM component is done.

First we create a standard singleton implementation since we only want to create one connection to iTunes.

public sealed class ITunesController : IDisposable
{
private static ITunesController instance;
private static object instanceLock = new object();
public static ITunesController GetInstance()
{
lock (instanceLock)
{
if (instance == null)
{
instance = new ITunesController();
}
}
return instance;
}

Then we need to add threading and locking code to handle synchronization between the worker thread and the calling thread.

private iTunesLib.iTunesApp iTunesApplication;
private readonly Thread workerThread;

private readonly object pendingWorkItemLock = new object();
private Work pendingWorkItem = null;

private readonly object workCompleteLock = new object();
private bool workComplete = false;

private delegate void Work();
private ITunesController()
{
// Create and start the worker thread.
this.workerThread = new Thread(DoWork);
this.workerThread.IsBackground = true;
this.workerThread.Name = "iTunes controller thread";
this.workerThread.Start();

// Create the COM object on the worker thread.
Invoke(
delegate
{
this.iTunesApplication = new iTunesLib.iTunesAppClass();
});
}
private void DoWork()
{
try
{
while (true)
{
lock (this.pendingWorkItemLock)
{
while (this.pendingWorkItem == null)
{
Monitor.Wait(this.pendingWorkItemLock);
}
}

Work todo = this.pendingWorkItem;
this.pendingWorkItem = null;

if (todo != null)
{
todo();
}
}

}
catch (ThreadAbortException)
{
Thread.ResetAbort();
}
}

private void Invoke(Work work)
{
lock (this)
{
lock (this.pendingWorkItemLock)
{
this.pendingWorkItem =
delegate()
{
try
{
work();
}
catch (Exception)
{
// Log and handle exception.
}
finally
{
// Signal that the work has been completed.
lock (this.workCompleteLock)
{
this.workComplete = true;
Monitor.PulseAll(this.workCompleteLock);
}
}
};
Monitor.PulseAll(this.pendingWorkItemLock);
}

lock (this.workCompleteLock)
{
while (!this.workComplete)
{
Monitor.Wait(this.workCompleteLock);
}

this.workComplete = false;
}
}
}

Now we have finished the framework that makes sure that the COM component is always called on the correct thread we can continue with actually making calls to iTunes.

Creating a data source

Our goal is to create a data table from a playlist in iTunes and the way to do that in the Spotfire API is to create a CustomDataSource. We start by creating a simple class with no state since we are always connecting to the same iTunes application. Note that we still need to make sure that the class is serializable and calls the appropriate serialization methods in the base class.

[Serializable]
[PersistenceVersion(1, 0)]
public sealed class ITunesDataSource : CustomDataSource
{
public ITunesDataSource()
{
// Empty
}

public ITunesDataSource(
SerializationInfo info,
StreamingContext context)
: base(info, context)
{
// Empty
}

public override void GetObjectData(
SerializationInfo info,
StreamingContext context)
{
base.GetObjectData(info, context);
}
}

Now we need to override some methods in the CustomDataSource class to actually do something, first we implement the DocumentTitle property to return “iTunes playlist” as the name of the document.

public override string DocumentTitle
{
get
{
return "iTunes Playlist";
}
}

Then we override the IsLinkable property to tell the application that this data source can be used for linked data. We do this since we want to be able to reload the data from iTunes whenever the data in iTunes changes.

public override bool IsLinkable
{
get
{
return true;
}
}

To provide some more information in the data table properties dialog we and also override the GenerateDataHistoryCore method to provide information that will show in that dialog.

protected override void GenerateDataHistoryCore(
DataHistoryBuilder builder)
{
base.GenerateDataHistoryCore(builder);

builder.AddDetail("Imported from iTunes playlist.");
}

The ConnectCore method is the method that is called when the framework wants to read data from the data source. It returns a DataSourceConnection that can be useful in some cases when one wants to keep a connection to an underlying database, but in our case we do not need the connection. Instead we use a static utility method on the DataSourceConnection class to create a connection which just returns an ITunesDataRowReader which we will create shortly.

protected override DataSourceConnection ConnectCore(
IServiceProvider serviceProvider,
DataSourcePromptMode promptMode)
{
return DataSourceConnection.CreateConnection2(
this,
delegate
{
return new ITunesDataRowReader();
},
serviceProvider);
}

Creating a data row reader

We start by creating a new class ITunesDataRowReader which inherits from CustomDataRowReader. This class is never serialized so we do not need to worry about implementing any serialization code.

public sealed class ITunesDataRowReader : CustomDataRowReader
{
private readonly ITunesController controller;

public ITunesDataRowReader()
{
this.controller = ITunesController.GetInstance();
}
}

First we override the GetColumnsCore method to specify which columns we return. Since it is pre-defined which information exists in iTunes we select a few of the more interesting columns. We also add the identity columns since they will be useful later when we want to be able to play a song in iTunes whenever it is marked in Spotfire.

Here we also create one MutableValueCursor for each result column. The MutableValueCursor is the object is used to provide the result values when iterating over the data.

protected override IEnumerable<DataRowReaderColumn> GetColumnsCore()
{
List<DataRowReaderColumn> resultColumns = new List<DataRowReaderColumn>();

resultColumns.Add(
new DataRowReaderColumn(
"Name",
DataType.String,
DataValueCursor.CreateMutableCursor(DataType.String)));

// Add result columns for:
// "Album", DataType.String
// “Artist”, DataType.String
// “Duration", DateType.Integer
// "Genre", DataType.String
// "PlayedCount", DataType.Integer
// "PlayedDate", DataType.DateTime
// "Rating", DataType.Integer
// "SourceID", DataType.Integer
// "PlaylistID", DataType.Integer
// "TrackID", DataType.Integer
// "DataBaseID", DataType.Integer

return resultColumns;
}

We also need to implement the GetResultPropertiesCore method which can be used to return metadata which should be placed at the table level. But in this example we do not need to add any data here so we just return an empty result properties object.

protected override ResultProperties GetResultPropertiesCore()
{
return new ResultProperties();
}

Now we almost ready to start returning data from iTunes but first we need to add a few method to the ITunesController class we created before to handle the thread safe invocation of the iTunes COM component. To make the class easier to use we first create a utility object called ITunesEntry which represents a song in the iTunes library, containing all the information we are interested in.

public sealed class ITunesEntry
{
private readonly string name;
// and so forth ...

public ITunesEntry
(string name,
// and so forth ...
)
{
this.name = name;
// and so forth ...
}

public string Name
{
get
{
return this.name;
}
}
// and so forth ...
}

Then we need a way to retrieve this information from iTunes and we do that by adding a new method to the iTunes controller class which returns an enumeration of these entries. The GetEntries method is added to the ITunesController class.

public IEnumerable<ITunesEntry> GetEntires()
{
IEnumerator tracksEnumerator = null;

Invoke(
delegate
{
iTunesLib.IITPlaylist playList = this.iTunesApplication.LibraryPlaylist;

if (playList != null)
{
tracksEnumerator = playList.Tracks.GetEnumerator();
}
});

if (tracksEnumerator == null)
{
// No playlist could be found.
yield break;
}

bool finished = false;
while (true)
{
ITunesEntry returnEntry = null;
Invoke(
delegate
{
if (!tracksEnumerator.MoveNext())
{
finished = true;
return;
}

iTunesLib.IITTrack currentTrack = (iTunesLib.IITTrack)tracksEnumerator.Current;

returnEntry =
new ITunesEntry(
currentTrack.Name,
. . .
currentTrack.TrackDatabaseID);
});

if (finished)
{
break;
}

yield return returnEntry;
}
}

Then we need to use this enumerator in the MoveNext method on the ITunesDataRowReader class.

protected override bool MoveNextCore()
{
if (this.rows == null)
{
this.rows = this.controller.GetEntires().GetEnumerator();
}

if (!this.rows.MoveNext())
{
return false;
}

ITunesEntry currentRow = rows.Current;
((MutableValueCursor<string>)this.Columns[0].Cursor).MutableDataValue.IsValid = currentRow.Name != null;
// and so forth ...

((MutableValueCursor<string>)this.Columns[0].Cursor).MutableDataValue.Value = currentRow.Name;
// and so forth . . .

return true;
}

Finally we need to implement the ResetCore method that should restart the reading of the rows to start from the beginning again.

protected override void ResetCore()
{
this.rows = this.controller.GetEntires().GetEnumerator();
}

Now we have a finished reader and data source that can be used to read data from iTunes.

Wrapping it up

To add the data source we also need to create an AddIn which registers the data source, but first we need to create a type identifier for the data source.

public class ITunesTypeIdentifiers : CustomTypeIdentifiers
{
public static CustomTypeIdentifier ITunesDataSourceTypeIdentifier =
CustomTypeIdentifiers.CreateTypeIdentifier(
"iTunes",
"iTunes",
"Opens data from the current iTunes application.");
}

Now we can create the AddIn and register the data source.

public class ITunesAddIn : AddIn
{
protected override void RegisterDataSources(
AddIn.DataSourceRegistrar registrar)
{
base.RegisterDataSources(registrar);

registrar.Register<ITunesDataSource>(
ITunesTypeIdentifiers.ITunesDataSourceTypeIdentifier);
}
}

The DataSource should now show up in the menu.

clip_image002[1]

If we now use the data source we will get the iTunes music library as a data table and since we made the data source linkable we can refresh the data which will update the data table with any new information added in iTunes.

clip_image004

Conclusion

While this article has been rather complex it is hopefully a good example on how to integrate Spotfire with external applications using COM interop. In the next article we will look into how to connect marking in Spotfire to playing songs in iTunes.

Comments