About
DART is a tool for analyzing system information and memory distribution of high volume network drives and folders. It gives graphical analysis of system memory along with usage history, snapshot & reporting making device maintenance easy and efficient.
Introduction
DART is a WPF based tool. Its architecture is purely based on MVVM pattern. DART makes extensive use of loosely coupled code, unit testing of modules, segregation of view from application logic and non-blocking asynchronous calls. It is developed using Visual Studio 2010.
Background
The idea first came to me when I ran out of memory with my two external hard disk drives containing over 1500 GB of movies. I then had to analyze each folder to check how many files it contained and how much memory it consumed one by one. What I needed was a graphical representation of a folder with its children and an easy viewing of its details. The existing XP and Windows 7 do not provide these details. Thus DART was born.
Objective
The objective of this article is to provide a high level architecture of DART. The article explains code snippets which render the crux of the functionality. Prerequisite knowledge of WPF and MVVM is recommended for comprehending the discussions below. For a more detailed elaboration or explanation on particular items, please contact me or visit the forum at - http://dart.codeplex.com/discussions.
Part 1 - The Framework
The Framework is responsible for gathering the required information for DART.
The Analyzer Attribute
class is a placeholder for collecting information about the system. It has a method GetDetails()
which returns a dictionary of details about an element depending on whether it is a drive, directory or file.
public Dictionary<string,string> GetDetails()
{
try
{
Dictionary<string, string> Details = new Dictionary<string, string>();
if (Type == ItemType.Drive)
{
DriveInfo drInfo = new DriveInfo(Path);
Details.Add("Drive Type", drInfo.DriveType.ToString());
Details.Add("Drive Format", drInfo.DriveFormat.ToString());
Details.Add("Volume Label", drInfo.VolumeLabel.ToString());
}
else if (Type == ItemType.Directory)
{
DirectoryInfo dirInfo = new DirectoryInfo(Path);
Details.Add("Creation Time",
dirInfo.CreationTime.ToString("MM-dd-yyyy"));
Details.Add("Last Access Time",
dirInfo.LastAccessTime.ToString("MM-dd-yyyy"));
Details.Add("Last Write Time",
dirInfo.LastWriteTime.ToString("MM-dd-yyyy"));
Details.Add("Root", dirInfo.Root.ToString());
}
else
{
FileInfo fileInfo = new FileInfo(Path);
Details.Add("Creation Time", fileInfo.CreationTime.ToString());
Details.Add("Last Access Time", fileInfo.LastAccessTime.ToString());
Details.Add("Last Write Time", fileInfo.LastWriteTime.ToString());
Details.Add("Directory Name", fileInfo.DirectoryName.ToString());
}
return Details;
}
catch (Exception)
{
return null;
}
}
The CalculateTotalMemory()
method returns the total memory of the element:
private void CalculateTotalMemory()
{
if (Utility.IsAccessible(this.Path))
{
if (this.Type==ItemType.Drive)
{
this.TotalMemory = new DriveInfo(this.Path).TotalSize /
Utility.DivisionNumber;
this.FreeMemory = new DriveInfo(this.Path).TotalFreeSpace/
Utility.DivisionNumber;
this.UsedMemory = this.TotalMemory - this.FreeMemory;
}
else
{
this.UsedMemory = this.TotalMemory;
}
}
}
Similarly, the SortMemoryList()
method sorts the details dictionary based on the memory. The idea is to hold the details of children and their corresponding memory in the MemoryList
property. This sorts items in the MemoryList
based on their memory.
The Analyzer
class is a singleton instance which contains a single most important function called GetMemory()
. This function enumerates through the children elements of an item (File, Folder or Drive) and calculates their total size. If the child is a file, the memory is its memory on device. If the child is a folder, then it calls itself recursively.
public AnalyzerAttribute GetMemory(string FullPathName)
{
try
{
if (Utility.IsAccessible(FullPathName))
{
DirectoryInfo dirInfo = new DirectoryInfo(FullPathName);
AnalyzerAttribute attr = new AnalyzerAttribute(FullPathName);
foreach (FileInfo file in dirInfo.GetFiles())
{
attr.MemoryList.Add(new FileMemoryList()
{
FileName = file.Name,
Memory = file.Length / Utility.DivisionNumber
});
attr.TotalMemory += file.Length / Utility.DivisionNumber;
}
foreach (string fileName in Directory.GetDirectories(FullPathName))
{
double memory = GetMemory(fileName).TotalMemory;
attr.MemoryList.Add(new FileMemoryList()
{
FileName = fileName,
Memory = memory
});
attr.TotalMemory += memory;
}
return attr;
}
else
{
return new AnalyzerAttribute(FullPathName)
{
MemoryList = new List<FileMemoryList>(){new FileMemoryList()
{FileName=FullPathName,Memory=0}}
};
}
}
catch (Exception)
{
return null;
}
}
The DeviceIO
is a class which holds information about any particular Io device – namely – File, Folder or Drive. It has a method called GetChildren()
which gets the children recursively. The other properties like, Name, Path, etc. are self explanatory.
public List<DeviceIO> GetChildren(string Path)
{
try
{
List<DeviceIO> children = new List<DeviceIO>();
foreach (string name in Directory.GetDirectories(Path))
{
if(Utility.IsAccessible(name))
children.Add(new DeviceIO(name)
{
Name = System.IO.Path.GetFileName(name),
Path = Path
});
}
foreach (string file in Directory.GetFiles(Path))
{
children.Add(new DeviceIO(file)
{
Name = System.IO.Path.GetFileName(file),
Path = Path
});
}
return children;
}
catch (Exception)
{
return null;
}
}
}
Part 2 - The Model
The Model more or less acts as a wrapper for the framework and acts as a black box encapsulating the framework when called from the ViewModel
.
The class AnalyzerModel
has the following properties - Path
, Name
, ElementType
, TotalMemory
. The property AccessList
gives the details of the access control for a particular folder. This is done by calling it from the framework.
public DataTable GetFolderGroups(string folderPath)
{
DataTable ACLTable = new DataTable();
DataColumn[] dc = { new DataColumn("Identity"),
new DataColumn("Control"), new DataColumn("Rights") };
ACLTable.Columns.AddRange(dc);
FileSecurity fs = System.IO.File.GetAccessControl(folderPath);
AuthorizationRuleCollection arc = fs.GetAccessRules
(true, true, typeof(NTAccount));
foreach (FileSystemAccessRule fsar in arc)
{
if (fsar.IdentityReference.Value.ToLower() == "everyone")
continue;
if (fsar.IdentityReference.Value.ToLower().StartsWith("builtin"))
continue;
if (fsar.IdentityReference.Value.ToUpper() == @"NT AUTHORITY\SYSTEM")
continue;
DataRow row = ACLTable.NewRow();
string group = fsar.IdentityReference.Value;
int nindex = group.IndexOf('\\');
if (nindex > 0)
{
row["Identity"] = group.Substring(nindex + 1,
group.Length - nindex - 1);
row["Control"] = fsar.AccessControlType.ToString();
row["Rights"] = fsar.FileSystemRights.ToString();
ACLTable.Rows.Add(row);
}
}
return ACLTable;
}
The DefineMemory
(double memory) transfers the raw memory data into a presentable format. The raw data is always in bytes. It converts that into applicable unit and rounds off.
private string DefineMemory(double memory)
{
try
{
int count=1;
string unit = string.Empty;
do
{
memory = System.Math.Round(memory / System.Math.Pow(1024, count),2);
count += 1;
} while (System.Math.Round(memory / 1024, 2) > 1);
if(count==0)
unit = "Bytes";
else if(count==1)
unit = "KB";
else if(count==2)
unit = "MB";
else
unit = "GB";
return memory + " " + unit;
}
catch (Exception)
{
throw;
}
}
}
Using these, the Model populates its properties as below:
this.Path = _attr.Path;
this.Name = _attr.Name;
this.ElementType = _attr.Type.ToString();
this.TotalMemory = DefineMemory(_attr.TotalMemory);
this.UsedMemory = DefineMemory(_attr.UsedMemory);
this.History = _attr.GetDetails();
this.InaccssibleList = _attr.InaccessibleList;
AccessList = _analyzer.GetFolderGroups(Path);
The Model is now ready to be consumed by the ViewModel
.
Part 3 – The ViewModel
The ViewModelBase
class is the base class for all the ViewModels
. It inherits from DispatcherObject
(to support asynchronous call), INotifyPropertyChanged
interface and IDisposable
interface.
The Dispose
functionality is implemented as follows:
public void Dispose()
{
Dispose(true);
System.GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
}
~ViewModelBase()
{
Dispose(false);
}
The INotifyPropertyChanged
is implemented as follows:
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string PropertyName)
{
if(this.PropertyChanged!=null)
this.PropertyChanged(this,new PropertyChangedEventArgs(PropertyName));
}
The ViewModelBase
also has to accommodate events for collection changed. For any view model which has an Observable
collection property and inherits from the ViewModelBase
has implement INotifyPropertyChanged
for items added into the collection. This is because, though Observable
collections implement INotifyCollection
changed, they don’t implicitly implement INotifypropertyChanged
by default.
protected virtual void Item_CollectionChanged
(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action.ToString() == "Add")
{
if (e.NewItems != null)
{
foreach (ViewModelBase item in e.NewItems)
{
item.PropertyChanged +=
new PropertyChangedEventHandler(Item_PropertyChanged);
}
}
}
else
{
if (e.OldItems != null)
{
foreach (ViewModelBase item in e.OldItems)
{
item.PropertyChanged -=
new PropertyChangedEventHandler(Item_PropertyChanged);
}
}
}
}
protected virtual void Item_PropertyChanged
(object sender, PropertyChangedEventArgs e)
{
if (e != null)
this.OnPropertyChanged(e.PropertyName);
}
protected virtual void OnPropertyChanged(string PropertyName)
{
if(this.PropertyChanged!=null)
this.PropertyChanged(this,new PropertyChangedEventArgs(PropertyName));
}
The workspace is a placeholder for a workspace in our View. The workspace can be considered as an individual presenter in the presentation layer. This too inherits from the ViewModelBase
.
public class WorkspaceViewModel:ViewModelBase
{
public event EventHandler RequestClose;
public WorkspaceViewModel()
{
}
protected virtual void OnRequestClose()
{
if (this.RequestClose != null)
this.RequestClose(this, new EventArgs());
}
protected virtual bool OnRequestCanClose()
{
return true;
}
}
The EntityViewModel
class is the place holder for the main presentation in the view. It has a property MemoryPercentage
which is a dictionary item having key value pairs of item name and their memory. This is loaded from the Model as:
private void InitializeGraph()
{
memoryList = new List<ModelFileMemoryList>();
memoryPercentage = new Dictionary<string, double>();
memoryList = model.MemoryList;
foreach (ModelFileMemoryList item in model.MemoryList)
{
string unit = item.ItemMemory.Split(' ')[1];
double memory = Convert.ToDouble(item.ItemMemory.Split(' ')[0]);
if (unit == "KB")
memory = memory / (1024 * 1024);
else if (unit == "MB")
memory = memory / 1024;
unit = model.TotalMemory.ToString().Split(' ')[1];
double totalMemory = Convert.ToDouble
(model.TotalMemory.ToString().Split(' ')[0]);
if (unit == "KB")
totalMemory = memory / (1024 * 1024);
else if (unit == "MB")
totalMemory = memory / 1024;
double percentage = System.Math.Round((memory / totalMemory) * 100, 2);
this.MemoryPercentage.Add(System.IO.Path.GetFileName
(item.FileName), percentage);
}
this.AccessList = model.AccessList;
}
The MyComputerTreeViewModel
is the view model for the “my computer” presentation element. It lists down the children of a selected element in a tree view.
Above all this sits the MainWindowViewModel
. This ViewModel
directly inherits from the WorkSpaceviewModel
and acts as a placeholder for all other view models.
Part 4 – The View
Each View is a separate user control and they are launched from the MainWindowView
which is the main WPF application project.
There are three main views worth describing:
EntityView
– The EntityView
holds the crux of the information. It lists down the children in a Listbox
which shows the AnalyzerItemView
.
<Border Grid.Row="1" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="1"
BorderBrush="Bisque" BorderThickness="1"
Margin="2,2,2,2" VerticalAlignment="Stretch">
<ScrollViewer Background="#FFF8F8F8" MaxHeight="400">
<ItemsControl ItemsSource="{Binding Items}"
Background="Transparent" BorderBrush="Transparent"
ItemTemplate="{StaticResource TreeViewDataTemplate}"
FontSize="15" VerticalAlignment="Top"
FontFamily="Buxton Sketch" FontWeight="Bold"
Margin="10,10,10,10"/>
</ScrollViewer>
</Border>
The graph is bound with the MemoryPercentage
property from the view model. I have used a simple pie chart with from WPF charting toolkit. Note the use of the properties – DependentValuePath
and IndependentValuePath
.
<chartingToolkit:Chart DataContext="{Binding}" HorizontalAlignment="Stretch"
Margin="2,2,2,2" Name="EntityChart" Grid.Column="1"
Grid.Row="2" Grid.ColumnSpan="2"
BorderBrush="BurlyWood"
Title="Graphical Analysis"
VerticalAlignment="Stretch"
Background="Silver" FontFamily="Buxton Sketch"
FontSize="15">
<chartingToolkit:Chart.TitleStyle>
<Style TargetType="datavis:Title">
<Setter Property="FontSize" Value="30"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</chartingToolkit:Chart.TitleStyle>
<chartingToolkit:PieSeries DependentValuePath="Value"
IndependentValuePath="Key"
ItemsSource="{Binding MemoryPercentage}"/>
MyComputerView
– This is used to display children elements of a parent element. It uses a tree view control and binds its children using a hierarchical data template as shown below. It also makes a two way binding for self explanatory properties – IsExpanded
, IsSelected
, etc.
<TreeView ItemsSource="{Binding FirstGeneration}" Background="Transparent"
BorderBrush="Transparent"
ToolTip="Select an item and click the button" >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<DockPanel Style="{StaticResource DockPanelStyle}" >
<TextBlock Text="{Binding Name}"
FontFamily="Buxton Sketch" FontSize="20"
Style="{StaticResource TextBlockStyle}"
ToolTip="{Binding ToolTipInformation}">
</TextBlock>
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
MainWindowView
– This can be termed as a launch pad for other views. The EntityView
is launched as:
<Border BorderBrush="Brown" BorderThickness="0" Grid.Column="1" Margin="2,2,2,2"
Grid.Row="1">
<TabControl ItemsSource="{Binding WorkSpaces}"
VerticalAlignment="Stretch"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedItem}"/>
</Border>
Similarly, the MyComputerView
is launched as:
<StackPanel Grid.Row="1" DataContext="{Binding}" Background="Transparent">
<Separator Background="SlateGray" HorizontalAlignment="Stretch"/>
<ItemsControl Background="Transparent"
ItemsSource="{Binding MyComputer}"/>
</StackPanel>
Ending Note
This tool has a lot of scope for improvement and additional features. If you are interested in contributing, please write to me at or visit - http://dart.codeplex.com/.
You may find the following links useful. Your feedback and contribution are appreciated.
- Bug report - http://dart.codeplex.com/workitem/list/basic
- Source code - http://dart.codeplex.com/SourceControl/list/changesets
- Download - http://dart.codeplex.com/releases/view/81162
- Join - http://dart.codeplex.com/team/view
- Home Page - http://dart.codeplex.com/