Ejemplo n.º 1
0
        public void UpdateColumnWidth(IDetector detector, string columnName, int columnWidth)
        {
            // TODO: detector should be in project; create unit test
            PreConditions.Argument("detector").Value(detector).IsNotNull();
            PreConditions.Argument("columnName").Value(columnName).IsNotNull().And.IsNotEmpty();
            if (columnWidth < 0)
            {
                throw new ArgumentOutOfRangeException("columnWidth");
            }

            List <IColumnInfo> columns;

            if (_visibleColumns.TryGetValue(detector, out columns))
            {
                for (int i = 0; i < columns.Count; i++)
                {
                    IColumnInfo columnInfo = columns[i];
                    if (columnInfo.Name == columnName)
                    {
                        IColumnInfo updatedColumnInfo = columnInfo.UpdateWidth(columnWidth);
                        if (updatedColumnInfo != columnInfo)
                        {
                            columns[i] = updatedColumnInfo;
                            UpdateProject(ProjectChangedType.VisibleColumnsChanged, detector);
                        }
                        return;
                    }
                }
            }
            throw new ArgumentException(string.Format("'{0}' is not a visible column.", columnName), "columnName");
        }
        /// <summary>
        /// Creates a new file scanner for scanning (multiple) files.
        /// </summary>
        /// <param name="containerDataScanner">The <see cref="IDataScanner"/> for container formats</param>
        /// <param name="codecDataScanner">The <see cref="IDataScanner"/> for codec formats</param>
        /// <param name="dataReaderPool">The shared pool of file data readers</param>
        /// <param name="createDataBlockBuilder">The factory method for creating data blocks</param>
        /// <param name="createSubProgressReporter">The factory method for creating a sub progress reporter</param>
        /// data scanner fors canning codec streams (of a detected data block)</param>
        /// <exception cref="ArgumentNullException">If any argument is <c>null</c></exception>
        public FileScanner(IDataScanner containerDataScanner, IDataScanner codecDataScanner, IDataScanner codecStreamDataScanner,
                           IDataReaderPool dataReaderPool, Creator <IDataBlockBuilder> createDataBlockBuilder,
                           Creator <IProgressReporter, IProgressReporter, long, long, long> createSubProgressReporter)
        {
            PreConditions.Argument("containerDataScanner").Value(containerDataScanner).IsNotNull();
            PreConditions.Argument("codecDataScanner").Value(codecDataScanner).IsNotNull();
            PreConditions.Argument("codecStreamDataScanner").Value(codecStreamDataScanner).IsNotNull();
            PreConditions.Argument("dataReaderPool").Value(dataReaderPool).IsNotNull();
            PreConditions.Argument("createDataBlockBuilder").Value(createDataBlockBuilder).IsNotNull();
            PreConditions.Argument("createSubProgressReporter").Value(createSubProgressReporter).IsNotNull();

            _containerDataScanner      = containerDataScanner;
            _codecDataScanner          = codecDataScanner;
            _codecStreamDataScanner    = codecStreamDataScanner;
            _dataReaderPool            = dataReaderPool;
            _createDataBlockBuilder    = createDataBlockBuilder;
            _createSubProgressReporter = createSubProgressReporter;

            _codecDataScanner.DataBlockDetected       += (sender, e) => FixAndReportDataBlock(e.DataBlock);
            _containerDataScanner.DataBlockDetected   += (sender, e) => ScanCodecStreams(e, e.DataBlock);
            _containerDataScanner.UnknownDataDetected += (sender, e) => ScanForCodecFormats(_inputFile.CreateDataPacket().GetSubPacket(e.Offset, e.Length));

            // The default is that container blocks are allowed to overlap. See also issue DEFR-867.
            AllowOverlap = true;
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Creates a new event args of the given <paramref name="type"/>.
        /// </summary>
        /// <param name="type">the type of change</param>
        public ProjectChangedEventArgs(ProjectChangedType type)
        {
            PreConditions.Argument("type").Value(type).IsDefinedOnEnum(typeof(ProjectChangedType));

            Type = type;
            Item = null;
        }
Ejemplo n.º 4
0
        public ForensicIntegrityLog(ProjectManager projectManager, IDetectorFormatter detectorFormatter)
        {
            PreConditions.Argument("projectManager").Value(projectManager).IsNotNull();

            _projectManager    = projectManager;
            _detectorFormatter = detectorFormatter;
        }
        /// <summary>
        /// Opens an existing project.
        /// </summary>
        /// <param name="path">the path for the project file</param>
        /// <returns>the project</returns>
        public IProject OpenProject(string path)
        {
            PreConditions.Argument("path").Value(path).IsNotNull().And.IsNotEmpty().And.IsExistingFile();
            PreConditions.Operation().IsInvalidIf(IsOpenProject(path), "Project already open");

            // Deserialize the project
            XmlObjectSerializer dataContractSerializer = _createXmlObjectSerializer();

            using (XmlReader reader = XmlReader.Create(path))
            {
                try
                {
                    IProject project = dataContractSerializer.ReadObject(reader) as IProject;
                    _projects.Add(project);

                    project.ProjectChanged  += OnProjectChanged;
                    project.PropertyChanged += OnPropertyChanged;
                    OnProjectChanged(this, new ProjectChangedEventArgs(ProjectChangedType.Opened, project));

                    return(project);
                }
                catch (SerializationException se)
                {
                    throw new InvalidProjectException("Project is invalid.", path, se);
                }
            }
        }
Ejemplo n.º 6
0
        public int Read(byte[] array, int arrayOffset, int count)
        {
            PreConditions.Object(this).IsDisposedIf(_dataReaderPool == null);
            PreConditions.Argument("array").Value(array).IsNotNull();
            PreConditions.Argument("arrayOffset").Value(arrayOffset).InRange(0, array.Length);
            PreConditions.Argument("count").Value(count).InRange(0, (array.Length - arrayOffset));

            int totalBytesToRead = (int)Math.Min(count, (Length - Position));
            int bytesRead        = 0;

            while (bytesRead < totalBytesToRead)
            {
                IDataPacket fragment = _dataPacket.GetFragment(_position + bytesRead);

                int fragmentBytes     = (int)Math.Min((totalBytesToRead - bytesRead), fragment.Length);
                int fragmentBytesRead = _dataReaderPool.ReadInputFile(fragment.InputFile, fragment.StartOffset, array, (arrayOffset + bytesRead), fragmentBytes);
                bytesRead += fragmentBytesRead;

                // Completed or cancelled if not read the _entire_ fragment
                if (fragmentBytesRead != fragmentBytes)
                {
                    break;
                }
            }
            return(bytesRead);
        }
        public void Add(IFragment fragment)
        {
            PreConditions.Argument("fragment").Value(fragment).IsNotNull();

            _fragments.Add(fragment);
            Length += fragment.Length;
        }
Ejemplo n.º 8
0
        public void Save(IEnumerable <object> items, IEnumerable <IDetector> detectors, IDataReaderPool dataReaderPool, string directory, IProgressReporter progressReporter, bool createForensicIntegrityLog)
        {
            PreConditions.Argument("items").Value(items).IsNotNull().And.IsNotEmpty();
            PreConditions.Argument("detectors").Value(detectors).IsNotNull().And.DoesNotContainNull();
            PreConditions.Argument("dataReaderPool").Value(dataReaderPool).IsNotNull();
            PreConditions.Argument("directory").Value(directory).IsNotNull().And.IsNotEmpty();
            PreConditions.Argument("progressReporter").Value(progressReporter).IsNotNull();

            if (progressReporter.CancellationPending)
            {
                return;
            }

            var overallProgressReporter = new OverallProgressReporter(progressReporter);

            overallProgressReporter.CountNumberOfParts(items);
            var handledContainers = new HandledContainers();

            int numSavedFiles = 0;

            handledContainers.ClearHandledFragmentedContainers();

            foreach (object item in items)
            {
                string path = Path.Combine(directory, ReplaceIllegalPathCharactersByUnderscore(GetFileName(item)));

                numSavedFiles += SaveItem(item, dataReaderPool, path, overallProgressReporter, handledContainers, createForensicIntegrityLog);

                if (overallProgressReporter.CancellationPending)
                {
                    break;
                }
            }
        }
Ejemplo n.º 9
0
        /// <summary>
        /// Reset one configuration item
        /// </summary>
        /// <param name="detector">the detector to set the configuration item on</param>
        /// <param name="configurationItemKey">The configuration item to reset to its default value</param>
        public static void ResetConfigurationItem(this IDetector detector, string configurationItemKey)
        {
            PreConditions.Argument("detector").Value(detector).IsNotNull();
            PreConditions.Argument("configurationItemKey").Value(configurationItemKey).IsNotNull().And.IsNotEmpty();

            GetConfigurationItem(detector, configurationItemKey).ResetDefault();
        }
Ejemplo n.º 10
0
        /// <summary>
        /// Saves the <paramref name="dataPacket"/> sequentially to a single file with
        /// given <paramref name="filePath"/>.
        /// </summary>
        /// <param name="dataPacket">The data packet to save</param>
        /// <param name="detectors">The detectors used to create data packet</param>
        /// <param name="dataReaderPool">The shared pool of file data readers</param>
        /// <param name="filePath">The path name of the file to write to</param>
        /// <param name="progressReporter">For reporting progress and checking cancellation</param>
        /// <param name="createForensicIntegrityLog">Create a forensic integrity log file along with the normal output</param>
        /// <exception cref="ArgumentNullException">If any argument is <c>null</c></exception>
        /// <exception cref="IOException">On error writing the output file</exception>
        public void Save(IDataPacket dataPacket, IEnumerable <IDetector> detectors, IDataReaderPool dataReaderPool, string filePath, IProgressReporter progressReporter, bool createForensicIntegrityLog)
        {
            PreConditions.Argument("dataPacket").Value(dataPacket).IsNotNull();
            PreConditions.Argument("detectors").Value(detectors).IsNotNull().And.DoesNotContainNull();
            PreConditions.Argument("dataReaderPool").Value(dataReaderPool).IsNotNull();
            PreConditions.Argument("filePath").Value(filePath).IsNotNull().And.IsNotEmpty();
            PreConditions.Argument("progressReporter").Value(progressReporter).IsNotNull();

            if (progressReporter.CancellationPending)
            {
                return;
            }

            using (IDataWriter dataWriter = _createDataWriter(filePath))
            {
                using (IDataReader dataReader = dataReaderPool.CreateDataReader(dataPacket, progressReporter))
                {
                    dataWriter.Write(dataReader);
                }
            }
            if (createForensicIntegrityLog)
            {
                string logFileName = string.Format("{0}.csv", filePath);
                using (FileStream fs = new FileStream(logFileName, FileMode.Create, FileAccess.Write, FileShare.Read))
                {
                    _forensicIntegrityLog.Log(dataPacket, detectors, filePath, fs, ForensicLogType.CopiedData);
                }
            }
        }
Ejemplo n.º 11
0
        /// <summary>
        /// Sorts the <paramref name="results"/> on a user selected column.
        /// This function returns <paramref name="results"/> unmodified if
        /// the list is already sorted
        /// </summary>
        /// <param name="results">the resuls to sort</param>
        /// <param name="columnName">the name of the column to compare</param>
        /// <param name="sortDirection">the sort direction</param>
        /// <returns>the results list (sorted)</returns>
        public static IEnumerable <IResultNode> Sort(this IEnumerable <IResultNode> results, string columnName, ListSortDirection sortDirection)
        {
            PreConditions.Argument("results").Value(results).IsNotNull();
            PreConditions.Argument("columnName").Value(columnName).IsNotNull();

            DefaultColumnIndex index;

            if (DefaultColumnExtensions.TryParse(columnName, out index))
            {
                switch (index)
                {
                case DefaultColumnIndex.Name:
                    return(results.OrderBy(r => r.Name, sortDirection));

                case DefaultColumnIndex.Detector:
                    return(results.OrderBy(r => r.Detectors.First().Name, sortDirection));

                case DefaultColumnIndex.DetectorVersion:
                    return(results.OrderBy(r => r.Detectors.First().VersionString(), sortDirection));

                case DefaultColumnIndex.Offset:
                    return(results.OrderBy(r => r.StartOffset, sortDirection));

                case DefaultColumnIndex.Length:
                    return(results.OrderBy(r => r.Length, sortDirection));

                case DefaultColumnIndex.EndOffset:
                    return(results.OrderBy(r => r.EndOffset, sortDirection));

                case DefaultColumnIndex.File:
                    return(results.OrderBy(r => GetResultFileName(r), sortDirection));
                }
            }
            return(results.OrderBy(r => r.FindAttributeByName(columnName) == null ? null : r.FindAttributeByName(columnName).Value, sortDirection));
        }
Ejemplo n.º 12
0
        /// <summary>
        /// Sorts the <paramref name="dataBlocks"/> on a user selected column.
        /// </summary>
        /// <param name="dataBlocks">the data blocks to sort</param>
        /// <param name="columnName">the name of the column to compare</param>
        /// <param name="sortDirection">the sort direction</param>
        /// <returns>the sorted data blocks</returns>
        public static IEnumerable <IDataBlock> Sort(this IEnumerable <IDataBlock> dataBlocks, string columnName, ListSortDirection sortDirection)
        {
            PreConditions.Argument("dataBlocks").Value(dataBlocks).IsNotNull();
            PreConditions.Argument("columnName").Value(columnName).IsNotNull();
            DefaultColumnIndex index;

            if (DefaultColumnExtensions.TryParse(columnName, out index))
            {
                switch (index)
                {
                case DefaultColumnIndex.Name:
                    return(dataBlocks);

                case DefaultColumnIndex.Detector:
                    return(dataBlocks.OrderBy(d => d.Detectors.First().Name, sortDirection));

                case DefaultColumnIndex.DetectorVersion:
                    return(dataBlocks.OrderBy(d => d.Detectors.First().VersionString(), sortDirection));

                case DefaultColumnIndex.Offset:
                    return(dataBlocks.OrderBy(d => d.StartOffset, sortDirection));

                case DefaultColumnIndex.Length:
                    return(dataBlocks.OrderBy(d => d.Length, sortDirection));

                case DefaultColumnIndex.EndOffset:
                    return(dataBlocks.OrderBy(d => d.EndOffset, sortDirection));
                }
            }
            throw new ArgumentException("Invalid sort column name.", "columnName");
        }
        /// <summary>
        /// Gets the results for the given <paramref name="fragment"/> and
        /// reports progress to the given <paramref name="progressReporter"/>.
        /// </summary>
        /// <param name="fragment">the data block or codec stream to scan</param>
        /// <param name="progressReporter">the progress reporter</param>
        /// <param name="dataReaderPool">The shared pool of file data readers</param>
        /// <returns>the root node of the results</returns>
        public IResultNode GetResults(IFragment fragment, IProgressReporter progressReporter, IDataReaderPool dataReaderPool)
        {
            PreConditions.Argument("fragment").Value(fragment).IsNotNull();
            // TODO: fragment.Detector can be null !!
            PreConditions.Argument("progressReporter").Value(progressReporter).IsNotNull();

            // Check if there exists a cached copy for the fragment
            IResultNode results;

            if (_resultsCache.TryGetValue(fragment, out results))
            {
                return(results);
            }

            // During the rescan, set the IsFragmented property as it was during the first scan.
            // to make sure the result will be the same
            results = RescanDetectable(fragment, progressReporter, dataReaderPool);

            if (!progressReporter.CancellationPending)
            {
                CacheResults(fragment, results);
            }

            return(results);
        }
        public void Remove(IFragment fragment)
        {
            PreConditions.Argument("fragment").Value(fragment).IsNotNull();

            _fragments.Remove(fragment);
            Length -= fragment.Length;
        }
Ejemplo n.º 15
0
        /// <summary>
        /// Creates a <see cref="IDataWriter"/> for writing to <paramref name="filePath"/>.
        /// </summary>
        /// <param name="filePath">The path of the file to write to</param>
        /// <exception cref="ArgumentNullException">If <paramref name="filePath"/> is <c>null</c></exception>
        public FileDataWriter(String filePath)
        {
            PreConditions.Argument("filePath").Value(filePath).IsNotNull().And.IsNotEmpty();

            _buffer       = new byte[DefaultBufferSize];
            _outputStream = File.Create(filePath);
        }
        /// <summary>
        /// Saves the given <paramref name="project"/>.
        /// </summary>
        /// <remarks>
        /// Saving a project automatically updates its modifcation date.
        /// </remarks>
        /// <param name="project">the project to save</param>
        public void SaveProject(IProject project)
        {
            PreConditions.Argument("project").Value(project).IsNotNull();
            PreConditions.Operation().IsInvalidIf(!_projects.Contains(project), "Project is not open");

            // Update modification date
            var metadata = new Dictionary <ProjectMetadataKey, string>(project.GetMetadata());

            metadata[ProjectMetadataKey.DateLastModified] = DateTime.Now.ToString(DateTimeFormat);

            project.SetMetadata(metadata);
            //ValidateFragmentenDataBlocks(project);

            // Serialize (save) the project
            try
            {
                var xmlObjectSerializer = _createXmlObjectSerializer();
                var writerSettings      = new XmlWriterSettings {
                    Indent = true
                };
                using (XmlWriter writer = XmlWriter.Create(project.FileName, writerSettings))
                {
                    xmlObjectSerializer.WriteObject(writer, project);
                }

                project.Dirty = false;
            }
            catch (Exception exception)
            {
                MessageBox.Show(exception.Message,
                                "Failed to save the project",
                                MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        public IDataPacket GetSubPacket(long offset, long length)
        {
            PreConditions.Argument("offset").Value(offset).InRange(0L, (Length - 1L));
            PreConditions.Argument("length").Value(length).InRange(1L, (Length - offset));

            if ((offset == 0) && (length == Length))
            {
                return(this);                   // Sub-packet is the entire packet
            }

            long firstLength = _firstDataPacket.Length;

            if (offset >= firstLength)
            {
                return(_secondDataPacket.GetSubPacket((offset - firstLength), length));
            }

            long relativeEndOffset = (offset + length);

            if (relativeEndOffset <= firstLength)
            {
                return(_firstDataPacket.GetSubPacket(offset, length));
            }

            IDataPacket firstSubPacket  = _firstDataPacket.GetSubPacket(offset, (firstLength - offset));
            IDataPacket secondSubPacket = _secondDataPacket.GetSubPacket(0, (relativeEndOffset - firstLength));

            return(firstSubPacket.Append(secondSubPacket));
        }
        /// <summary>
        /// Creates a new <see cref="HexWorkshopEventArgs"/>.
        /// </summary>
        /// <param name="filePath">the path of the file to open</param>
        /// <param name="offset">the offset in the file</param>
        public HexWorkshopEventArgs(string filePath, long offset)
        {
            PreConditions.Argument("filePath").Value(filePath).IsNotNull();
            PreConditions.Argument("offset").Value(offset).IsNotNegative();

            FilePath = filePath;
            Offset   = offset;
        }
Ejemplo n.º 19
0
            public SerializationContext(Creator <IDataPacket, IInputFile> createDataPacket, Creator <ByteArrayDataReader, IDataPacket, byte[]> createDataReader)
            {
                PreConditions.Argument("createDataPacket").Value(createDataPacket).IsNotNull();
                PreConditions.Argument("createDataReader").Value(createDataReader).IsNotNull();

                _createDataPacket = createDataPacket;
                _createDataReader = createDataReader;
            }
Ejemplo n.º 20
0
        /// <summary>
        /// Set a configuration item
        /// </summary>
        /// <param name="detector">the detector to set the configuration item on</param>
        /// <param name="configurationItemKey">the configuration item to set</param>
        /// <param name="userValue">the new value for the configuration item</param>
        public static void SetConfigurationItem(this IDetector detector, string configurationItemKey, string userValue)
        {
            PreConditions.Argument("detector").Value(detector).IsNotNull();
            PreConditions.Argument("configurationItemKey").Value(configurationItemKey).IsNotNull().And.IsNotEmpty();
            PreConditions.Argument("userValue").Value(userValue).IsNotNull().And.IsNotEmpty();

            GetConfigurationItem(detector, configurationItemKey).SetUserValue(userValue);
        }
        public IFragment this[int index]
        {
            get
            {
                PreConditions.Argument("index").Value(index).InRange(0, (_fragments.Count - 1));

                return(_fragments[index]);
            }
        }
        public AdvancedDetectorConfiguration(bool editMode, IDetectorFactory detectorFactory)
        {
            PreConditions.Argument("detectorFactory").Value(detectorFactory).IsNotNull();

            _editMode        = editMode;
            _detectorFactory = detectorFactory;

            InitializeComponent();
        }
Ejemplo n.º 23
0
        /// <summary>
        /// Creates a new <see cref="SaveAsContiguousFile"/> strategy.
        /// </summary>
        /// <param name="createDataWriter">The factory method for creating a file data writer</param>
        /// <param name="forensicIntegrityLog">The forensic integrity log</param>
        public SaveAsContiguousFile(Creator <IDataWriter, string> createDataWriter,
                                    IForensicIntegrityLog forensicIntegrityLog)
        {
            PreConditions.Argument("createDataWriter").Value(createDataWriter).IsNotNull();
            PreConditions.Argument("forensicIntegrityLog").Value(forensicIntegrityLog).IsNotNull();

            _createDataWriter     = createDataWriter;
            _forensicIntegrityLog = forensicIntegrityLog;
        }
        /// <summary>
        /// Creates a new project manager.
        /// </summary>
        public ProjectManager(Creator <IProject, string> createProject, Creator <XmlObjectSerializer> createXmlObjectSerializer)
        {
            PreConditions.Argument("createProject").Value(createProject).IsNotNull();
            PreConditions.Argument("createXmlObjectSerializer").Value(createXmlObjectSerializer).IsNotNull();

            _createProject             = createProject;
            _createXmlObjectSerializer = createXmlObjectSerializer;
            _projects = new List <IProject>();
        }
        /// <summary>
        /// Creates a new <see cref="ExportToXml"/> strategy.
        /// </summary>
        /// <param name="createTextWriter">The factory method for creating a text writer</param>
        public ExportToXml(Creator <TextWriter, string> createTextWriter, DataBlockScanner dataBlockScanner, IDetectorFormatter detectorFormatter)
        {
            PreConditions.Argument("createTextWriter").Value(createTextWriter).IsNotNull();
            PreConditions.Argument("dataBlockScanner").Value(dataBlockScanner).IsNotNull();

            _createTextWriter  = createTextWriter;
            _dataBlockScanner  = dataBlockScanner;
            _detectorFormatter = detectorFormatter;
        }
Ejemplo n.º 26
0
        /// <summary>
        /// Creates a new <see cref="FragmentedDataReader"/> for reading the
        /// (possibly) fragmented <paramref name="dataPacket"/> using the given
        /// <paramref name="dataReaderPool"/>.
        /// </summary>
        /// <remarks>
        /// The fragments of the data packet can refer to multiple files, which will
        /// be opened using the <paramref name="dataReaderPool"/>. The <see cref="Dispose"/>
        /// method will close all open files by disposing the <see cref="IDataReaderPool"/>.
        /// </remarks>
        /// <param name="dataPacket">the data packet</param>
        /// <param name="dataReaderPool">the pool of data readers to use</param>
        public FragmentedDataReader(IDataPacket dataPacket, IDataReaderPool dataReaderPool)
        {
            PreConditions.Argument("dataPacket").Value(dataPacket).IsNotNull();
            PreConditions.Argument("dataReaderPool").Value(dataReaderPool).IsNotNull();

            _dataPacket     = dataPacket;
            _dataReaderPool = dataReaderPool;
            _position       = 0;
        }
        /// <summary>
        /// Creates a new <see cref="DataPacketNode"/> that concatenates two data packets.
        /// </summary>
        /// <param name="first">The first data packet</param>
        /// <param name="second">The second data packet</param>
        public DataPacketNode(IDataPacket first, IDataPacket second)
        {
            PreConditions.Argument("first").Value(first).IsNotNull();
            PreConditions.Argument("second").Value(second).IsNotNull();

            _firstDataPacket  = first;
            _secondDataPacket = second;
            _length           = (first.Length + second.Length);
        }
        /// <summary>
        /// Creates a new data block scanner.
        /// </summary>
        public DataBlockScanner(Creator <IDataBlockBuilder> createDataBlockBuilder, Creator <IScanContext, IProject> createScanContext)
        {
            PreConditions.Argument("createDataBlockBuilder").Value(createDataBlockBuilder).IsNotNull();
            PreConditions.Argument("createScanContext").Value(createScanContext).IsNotNull();

            _createDataBlockBuilder = createDataBlockBuilder;
            _createScanContext      = createScanContext;
            _resultsCache           = new WeakDictionary <IDataPacket, IResultNode>();
            _dataCache = new WeakDictionary <IDataPacket, IDataPacket>();
        }
        /// <summary>
        /// Creates a new <see cref="FileExportEventArgs"/>.
        /// </summary>
        /// <param name="items">the items to export</param>
        /// <param name="outputPath">the target file or directory for storing the items</param>
        /// <param name="progressReporter">the progress reporter</param>
        public FileExportEventArgs(T items, string outputPath, IProgressReporter progressReporter)
        {
            PreConditions.Argument("items").Value(items).IsNotNull();
            PreConditions.Argument("outputPath").Value(outputPath).IsNotNull();
            PreConditions.Argument("progressReporter").Value(progressReporter).IsNotNull();

            Items            = items;
            OutputPath       = outputPath;
            ProgressReporter = progressReporter;
        }
        /// <summary>
        /// Creates a new <see cref="SubProgressReporter"/>.
        /// </summary>
        /// <remarks>
        /// The progress reported to the overall progress reporter ranges from
        /// <paramref name="startProgressPercentage"/> for <c>0</c> percent of this
        /// <see cref="SubProgressReporter"/> to <paramref name="endProgressPercentage"/>
        /// for <c>100</c> percent of this <see cref="SubProgressReporter"/>.
        /// </remarks>
        /// <param name="progressReporter">The overall progress reporter</param>
        /// <param name="startProgressPercentage">The start progress percentage in <paramref name="progressReporter"/></param>
        /// <param name="endProgressPercentage">The end progress percentage in <paramref name="progressReporter"/></param>
        public SubProgressReporter(IProgressReporter progressReporter, int startProgressPercentage, int endProgressPercentage)
        {
            PreConditions.Argument("progressReporter").Value(progressReporter).IsNotNull();
            PreConditions.Argument("startProgressPercentage").Value(startProgressPercentage).IsNotNegative();
            PreConditions.Argument("endProgressPercentage").Value(endProgressPercentage).InRange(startProgressPercentage, 100);

            _progressReporter          = progressReporter;
            _startProgressPercentage   = startProgressPercentage;
            _progressPercentageRange   = (endProgressPercentage - startProgressPercentage);
            _currentProgressPercentage = -1;
        }