Source code

Revision control

Copy as Markdown

Other Tools

// UpdateCallback.cpp↩
#include "StdAfx.h"
#ifndef _7ZIP_ST↩
#include "../../../Windows/Synchronization.h"
#endif
#include "../../../Common/ComTry.h"
#include "../../../Common/IntToString.h"
#include "../../../Common/StringConvert.h"
#include "../../../Common/Wildcard.h"
#include "../../../Windows/FileDir.h"
#include "../../../Windows/FileName.h"
#include "../../../Windows/PropVariant.h"
#include "../../Common/StreamObjects.h"
#include "UpdateCallback.h"
#if defined(_WIN32) && !defined(UNDER_CE)↩
#define _USE_SECURITY_CODE↩
#include "../../../Windows/SecurityUtils.h"
#endif
using namespace NWindows;↩
using namespace NFile;↩
#ifndef _7ZIP_ST↩
static NSynchronization::CCriticalSection g_CriticalSection;↩
#define MT_LOCK NSynchronization::CCriticalSectionLock lock(g_CriticalSection);↩
#else
#define MT_LOCK↩
#endif
#ifdef _USE_SECURITY_CODE↩
bool InitLocalPrivileges();↩
#endif
CArchiveUpdateCallback::CArchiveUpdateCallback():↩
_hardIndex_From((UInt32)(Int32)-1),↩
Callback(NULL),↩
DirItems(NULL),↩
ParentDirItem(NULL),↩
Arc(NULL),↩
ArcItems(NULL),↩
UpdatePairs(NULL),↩
NewNames(NULL),↩
CommentIndex(-1),↩
Comment(NULL),↩
ShareForWrite(false),↩
StopAfterOpenError(false),↩
StdInMode(false),↩
KeepOriginalItemNames(false),↩
StoreNtSecurity(false),↩
StoreHardLinks(false),↩
StoreSymLinks(false),↩
ProcessedItemsStatuses(NULL)↩
{↩
#ifdef _USE_SECURITY_CODE↩
_saclEnabled = InitLocalPrivileges();↩
#endif
}↩
STDMETHODIMP CArchiveUpdateCallback::SetTotal(UInt64 size)↩
{↩
COM_TRY_BEGIN↩
return Callback->SetTotal(size);↩
COM_TRY_END↩
}↩
STDMETHODIMP CArchiveUpdateCallback::SetCompleted(const UInt64 *completeValue)↩
{↩
COM_TRY_BEGIN↩
return Callback->SetCompleted(completeValue);↩
COM_TRY_END↩
}↩
STDMETHODIMP CArchiveUpdateCallback::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)↩
{↩
COM_TRY_BEGIN↩
return Callback->SetRatioInfo(inSize, outSize);↩
COM_TRY_END↩
}↩
/*↩
static const CStatProp kProps[] =↩
{↩
{ NULL, kpidPath, VT_BSTR},↩
{ NULL, kpidIsDir, VT_BOOL},↩
{ NULL, kpidSize, VT_UI8},↩
{ NULL, kpidCTime, VT_FILETIME},↩
{ NULL, kpidATime, VT_FILETIME},↩
{ NULL, kpidMTime, VT_FILETIME},↩
{ NULL, kpidAttrib, VT_UI4},↩
{ NULL, kpidIsAnti, VT_BOOL}↩
};↩
STDMETHODIMP CArchiveUpdateCallback::EnumProperties(IEnumSTATPROPSTG **)↩
{↩
return CStatPropEnumerator::CreateEnumerator(kProps, ARRAY_SIZE(kProps), enumerator);↩
}↩
*/
STDMETHODIMP CArchiveUpdateCallback::GetUpdateItemInfo(UInt32 index,↩
Int32 *newData, Int32 *newProps, UInt32 *indexInArchive)↩
{↩
COM_TRY_BEGIN↩
RINOK(Callback->CheckBreak());↩
const CUpdatePair2 &up = (*UpdatePairs)[index];↩
if (newData) *newData = BoolToInt(up.NewData);↩
if (newProps) *newProps = BoolToInt(up.NewProps);↩
if (indexInArchive)↩
{↩
*indexInArchive = (UInt32)(Int32)-1;↩
if (up.ExistInArchive())↩
*indexInArchive = (ArcItems == 0) ? up.ArcIndex : (*ArcItems)[up.ArcIndex].IndexInServer;↩
}↩
return S_OK;↩
COM_TRY_END↩
}↩
STDMETHODIMP CArchiveUpdateCallback::GetRootProp(PROPID propID, PROPVARIANT *value)↩
{↩
NCOM::CPropVariant prop;↩
switch (propID)↩
{↩
case kpidIsDir: prop = true; break;↩
case kpidAttrib: if (ParentDirItem) prop = ParentDirItem->Attrib; break;↩
case kpidCTime: if (ParentDirItem) prop = ParentDirItem->CTime; break;↩
case kpidATime: if (ParentDirItem) prop = ParentDirItem->ATime; break;↩
case kpidMTime: if (ParentDirItem) prop = ParentDirItem->MTime; break;↩
}↩
prop.Detach(value);↩
return S_OK;↩
}↩
STDMETHODIMP CArchiveUpdateCallback::GetParent(UInt32 /* index */, UInt32 *parent, UInt32 *parentType)↩
{↩
*parentType = NParentType::kDir;↩
*parent = (UInt32)(Int32)-1;↩
return S_OK;↩
}↩
STDMETHODIMP CArchiveUpdateCallback::GetNumRawProps(UInt32 *numProps)↩
{↩
*numProps = 0;↩
if (StoreNtSecurity)↩
*numProps = 1;↩
return S_OK;↩
}↩
STDMETHODIMP CArchiveUpdateCallback::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID)↩
{↩
*name = NULL;↩
*propID = kpidNtSecure;↩
return S_OK;↩
}↩
STDMETHODIMP CArchiveUpdateCallback::GetRootRawProp(PROPID↩
#ifdef _USE_SECURITY_CODE↩
propID↩
#endif
, const void **data, UInt32 *dataSize, UInt32 *propType)↩
{↩
*data = 0;↩
*dataSize = 0;↩
*propType = 0;↩
if (!StoreNtSecurity)↩
return S_OK;↩
#ifdef _USE_SECURITY_CODE↩
if (propID == kpidNtSecure)↩
{↩
if (StdInMode)↩
return S_OK;↩
if (ParentDirItem)↩
{↩
if (ParentDirItem->SecureIndex < 0)↩
return S_OK;↩
const CByteBuffer &buf = DirItems->SecureBlocks.Bufs[ParentDirItem->SecureIndex];↩
*data = buf;↩
*dataSize = (UInt32)buf.Size();↩
*propType = NPropDataType::kRaw;↩
return S_OK;↩
}↩
if (Arc && Arc->GetRootProps)↩
return Arc->GetRootProps->GetRootRawProp(propID, data, dataSize, propType);↩
}↩
#endif
return S_OK;↩
}↩
// #ifdef _USE_SECURITY_CODE↩
// #endif↩
STDMETHODIMP CArchiveUpdateCallback::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)↩
{↩
*data = 0;↩
*dataSize = 0;↩
*propType = 0;↩
if (propID == kpidNtSecure ||↩
propID == kpidNtReparse)↩
{↩
if (StdInMode)↩
return S_OK;↩
const CUpdatePair2 &up = (*UpdatePairs)[index];↩
if (up.UseArcProps && up.ExistInArchive() && Arc->GetRawProps)↩
return Arc->GetRawProps->GetRawProp(↩
ArcItems ? (*ArcItems)[up.ArcIndex].IndexInServer : up.ArcIndex,↩
propID, data, dataSize, propType);↩
{↩
/*↩
if (!up.NewData)↩
return E_FAIL;↩
*/
if (up.IsAnti)↩
return S_OK;↩
#ifndef UNDER_CE↩
const CDirItem &di = DirItems->Items[up.DirIndex];↩
#endif
#ifdef _USE_SECURITY_CODE↩
if (propID == kpidNtSecure)↩
{↩
if (!StoreNtSecurity)↩
return S_OK;↩
if (di.SecureIndex < 0)↩
return S_OK;↩
const CByteBuffer &buf = DirItems->SecureBlocks.Bufs[di.SecureIndex];↩
*data = buf;↩
*dataSize = (UInt32)buf.Size();↩
*propType = NPropDataType::kRaw;↩
}↩
else
#endif
{↩
// propID == kpidNtReparse↩
if (!StoreSymLinks)↩
return S_OK;↩
#ifndef UNDER_CE↩
const CByteBuffer *buf = &di.ReparseData2;↩
if (buf->Size() == 0)↩
buf = &di.ReparseData;↩
if (buf->Size() != 0)↩
{↩
*data = *buf;↩
*dataSize = (UInt32)buf->Size();↩
*propType = NPropDataType::kRaw;↩
}↩
#endif
}↩
return S_OK;↩
}↩
}↩
return S_OK;↩
}↩
#ifndef UNDER_CE↩
static UString GetRelativePath(const UString &to, const UString &from)↩
{↩
UStringVector partsTo, partsFrom;↩
SplitPathToParts(to, partsTo);↩
SplitPathToParts(from, partsFrom);↩
unsigned i;↩
for (i = 0;; i++)↩
{↩
if (i + 1 >= partsFrom.Size() ||↩
i + 1 >= partsTo.Size())↩
break;↩
if (CompareFileNames(partsFrom[i], partsTo[i]) != 0)↩
break;↩
}↩
if (i == 0)↩
{↩
#ifdef _WIN32↩
if (NName::IsDrivePath(to) ||↩
NName::IsDrivePath(from))↩
return to;↩
#endif
}↩
UString s;↩
unsigned k;↩
for (k = i + 1; k < partsFrom.Size(); k++)↩
s += ".." STRING_PATH_SEPARATOR;↩
for (k = i; k < partsTo.Size(); k++)↩
{↩
if (k != i)↩
s.Add_PathSepar();↩
s += partsTo[k];↩
}↩
return s;↩
}↩
#endif
STDMETHODIMP CArchiveUpdateCallback::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)↩
{↩
COM_TRY_BEGIN↩
const CUpdatePair2 &up = (*UpdatePairs)[index];↩
NCOM::CPropVariant prop;↩
if (up.NewData)↩
{↩
/*↩
if (propID == kpidIsHardLink)↩
{↩
prop = _isHardLink;↩
prop.Detach(value);↩
return S_OK;↩
}↩
*/
if (propID == kpidSymLink)↩
{↩
if (index == _hardIndex_From)↩
{↩
prop.Detach(value);↩
return S_OK;↩
}↩
if (up.DirIndex >= 0)↩
{↩
#ifndef UNDER_CE↩
const CDirItem &di = DirItems->Items[up.DirIndex];↩
// if (di.IsDir())↩
{↩
CReparseAttr attr;↩
DWORD errorCode = 0;↩
if (attr.Parse(di.ReparseData, di.ReparseData.Size(), errorCode))↩
{↩
UString simpleName = attr.GetPath();↩
if (attr.IsRelative())↩
prop = simpleName;↩
else
{↩
const FString phyPath = DirItems->GetPhyPath(up.DirIndex);↩
FString fullPath;↩
if (NDir::MyGetFullPathName(phyPath, fullPath))↩
{↩
prop = GetRelativePath(simpleName, fs2us(fullPath));↩
}↩
}↩
prop.Detach(value);↩
return S_OK;↩
}↩
}↩
#endif
}↩
}↩
else if (propID == kpidHardLink)↩
{↩
if (index == _hardIndex_From)↩
{↩
const CKeyKeyValPair &pair = _map[_hardIndex_To];↩
const CUpdatePair2 &up2 = (*UpdatePairs)[pair.Value];↩
prop = DirItems->GetLogPath(up2.DirIndex);↩
prop.Detach(value);↩
return S_OK;↩
}↩
if (up.DirIndex >= 0)↩
{↩
prop.Detach(value);↩
return S_OK;↩
}↩
}↩
}↩
if (up.IsAnti↩
&& propID != kpidIsDir↩
&& propID != kpidPath↩
&& propID != kpidIsAltStream)↩
{↩
switch (propID)↩
{↩
case kpidSize: prop = (UInt64)0; break;↩
case kpidIsAnti: prop = true; break;↩
}↩
}↩
else if (propID == kpidPath && up.NewNameIndex >= 0)↩
prop = (*NewNames)[up.NewNameIndex];↩
else if (propID == kpidComment↩
&& CommentIndex >= 0↩
&& (unsigned)CommentIndex == index↩
&& Comment)↩
prop = *Comment;↩
else if (propID == kpidShortName && up.NewNameIndex >= 0 && up.IsMainRenameItem)↩
{↩
// we can generate new ShortName here;↩
}↩
else if ((up.UseArcProps || (KeepOriginalItemNames && (propID == kpidPath || propID == kpidIsAltStream)))↩
&& up.ExistInArchive() && Archive)↩
return Archive->GetProperty(ArcItems ? (*ArcItems)[up.ArcIndex].IndexInServer : up.ArcIndex, propID, value);↩
else if (up.ExistOnDisk())↩
{↩
const CDirItem &di = DirItems->Items[up.DirIndex];↩
switch (propID)↩
{↩
case kpidPath: prop = DirItems->GetLogPath(up.DirIndex); break;↩
case kpidIsDir: prop = di.IsDir(); break;↩
case kpidSize: prop = di.IsDir() ? (UInt64)0 : di.Size; break;↩
case kpidAttrib: prop = di.Attrib; break;↩
case kpidCTime: prop = di.CTime; break;↩
case kpidATime: prop = di.ATime; break;↩
case kpidMTime: prop = di.MTime; break;↩
case kpidIsAltStream: prop = di.IsAltStream; break;↩
#if defined(_WIN32) && !defined(UNDER_CE)↩
// case kpidShortName: prop = di.ShortName; break;↩
#endif
}↩
}↩
prop.Detach(value);↩
return S_OK;↩
COM_TRY_END↩
}↩
#ifndef _7ZIP_ST↩
static NSynchronization::CCriticalSection CS;↩
#endif
STDMETHODIMP CArchiveUpdateCallback::GetStream2(UInt32 index, ISequentialInStream **inStream, UInt32 mode)↩
{↩
COM_TRY_BEGIN↩
*inStream = NULL;↩
const CUpdatePair2 &up = (*UpdatePairs)[index];↩
if (!up.NewData)↩
return E_FAIL;↩
RINOK(Callback->CheckBreak());↩
// RINOK(Callback->Finalize());↩
bool isDir = IsDir(up);↩
if (up.IsAnti)↩
{↩
UString name;↩
if (up.ArcIndex >= 0)↩
name = (*ArcItems)[up.ArcIndex].Name;↩
else if (up.DirIndex >= 0)↩
name = DirItems->GetLogPath(up.DirIndex);↩
RINOK(Callback->GetStream(name, isDir, true, mode));↩
/* 9.33: fixed. Handlers expect real stream object for files, even for anti-file.↩
so we return empty stream */
if (!isDir)↩
{↩
CBufInStream *inStreamSpec = new CBufInStream();↩
CMyComPtr<ISequentialInStream> inStreamLoc = inStreamSpec;↩
inStreamSpec->Init(NULL, 0);↩
*inStream = inStreamLoc.Detach();↩
}↩
return S_OK;↩
}↩
RINOK(Callback->GetStream(DirItems->GetLogPath(up.DirIndex), isDir, false, mode));↩
if (isDir)↩
return S_OK;↩
if (StdInMode)↩
{↩
if (mode != NUpdateNotifyOp::kAdd &&↩
mode != NUpdateNotifyOp::kUpdate)↩
return S_OK;↩
CStdInFileStream *inStreamSpec = new CStdInFileStream;↩
CMyComPtr<ISequentialInStream> inStreamLoc(inStreamSpec);↩
*inStream = inStreamLoc.Detach();↩
}↩
else
{↩
CInFileStream *inStreamSpec = new CInFileStream;↩
CMyComPtr<ISequentialInStream> inStreamLoc(inStreamSpec);↩
inStreamSpec->SupportHardLinks = StoreHardLinks;↩
inStreamSpec->Callback = this;↩
inStreamSpec->CallbackRef = index;↩
const FString path = DirItems->GetPhyPath(up.DirIndex);↩
_openFiles_Indexes.Add(index);↩
_openFiles_Paths.Add(path);↩
#if defined(_WIN32) && !defined(UNDER_CE)↩
if (DirItems->Items[up.DirIndex].AreReparseData())↩
{↩
if (!inStreamSpec->File.OpenReparse(path))↩
{↩
return Callback->OpenFileError(path, ::GetLastError());↩
}↩
}↩
else
#endif
if (!inStreamSpec->OpenShared(path, ShareForWrite))↩
{↩
DWORD error = ::GetLastError();↩
HRESULT hres = Callback->OpenFileError(path, error);↩
if (StopAfterOpenError)↩
if (hres == S_OK || hres == S_FALSE)↩
return HRESULT_FROM_WIN32(error);↩
return hres;↩
}↩
if (StoreHardLinks)↩
{↩
CStreamFileProps props;↩
if (inStreamSpec->GetProps2(&props) == S_OK)↩
{↩
if (props.NumLinks > 1)↩
{↩
CKeyKeyValPair pair;↩
pair.Key1 = props.VolID;↩
pair.Key2 = props.FileID_Low;↩
pair.Value = index;↩
unsigned numItems = _map.Size();↩
unsigned pairIndex = _map.AddToUniqueSorted2(pair);↩
if (numItems == _map.Size())↩
{↩
// const CKeyKeyValPair &pair2 = _map.Pairs[pairIndex];↩
_hardIndex_From = index;↩
_hardIndex_To = pairIndex;↩
// we could return NULL as stream, but it's better to return real stream↩
// return S_OK;↩
}↩
}↩
}↩
}↩
if (ProcessedItemsStatuses)↩
{↩
#ifndef _7ZIP_ST↩
NSynchronization::CCriticalSectionLock lock(CS);↩
#endif
ProcessedItemsStatuses[(unsigned)up.DirIndex] = 1;↩
}↩
*inStream = inStreamLoc.Detach();↩
}↩
return S_OK;↩
COM_TRY_END↩
}↩
STDMETHODIMP CArchiveUpdateCallback::SetOperationResult(Int32 opRes)↩
{↩
COM_TRY_BEGIN↩
return Callback->SetOperationResult(opRes);↩
COM_TRY_END↩
}↩
STDMETHODIMP CArchiveUpdateCallback::GetStream(UInt32 index, ISequentialInStream **inStream)↩
{↩
COM_TRY_BEGIN↩
return GetStream2(index, inStream,↩
(*UpdatePairs)[index].ArcIndex < 0 ?↩
NUpdateNotifyOp::kAdd :↩
NUpdateNotifyOp::kUpdate);↩
COM_TRY_END↩
}↩
STDMETHODIMP CArchiveUpdateCallback::ReportOperation(UInt32 indexType, UInt32 index, UInt32 op)↩
{↩
COM_TRY_BEGIN↩
bool isDir = false;↩
if (indexType == NArchive::NEventIndexType::kOutArcIndex)↩
{↩
UString name;↩
if (index != (UInt32)(Int32)-1)↩
{↩
const CUpdatePair2 &up = (*UpdatePairs)[index];↩
if (up.ExistOnDisk())↩
{↩
name = DirItems->GetLogPath(up.DirIndex);↩
isDir = DirItems->Items[up.DirIndex].IsDir();↩
}↩
}↩
return Callback->ReportUpdateOpeartion(op, name.IsEmpty() ? NULL : name.Ptr(), isDir);↩
}↩
wchar_t temp[16];↩
UString s2;↩
const wchar_t *s = NULL;↩
if (indexType == NArchive::NEventIndexType::kInArcIndex)↩
{↩
if (index != (UInt32)(Int32)-1)↩
{↩
if (ArcItems)↩
{↩
const CArcItem &ai = (*ArcItems)[index];↩
s = ai.Name;↩
isDir = ai.IsDir;↩
}↩
else if (Arc)↩
{↩
RINOK(Arc->GetItemPath(index, s2));↩
s = s2;↩
RINOK(Archive_IsItem_Dir(Arc->Archive, index, isDir));↩
}↩
}↩
}↩
else if (indexType == NArchive::NEventIndexType::kBlockIndex)↩
{↩
temp[0] = '#';↩
ConvertUInt32ToString(index, temp + 1);↩
s = temp;↩
}↩
if (!s)↩
s = L"";↩
return Callback->ReportUpdateOpeartion(op, s, isDir);↩
COM_TRY_END↩
}↩
STDMETHODIMP CArchiveUpdateCallback::ReportExtractResult(UInt32 indexType, UInt32 index, Int32 opRes)↩
{↩
COM_TRY_BEGIN↩
bool isEncrypted = false;↩
wchar_t temp[16];↩
UString s2;↩
const wchar_t *s = NULL;↩
if (indexType == NArchive::NEventIndexType::kOutArcIndex)↩
{↩
/*↩
UString name;↩
if (index != (UInt32)(Int32)-1)↩
{↩
const CUpdatePair2 &up = (*UpdatePairs)[index];↩
if (up.ExistOnDisk())↩
{↩
s2 = DirItems->GetLogPath(up.DirIndex);↩
s = s2;↩
}↩
}↩
*/
return E_FAIL;↩
}↩
if (indexType == NArchive::NEventIndexType::kInArcIndex)↩
{↩
if (index != (UInt32)(Int32)-1)↩
{↩
if (ArcItems)↩
s = (*ArcItems)[index].Name;↩
else if (Arc)↩
{↩
RINOK(Arc->GetItemPath(index, s2));↩
s = s2;↩
}↩
if (Archive)↩
{↩
RINOK(Archive_GetItemBoolProp(Archive, index, kpidEncrypted, isEncrypted));↩
}↩
}↩
}↩
else if (indexType == NArchive::NEventIndexType::kBlockIndex)↩
{↩
temp[0] = '#';↩
ConvertUInt32ToString(index, temp + 1);↩
s = temp;↩
}↩
return Callback->ReportExtractResult(opRes, BoolToInt(isEncrypted), s);↩
COM_TRY_END↩
}↩
STDMETHODIMP CArchiveUpdateCallback::GetVolumeSize(UInt32 index, UInt64 *size)↩
{↩
if (VolumesSizes.Size() == 0)↩
return S_FALSE;↩
if (index >= (UInt32)VolumesSizes.Size())↩
index = VolumesSizes.Size() - 1;↩
*size = VolumesSizes[index];↩
return S_OK;↩
}↩
STDMETHODIMP CArchiveUpdateCallback::GetVolumeStream(UInt32 index, ISequentialOutStream **volumeStream)↩
{↩
COM_TRY_BEGIN↩
char temp[16];↩
ConvertUInt32ToString(index + 1, temp);↩
FString res (temp);↩
while (res.Len() < 2)↩
res.InsertAtFront(FTEXT('0'));↩
FString fileName = VolName;↩
fileName += '.';↩
fileName += res;↩
fileName += VolExt;↩
COutFileStream *streamSpec = new COutFileStream;↩
CMyComPtr<ISequentialOutStream> streamLoc(streamSpec);↩
if (!streamSpec->Create(fileName, false))↩
return ::GetLastError();↩
*volumeStream = streamLoc.Detach();↩
return S_OK;↩
COM_TRY_END↩
}↩
STDMETHODIMP CArchiveUpdateCallback::CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password)↩
{↩
COM_TRY_BEGIN↩
return Callback->CryptoGetTextPassword2(passwordIsDefined, password);↩
COM_TRY_END↩
}↩
STDMETHODIMP CArchiveUpdateCallback::CryptoGetTextPassword(BSTR *password)↩
{↩
COM_TRY_BEGIN↩
return Callback->CryptoGetTextPassword(password);↩
COM_TRY_END↩
}↩
HRESULT CArchiveUpdateCallback::InFileStream_On_Error(UINT_PTR val, DWORD error)↩
{↩
if (error == ERROR_LOCK_VIOLATION)↩
{↩
MT_LOCK↩
UInt32 index = (UInt32)val;↩
FOR_VECTOR(i, _openFiles_Indexes)↩
{↩
if (_openFiles_Indexes[i] == index)↩
{↩
RINOK(Callback->ReadingFileError(_openFiles_Paths[i], error));↩
break;↩
}↩
}↩
}↩
return HRESULT_FROM_WIN32(error);↩
}↩
void CArchiveUpdateCallback::InFileStream_On_Destroy(UINT_PTR val)↩
{↩
MT_LOCK↩
UInt32 index = (UInt32)val;↩
FOR_VECTOR(i, _openFiles_Indexes)↩
{↩
if (_openFiles_Indexes[i] == index)↩
{↩
_openFiles_Indexes.Delete(i);↩
_openFiles_Paths.Delete(i);↩
return;↩
}↩
}↩
throw 20141125;↩
}↩