Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Device Ananlysis & Reporting Tool (DART)

0.00/5 (No votes)
27 Jan 2012 1  
An open source WPF tool for system memory analysis and management

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)
                {
                    // Get Details for 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)
                {
                    // Get details for 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
                {
                    // Get details for file
                    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)
            {                
                // log 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
            {
                //DeviceIO parent = new DeviceIO(Path) 
                //{ Name = System.IO.Path.GetFileName(Path) };
                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)
            {

                //ignore everyone
                if (fsar.IdentityReference.Value.ToLower() == "everyone")
                    continue;
                //ignore BUILTIN
                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);
                    //Debug.WriteLine(row["Identity"]);

                    row["Control"] = fsar.AccessControlType.ToString();
                    //Debug.WriteLine(row["AcceptControlType"]);

                    row["Rights"] = fsar.FileSystemRights.ToString();
                    //Debug.WriteLine(row["FileSystemRights"]);

                    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)
            {
                // dispose managed resources
            }

                // dispose unmanaged resources
        }

        ~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()
        {
            //model = new AnalyzerModel(Path);
            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.

  1. Bug report - http://dart.codeplex.com/workitem/list/basic
  2. Source code - http://dart.codeplex.com/SourceControl/list/changesets
  3. Download - http://dart.codeplex.com/releases/view/81162
  4. Join - http://dart.codeplex.com/team/view
  5. Home Page - http://dart.codeplex.com/

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here