I'm designing a media player, and I've designed a Movie class. The Movie class has a MovieInfo member, which inherits from MediaInfo. MediaInfo has several properties representing the metadata (is that what you call it?) of the Movie file, such as file length, file size, file path, etc. In order to extract this information, I use Shell32.
The problem is that the methods provided in Shell32 are very, very slow. With 1 movie in my database, it's not a problem, but with 10 movies, it starts becoming noticeable, and with 100 movies, it takes around 5 minutes for the program to load, and there are a few cases where I have to reinitialize the Movies during runtime, which again halts the flow of the program.
The MediaInfo constructor calls the Initialize method:
/// <summary>
/// Initializes all the information variables of the class.
/// </summary>
private void Initialize()
{
Folder mediaFolder = Shell32Processing.GetShellFolder(this.path);
FolderItem media = Shell32Processing.GetShellFolderItem(this.path);
//initialize bit rate value
this.bitrate = mediaFolder.GetDetailsOf(media, 28);
//initialize date accessed value
this.dateAccessed = mediaFolder.GetDetailsOf(media, 5);
//initialize date created value
this.dateCreated = mediaFolder.GetDetailsOf(media, 4);
//initialize date modified value
this.dateModified = mediaFolder.GetDetailsOf(media, 3);
//initialize data rate value
this.dataRate = mediaFolder.GetDetailsOf(media, 279);
//initialize file name value
this.fileName = mediaFolder.GetDetailsOf(media, 155);
//initialize file type value
this.fileType = mediaFolder.GetDetailsOf(media, 181);
//initialize folder value
this.folder = mediaFolder.GetDetailsOf(media, 177);
//initialize folder name value
this.folderName = mediaFolder.GetDetailsOf(media, 175);
//initialize folder path value
this.folderPath = mediaFolder.GetDetailsOf(media, 176);
//initialize frame height value
this.frameHeight = mediaFolder.GetDetailsOf(media, 280);
//initialize frame rate value
this.frameRate = mediaFolder.GetDetailsOf(media, 281);
//initialize frame width value
this.frameWidth = mediaFolder.GetDetailsOf(media, 282);
//initialize length value
this.length = mediaFolder.GetDetailsOf(media, 27);
//initialize title value
this.title = FileProcessing.GetFileTitle(this.path);
//initialize total bitrate value
this.totalBitrate = mediaFolder.GetDetailsOf(media, 283);
//initialize size value
this.size = mediaFolder.GetDetailsOf(media, 1);
}
Here is the Shell32Processing.GetShellFolder method:
/// <summary>
/// Gets a Shell32 Folder, initialized to the directory of the file path.
/// </summary>
/// <param name="path">A string representing the file/folder path</param>
/// <returns>The Shell32 Folder.</returns>
public static Folder GetShellFolder(string path)
{
//extract the folder subpath from the file path
//i.e extract "C:\Example" from "C:\Example\example.avi"
string directoryPath = new FileInfo(path).Directory.ToString();
return new Shell().NameSpace(directoryPath);
}
And the Shell32Processing.GetShellFolderItem method:
/// <summary>
/// Gets a Shell32 FolderItem, initialized to the item specified in the file path.
/// </summary>
/// <param name="path">A string representing the file path.</param>
/// <returns>The Shell32 FolderItem.</returns>
/// <exception cref="System.ArgumentException">Thrown when the path parameter does not lead to a file.</exception>
public static FolderItem GetShellFolderItem(string path)
{
if (!FileProcessing.IsFile(path))
{
throw new ArgumentException("Path did not lead to a file", path);
}
int index = -1;
//get the index of the path item
FileInfo info = new FileInfo(path);
DirectoryInfo directoryInfo = info.Directory;
for (int i = 0; i < directoryInfo.GetFileSystemInfos().Count(); i++)
{
if (directoryInfo.GetFileSystemInfos().ElementAt(i).Name == info.Name) //we've found the item in the folder
{
index = i;
break;
}
}
return GetShellFolder(path).Items().Item(index);
}
Each call to GetDetailsOf (which is code provided in Shell32) takes an incredible amount of time to process - I used the ANTS profiler to find this, because at first I wasn't able to identify what was slowing down my program so much.
So the question is: How can I optimize the Shell32 methods, and if I can't, is there an alternative?
There are a number of things you are doing wrong in your code. You state that the problem lies outside the code you provided, but perhaps we can get somewhere by fixing what is broken.
You are going out and accessing the file system just to get the directory portion of a file path (
new FileInfo(path).Directory
). You can do this without hitting the disk drive withSystem.IO.Path.GetDirectoryName(path)
.You are creating a new shell object every time you start processing a new item. I would create one, process all the items, then release it. So let's change
GetShellFolder
like this:And pass a
Shell
object to yourInitialize
method. Next upGetShellFolderItem
. Here is your code again:The first mistake is using "does file exist" prior to accessing that file. Don't do this. Just access the file, if it doesn't exist, a
FileNotFoundException
will occur. All you're doing is adding extra work that will already get done. Whether you do it or not, it's still possible that it passes the "file exist test", but fails to be accessed.Next you are parsing the directory to get the index of the file in the folder. This is a serious race condition. It's entirely possible for you to get the wrong index value here. It's also not necessary since
Folder
exposes a method to get theFolderItem
by name:ParseName
.Finally, you are creating yet another
Folder
(by calling GetShellFolder) which also creates yet anotherShell
item. You already have aFolder
, use it.So we can alter
GetShellFolderItem
by removing it entirely:And we can get rid of
GetShellFolder
just as neatly:Let's see how much of a difference all of that makes.
You are also calling GetDetailsOf for things you already know or can get from the media object:
Change these to: