Click here to Skip to main content
15,885,546 members
Articles / Desktop Programming / Windows Forms

GBackupSolution - Visual Studio Extension VSX 2008/2010

Rate me:
Please Sign up or sign in to vote.
4.92/5 (54 votes)
17 Mar 2010CPOL9 min read 84.6K   1.1K   93  
Visual Studio 2008/2010 Extension for backing-up your solution to Gmail.
//#define SelectorTrace

// FileSelector.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2008-2010 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 (in emacs):
// Time-stamp: <2010-February-24 22:58:10>
//
// ------------------------------------------------------------------
//
// 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.IO;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using System.Text.RegularExpressions;

namespace 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 abstract bool Evaluate(string filename);

        [System.Diagnostics.Conditional("SelectorTrace")]
        protected 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);
            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.GetLastAccessTimeUtc(filename);
                    break;
                case WhichTime.mtime:
                    x = System.IO.File.GetLastWriteTimeUtc(filename);
                    break;
                case WhichTime.ctime:
                    x = System.IO.File.GetCreationTimeUtc(filename);
                    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(@"\?", @"[^\\\.]")
                + "$";

                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);
            return sb.ToString();
        }


        internal override bool Evaluate(string filename)
        {
            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;
        }
    }


    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.NotContentIndexed) != 0)
                    result += "I";
                if ((_Attributes & FileAttributes.ReparsePoint) != 0)
                    result += "L";
                return result;
            }

            set
            {
                _Attributes = FileAttributes.Normal;
                foreach (char c in value.ToUpper())
                {
                    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)
        {
            //Console.WriteLine("fileattrs[{0}]={1}", filename, fileAttrs.ToString());

            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;
        }
    }



    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>
    public 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 &gt;,
        /// &gt;=, &lt;, &lt;= 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 &gt;= 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 &gt; 1000 or attributes = H" implies
        /// "((name = *.txt AND size &gt; 1000) OR attributes = H)" while "attributes =
        /// H OR name = *.txt and size &gt; 1000" evaluates to "((attributes = H OR name
        /// = *.txt) AND size &gt; 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 &gt;=
        /// 2009-02-14 AND mtime &lt; 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 SelectionCriterion _ParseCriterion(String s)
        {
            if (s == null) return null;

            // inject spaces after open paren and before close paren
            string[][] prPairs =
                {
                    new string[] { @"\(\(", "( (" },
                    new string[] { @"\)\)", ") )" },
                    new string[] { @"\((\S)", "( $1" },
                    new string[] { @"(\S)\)", "$1 )" },
                    new string[] { @"(\S)\(", "$1 (" },
                    new string[] { @"\)(\S)", ") $1" },
                    new string[] { @"([^ ]+)>([^ ]+)", "$1 > $2" },
                    new string[] { @"([^ ]+)<([^ ]+)", "$1 < $2" },
                    new string[] { @"([^ ]+)!=([^ ]+)", "$1 != $2" },
                    new string[] { @"([^ ]+)=([^ ]+)", "$1 = $2" },
                };
            for (int i = 0; i < prPairs.Length; i++)
            {
                Regex rgx = new Regex(prPairs[i][0]);
                s = rgx.Replace(s, prPairs[i][1]);
            }

            // 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();
                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());
                        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]),
                            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.ToUpper().EndsWith("K"))
                            sz = Int64.Parse(v.Substring(0, v.Length - 1)) * 1024;
                        else if (v.ToUpper().EndsWith("KB"))
                            sz = Int64.Parse(v.Substring(0, v.Length - 2)) * 1024;
                        else if (v.ToUpper().EndsWith("M"))
                            sz = Int64.Parse(v.Substring(0, v.Length - 1)) * 1024 * 1024;
                        else if (v.ToUpper().EndsWith("MB"))
                            sz = Int64.Parse(v.Substring(0, v.Length - 2)) * 1024 * 1024;
                        else if (v.ToUpper().EndsWith("G"))
                            sz = Int64.Parse(v.Substring(0, v.Length - 1)) * 1024 * 1024 * 1024;
                        else if (v.ToUpper().EndsWith("GB"))
                            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("'"))
                            {
                                int ix = i;
                                if (!m.EndsWith("'"))
                                {
                                    do
                                    {
                                        i++;
                                        if (tokens.Length <= i + 2)
                                            throw new ArgumentException(String.Join(" ", tokens, ix, tokens.Length - ix));
                                        m += " " + tokens[i + 2];
                                    } while (!tokens[i + 2].EndsWith("'"));
                                }
                                // trim off leading and trailing single quotes
                                m = m.Substring(1, m.Length - 2);
                            }

                            current = new NameCriterion
                            {
                                MatchingFileSpec = m,
                                Operator = c
                            };
                            i += 2;
                            stateStack.Push(ParseState.CriterionDone);
                        }
                        break;

                    case "attrs":
                    case "attributes":
                    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));

                            current = (tok1 == "type")
                                ? (SelectionCriterion) new TypeCriterion
                                    {
                                        AttributeString = tokens[i + 2],
                                        Operator = c
                                    }
                                : (SelectionCriterion) new AttributesCriterion
                                    {
                                        AttributeString = tokens[i + 2],
                                        Operator = c
                                    };

                            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)
        {
            bool result = _Criterion.Evaluate(filename);
            return result;
        }


        /// <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>
        /// An 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 System.Collections.Generic.List<String>();
            try
            {
                if (Directory.Exists(directory))
                {
                    String[] filenames = System.IO.Directory.GetFiles(directory);

                    // add the files:
                    foreach (String filename in filenames)
                    {
                        if (Evaluate(filename))
                            list.Add(filename);
                    }

                    if (recurseDirectories)
                    {
                        // add the subdirectories:
                        String[] dirnames = System.IO.Directory.GetDirectories(directory);
                        foreach (String dir in dirnames)
                        {
                            if (this.TraverseReparsePoints ||
                                ((File.GetAttributes(dir) & FileAttributes.ReparsePoint) == 0))
                            {
                                // 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);
        }

        /// <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();

            foreach (System.Enum enumVal in System.Enum.GetValues(enumType))
            {
                string description = GetDescription(enumVal);
                if (ignoreCase)
                    description = description.ToLower();
                if (description == stringRepresentation)
                    return enumVal;
            }

            return System.Enum.Parse(enumType, stringRepresentation, ignoreCase);
        }
    }


#if DEMO
    public class DemonstrateFileSelector
    {
        // Fields
        private string _directory;
        private bool _recurse;
        private string _selectionCriteria;
        private FileSelector f;

        // Methods
        public DemonstrateFileSelector()
        {
            this._directory = ".";
            this._recurse = true;
        }

        public DemonstrateFileSelector(string[] args)
        {
            this._directory = ".";
            this._recurse = true;
            for (int i = 0; i < args.Length; i++)
            {
                switch(args[i])
                {
                case"-?":
                    Usage();
                    Environment.Exit(0);
                    break;
                case "-directory":
                    i++;
                    if (args.Length <= i)
                    {
                        throw new ArgumentException("-directory");
                    }
                    this._directory = args[i];
                    break;
                case "-norecurse":
                    this._recurse = false;
                    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
            {
                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-00:00:00)");
            }
            Console.WriteLine("\nSelecting 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>  [-directory <dir>] [-norecurse]");
        }
    }

#endif




}


By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer TMA Solutions
Vietnam Vietnam
Impossible = I'm possible!

Comments and Discussions