#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * * OpenRCT2 is the work of many authors, a full list can be found in contributors.md * For more information, visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * A full copy of the GNU General Public License can be found in licence.txt *****************************************************************************/ #pragma endregion #include "../common.h" #ifdef __WINDOWS__ #define WIN32_LEAN_AND_MEAN #include #endif #include #include #include "FileScanner.h" #include "Memory.hpp" #include "Path.hpp" #include "String.hpp" extern "C" { #include "../localisation/localisation.h" #include "../platform/platform.h" } enum DIRECTORY_CHILD_TYPE { DCT_DIRECTORY, DCT_FILE, }; struct DirectoryChild { DIRECTORY_CHILD_TYPE Type; std::string Name; // Files only uint64 Size; uint64 LastModified; }; static bool MatchWildcard(const utf8 * fileName, const utf8 * pattern); class FileScannerBase : public IFileScanner { private: struct DirectoryState { std::string Path; std::vector Listing; sint32 Index; }; // Options utf8 * _rootPath; utf8 * * _patterns; size_t _numPatterns; bool _recurse; // State bool _started; std::stack _directoryStack; // Current FileInfo * _currentFileInfo; utf8 * _currentPath; public: FileScannerBase(const utf8 * pattern, bool recurse) { _rootPath = Memory::Allocate(MAX_PATH); Path::GetDirectory(_rootPath, MAX_PATH, pattern); _recurse = recurse; _numPatterns = GetPatterns(&_patterns, Path::GetFileName(pattern)); _currentPath = Memory::Allocate(MAX_PATH); _currentFileInfo = Memory::Allocate(); Reset(); } ~FileScannerBase() override { Memory::Free(_rootPath); Memory::FreeArray(_patterns, _numPatterns); Memory::Free(_currentPath); Memory::Free(_currentFileInfo); } const FileInfo * GetFileInfo() const override { return _currentFileInfo; } const utf8 * GetPath() const override { return _currentPath; } void Reset() override { _started = false; _directoryStack = std::stack(); _currentPath[0] = 0; } bool Next() override { if (!_started) { _started = true; PushState(_rootPath); } while (_directoryStack.size() != 0) { DirectoryState * state = &_directoryStack.top(); state->Index++; if (state->Index >= state->Listing.size()) { _directoryStack.pop(); } else { const DirectoryChild * child = &state->Listing[state->Index]; if (child->Type == DCT_DIRECTORY) { utf8 childPath[MAX_PATH]; String::Set(childPath, sizeof(childPath), state->Path.c_str()); Path::Append(childPath, sizeof(childPath), child->Name.c_str()); PushState(childPath); } else if (PatternMatch(child->Name.c_str())) { String::Set(_currentPath, MAX_PATH, state->Path.c_str()); Path::Append(_currentPath, MAX_PATH, child->Name.c_str()); _currentFileInfo->Name = child->Name.c_str(); _currentFileInfo->Size = child->Size; _currentFileInfo->LastModified = child->LastModified; return true; } } } return false; } private: void PushState(const utf8 * directory) { DirectoryState newState; newState.Path = std::string(directory); newState.Index = -1; GetDirectoryChildren(newState.Listing, directory); _directoryStack.push(newState); } bool PatternMatch(const utf8 * fileName) { for (size_t i = 0; i < _numPatterns; i++) { if (MatchWildcard(fileName, _patterns[i])) { return true; } } return false; } static size_t GetPatterns(utf8 * * * outPatterns, const utf8 * delimitedPatterns) { std::vector patterns; const utf8 * start = delimitedPatterns; const utf8 * ch = start; utf8 c; do { c = *ch; if (c == '\0' || c == ';') { size_t length = (size_t)(ch - start); if (length > 0) { utf8 * newPattern = Memory::Allocate(length + 1); Memory::Copy(newPattern, start, length); newPattern[length] = '\0'; patterns.push_back(newPattern); } start = ch + 1; } ch++; } while (c != '\0'); *outPatterns = Memory::DuplicateArray(patterns.data(), patterns.size()); return patterns.size(); } protected: virtual void GetDirectoryChildren(std::vector &children, const utf8 * path) abstract; }; #ifdef __WINDOWS__ class FileScannerWindows final : public FileScannerBase { public: FileScannerWindows(const utf8 * pattern, bool recurse) : FileScannerBase(pattern, recurse) { } protected: void GetDirectoryChildren(std::vector &children, const utf8 * path) override { size_t pathLength = String::SizeOf(path); utf8 * pattern = Memory::Duplicate(path, pathLength + 3); pattern[pathLength + 0] = '\\'; pattern[pathLength + 1] = '*'; pattern[pathLength + 2] = '\0'; wchar_t * wPattern = utf8_to_widechar(pattern); WIN32_FIND_DATAW findData; HANDLE hFile = FindFirstFileW(wPattern, &findData); if (hFile != INVALID_HANDLE_VALUE) { do { if (lstrcmpW(findData.cFileName, L".") != 0 && lstrcmpW(findData.cFileName, L"..") != 0) { DirectoryChild child = CreateChild(&findData); children.push_back(child); } } while (FindNextFileW(hFile, &findData)); FindClose(hFile); } } private: static DirectoryChild CreateChild(const WIN32_FIND_DATAW * child) { DirectoryChild result; utf8 * name = widechar_to_utf8(child->cFileName); result.Name = std::string(name); Memory::Free(name); if (child->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { result.Type = DCT_DIRECTORY; } else { result.Type = DCT_FILE; result.Size = ((uint64)child->nFileSizeHigh << 32ULL) | (uint64)child->nFileSizeLow; result.LastModified = ((uint64)child->ftLastWriteTime.dwHighDateTime << 32ULL) | (uint64)child->ftLastWriteTime.dwLowDateTime; } return result; } }; #endif // __WINDOWS__ IFileScanner * Path::ScanDirectory(const utf8 * pattern, bool recurse) { #ifdef __WINDOWS__ return new FileScannerWindows(pattern, recurse); #endif } /** * Due to FindFirstFile / FindNextFile searching for DOS names as well, *.doc also matches *.docx which isn't what the pattern * specified. This will verify if a filename does indeed match the pattern we asked for. * @remarks Based on algorithm (http://xoomer.virgilio.it/acantato/dev/wildcard/wildmatch.html) */ static bool MatchWildcard(const utf8 * fileName, const utf8 * pattern) { while (*fileName != '\0') { switch (*pattern) { case '?': if (*fileName == '.') { return false; } break; case '*': do { pattern++; } while (*pattern == '*'); if (*pattern == '\0') { return false; } while (*fileName != '\0') { if (MatchWildcard(fileName++, pattern)) { return true; } } return false; default: if (toupper(*fileName) != toupper(*pattern)) { return false; } break; } pattern++; fileName++; } while (*pattern == '*') { ++fileName; } return *pattern == '\0'; }