| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/files/file_enumerator.h" |
| |
| #include <shlwapi.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include "base/logging.h" |
| #include "base/win/win_util.h" |
| |
| namespace base { |
| |
| namespace { |
| |
| FilePath BuildSearchFilter(FileEnumerator::FolderSearchPolicy policy, |
| const FilePath& root_path, |
| const FilePath::StringType& pattern) { |
| // MATCH_ONLY policy filters incoming files by pattern on OS side. ALL policy |
| // collects all files and filters them manually. |
| switch (policy) { |
| case FileEnumerator::FolderSearchPolicy::MATCH_ONLY: |
| return root_path.Append(pattern); |
| case FileEnumerator::FolderSearchPolicy::ALL: |
| return root_path.Append(u"*"); |
| } |
| NOTREACHED(); |
| return {}; |
| } |
| |
| } // namespace |
| |
| // FileEnumerator::FileInfo ---------------------------------------------------- |
| |
| FileEnumerator::FileInfo::FileInfo() { |
| memset(&find_data_, 0, sizeof(find_data_)); |
| } |
| |
| bool FileEnumerator::FileInfo::IsDirectory() const { |
| return (find_data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; |
| } |
| |
| FilePath FileEnumerator::FileInfo::GetName() const { |
| return FilePath(reinterpret_cast<const char16_t*>(find_data_.cFileName)); |
| } |
| |
| int64_t FileEnumerator::FileInfo::GetSize() const { |
| ULARGE_INTEGER size; |
| size.HighPart = find_data_.nFileSizeHigh; |
| size.LowPart = find_data_.nFileSizeLow; |
| DCHECK_LE(size.QuadPart, |
| static_cast<ULONGLONG>(std::numeric_limits<int64_t>::max())); |
| return static_cast<int64_t>(size.QuadPart); |
| } |
| |
| Ticks FileEnumerator::FileInfo::GetLastModifiedTime() const { |
| return *reinterpret_cast<const uint64_t*>(&find_data_.ftLastWriteTime); |
| } |
| |
| // FileEnumerator -------------------------------------------------------------- |
| |
| FileEnumerator::FileEnumerator(const FilePath& root_path, |
| bool recursive, |
| int file_type) |
| : FileEnumerator(root_path, |
| recursive, |
| file_type, |
| FilePath::StringType(), |
| FolderSearchPolicy::MATCH_ONLY) {} |
| |
| FileEnumerator::FileEnumerator(const FilePath& root_path, |
| bool recursive, |
| int file_type, |
| const FilePath::StringType& pattern) |
| : FileEnumerator(root_path, |
| recursive, |
| file_type, |
| pattern, |
| FolderSearchPolicy::MATCH_ONLY) {} |
| |
| FileEnumerator::FileEnumerator(const FilePath& root_path, |
| bool recursive, |
| int file_type, |
| const FilePath::StringType& pattern, |
| FolderSearchPolicy folder_search_policy) |
| : recursive_(recursive), |
| file_type_(file_type), |
| pattern_(!pattern.empty() ? pattern : u"*"), |
| folder_search_policy_(folder_search_policy) { |
| // INCLUDE_DOT_DOT must not be specified if recursive. |
| DCHECK(!(recursive && (INCLUDE_DOT_DOT & file_type_))); |
| memset(&find_data_, 0, sizeof(find_data_)); |
| pending_paths_.push(root_path); |
| } |
| |
| FileEnumerator::~FileEnumerator() { |
| if (find_handle_ != INVALID_HANDLE_VALUE) |
| FindClose(find_handle_); |
| } |
| |
| FileEnumerator::FileInfo FileEnumerator::GetInfo() const { |
| if (!has_find_data_) { |
| NOTREACHED(); |
| return FileInfo(); |
| } |
| FileInfo ret; |
| memcpy(&ret.find_data_, &find_data_, sizeof(find_data_)); |
| return ret; |
| } |
| |
| FilePath FileEnumerator::Next() { |
| while (has_find_data_ || !pending_paths_.empty()) { |
| if (!has_find_data_) { |
| // The last find FindFirstFile operation is done, prepare a new one. |
| root_path_ = pending_paths_.top(); |
| pending_paths_.pop(); |
| |
| // Start a new find operation. |
| const FilePath src = |
| BuildSearchFilter(folder_search_policy_, root_path_, pattern_); |
| find_handle_ = FindFirstFileEx(ToWCharT(&src.value()), |
| FindExInfoBasic, // Omit short name. |
| &find_data_, FindExSearchNameMatch, |
| nullptr, FIND_FIRST_EX_LARGE_FETCH); |
| has_find_data_ = true; |
| } else { |
| // Search for the next file/directory. |
| if (!FindNextFile(find_handle_, &find_data_)) { |
| FindClose(find_handle_); |
| find_handle_ = INVALID_HANDLE_VALUE; |
| } |
| } |
| |
| if (INVALID_HANDLE_VALUE == find_handle_) { |
| has_find_data_ = false; |
| |
| // MATCH_ONLY policy clears pattern for matched subfolders. ALL policy |
| // applies pattern for all subfolders. |
| if (folder_search_policy_ == FolderSearchPolicy::MATCH_ONLY) { |
| // This is reached when we have finished a directory and are advancing |
| // to the next one in the queue. We applied the pattern (if any) to the |
| // files in the root search directory, but for those directories which |
| // were matched, we want to enumerate all files inside them. This will |
| // happen when the handle is empty. |
| pattern_ = u"*"; |
| } |
| |
| continue; |
| } |
| |
| const FilePath filename(reinterpret_cast<char16_t*>(find_data_.cFileName)); |
| if (ShouldSkip(filename)) |
| continue; |
| |
| const bool is_dir = |
| (find_data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; |
| const FilePath abs_path = root_path_.Append(filename); |
| |
| // Check if directory should be processed recursive. |
| if (is_dir && recursive_) { |
| // If |cur_file| is a directory, and we are doing recursive searching, |
| // add it to pending_paths_ so we scan it after we finish scanning this |
| // directory. However, don't do recursion through reparse points or we |
| // may end up with an infinite cycle. |
| DWORD attributes = GetFileAttributes(ToWCharT(&abs_path.value())); |
| if (!(attributes & FILE_ATTRIBUTE_REPARSE_POINT)) |
| pending_paths_.push(abs_path); |
| } |
| |
| if (IsTypeMatched(is_dir) && IsPatternMatched(filename)) |
| return abs_path; |
| } |
| return FilePath(); |
| } |
| |
| bool FileEnumerator::IsPatternMatched(const FilePath& src) const { |
| switch (folder_search_policy_) { |
| case FolderSearchPolicy::MATCH_ONLY: |
| // MATCH_ONLY policy filters by pattern on search request, so all found |
| // files already fits to pattern. |
| return true; |
| case FolderSearchPolicy::ALL: |
| // ALL policy enumerates all files, we need to check pattern match |
| // manually. |
| return PathMatchSpec(ToWCharT(&src.value()), ToWCharT(&pattern_)) == TRUE; |
| } |
| NOTREACHED(); |
| return false; |
| } |
| |
| } // namespace base |