File: FileEnumerator.cs

package info (click to toggle)
mono 6.8.0.105%2Bdfsg-3.3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 1,284,512 kB
  • sloc: cs: 11,172,132; xml: 2,850,069; ansic: 671,653; cpp: 122,091; perl: 59,366; javascript: 30,841; asm: 22,168; makefile: 20,093; sh: 15,020; python: 4,827; pascal: 925; sql: 859; sed: 16; php: 1
file content (161 lines) | stat: -rw-r--r-- 5,213 bytes parent folder | download | duplicates (7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//------------------------------------------------------------------------------
// <copyright file="FileEnumerator.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------

/*
 * FileEnumerator class
 * 
 * Copyright (c) 2003 Microsoft Corporation
 *
 * Class to efficiently enumerate the files in a directory.  The only thing the framework provides
 * to do this is Directory.GetFiles(), which is unusable on large directories because it returns an
 * array containing all the file names at once (huge memory allocation).
 *
 * An efficient alternative is to use FindFirstFile/FindNextFile, which works but requires a lot
 * more code.  Also, it makes our code base harder to port to non-windows platforms.
 *
 * This FileEnumerator class solves both problem, by providing a simple and efficient wrapper.
 * By working with a single object, it is almost as efficient as calling FindFirstFile/FindNextFile,
 * but is much easier to use.  e.g. instead of:
 *
 *      UnsafeNativeMethods.WIN32_FIND_DATA wfd;
 *      IntPtr hFindFile = UnsafeNativeMethods.FindFirstFile(physicalDir + @"\*.*", out wfd);
 *
 *      if (hFindFile == INVALID_HANDLE_VALUE)
 *          return;
 *
 *      try {
 *          for (bool more=true; more; more=UnsafeNativeMethods.FindNextFile(hFindFile, out wfd)) {
 *
 *              // Skip false directories
 *              if (wfd.cFileName == "." || wfd.cFileName == "..")
 *                  continue;
 *              
 *              string fullPath = Path.Combine(physicalDir, wfd.cFileName);
 *
 *              ProcessFile(fullPath);
 *          }
 *      }
 *      finally {
 *          UnsafeNativeMethods.FindClose(hFindFile);
 *      }
 *
 * we can simply write
 *
 *      foreach (FileData fileData in FileEnumerator.Create(physicalDir)) {
 *          ProcessFile(fileData.FullName);
 *      }
 */


namespace System.Web.Util {

using System.IO;
using System.Collections;

/*
 * This is a somewhat artificial base class for FileEnumerator.  The main reason
 * for it is to allow user code to be more readable, by looking like:
 *      foreach (FileData fileData in FileEnumerator.Create(path)) { ... }
 * instead of
 *      foreach (FileEnumerator fileData in FileEnumerator.Create(path)) { ... }
 */
internal abstract class FileData {

    protected string _path;
    protected UnsafeNativeMethods.WIN32_FIND_DATA _wfd;

    internal string Name {
        get { return _wfd.cFileName; }
    }

    internal string FullName {
        get { return _path + @"\" + _wfd.cFileName; }
    }

    internal bool IsDirectory {
        get { return (_wfd.dwFileAttributes & UnsafeNativeMethods.FILE_ATTRIBUTE_DIRECTORY) != 0; }
    }

    internal bool IsHidden {
        get { return (_wfd.dwFileAttributes & UnsafeNativeMethods.FILE_ATTRIBUTE_HIDDEN) != 0; }
    }

    internal FindFileData GetFindFileData() {
        return new FindFileData(ref _wfd);
    }
}

internal class FileEnumerator: FileData, IEnumerable, IEnumerator, IDisposable {
    private IntPtr _hFindFile = UnsafeNativeMethods.INVALID_HANDLE_VALUE;

    internal static FileEnumerator Create(string path) {
        return new FileEnumerator(path);
    }

    private FileEnumerator(string path) {
        _path = Path.GetFullPath(path);
    }

    ~FileEnumerator() {
        ((IDisposable)this).Dispose();
    }

    // Should the current file be excluded from the enumeration
    private bool SkipCurrent() {
    
        // Skip false directories
        if (_wfd.cFileName == "." || _wfd.cFileName == "..")
            return true;

        return false;
    }

    // We just return ourselves for the enumerator, to avoid creating a new object
    IEnumerator IEnumerable.GetEnumerator() {
        return this;
    }

    bool IEnumerator.MoveNext() {

        for (;;) {
            if (_hFindFile == UnsafeNativeMethods.INVALID_HANDLE_VALUE) {
                _hFindFile = UnsafeNativeMethods.FindFirstFile(_path + @"\*.*", out _wfd);
                
                // Empty enumeration case
                if (_hFindFile == UnsafeNativeMethods.INVALID_HANDLE_VALUE)
                    return false;
            }
            else {
                bool hasMoreFiles = UnsafeNativeMethods.FindNextFile(_hFindFile, out _wfd);
                if (!hasMoreFiles)
                    return false;
            }
            
            if (!SkipCurrent())
                return true;
        }
    }

    // The current object of the enumeration is always ourselves.  No new object created.
    object IEnumerator.Current {
        get { return this; }
    }

    void IEnumerator.Reset() {
        // We don't support reset, though it would be easy to add if needed
        throw new InvalidOperationException();
    }

    void IDisposable.Dispose() {
        if (_hFindFile != UnsafeNativeMethods.INVALID_HANDLE_VALUE) {
            UnsafeNativeMethods.FindClose(_hFindFile);
            _hFindFile = UnsafeNativeMethods.INVALID_HANDLE_VALUE;
        }
        System.GC.SuppressFinalize(this);
    }
}

}