| //#define SelectorTrace | |
| // FileSelector.cs | |
| // ------------------------------------------------------------------ | |
| // | |
| // Copyright (c) 2008-2011 Dino Chiesa. | |
| // All rights reserved. | |
| // | |
| // This code module is part of DotNetZip, a zipfile class library. | |
| // | |
| // ------------------------------------------------------------------ | |
| // | |
| // This code is licensed under the Microsoft Public License. | |
| // See the file License.txt for the license details. | |
| // More info on: http://dotnetzip.codeplex.com | |
| // | |
| // ------------------------------------------------------------------ | |
| // | |
| // last saved: <2011-August-05 11:03:11> | |
| // | |
| // ------------------------------------------------------------------ | |
| // | |
| // This module implements a "file selector" that finds files based on a | |
| // set of inclusion criteria, including filename, size, file time, and | |
| // potentially file attributes. The criteria are given in a string with | |
| // a simple expression language. Examples: | |
| // | |
| // find all .txt files: | |
| // name = *.txt | |
| // | |
| // shorthand for the above | |
| // *.txt | |
| // | |
| // all files modified after January 1st, 2009 | |
| // mtime > 2009-01-01 | |
| // | |
| // All .txt files modified after the first of the year | |
| // name = *.txt AND mtime > 2009-01-01 | |
| // | |
| // All .txt files modified after the first of the year, or any file with the archive bit set | |
| // (name = *.txt AND mtime > 2009-01-01) or (attribtues = A) | |
| // | |
| // All .txt files or any file greater than 1mb in size | |
| // (name = *.txt or size > 1mb) | |
| // | |
| // and so on. | |
| // ------------------------------------------------------------------ | |
| using System; | |
| using System.Globalization; | |
| using System.IO; | |
| using System.Text; | |
| using System.Reflection; | |
| using System.ComponentModel; | |
| using System.Text.RegularExpressions; | |
| using System.Collections.Generic; | |
| #if SILVERLIGHT | |
| using System.Linq; | |
| #endif | |
| namespace OfficeOpenXml.Packaging.Ionic | |
| { | |
| /// <summary> | |
| /// Enumerates the options for a logical conjunction. This enum is intended for use | |
| /// internally by the FileSelector class. | |
| /// </summary> | |
| internal enum LogicalConjunction | |
| { | |
| NONE, | |
| AND, | |
| OR, | |
| XOR, | |
| } | |
| internal enum WhichTime | |
| { | |
| atime, | |
| mtime, | |
| ctime, | |
| } | |
| internal enum ComparisonOperator | |
| { | |
| [Description(">")] | |
| GreaterThan, | |
| [Description(">=")] | |
| GreaterThanOrEqualTo, | |
| [Description("<")] | |
| LesserThan, | |
| [Description("<=")] | |
| LesserThanOrEqualTo, | |
| [Description("=")] | |
| EqualTo, | |
| [Description("!=")] | |
| NotEqualTo | |
| } | |
| internal abstract partial class SelectionCriterion | |
| { | |
| internal virtual bool Verbose | |
| { | |
| get;set; | |
| } | |
| internal abstract bool Evaluate(string filename); | |
| [System.Diagnostics.Conditional("SelectorTrace")] | |
| protected static void CriterionTrace(string format, params object[] args) | |
| { | |
| //System.Console.WriteLine(" " + format, args); | |
| } | |
| } | |
| internal partial class SizeCriterion : SelectionCriterion | |
| { | |
| internal ComparisonOperator Operator; | |
| internal Int64 Size; | |
| public override String ToString() | |
| { | |
| StringBuilder sb = new StringBuilder(); | |
| sb.Append("size ").Append(EnumUtil.GetDescription(Operator)).Append(" ").Append(Size.ToString()); | |
| return sb.ToString(); | |
| } | |
| internal override bool Evaluate(string filename) | |
| { | |
| System.IO.FileInfo fi = new System.IO.FileInfo(filename); | |
| CriterionTrace("SizeCriterion::Evaluate('{0}' [{1}])", | |
| filename, this.ToString()); | |
| return _Evaluate(fi.Length); | |
| } | |
| private bool _Evaluate(Int64 Length) | |
| { | |
| bool result = false; | |
| switch (Operator) | |
| { | |
| case ComparisonOperator.GreaterThanOrEqualTo: | |
| result = Length >= Size; | |
| break; | |
| case ComparisonOperator.GreaterThan: | |
| result = Length > Size; | |
| break; | |
| case ComparisonOperator.LesserThanOrEqualTo: | |
| result = Length <= Size; | |
| break; | |
| case ComparisonOperator.LesserThan: | |
| result = Length < Size; | |
| break; | |
| case ComparisonOperator.EqualTo: | |
| result = Length == Size; | |
| break; | |
| case ComparisonOperator.NotEqualTo: | |
| result = Length != Size; | |
| break; | |
| default: | |
| throw new ArgumentException("Operator"); | |
| } | |
| return result; | |
| } | |
| } | |
| internal partial class TimeCriterion : SelectionCriterion | |
| { | |
| internal ComparisonOperator Operator; | |
| internal WhichTime Which; | |
| internal DateTime Time; | |
| public override String ToString() | |
| { | |
| StringBuilder sb = new StringBuilder(); | |
| sb.Append(Which.ToString()).Append(" ").Append(EnumUtil.GetDescription(Operator)).Append(" ").Append(Time.ToString("yyyy-MM-dd-HH:mm:ss")); | |
| return sb.ToString(); | |
| } | |
| internal override bool Evaluate(string filename) | |
| { | |
| DateTime x; | |
| switch (Which) | |
| { | |
| case WhichTime.atime: | |
| x = System.IO.File.GetLastAccessTime(filename).ToUniversalTime(); | |
| break; | |
| case WhichTime.mtime: | |
| x = System.IO.File.GetLastWriteTime(filename).ToUniversalTime(); | |
| break; | |
| case WhichTime.ctime: | |
| x = System.IO.File.GetCreationTime(filename).ToUniversalTime(); | |
| break; | |
| default: | |
| throw new ArgumentException("Operator"); | |
| } | |
| CriterionTrace("TimeCriterion({0},{1})= {2}", filename, Which.ToString(), x); | |
| return _Evaluate(x); | |
| } | |
| private bool _Evaluate(DateTime x) | |
| { | |
| bool result = false; | |
| switch (Operator) | |
| { | |
| case ComparisonOperator.GreaterThanOrEqualTo: | |
| result = (x >= Time); | |
| break; | |
| case ComparisonOperator.GreaterThan: | |
| result = (x > Time); | |
| break; | |
| case ComparisonOperator.LesserThanOrEqualTo: | |
| result = (x <= Time); | |
| break; | |
| case ComparisonOperator.LesserThan: | |
| result = (x < Time); | |
| break; | |
| case ComparisonOperator.EqualTo: | |
| result = (x == Time); | |
| break; | |
| case ComparisonOperator.NotEqualTo: | |
| result = (x != Time); | |
| break; | |
| default: | |
| throw new ArgumentException("Operator"); | |
| } | |
| CriterionTrace("TimeCriterion: {0}", result); | |
| return result; | |
| } | |
| } | |
| internal partial class NameCriterion : SelectionCriterion | |
| { | |
| private Regex _re; | |
| private String _regexString; | |
| internal ComparisonOperator Operator; | |
| private string _MatchingFileSpec; | |
| internal virtual string MatchingFileSpec | |
| { | |
| set | |
| { | |
| // workitem 8245 | |
| if (Directory.Exists(value)) | |
| { | |
| _MatchingFileSpec = ".\\" + value + "\\*.*"; | |
| } | |
| else | |
| { | |
| _MatchingFileSpec = value; | |
| } | |
| _regexString = "^" + | |
| Regex.Escape(_MatchingFileSpec) | |
| .Replace(@"\\\*\.\*", @"\\([^\.]+|.*\.[^\\\.]*)") | |
| .Replace(@"\.\*", @"\.[^\\\.]*") | |
| .Replace(@"\*", @".*") | |
| //.Replace(@"\*", @"[^\\\.]*") // ill-conceived | |
| .Replace(@"\?", @"[^\\\.]") | |
| + "$"; | |
| CriterionTrace("NameCriterion regexString({0})", _regexString); | |
| _re = new Regex(_regexString, RegexOptions.IgnoreCase); | |
| } | |
| } | |
| public override String ToString() | |
| { | |
| StringBuilder sb = new StringBuilder(); | |
| sb.Append("name ").Append(EnumUtil.GetDescription(Operator)) | |
| .Append(" '") | |
| .Append(_MatchingFileSpec) | |
| .Append("'"); | |
| return sb.ToString(); | |
| } | |
| internal override bool Evaluate(string filename) | |
| { | |
| CriterionTrace("NameCriterion::Evaluate('{0}' pattern[{1}])", | |
| filename, _MatchingFileSpec); | |
| return _Evaluate(filename); | |
| } | |
| private bool _Evaluate(string fullpath) | |
| { | |
| CriterionTrace("NameCriterion::Evaluate({0})", fullpath); | |
| // No slash in the pattern implicitly means recurse, which means compare to | |
| // filename only, not full path. | |
| String f = (_MatchingFileSpec.IndexOf('\\') == -1) | |
| ? System.IO.Path.GetFileName(fullpath) | |
| : fullpath; // compare to fullpath | |
| bool result = _re.IsMatch(f); | |
| if (Operator != ComparisonOperator.EqualTo) | |
| result = !result; | |
| return result; | |
| } | |
| } | |
| internal partial class TypeCriterion : SelectionCriterion | |
| { | |
| private char ObjectType; // 'D' = Directory, 'F' = File | |
| internal ComparisonOperator Operator; | |
| internal string AttributeString | |
| { | |
| get | |
| { | |
| return ObjectType.ToString(); | |
| } | |
| set | |
| { | |
| if (value.Length != 1 || | |
| (value[0]!='D' && value[0]!='F')) | |
| throw new ArgumentException("Specify a single character: either D or F"); | |
| ObjectType = value[0]; | |
| } | |
| } | |
| public override String ToString() | |
| { | |
| StringBuilder sb = new StringBuilder(); | |
| sb.Append("type ").Append(EnumUtil.GetDescription(Operator)).Append(" ").Append(AttributeString); | |
| return sb.ToString(); | |
| } | |
| internal override bool Evaluate(string filename) | |
| { | |
| CriterionTrace("TypeCriterion::Evaluate({0})", filename); | |
| bool result = (ObjectType == 'D') | |
| ? Directory.Exists(filename) | |
| : File.Exists(filename); | |
| if (Operator != ComparisonOperator.EqualTo) | |
| result = !result; | |
| return result; | |
| } | |
| } | |
| #if !SILVERLIGHT | |
| internal partial class AttributesCriterion : SelectionCriterion | |
| { | |
| private FileAttributes _Attributes; | |
| internal ComparisonOperator Operator; | |
| internal string AttributeString | |
| { | |
| get | |
| { | |
| string result = ""; | |
| if ((_Attributes & FileAttributes.Hidden) != 0) | |
| result += "H"; | |
| if ((_Attributes & FileAttributes.System) != 0) | |
| result += "S"; | |
| if ((_Attributes & FileAttributes.ReadOnly) != 0) | |
| result += "R"; | |
| if ((_Attributes & FileAttributes.Archive) != 0) | |
| result += "A"; | |
| if ((_Attributes & FileAttributes.ReparsePoint) != 0) | |
| result += "L"; | |
| if ((_Attributes & FileAttributes.NotContentIndexed) != 0) | |
| result += "I"; | |
| return result; | |
| } | |
| set | |
| { | |
| _Attributes = FileAttributes.Normal; | |
| foreach (char c in value.ToUpper(CultureInfo.InvariantCulture)) | |
| { | |
| switch (c) | |
| { | |
| case 'H': | |
| if ((_Attributes & FileAttributes.Hidden) != 0) | |
| throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value"); | |
| _Attributes |= FileAttributes.Hidden; | |
| break; | |
| case 'R': | |
| if ((_Attributes & FileAttributes.ReadOnly) != 0) | |
| throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value"); | |
| _Attributes |= FileAttributes.ReadOnly; | |
| break; | |
| case 'S': | |
| if ((_Attributes & FileAttributes.System) != 0) | |
| throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value"); | |
| _Attributes |= FileAttributes.System; | |
| break; | |
| case 'A': | |
| if ((_Attributes & FileAttributes.Archive) != 0) | |
| throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value"); | |
| _Attributes |= FileAttributes.Archive; | |
| break; | |
| case 'I': | |
| if ((_Attributes & FileAttributes.NotContentIndexed) != 0) | |
| throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value"); | |
| _Attributes |= FileAttributes.NotContentIndexed; | |
| break; | |
| case 'L': | |
| if ((_Attributes & FileAttributes.ReparsePoint) != 0) | |
| throw new ArgumentException(String.Format("Repeated flag. ({0})", c), "value"); | |
| _Attributes |= FileAttributes.ReparsePoint; | |
| break; | |
| default: | |
| throw new ArgumentException(value); | |
| } | |
| } | |
| } | |
| } | |
| public override String ToString() | |
| { | |
| StringBuilder sb = new StringBuilder(); | |
| sb.Append("attributes ").Append(EnumUtil.GetDescription(Operator)).Append(" ").Append(AttributeString); | |
| return sb.ToString(); | |
| } | |
| private bool _EvaluateOne(FileAttributes fileAttrs, FileAttributes criterionAttrs) | |
| { | |
| bool result = false; | |
| if ((_Attributes & criterionAttrs) == criterionAttrs) | |
| result = ((fileAttrs & criterionAttrs) == criterionAttrs); | |
| else | |
| result = true; | |
| return result; | |
| } | |
| internal override bool Evaluate(string filename) | |
| { | |
| // workitem 10191 | |
| if (Directory.Exists(filename)) | |
| { | |
| // Directories don't have file attributes, so the result | |
| // of an evaluation is always NO. This gets negated if | |
| // the operator is NotEqualTo. | |
| return (Operator != ComparisonOperator.EqualTo); | |
| } | |
| #if NETCF | |
| FileAttributes fileAttrs = NetCfFile.GetAttributes(filename); | |
| #else | |
| FileAttributes fileAttrs = System.IO.File.GetAttributes(filename); | |
| #endif | |
| return _Evaluate(fileAttrs); | |
| } | |
| private bool _Evaluate(FileAttributes fileAttrs) | |
| { | |
| bool result = _EvaluateOne(fileAttrs, FileAttributes.Hidden); | |
| if (result) | |
| result = _EvaluateOne(fileAttrs, FileAttributes.System); | |
| if (result) | |
| result = _EvaluateOne(fileAttrs, FileAttributes.ReadOnly); | |
| if (result) | |
| result = _EvaluateOne(fileAttrs, FileAttributes.Archive); | |
| if (result) | |
| result = _EvaluateOne(fileAttrs, FileAttributes.NotContentIndexed); | |
| if (result) | |
| result = _EvaluateOne(fileAttrs, FileAttributes.ReparsePoint); | |
| if (Operator != ComparisonOperator.EqualTo) | |
| result = !result; | |
| return result; | |
| } | |
| } | |
| #endif | |
| internal partial class CompoundCriterion : SelectionCriterion | |
| { | |
| internal LogicalConjunction Conjunction; | |
| internal SelectionCriterion Left; | |
| private SelectionCriterion _Right; | |
| internal SelectionCriterion Right | |
| { | |
| get { return _Right; } | |
| set | |
| { | |
| _Right = value; | |
| if (value == null) | |
| Conjunction = LogicalConjunction.NONE; | |
| else if (Conjunction == LogicalConjunction.NONE) | |
| Conjunction = LogicalConjunction.AND; | |
| } | |
| } | |
| internal override bool Evaluate(string filename) | |
| { | |
| bool result = Left.Evaluate(filename); | |
| switch (Conjunction) | |
| { | |
| case LogicalConjunction.AND: | |
| if (result) | |
| result = Right.Evaluate(filename); | |
| break; | |
| case LogicalConjunction.OR: | |
| if (!result) | |
| result = Right.Evaluate(filename); | |
| break; | |
| case LogicalConjunction.XOR: | |
| result ^= Right.Evaluate(filename); | |
| break; | |
| default: | |
| throw new ArgumentException("Conjunction"); | |
| } | |
| return result; | |
| } | |
| public override String ToString() | |
| { | |
| StringBuilder sb = new StringBuilder(); | |
| sb.Append("(") | |
| .Append((Left != null) ? Left.ToString() : "null") | |
| .Append(" ") | |
| .Append(Conjunction.ToString()) | |
| .Append(" ") | |
| .Append((Right != null) ? Right.ToString() : "null") | |
| .Append(")"); | |
| return sb.ToString(); | |
| } | |
| } | |
| /// <summary> | |
| /// FileSelector encapsulates logic that selects files from a source - a zip file | |
| /// or the filesystem - based on a set of criteria. This class is used internally | |
| /// by the DotNetZip library, in particular for the AddSelectedFiles() methods. | |
| /// This class can also be used independently of the zip capability in DotNetZip. | |
| /// </summary> | |
| /// | |
| /// <remarks> | |
| /// | |
| /// <para> | |
| /// The FileSelector class is used internally by the ZipFile class for selecting | |
| /// files for inclusion into the ZipFile, when the <see | |
| /// cref="Ionic.Zip.ZipFile.AddSelectedFiles(String,String)"/> method, or one of | |
| /// its overloads, is called. It's also used for the <see | |
| /// cref="Ionic.Zip.ZipFile.ExtractSelectedEntries(String)"/> methods. Typically, an | |
| /// application that creates or manipulates Zip archives will not directly | |
| /// interact with the FileSelector class. | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// Some applications may wish to use the FileSelector class directly, to | |
| /// select files from disk volumes based on a set of criteria, without creating or | |
| /// querying Zip archives. The file selection criteria include: a pattern to | |
| /// match the filename; the last modified, created, or last accessed time of the | |
| /// file; the size of the file; and the attributes of the file. | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// Consult the documentation for <see cref="SelectionCriteria"/> | |
| /// for more information on specifying the selection criteria. | |
| /// </para> | |
| /// | |
| /// </remarks> | |
| internal partial class FileSelector | |
| { | |
| internal SelectionCriterion _Criterion; | |
| #if NOTUSED | |
| /// <summary> | |
| /// The default constructor. | |
| /// </summary> | |
| /// <remarks> | |
| /// Typically, applications won't use this constructor. Instead they'll | |
| /// call the constructor that accepts a selectionCriteria string. If you | |
| /// use this constructor, you'll want to set the SelectionCriteria | |
| /// property on the instance before calling SelectFiles(). | |
| /// </remarks> | |
| protected FileSelector() { } | |
| #endif | |
| /// <summary> | |
| /// Constructor that allows the caller to specify file selection criteria. | |
| /// </summary> | |
| /// | |
| /// <remarks> | |
| /// <para> | |
| /// This constructor allows the caller to specify a set of criteria for | |
| /// selection of files. | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// See <see cref="FileSelector.SelectionCriteria"/> for a description of | |
| /// the syntax of the selectionCriteria string. | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// By default the FileSelector will traverse NTFS Reparse Points. To | |
| /// change this, use <see cref="FileSelector(String, | |
| /// bool)">FileSelector(String, bool)</see>. | |
| /// </para> | |
| /// </remarks> | |
| /// | |
| /// <param name="selectionCriteria">The criteria for file selection.</param> | |
| public FileSelector(String selectionCriteria) | |
| : this(selectionCriteria, true) | |
| { | |
| } | |
| /// <summary> | |
| /// Constructor that allows the caller to specify file selection criteria. | |
| /// </summary> | |
| /// | |
| /// <remarks> | |
| /// <para> | |
| /// This constructor allows the caller to specify a set of criteria for | |
| /// selection of files. | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// See <see cref="FileSelector.SelectionCriteria"/> for a description of | |
| /// the syntax of the selectionCriteria string. | |
| /// </para> | |
| /// </remarks> | |
| /// | |
| /// <param name="selectionCriteria">The criteria for file selection.</param> | |
| /// <param name="traverseDirectoryReparsePoints"> | |
| /// whether to traverse NTFS reparse points (junctions). | |
| /// </param> | |
| public FileSelector(String selectionCriteria, bool traverseDirectoryReparsePoints) | |
| { | |
| if (!String.IsNullOrEmpty(selectionCriteria)) | |
| _Criterion = _ParseCriterion(selectionCriteria); | |
| TraverseReparsePoints = traverseDirectoryReparsePoints; | |
| } | |
| /// <summary> | |
| /// The string specifying which files to include when retrieving. | |
| /// </summary> | |
| /// <remarks> | |
| /// | |
| /// <para> | |
| /// Specify the criteria in statements of 3 elements: a noun, an operator, | |
| /// and a value. Consider the string "name != *.doc" . The noun is | |
| /// "name". The operator is "!=", implying "Not Equal". The value is | |
| /// "*.doc". That criterion, in English, says "all files with a name that | |
| /// does not end in the .doc extension." | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// Supported nouns include "name" (or "filename") for the filename; | |
| /// "atime", "mtime", and "ctime" for last access time, last modfied time, | |
| /// and created time of the file, respectively; "attributes" (or "attrs") | |
| /// for the file attributes; "size" (or "length") for the file length | |
| /// (uncompressed); and "type" for the type of object, either a file or a | |
| /// directory. The "attributes", "type", and "name" nouns all support = | |
| /// and != as operators. The "size", "atime", "mtime", and "ctime" nouns | |
| /// support = and !=, and >, >=, <, <= as well. The times are | |
| /// taken to be expressed in local time. | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// Specify values for the file attributes as a string with one or more of | |
| /// the characters H,R,S,A,I,L in any order, implying file attributes of | |
| /// Hidden, ReadOnly, System, Archive, NotContextIndexed, and ReparsePoint | |
| /// (symbolic link) respectively. | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// To specify a time, use YYYY-MM-DD-HH:mm:ss or YYYY/MM/DD-HH:mm:ss as | |
| /// the format. If you omit the HH:mm:ss portion, it is assumed to be | |
| /// 00:00:00 (midnight). | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// The value for a size criterion is expressed in integer quantities of | |
| /// bytes, kilobytes (use k or kb after the number), megabytes (m or mb), | |
| /// or gigabytes (g or gb). | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// The value for a name is a pattern to match against the filename, | |
| /// potentially including wildcards. The pattern follows CMD.exe glob | |
| /// rules: * implies one or more of any character, while ? implies one | |
| /// character. If the name pattern contains any slashes, it is matched to | |
| /// the entire filename, including the path; otherwise, it is matched | |
| /// against only the filename without the path. This means a pattern of | |
| /// "*\*.*" matches all files one directory level deep, while a pattern of | |
| /// "*.*" matches all files in all directories. | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// To specify a name pattern that includes spaces, use single quotes | |
| /// around the pattern. A pattern of "'* *.*'" will match all files that | |
| /// have spaces in the filename. The full criteria string for that would | |
| /// be "name = '* *.*'" . | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// The value for a type criterion is either F (implying a file) or D | |
| /// (implying a directory). | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// Some examples: | |
| /// </para> | |
| /// | |
| /// <list type="table"> | |
| /// <listheader> | |
| /// <term>criteria</term> | |
| /// <description>Files retrieved</description> | |
| /// </listheader> | |
| /// | |
| /// <item> | |
| /// <term>name != *.xls </term> | |
| /// <description>any file with an extension that is not .xls | |
| /// </description> | |
| /// </item> | |
| /// | |
| /// <item> | |
| /// <term>name = *.mp3 </term> | |
| /// <description>any file with a .mp3 extension. | |
| /// </description> | |
| /// </item> | |
| /// | |
| /// <item> | |
| /// <term>*.mp3</term> | |
| /// <description>(same as above) any file with a .mp3 extension. | |
| /// </description> | |
| /// </item> | |
| /// | |
| /// <item> | |
| /// <term>attributes = A </term> | |
| /// <description>all files whose attributes include the Archive bit. | |
| /// </description> | |
| /// </item> | |
| /// | |
| /// <item> | |
| /// <term>attributes != H </term> | |
| /// <description>all files whose attributes do not include the Hidden bit. | |
| /// </description> | |
| /// </item> | |
| /// | |
| /// <item> | |
| /// <term>mtime > 2009-01-01</term> | |
| /// <description>all files with a last modified time after January 1st, 2009. | |
| /// </description> | |
| /// </item> | |
| /// | |
| /// <item> | |
| /// <term>ctime > 2009/01/01-03:00:00</term> | |
| /// <description>all files with a created time after 3am (local time), | |
| /// on January 1st, 2009. | |
| /// </description> | |
| /// </item> | |
| /// | |
| /// <item> | |
| /// <term>size > 2gb</term> | |
| /// <description>all files whose uncompressed size is greater than 2gb. | |
| /// </description> | |
| /// </item> | |
| /// | |
| /// <item> | |
| /// <term>type = D</term> | |
| /// <description>all directories in the filesystem. </description> | |
| /// </item> | |
| /// | |
| /// </list> | |
| /// | |
| /// <para> | |
| /// You can combine criteria with the conjunctions AND, OR, and XOR. Using | |
| /// a string like "name = *.txt AND size >= 100k" for the | |
| /// selectionCriteria retrieves entries whose names end in .txt, and whose | |
| /// uncompressed size is greater than or equal to 100 kilobytes. | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// For more complex combinations of criteria, you can use parenthesis to | |
| /// group clauses in the boolean logic. Absent parenthesis, the | |
| /// precedence of the criterion atoms is determined by order of | |
| /// appearance. Unlike the C# language, the AND conjunction does not take | |
| /// precendence over the logical OR. This is important only in strings | |
| /// that contain 3 or more criterion atoms. In other words, "name = *.txt | |
| /// and size > 1000 or attributes = H" implies "((name = *.txt AND size | |
| /// > 1000) OR attributes = H)" while "attributes = H OR name = *.txt | |
| /// and size > 1000" evaluates to "((attributes = H OR name = *.txt) | |
| /// AND size > 1000)". When in doubt, use parenthesis. | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// Using time properties requires some extra care. If you want to | |
| /// retrieve all entries that were last updated on 2009 February 14, | |
| /// specify "mtime >= 2009-02-14 AND mtime < 2009-02-15". Read this | |
| /// to say: all files updated after 12:00am on February 14th, until | |
| /// 12:00am on February 15th. You can use the same bracketing approach to | |
| /// specify any time period - a year, a month, a week, and so on. | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// The syntax allows one special case: if you provide a string with no | |
| /// spaces, it is treated as a pattern to match for the filename. | |
| /// Therefore a string like "*.xls" will be equivalent to specifying "name | |
| /// = *.xls". This "shorthand" notation does not work with compound | |
| /// criteria. | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// There is no logic in this class that insures that the inclusion | |
| /// criteria are internally consistent. For example, it's possible to | |
| /// specify criteria that says the file must have a size of less than 100 | |
| /// bytes, as well as a size that is greater than 1000 bytes. Obviously | |
| /// no file will ever satisfy such criteria, but this class does not check | |
| /// for or detect such inconsistencies. | |
| /// </para> | |
| /// | |
| /// </remarks> | |
| /// | |
| /// <exception cref="System.Exception"> | |
| /// Thrown in the setter if the value has an invalid syntax. | |
| /// </exception> | |
| public String SelectionCriteria | |
| { | |
| get | |
| { | |
| if (_Criterion == null) return null; | |
| return _Criterion.ToString(); | |
| } | |
| set | |
| { | |
| if (value == null) _Criterion = null; | |
| else if (value.Trim() == "") _Criterion = null; | |
| else | |
| _Criterion = _ParseCriterion(value); | |
| } | |
| } | |
| /// <summary> | |
| /// Indicates whether searches will traverse NTFS reparse points, like Junctions. | |
| /// </summary> | |
| public bool TraverseReparsePoints | |
| { | |
| get; set; | |
| } | |
| private enum ParseState | |
| { | |
| Start, | |
| OpenParen, | |
| CriterionDone, | |
| ConjunctionPending, | |
| Whitespace, | |
| } | |
| private static class RegexAssertions | |
| { | |
| public static readonly String PrecededByOddNumberOfSingleQuotes = "(?<=(?:[^']*'[^']*')*'[^']*)"; | |
| public static readonly String FollowedByOddNumberOfSingleQuotesAndLineEnd = "(?=[^']*'(?:[^']*'[^']*')*[^']*$)"; | |
| public static readonly String PrecededByEvenNumberOfSingleQuotes = "(?<=(?:[^']*'[^']*')*[^']*)"; | |
| public static readonly String FollowedByEvenNumberOfSingleQuotesAndLineEnd = "(?=(?:[^']*'[^']*')*[^']*$)"; | |
| } | |
| private static string NormalizeCriteriaExpression(string source) | |
| { | |
| // The goal here is to normalize the criterion expression. At output, in | |
| // the transformed criterion string, every significant syntactic element | |
| // - a property element, grouping paren for the boolean logic, operator | |
| // ( = < > != ), conjunction, or property value - will be separated from | |
| // its neighbors by at least one space. Thus, | |
| // | |
| // before after | |
| // ------------------------------------------------------------------- | |
| // name=*.txt name = *.txt | |
| // (size>100)AND(name=*.txt) ( size > 100 ) AND ( name = *.txt ) | |
| // | |
| // This is relatively straightforward using regular expression | |
| // replacement. This method applies a distinct regex pattern and | |
| // corresponding replacement string for each one of a number of cases: | |
| // an open paren followed by a word; a word followed by a close-paren; a | |
| // pair of open parens; a close paren followed by a word (which should | |
| // then be followed by an open paren). And so on. These patterns and | |
| // replacements are all stored in prPairs. By applying each of these | |
| // regex replacements in turn, we get the transformed string. Easy. | |
| // | |
| // The resulting "normalized" criterion string, is then used as the | |
| // subject that gets parsed, by splitting the string into tokens that | |
| // are separated by spaces. Here, there's a twist. The spaces within | |
| // single-quote delimiters do not delimit distinct tokens. So, this | |
| // normalization method temporarily replaces those spaces with | |
| // ASCII 6 (0x06), a control character which is not a legal | |
| // character in a filename. The parsing logic that happens later will | |
| // revert that change, restoring the original value of the filename | |
| // specification. | |
| // | |
| // To illustrate, for a "before" string of [(size>100)AND(name='Name | |
| // (with Parens).txt')] , the "after" string is [( size > 100 ) AND | |
| // ( name = 'Name\u0006(with\u0006Parens).txt' )]. | |
| // | |
| string[][] prPairs = | |
| { | |
| // A. opening double parens - insert a space between them | |
| new string[] { @"([^']*)\(\(([^']+)", "$1( ($2" }, | |
| // B. closing double parens - insert a space between | |
| new string[] { @"(.)\)\)", "$1) )" }, | |
| // C. single open paren with a following word - insert a space between | |
| new string[] { @"\((\S)", "( $1" }, | |
| // D. single close paren with a preceding word - insert a space between the two | |
| new string[] { @"(\S)\)", "$1 )" }, | |
| // E. close paren at line start?, insert a space before the close paren | |
| // this seems like a degenerate case. I don't recall why it's here. | |
| new string[] { @"^\)", " )" }, | |
| // F. a word (likely a conjunction) followed by an open paren - insert a space between | |
| new string[] { @"(\S)\(", "$1 (" }, | |
| // G. single close paren followed by word - insert a paren after close paren | |
| new string[] { @"\)(\S)", ") $1" }, | |
| // H. insert space between = and a following single quote | |
| //new string[] { @"(=|!=)('[^']*')", "$1 $2" }, | |
| new string[] { @"(=)('[^']*')", "$1 $2" }, | |
| // I. insert space between property names and the following operator | |
| //new string[] { @"([^ ])([><(?:!=)=])", "$1 $2" }, | |
| new string[] { @"([^ !><])(>|<|!=|=)", "$1 $2" }, | |
| // J. insert spaces between operators and the following values | |
| //new string[] { @"([><(?:!=)=])([^ ])", "$1 $2" }, | |
| new string[] { @"(>|<|!=|=)([^ =])", "$1 $2" }, | |
| // K. replace fwd slash with backslash | |
| new string[] { @"/", "\\" }, | |
| }; | |
| string interim = source; | |
| for (int i=0; i < prPairs.Length; i++) | |
| { | |
| //char caseIdx = (char)('A' + i); | |
| string pattern = RegexAssertions.PrecededByEvenNumberOfSingleQuotes + | |
| prPairs[i][0] + | |
| RegexAssertions.FollowedByEvenNumberOfSingleQuotesAndLineEnd; | |
| interim = Regex.Replace(interim, pattern, prPairs[i][1]); | |
| } | |
| // match a fwd slash, followed by an odd number of single quotes. | |
| // This matches fwd slashes only inside a pair of single quote delimiters, | |
| // eg, a filename. This must be done as well as the case above, to handle | |
| // filenames specified inside quotes as well as filenames without quotes. | |
| var regexPattern = @"/" + | |
| RegexAssertions.FollowedByOddNumberOfSingleQuotesAndLineEnd; | |
| // replace with backslash | |
| interim = Regex.Replace(interim, regexPattern, "\\"); | |
| // match a space, followed by an odd number of single quotes. | |
| // This matches spaces only inside a pair of single quote delimiters. | |
| regexPattern = " " + | |
| RegexAssertions.FollowedByOddNumberOfSingleQuotesAndLineEnd; | |
| // Replace all spaces that appear inside single quotes, with | |
| // ascii 6. This allows a split on spaces to get tokens in | |
| // the expression. The split will not split any filename or | |
| // wildcard that appears within single quotes. After tokenizing, we | |
| // need to replace ascii 6 with ascii 32 to revert the | |
| // spaces within quotes. | |
| return Regex.Replace(interim, regexPattern, "\u0006"); | |
| } | |
| private static SelectionCriterion _ParseCriterion(String s) | |
| { | |
| if (s == null) return null; | |
| // inject spaces after open paren and before close paren, etc | |
| s = NormalizeCriteriaExpression(s); | |
| // no spaces in the criteria is shorthand for filename glob | |
| if (s.IndexOf(" ") == -1) | |
| s = "name = " + s; | |
| // split the expression into tokens | |
| string[] tokens = s.Trim().Split(' ', '\t'); | |
| if (tokens.Length < 3) throw new ArgumentException(s); | |
| SelectionCriterion current = null; | |
| LogicalConjunction pendingConjunction = LogicalConjunction.NONE; | |
| ParseState state; | |
| var stateStack = new System.Collections.Generic.Stack<ParseState>(); | |
| var critStack = new System.Collections.Generic.Stack<SelectionCriterion>(); | |
| stateStack.Push(ParseState.Start); | |
| for (int i = 0; i < tokens.Length; i++) | |
| { | |
| string tok1 = tokens[i].ToLower(CultureInfo.InvariantCulture); | |
| switch (tok1) | |
| { | |
| case "and": | |
| case "xor": | |
| case "or": | |
| state = stateStack.Peek(); | |
| if (state != ParseState.CriterionDone) | |
| throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | |
| if (tokens.Length <= i + 3) | |
| throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | |
| pendingConjunction = (LogicalConjunction)Enum.Parse(typeof(LogicalConjunction), tokens[i].ToUpper(CultureInfo.InvariantCulture), true); | |
| current = new CompoundCriterion { Left = current, Right = null, Conjunction = pendingConjunction }; | |
| stateStack.Push(state); | |
| stateStack.Push(ParseState.ConjunctionPending); | |
| critStack.Push(current); | |
| break; | |
| case "(": | |
| state = stateStack.Peek(); | |
| if (state != ParseState.Start && state != ParseState.ConjunctionPending && state != ParseState.OpenParen) | |
| throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | |
| if (tokens.Length <= i + 4) | |
| throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | |
| stateStack.Push(ParseState.OpenParen); | |
| break; | |
| case ")": | |
| state = stateStack.Pop(); | |
| if (stateStack.Peek() != ParseState.OpenParen) | |
| throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | |
| stateStack.Pop(); | |
| stateStack.Push(ParseState.CriterionDone); | |
| break; | |
| case "atime": | |
| case "ctime": | |
| case "mtime": | |
| if (tokens.Length <= i + 2) | |
| throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | |
| DateTime t; | |
| try | |
| { | |
| t = DateTime.ParseExact(tokens[i + 2], "yyyy-MM-dd-HH:mm:ss", null); | |
| } | |
| catch (FormatException) | |
| { | |
| try | |
| { | |
| t = DateTime.ParseExact(tokens[i + 2], "yyyy/MM/dd-HH:mm:ss", null); | |
| } | |
| catch (FormatException) | |
| { | |
| try | |
| { | |
| t = DateTime.ParseExact(tokens[i + 2], "yyyy/MM/dd", null); | |
| } | |
| catch (FormatException) | |
| { | |
| try | |
| { | |
| t = DateTime.ParseExact(tokens[i + 2], "MM/dd/yyyy", null); | |
| } | |
| catch (FormatException) | |
| { | |
| t = DateTime.ParseExact(tokens[i + 2], "yyyy-MM-dd", null); | |
| } | |
| } | |
| } | |
| } | |
| t= DateTime.SpecifyKind(t, DateTimeKind.Local).ToUniversalTime(); | |
| current = new TimeCriterion | |
| { | |
| Which = (WhichTime)Enum.Parse(typeof(WhichTime), tokens[i], true), | |
| Operator = (ComparisonOperator)EnumUtil.Parse(typeof(ComparisonOperator), tokens[i + 1]), | |
| Time = t | |
| }; | |
| i += 2; | |
| stateStack.Push(ParseState.CriterionDone); | |
| break; | |
| case "length": | |
| case "size": | |
| if (tokens.Length <= i + 2) | |
| throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | |
| Int64 sz = 0; | |
| string v = tokens[i + 2]; | |
| if (v.EndsWith("K", StringComparison.InvariantCultureIgnoreCase)) | |
| sz = Int64.Parse(v.Substring(0, v.Length - 1)) * 1024; | |
| else if (v.EndsWith("KB", StringComparison.InvariantCultureIgnoreCase)) | |
| sz = Int64.Parse(v.Substring(0, v.Length - 2)) * 1024; | |
| else if (v.EndsWith("M", StringComparison.InvariantCultureIgnoreCase)) | |
| sz = Int64.Parse(v.Substring(0, v.Length - 1)) * 1024 * 1024; | |
| else if (v.EndsWith("MB", StringComparison.InvariantCultureIgnoreCase)) | |
| sz = Int64.Parse(v.Substring(0, v.Length - 2)) * 1024 * 1024; | |
| else if (v.EndsWith("G", StringComparison.InvariantCultureIgnoreCase)) | |
| sz = Int64.Parse(v.Substring(0, v.Length - 1)) * 1024 * 1024 * 1024; | |
| else if (v.EndsWith("GB", StringComparison.InvariantCultureIgnoreCase)) | |
| sz = Int64.Parse(v.Substring(0, v.Length - 2)) * 1024 * 1024 * 1024; | |
| else sz = Int64.Parse(tokens[i + 2]); | |
| current = new SizeCriterion | |
| { | |
| Size = sz, | |
| Operator = (ComparisonOperator)EnumUtil.Parse(typeof(ComparisonOperator), tokens[i + 1]) | |
| }; | |
| i += 2; | |
| stateStack.Push(ParseState.CriterionDone); | |
| break; | |
| case "filename": | |
| case "name": | |
| { | |
| if (tokens.Length <= i + 2) | |
| throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | |
| ComparisonOperator c = | |
| (ComparisonOperator)EnumUtil.Parse(typeof(ComparisonOperator), tokens[i + 1]); | |
| if (c != ComparisonOperator.NotEqualTo && c != ComparisonOperator.EqualTo) | |
| throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | |
| string m = tokens[i + 2]; | |
| // handle single-quoted filespecs (used to include | |
| // spaces in filename patterns) | |
| if (m.StartsWith("'") && m.EndsWith("'")) | |
| { | |
| // trim off leading and trailing single quotes and | |
| // revert the control characters to spaces. | |
| m = m.Substring(1, m.Length - 2) | |
| .Replace("\u0006", " "); | |
| } | |
| // if (m.StartsWith("'")) | |
| // m = m.Replace("\u0006", " "); | |
| current = new NameCriterion | |
| { | |
| MatchingFileSpec = m, | |
| Operator = c | |
| }; | |
| i += 2; | |
| stateStack.Push(ParseState.CriterionDone); | |
| } | |
| break; | |
| #if !SILVERLIGHT | |
| case "attrs": | |
| case "attributes": | |
| #endif | |
| case "type": | |
| { | |
| if (tokens.Length <= i + 2) | |
| throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | |
| ComparisonOperator c = | |
| (ComparisonOperator)EnumUtil.Parse(typeof(ComparisonOperator), tokens[i + 1]); | |
| if (c != ComparisonOperator.NotEqualTo && c != ComparisonOperator.EqualTo) | |
| throw new ArgumentException(String.Join(" ", tokens, i, tokens.Length - i)); | |
| #if SILVERLIGHT | |
| current = (SelectionCriterion) new TypeCriterion | |
| { | |
| AttributeString = tokens[i + 2], | |
| Operator = c | |
| }; | |
| #else | |
| current = (tok1 == "type") | |
| ? (SelectionCriterion) new TypeCriterion | |
| { | |
| AttributeString = tokens[i + 2], | |
| Operator = c | |
| } | |
| : (SelectionCriterion) new AttributesCriterion | |
| { | |
| AttributeString = tokens[i + 2], | |
| Operator = c | |
| }; | |
| #endif | |
| i += 2; | |
| stateStack.Push(ParseState.CriterionDone); | |
| } | |
| break; | |
| case "": | |
| // NOP | |
| stateStack.Push(ParseState.Whitespace); | |
| break; | |
| default: | |
| throw new ArgumentException("'" + tokens[i] + "'"); | |
| } | |
| state = stateStack.Peek(); | |
| if (state == ParseState.CriterionDone) | |
| { | |
| stateStack.Pop(); | |
| if (stateStack.Peek() == ParseState.ConjunctionPending) | |
| { | |
| while (stateStack.Peek() == ParseState.ConjunctionPending) | |
| { | |
| var cc = critStack.Pop() as CompoundCriterion; | |
| cc.Right = current; | |
| current = cc; // mark the parent as current (walk up the tree) | |
| stateStack.Pop(); // the conjunction is no longer pending | |
| state = stateStack.Pop(); | |
| if (state != ParseState.CriterionDone) | |
| throw new ArgumentException("??"); | |
| } | |
| } | |
| else stateStack.Push(ParseState.CriterionDone); // not sure? | |
| } | |
| if (state == ParseState.Whitespace) | |
| stateStack.Pop(); | |
| } | |
| return current; | |
| } | |
| /// <summary> | |
| /// Returns a string representation of the FileSelector object. | |
| /// </summary> | |
| /// <returns>The string representation of the boolean logic statement of the file | |
| /// selection criteria for this instance. </returns> | |
| public override String ToString() | |
| { | |
| return "FileSelector("+_Criterion.ToString()+")"; | |
| } | |
| private bool Evaluate(string filename) | |
| { | |
| // dinoch - Thu, 11 Feb 2010 18:34 | |
| SelectorTrace("Evaluate({0})", filename); | |
| bool result = _Criterion.Evaluate(filename); | |
| return result; | |
| } | |
| [System.Diagnostics.Conditional("SelectorTrace")] | |
| private void SelectorTrace(string format, params object[] args) | |
| { | |
| if (_Criterion != null && _Criterion.Verbose) | |
| System.Console.WriteLine(format, args); | |
| } | |
| /// <summary> | |
| /// Returns the names of the files in the specified directory | |
| /// that fit the selection criteria specified in the FileSelector. | |
| /// </summary> | |
| /// | |
| /// <remarks> | |
| /// This is equivalent to calling <see cref="SelectFiles(String, bool)"/> | |
| /// with recurseDirectories = false. | |
| /// </remarks> | |
| /// | |
| /// <param name="directory"> | |
| /// The name of the directory over which to apply the FileSelector | |
| /// criteria. | |
| /// </param> | |
| /// | |
| /// <returns> | |
| /// A collection of strings containing fully-qualified pathnames of files | |
| /// that match the criteria specified in the FileSelector instance. | |
| /// </returns> | |
| public System.Collections.Generic.ICollection<String> SelectFiles(String directory) | |
| { | |
| return SelectFiles(directory, false); | |
| } | |
| /// <summary> | |
| /// Returns the names of the files in the specified directory that fit the | |
| /// selection criteria specified in the FileSelector, optionally recursing | |
| /// through subdirectories. | |
| /// </summary> | |
| /// | |
| /// <remarks> | |
| /// This method applies the file selection criteria contained in the | |
| /// FileSelector to the files contained in the given directory, and | |
| /// returns the names of files that conform to the criteria. | |
| /// </remarks> | |
| /// | |
| /// <param name="directory"> | |
| /// The name of the directory over which to apply the FileSelector | |
| /// criteria. | |
| /// </param> | |
| /// | |
| /// <param name="recurseDirectories"> | |
| /// Whether to recurse through subdirectories when applying the file | |
| /// selection criteria. | |
| /// </param> | |
| /// | |
| /// <returns> | |
| /// A collection of strings containing fully-qualified pathnames of files | |
| /// that match the criteria specified in the FileSelector instance. | |
| /// </returns> | |
| public System.Collections.ObjectModel.ReadOnlyCollection<String> | |
| SelectFiles(String directory, | |
| bool recurseDirectories) | |
| { | |
| if (_Criterion == null) | |
| throw new ArgumentException("SelectionCriteria has not been set"); | |
| var list = new List<String>(); | |
| try | |
| { | |
| if (Directory.Exists(directory)) | |
| { | |
| String[] filenames = Directory.GetFiles(directory); | |
| // add the files: | |
| foreach (String filename in filenames) | |
| { | |
| if (Evaluate(filename)) | |
| list.Add(filename); | |
| } | |
| if (recurseDirectories) | |
| { | |
| // add the subdirectories: | |
| String[] dirnames = Directory.GetDirectories(directory); | |
| foreach (String dir in dirnames) | |
| { | |
| if (this.TraverseReparsePoints | |
| #if !SILVERLIGHT | |
| || ((File.GetAttributes(dir) & FileAttributes.ReparsePoint) == 0) | |
| #endif | |
| ) | |
| { | |
| // workitem 10191 | |
| if (Evaluate(dir)) list.Add(dir); | |
| list.AddRange(this.SelectFiles(dir, recurseDirectories)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // can get System.UnauthorizedAccessException here | |
| catch (System.UnauthorizedAccessException) | |
| { | |
| } | |
| catch (System.IO.IOException) | |
| { | |
| } | |
| return list.AsReadOnly(); | |
| } | |
| } | |
| /// <summary> | |
| /// Summary description for EnumUtil. | |
| /// </summary> | |
| internal sealed class EnumUtil | |
| { | |
| private EnumUtil() { } | |
| /// <summary> | |
| /// Returns the value of the DescriptionAttribute if the specified Enum | |
| /// value has one. If not, returns the ToString() representation of the | |
| /// Enum value. | |
| /// </summary> | |
| /// <param name="value">The Enum to get the description for</param> | |
| /// <returns></returns> | |
| internal static string GetDescription(System.Enum value) | |
| { | |
| FieldInfo fi = value.GetType().GetField(value.ToString()); | |
| var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); | |
| if (attributes.Length > 0) | |
| return attributes[0].Description; | |
| else | |
| return value.ToString(); | |
| } | |
| /// <summary> | |
| /// Converts the string representation of the name or numeric value of one | |
| /// or more enumerated constants to an equivalent enumerated object. | |
| /// Note: use the DescriptionAttribute on enum values to enable this. | |
| /// </summary> | |
| /// <param name="enumType">The System.Type of the enumeration.</param> | |
| /// <param name="stringRepresentation"> | |
| /// A string containing the name or value to convert. | |
| /// </param> | |
| /// <returns></returns> | |
| internal static object Parse(Type enumType, string stringRepresentation) | |
| { | |
| return Parse(enumType, stringRepresentation, false); | |
| } | |
| #if SILVERLIGHT | |
| public static System.Enum[] GetEnumValues(Type type) | |
| { | |
| if (!type.IsEnum) | |
| throw new ArgumentException("not an enum"); | |
| return ( | |
| from field in type.GetFields(BindingFlags.Public | BindingFlags.Static) | |
| where field.IsLiteral | |
| select (System.Enum)field.GetValue(null) | |
| ).ToArray(); | |
| } | |
| public static string[] GetEnumStrings<T>() | |
| { | |
| var type = typeof(T); | |
| if (!type.IsEnum) | |
| throw new ArgumentException("not an enum"); | |
| return ( | |
| from field in type.GetFields(BindingFlags.Public | BindingFlags.Static) | |
| where field.IsLiteral | |
| select field.Name | |
| ).ToArray(); | |
| } | |
| #endif | |
| /// <summary> | |
| /// Converts the string representation of the name or numeric value of one | |
| /// or more enumerated constants to an equivalent enumerated object. A | |
| /// parameter specified whether the operation is case-sensitive. Note: | |
| /// use the DescriptionAttribute on enum values to enable this. | |
| /// </summary> | |
| /// <param name="enumType">The System.Type of the enumeration.</param> | |
| /// <param name="stringRepresentation"> | |
| /// A string containing the name or value to convert. | |
| /// </param> | |
| /// <param name="ignoreCase"> | |
| /// Whether the operation is case-sensitive or not.</param> | |
| /// <returns></returns> | |
| internal static object Parse(Type enumType, string stringRepresentation, bool ignoreCase) | |
| { | |
| if (ignoreCase) | |
| stringRepresentation = stringRepresentation.ToLower(CultureInfo.InvariantCulture); | |
| #if SILVERLIGHT | |
| foreach (System.Enum enumVal in GetEnumValues(enumType)) | |
| #else | |
| foreach (System.Enum enumVal in System.Enum.GetValues(enumType)) | |
| #endif | |
| { | |
| string description = GetDescription(enumVal); | |
| if (ignoreCase) | |
| description = description.ToLower(CultureInfo.InvariantCulture); | |
| if (description == stringRepresentation) | |
| return enumVal; | |
| } | |
| return System.Enum.Parse(enumType, stringRepresentation, ignoreCase); | |
| } | |
| } | |
| #if DEMO | |
| internal class DemonstrateFileSelector | |
| { | |
| private string _directory; | |
| private bool _recurse; | |
| private bool _traverse; | |
| private bool _verbose; | |
| private string _selectionCriteria; | |
| private FileSelector f; | |
| public DemonstrateFileSelector() | |
| { | |
| this._directory = "."; | |
| this._recurse = true; | |
| } | |
| public DemonstrateFileSelector(string[] args) : this() | |
| { | |
| for (int i = 0; i < args.Length; i++) | |
| { | |
| switch(args[i]) | |
| { | |
| case"-?": | |
| Usage(); | |
| Environment.Exit(0); | |
| break; | |
| case "-d": | |
| i++; | |
| if (args.Length <= i) | |
| throw new ArgumentException("-directory"); | |
| this._directory = args[i]; | |
| break; | |
| case "-norecurse": | |
| this._recurse = false; | |
| break; | |
| case "-j-": | |
| this._traverse = false; | |
| break; | |
| case "-j+": | |
| this._traverse = true; | |
| break; | |
| case "-v": | |
| this._verbose = true; | |
| break; | |
| default: | |
| if (this._selectionCriteria != null) | |
| throw new ArgumentException(args[i]); | |
| this._selectionCriteria = args[i]; | |
| break; | |
| } | |
| if (this._selectionCriteria != null) | |
| this.f = new FileSelector(this._selectionCriteria); | |
| } | |
| } | |
| public static void Main(string[] args) | |
| { | |
| try | |
| { | |
| Console.WriteLine(); | |
| new DemonstrateFileSelector(args).Run(); | |
| } | |
| catch (Exception exc1) | |
| { | |
| Console.WriteLine("Exception: {0}", exc1.ToString()); | |
| Usage(); | |
| } | |
| } | |
| public void Run() | |
| { | |
| if (this.f == null) | |
| this.f = new FileSelector("name = *.jpg AND (size > 1000 OR atime < 2009-02-14-01:00:00)"); | |
| this.f.TraverseReparsePoints = _traverse; | |
| this.f.Verbose = this._verbose; | |
| Console.WriteLine(); | |
| Console.WriteLine(new String(':', 88)); | |
| Console.WriteLine("Selecting files:\n" + this.f.ToString()); | |
| var files = this.f.SelectFiles(this._directory, this._recurse); | |
| if (files.Count == 0) | |
| { | |
| Console.WriteLine("no files."); | |
| } | |
| else | |
| { | |
| Console.WriteLine("files: {0}", files.Count); | |
| foreach (string file in files) | |
| { | |
| Console.WriteLine(" " + file); | |
| } | |
| } | |
| } | |
| public static void Usage() | |
| { | |
| Console.WriteLine("FileSelector: select files based on selection criteria.\n"); | |
| Console.WriteLine("Usage:\n FileSelector <selectionCriteria> [options]\n" + | |
| "\n" + | |
| " -d <dir> directory to select from (Default .)\n" + | |
| " -norecurse don't recurse into subdirs\n" + | |
| " -j- don't traverse junctions\n" + | |
| " -v verbose output\n"); | |
| } | |
| } | |
| #endif | |
| } | |