diff options
Diffstat (limited to '3rdparty/lzma/CPP/Windows/FileFind.cpp')
-rw-r--r-- | 3rdparty/lzma/CPP/Windows/FileFind.cpp | 546 |
1 files changed, 468 insertions, 78 deletions
diff --git a/3rdparty/lzma/CPP/Windows/FileFind.cpp b/3rdparty/lzma/CPP/Windows/FileFind.cpp index 081bd9bc51d..94486977acd 100644 --- a/3rdparty/lzma/CPP/Windows/FileFind.cpp +++ b/3rdparty/lzma/CPP/Windows/FileFind.cpp @@ -2,36 +2,66 @@ #include "StdAfx.h" -#include "FileFind.h" -#include "FileIO.h" #ifndef _UNICODE #include "../Common/StringConvert.h" #endif +#include "FileFind.h" +#include "FileIO.h" +#include "FileName.h" + #ifndef _UNICODE extern bool g_IsNT; #endif +using namespace NWindows; +using namespace NFile; +using namespace NName; + +#if defined(_WIN32) && !defined(UNDER_CE) + +EXTERN_C_BEGIN + +typedef enum +{ + My_FindStreamInfoStandard, + My_FindStreamInfoMaxInfoLevel +} MY_STREAM_INFO_LEVELS; + +typedef struct +{ + LARGE_INTEGER StreamSize; + WCHAR cStreamName[MAX_PATH + 36]; +} MY_WIN32_FIND_STREAM_DATA, *MY_PWIN32_FIND_STREAM_DATA; + +typedef WINBASEAPI HANDLE (WINAPI *FindFirstStreamW_Ptr)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel, + LPVOID findStreamData, DWORD flags); + +typedef WINBASEAPI BOOL (APIENTRY *FindNextStreamW_Ptr)(HANDLE findStream, LPVOID findStreamData); + +EXTERN_C_END + +#endif + namespace NWindows { namespace NFile { #ifdef SUPPORT_DEVICE_FILE -bool IsDeviceName(CFSTR n); -#endif - -#if defined(WIN_LONG_PATH) -bool GetLongPath(CFSTR fileName, UString &res); +namespace NSystem +{ +bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize); +} #endif namespace NFind { -bool CFileInfo::IsDots() const +bool CFileInfo::IsDots() const throw() { if (!IsDir() || Name.IsEmpty()) return false; - if (Name[0] != FTEXT('.')) + if (Name[0] != '.') return false; - return Name.Length() == 1 || (Name.Length() == 2 && Name[1] == FTEXT('.')); + return Name.Len() == 1 || (Name.Len() == 2 && Name[1] == '.'); } #define WIN_FD_TO_MY_FI(fi, fd) \ @@ -40,6 +70,7 @@ bool CFileInfo::IsDots() const fi.ATime = fd.ftLastAccessTime; \ fi.MTime = fd.ftLastWriteTime; \ fi.Size = (((UInt64)fd.nFileSizeHigh) << 32) + fd.nFileSizeLow; \ + fi.IsAltStream = false; \ fi.IsDevice = false; /* @@ -50,27 +81,31 @@ bool CFileInfo::IsDots() const #endif */ -static void ConvertWIN32_FIND_DATA_To_FileInfo(const WIN32_FIND_DATAW &fd, CFileInfo &fi) +static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW &fd, CFileInfo &fi) { WIN_FD_TO_MY_FI(fi, fd); fi.Name = us2fs(fd.cFileName); + #if defined(_WIN32) && !defined(UNDER_CE) + // fi.ShortName = us2fs(fd.cAlternateFileName); + #endif } #ifndef _UNICODE -static inline UINT GetCurrentCodePage() { return ::AreFileApisANSI() ? CP_ACP : CP_OEMCP; } - -static void ConvertWIN32_FIND_DATA_To_FileInfo(const WIN32_FIND_DATA &fd, CFileInfo &fi) +static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATA &fd, CFileInfo &fi) { WIN_FD_TO_MY_FI(fi, fd); fi.Name = fas2fs(fd.cFileName); + #if defined(_WIN32) && !defined(UNDER_CE) + // fi.ShortName = fas2fs(fd.cAlternateFileName); + #endif } #endif //////////////////////////////// // CFindFile -bool CFindFile::Close() +bool CFindFileBase::Close() throw() { if (_handle == INVALID_HANDLE_VALUE) return true; @@ -80,7 +115,35 @@ bool CFindFile::Close() return true; } -bool CFindFile::FindFirst(CFSTR wildcard, CFileInfo &fi) +/* +WinXP-64 FindFirstFile(): + "" - ERROR_PATH_NOT_FOUND + folder\ - ERROR_FILE_NOT_FOUND + \ - ERROR_FILE_NOT_FOUND + c:\ - ERROR_FILE_NOT_FOUND + c: - ERROR_FILE_NOT_FOUND, if current dir is ROOT ( c:\ ) + c: - OK, if current dir is NOT ROOT ( c:\folder ) + folder - OK + + \\ - ERROR_INVALID_NAME + \\Server - ERROR_INVALID_NAME + \\Server\ - ERROR_INVALID_NAME + + \\Server\Share - ERROR_BAD_NETPATH + \\Server\Share - ERROR_BAD_NET_NAME (Win7) + \\Server\Share\ - ERROR_FILE_NOT_FOUND + + \\?\UNC\Server\Share - ERROR_INVALID_NAME + \\?\UNC\Server\Share - ERROR_BAD_PATHNAME (Win7) + \\?\UNC\Server\Share\ - ERROR_FILE_NOT_FOUND + + \\Server\Share_RootDrive - ERROR_INVALID_NAME + \\Server\Share_RootDrive\ - ERROR_INVALID_NAME + + c:\* - ERROR_FILE_NOT_FOUND, if thare are no item in that folder +*/ + +bool CFindFile::FindFirst(CFSTR path, CFileInfo &fi) { if (!Close()) return false; @@ -88,27 +151,29 @@ bool CFindFile::FindFirst(CFSTR wildcard, CFileInfo &fi) if (!g_IsNT) { WIN32_FIND_DATAA fd; - _handle = ::FindFirstFileA(fs2fas(wildcard), &fd); + _handle = ::FindFirstFileA(fs2fas(path), &fd); if (_handle == INVALID_HANDLE_VALUE) return false; - ConvertWIN32_FIND_DATA_To_FileInfo(fd, fi); + Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi); } else #endif { WIN32_FIND_DATAW fd; - _handle = ::FindFirstFileW(fs2us(wildcard), &fd); + + IF_USE_MAIN_PATH + _handle = ::FindFirstFileW(fs2us(path), &fd); #ifdef WIN_LONG_PATH - if (_handle == INVALID_HANDLE_VALUE) + if (_handle == INVALID_HANDLE_VALUE && USE_SUPER_PATH) { - UString longPath; - if (GetLongPath(wildcard, longPath)) - _handle = ::FindFirstFileW(longPath, &fd); + UString superPath; + if (GetSuperPath(path, superPath, USE_MAIN_PATH)) + _handle = ::FindFirstFileW(superPath, &fd); } #endif if (_handle == INVALID_HANDLE_VALUE) return false; - ConvertWIN32_FIND_DATA_To_FileInfo(fd, fi); + Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi); } return true; } @@ -121,7 +186,7 @@ bool CFindFile::FindNext(CFileInfo &fi) WIN32_FIND_DATAA fd; if (!::FindNextFileA(_handle, &fd)) return false; - ConvertWIN32_FIND_DATA_To_FileInfo(fd, fi); + Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi); } else #endif @@ -129,72 +194,395 @@ bool CFindFile::FindNext(CFileInfo &fi) WIN32_FIND_DATAW fd; if (!::FindNextFileW(_handle, &fd)) return false; - ConvertWIN32_FIND_DATA_To_FileInfo(fd, fi); + Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi); } return true; } +#if defined(_WIN32) && !defined(UNDER_CE) + +//////////////////////////////// +// AltStreams + +static FindFirstStreamW_Ptr g_FindFirstStreamW; +static FindNextStreamW_Ptr g_FindNextStreamW; + +struct CFindStreamLoader +{ + CFindStreamLoader() + { + g_FindFirstStreamW = (FindFirstStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindFirstStreamW"); + g_FindNextStreamW = (FindNextStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindNextStreamW"); + } +} g_FindStreamLoader; + +bool CStreamInfo::IsMainStream() const throw() +{ + return StringsAreEqualNoCase_Ascii(Name, "::$DATA"); +}; + +UString CStreamInfo::GetReducedName() const +{ + // remove ":$DATA" postfix, but keep postfix, if Name is "::$DATA" + UString s = Name; + if (s.Len() > 6 + 1 && StringsAreEqualNoCase_Ascii(s.RightPtr(6), ":$DATA")) + s.DeleteFrom(s.Len() - 6); + return s; +} + +/* +UString CStreamInfo::GetReducedName2() const +{ + UString s = GetReducedName(); + if (!s.IsEmpty() && s[0] == ':') + s.Delete(0); + return s; +} +*/ + +static void Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA &sd, CStreamInfo &si) +{ + si.Size = sd.StreamSize.QuadPart; + si.Name = sd.cStreamName; +} + +/* + WinXP-64 FindFirstStream(): + "" - ERROR_PATH_NOT_FOUND + folder\ - OK + folder - OK + \ - OK + c:\ - OK + c: - OK, if current dir is ROOT ( c:\ ) + c: - OK, if current dir is NOT ROOT ( c:\folder ) + \\Server\Share - OK + \\Server\Share\ - OK + + \\ - ERROR_INVALID_NAME + \\Server - ERROR_INVALID_NAME + \\Server\ - ERROR_INVALID_NAME +*/ + +bool CFindStream::FindFirst(CFSTR path, CStreamInfo &si) +{ + if (!Close()) + return false; + if (!g_FindFirstStreamW) + { + ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return false; + } + { + MY_WIN32_FIND_STREAM_DATA sd; + SetLastError(0); + IF_USE_MAIN_PATH + _handle = g_FindFirstStreamW(fs2us(path), My_FindStreamInfoStandard, &sd, 0); + if (_handle == INVALID_HANDLE_VALUE) + { + if (::GetLastError() == ERROR_HANDLE_EOF) + return false; + // long name can be tricky for path like ".\dirName". + #ifdef WIN_LONG_PATH + if (USE_SUPER_PATH) + { + UString superPath; + if (GetSuperPath(path, superPath, USE_MAIN_PATH)) + _handle = g_FindFirstStreamW(superPath, My_FindStreamInfoStandard, &sd, 0); + } + #endif + } + if (_handle == INVALID_HANDLE_VALUE) + return false; + Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si); + } + return true; +} + +bool CFindStream::FindNext(CStreamInfo &si) +{ + if (!g_FindNextStreamW) + { + ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return false; + } + { + MY_WIN32_FIND_STREAM_DATA sd; + if (!g_FindNextStreamW(_handle, &sd)) + return false; + Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si); + } + return true; +} + +bool CStreamEnumerator::Next(CStreamInfo &si, bool &found) +{ + bool res; + if (_find.IsHandleAllocated()) + res = _find.FindNext(si); + else + res = _find.FindFirst(_filePath, si); + if (res) + { + found = true; + return true; + } + found = false; + return (::GetLastError() == ERROR_HANDLE_EOF); +} + +#endif + + #define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0; -void CFileInfoBase::Clear() +void CFileInfoBase::ClearBase() throw() { Size = 0; MY_CLEAR_FILETIME(CTime); MY_CLEAR_FILETIME(ATime); MY_CLEAR_FILETIME(MTime); Attrib = 0; + IsAltStream = false; + IsDevice = false; } - -bool CFileInfo::Find(CFSTR wildcard) + +/* +WinXP-64 GetFileAttributes(): + If the function fails, it returns INVALID_FILE_ATTRIBUTES and use GetLastError() to get error code + + \ - OK + C:\ - OK, if there is such drive, + D:\ - ERROR_PATH_NOT_FOUND, if there is no such drive, + + C:\folder - OK + C:\folder\ - OK + C:\folderBad - ERROR_FILE_NOT_FOUND + + \\Server\BadShare - ERROR_BAD_NETPATH + \\Server\Share - WORKS OK, but MSDN says: + GetFileAttributes for a network share, the function fails, and GetLastError + returns ERROR_BAD_NETPATH. You must specify a path to a subfolder on that share. +*/ + +DWORD GetFileAttrib(CFSTR path) +{ + #ifndef _UNICODE + if (!g_IsNT) + return ::GetFileAttributes(fs2fas(path)); + else + #endif + { + IF_USE_MAIN_PATH + { + DWORD dw = ::GetFileAttributesW(fs2us(path)); + if (dw != INVALID_FILE_ATTRIBUTES) + return dw; + } + #ifdef WIN_LONG_PATH + if (USE_SUPER_PATH) + { + UString superPath; + if (GetSuperPath(path, superPath, USE_MAIN_PATH)) + return ::GetFileAttributesW(superPath); + } + #endif + return INVALID_FILE_ATTRIBUTES; + } +} + +/* if path is "c:" or "c::" then CFileInfo::Find() returns name of current folder for that disk + so instead of absolute path we have relative path in Name. That is not good in some calls */ + +/* In CFileInfo::Find() we want to support same names for alt streams as in CreateFile(). */ + +/* CFileInfo::Find() +We alow the following paths (as FindFirstFile): + C:\folder + c: - if current dir is NOT ROOT ( c:\folder ) + +also we support paths that are not supported by FindFirstFile: + \ + \\.\c: + c:\ - Name will be without tail slash ( c: ) + \\?\c:\ - Name will be without tail slash ( c: ) + \\Server\Share + \\?\UNC\Server\Share + + c:\folder:stream - Name = folder:stream + c:\:stream - Name = :stream + c::stream - Name = c::stream +*/ + +bool CFileInfo::Find(CFSTR path) { #ifdef SUPPORT_DEVICE_FILE - if (IsDeviceName(wildcard)) + if (IsDevicePath(path)) { - Clear(); + ClearBase(); + Name = path + 4; IsDevice = true; + + if (NName::IsDrivePath2(path + 4) && path[6] == 0) + { + FChar drive[4] = { path[4], ':', '\\', 0 }; + UInt64 clusterSize, totalSize, freeSize; + if (NSystem::MyGetDiskFreeSpace(drive, clusterSize, totalSize, freeSize)) + { + Size = totalSize; + return true; + } + } + NIO::CInFile inFile; - if (!inFile.Open(wildcard)) + // ::OutputDebugStringW(path); + if (!inFile.Open(path)) return false; - Name = wildcard + 4; - if (inFile.LengthDefined) - Size = inFile.Length; + // ::OutputDebugStringW(L"---"); + if (inFile.SizeDefined) + Size = inFile.Size; return true; } #endif + + #if defined(_WIN32) && !defined(UNDER_CE) + + int colonPos = FindAltStreamColon(path); + if (colonPos >= 0 && path[(unsigned)colonPos + 1] != 0) + { + UString streamName = fs2us(path + (unsigned)colonPos); + FString filePath = path; + filePath.DeleteFrom(colonPos); + /* we allow both cases: + name:stream + name:stream:$DATA + */ + const unsigned kPostfixSize = 6; + if (streamName.Len() <= kPostfixSize + || !StringsAreEqualNoCase_Ascii(streamName.RightPtr(kPostfixSize), ":$DATA")) + streamName += L":$DATA"; + + bool isOk = true; + + if (IsDrivePath2(filePath) && + (colonPos == 2 || colonPos == 3 && filePath[2] == '\\')) + { + // FindFirstFile doesn't work for "c:\" and for "c:" (if current dir is ROOT) + ClearBase(); + Name.Empty(); + if (colonPos == 2) + Name = filePath; + } + else + isOk = Find(filePath); + + if (isOk) + { + Attrib &= ~(FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT); + Size = 0; + CStreamEnumerator enumerator(filePath); + for (;;) + { + CStreamInfo si; + bool found; + if (!enumerator.Next(si, found)) + return false; + if (!found) + { + ::SetLastError(ERROR_FILE_NOT_FOUND); + return false; + } + if (si.Name.IsEqualTo_NoCase(streamName)) + { + // we delete postfix, if alt stream name is not "::$DATA" + if (si.Name.Len() > kPostfixSize + 1) + si.Name.DeleteFrom(si.Name.Len() - kPostfixSize); + Name += us2fs(si.Name); + Size = si.Size; + IsAltStream = true; + return true; + } + } + } + } + + #endif + CFindFile finder; - if (finder.FindFirst(wildcard, *this)) + if (finder.FindFirst(path, *this)) return true; - #ifdef _WIN32 + + #if defined(_WIN32) && !defined(UNDER_CE) { DWORD lastError = GetLastError(); - if (lastError == ERROR_BAD_NETPATH || lastError == ERROR_FILE_NOT_FOUND) + if (lastError == ERROR_FILE_NOT_FOUND + || lastError == ERROR_BAD_NETPATH // XP64: "\\Server\Share" + || lastError == ERROR_BAD_NET_NAME // Win7: "\\Server\Share" + || lastError == ERROR_INVALID_NAME // XP64: "\\?\UNC\Server\Share" + || lastError == ERROR_BAD_PATHNAME // Win7: "\\?\UNC\Server\Share" + ) { - int len = MyStringLen(wildcard); - if (len > 2 && wildcard[0] == '\\' && wildcard[1] == '\\') + unsigned rootSize = 0; + if (IsSuperPath(path)) + rootSize = kSuperPathPrefixSize; + if (IS_PATH_SEPAR(path[0]) && path[1] == 0) { - int pos = FindCharPosInString(wildcard + 2, FTEXT('\\')); - if (pos >= 0) + DWORD attrib = GetFileAttrib(path); + if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0) { - pos += 2 + 1; - len -= pos; - CFSTR remString = wildcard + pos; - int pos2 = FindCharPosInString(remString, FTEXT('\\')); - FString s = wildcard; - if (pos2 < 0 || pos2 == len - 1) + ClearBase(); + Name.Empty(); + Attrib = attrib; + return true; + } + } + else if (NName::IsDrivePath(path + rootSize) && path[rootSize + 3] == 0) + { + DWORD attrib = GetFileAttrib(path); + if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + ClearBase(); + Attrib = attrib; + Name = path + rootSize; + Name.DeleteFrom(2); // we don't need backslash (C:) + return true; + } + } + else + { + unsigned prefixSize = GetNetworkServerPrefixSize(path); + if (prefixSize > 0 && path[prefixSize] != 0) + { + if (NName::FindSepar(path + prefixSize) < 0) { - FString s = wildcard; - if (pos2 < 0) - { - pos2 = len; - s += FTEXT('\\'); - } + FString s = path; + s.Add_PathSepar(); s += FCHAR_ANY_MASK; + + bool isOK = false; if (finder.FindFirst(s, *this)) + { if (Name == FTEXT(".")) { - Name = s.Mid(pos, pos2); + Name = path + prefixSize;; + return true; + } + isOK = true; + /* if "\\server\share" maps to root folder "d:\", there is no "." item. + But it's possible that there are another items */ + } + { + DWORD attrib = GetFileAttrib(path); + if (isOK || attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + ClearBase(); + if (attrib != INVALID_FILE_ATTRIBUTES) + Attrib = attrib; + else + SetAsDir(); + Name = path + prefixSize; return true; } + } ::SetLastError(lastError); } } @@ -202,6 +590,7 @@ bool CFileInfo::Find(CFSTR wildcard) } } #endif + return false; } @@ -216,12 +605,14 @@ bool DoesDirExist(CFSTR name) CFileInfo fi; return fi.Find(name) && fi.IsDir(); } + bool DoesFileOrDirExist(CFSTR name) { CFileInfo fi; return fi.Find(name); } + bool CEnumerator::NextAny(CFileInfo &fi) { if (_findFile.IsHandleAllocated()) @@ -256,7 +647,7 @@ bool CEnumerator::Next(CFileInfo &fi, bool &found) // CFindChangeNotification // FindFirstChangeNotification can return 0. MSDN doesn't tell about it. -bool CFindChangeNotification::Close() +bool CFindChangeNotification::Close() throw() { if (!IsHandleAllocated()) return true; @@ -266,21 +657,22 @@ bool CFindChangeNotification::Close() return true; } -HANDLE CFindChangeNotification::FindFirst(CFSTR pathName, bool watchSubtree, DWORD notifyFilter) +HANDLE CFindChangeNotification::FindFirst(CFSTR path, bool watchSubtree, DWORD notifyFilter) { #ifndef _UNICODE if (!g_IsNT) - _handle = ::FindFirstChangeNotification(fs2fas(pathName), BoolToBOOL(watchSubtree), notifyFilter); + _handle = ::FindFirstChangeNotification(fs2fas(path), BoolToBOOL(watchSubtree), notifyFilter); else #endif { - _handle = ::FindFirstChangeNotificationW(fs2us(pathName), BoolToBOOL(watchSubtree), notifyFilter); + IF_USE_MAIN_PATH + _handle = ::FindFirstChangeNotificationW(fs2us(path), BoolToBOOL(watchSubtree), notifyFilter); #ifdef WIN_LONG_PATH if (!IsHandleAllocated()) { - UString longPath; - if (GetLongPath(pathName, longPath)) - _handle = ::FindFirstChangeNotificationW(longPath, BoolToBOOL(watchSubtree), notifyFilter); + UString superPath; + if (GetSuperPath(path, superPath, USE_MAIN_PATH)) + _handle = ::FindFirstChangeNotificationW(superPath, BoolToBOOL(watchSubtree), notifyFilter); } #endif } @@ -299,23 +691,22 @@ bool MyGetLogicalDriveStrings(CObjectVector<FString> &driveStrings) UINT32 size = GetLogicalDriveStrings(0, NULL); if (size == 0) return false; - AString buf; - UINT32 newSize = GetLogicalDriveStrings(size, buf.GetBuffer(size)); + CObjArray<char> buf(size); + UINT32 newSize = GetLogicalDriveStrings(size, buf); if (newSize == 0 || newSize > size) return false; AString s; + UINT32 prev = 0; for (UINT32 i = 0; i < newSize; i++) { - char c = buf[i]; - if (c == '\0') + if (buf[i] == 0) { + s = buf + prev; + prev = i + 1; driveStrings.Add(fas2fs(s)); - s.Empty(); } - else - s += c; } - return s.IsEmpty(); + return prev == newSize;; } else #endif @@ -323,23 +714,22 @@ bool MyGetLogicalDriveStrings(CObjectVector<FString> &driveStrings) UINT32 size = GetLogicalDriveStringsW(0, NULL); if (size == 0) return false; - UString buf; - UINT32 newSize = GetLogicalDriveStringsW(size, buf.GetBuffer(size)); + CObjArray<wchar_t> buf(size); + UINT32 newSize = GetLogicalDriveStringsW(size, buf); if (newSize == 0 || newSize > size) return false; UString s; + UINT32 prev = 0; for (UINT32 i = 0; i < newSize; i++) { - WCHAR c = buf[i]; - if (c == L'\0') + if (buf[i] == 0) { + s = buf + prev; + prev = i + 1; driveStrings.Add(us2fs(s)); - s.Empty(); } - else - s += c; } - return s.IsEmpty(); + return prev == newSize; } } |