mirror of
https://github.com/Xevion/easy7zip.git
synced 2026-01-31 22:24:08 -06:00
4.59 beta
This commit is contained in:
committed by
Kornel Lesiński
parent
3901bf0ab8
commit
173c07e166
@@ -30,13 +30,20 @@ using namespace NSynchronization;
|
||||
namespace NArchive {
|
||||
namespace NZip {
|
||||
|
||||
static const Byte kMadeByHostOS = NFileHeader::NHostOS::kFAT;
|
||||
static const Byte kExtractHostOS = NFileHeader::NHostOS::kFAT;
|
||||
static const Byte kHostOS =
|
||||
#ifdef _WIN32
|
||||
NFileHeader::NHostOS::kFAT;
|
||||
#else
|
||||
NFileHeader::NHostOS::kUnix;
|
||||
#endif
|
||||
|
||||
static const Byte kMadeByHostOS = kHostOS;
|
||||
static const Byte kExtractHostOS = kHostOS;
|
||||
|
||||
static const Byte kMethodForDirectory = NFileHeader::NCompressionMethod::kStored;
|
||||
static const Byte kExtractVersionForDirectory = NFileHeader::NCompressionMethod::kStoreExtractVersion;
|
||||
|
||||
static HRESULT CopyBlockToArchive(ISequentialInStream *inStream,
|
||||
static HRESULT CopyBlockToArchive(ISequentialInStream *inStream,
|
||||
COutArchive &outArchive, ICompressProgressInfo *progress)
|
||||
{
|
||||
CMyComPtr<ISequentialOutStream> outStream;
|
||||
@@ -45,7 +52,7 @@ static HRESULT CopyBlockToArchive(ISequentialInStream *inStream,
|
||||
return copyCoder->Code(inStream, outStream, NULL, NULL, progress);
|
||||
}
|
||||
|
||||
static HRESULT WriteRange(IInStream *inStream, COutArchive &outArchive,
|
||||
static HRESULT WriteRange(IInStream *inStream, COutArchive &outArchive,
|
||||
const CUpdateRange &range, ICompressProgressInfo *progress)
|
||||
{
|
||||
UInt64 position;
|
||||
@@ -61,30 +68,30 @@ static HRESULT WriteRange(IInStream *inStream, COutArchive &outArchive,
|
||||
}
|
||||
|
||||
static void SetFileHeader(
|
||||
COutArchive &archive,
|
||||
COutArchive &archive,
|
||||
const CCompressionMethodMode &options,
|
||||
const CUpdateItem &updateItem,
|
||||
const CUpdateItem &ui,
|
||||
CItem &item)
|
||||
{
|
||||
item.UnPackSize = updateItem.Size;
|
||||
bool isDirectory;
|
||||
item.UnPackSize = ui.Size;
|
||||
bool isDir;
|
||||
|
||||
item.ClearFlags();
|
||||
|
||||
if (updateItem.NewProperties)
|
||||
if (ui.NewProperties)
|
||||
{
|
||||
isDirectory = updateItem.IsDirectory;
|
||||
item.Name = updateItem.Name;
|
||||
item.SetUtf8(updateItem.IsUtf8);
|
||||
item.ExternalAttributes = updateItem.Attributes;
|
||||
item.Time = updateItem.Time;
|
||||
item.NtfsMTime = updateItem.NtfsMTime;
|
||||
item.NtfsATime = updateItem.NtfsATime;
|
||||
item.NtfsCTime = updateItem.NtfsCTime;
|
||||
item.NtfsTimeIsDefined = updateItem.NtfsTimeIsDefined;
|
||||
isDir = ui.IsDir;
|
||||
item.Name = ui.Name;
|
||||
item.SetUtf8(ui.IsUtf8);
|
||||
item.ExternalAttributes = ui.Attributes;
|
||||
item.Time = ui.Time;
|
||||
item.NtfsMTime = ui.NtfsMTime;
|
||||
item.NtfsATime = ui.NtfsATime;
|
||||
item.NtfsCTime = ui.NtfsCTime;
|
||||
item.NtfsTimeIsDefined = ui.NtfsTimeIsDefined;
|
||||
}
|
||||
else
|
||||
isDirectory = item.IsDirectory();
|
||||
isDir = item.IsDir();
|
||||
|
||||
item.LocalHeaderPosition = archive.GetCurrentPosition();
|
||||
item.MadeByVersion.HostOS = kMadeByHostOS;
|
||||
@@ -93,8 +100,8 @@ static void SetFileHeader(
|
||||
item.ExtractVersion.HostOS = kExtractHostOS;
|
||||
|
||||
item.InternalAttributes = 0; // test it
|
||||
item.SetEncrypted(!isDirectory && options.PasswordIsDefined);
|
||||
if (isDirectory)
|
||||
item.SetEncrypted(!isDir && options.PasswordIsDefined);
|
||||
if (isDir)
|
||||
{
|
||||
item.ExtractVersion.Version = kExtractVersionForDirectory;
|
||||
item.CompressionMethod = kMethodForDirectory;
|
||||
@@ -103,7 +110,7 @@ static void SetFileHeader(
|
||||
}
|
||||
}
|
||||
|
||||
static void SetItemInfoFromCompressingResult(const CCompressingResult &compressingResult,
|
||||
static void SetItemInfoFromCompressingResult(const CCompressingResult &compressingResult,
|
||||
bool isAesMode, Byte aesKeyMode, CItem &item)
|
||||
{
|
||||
item.ExtractVersion.Version = compressingResult.ExtractVersion;
|
||||
@@ -196,7 +203,7 @@ void CThreadInfo::WaitAndCode()
|
||||
return;
|
||||
Result = Coder.Compress(
|
||||
#ifdef EXTERNAL_CODECS
|
||||
_codecsInfo, _externalCodecs,
|
||||
_codecsInfo, _externalCodecs,
|
||||
#endif
|
||||
InStream, OutStream, Progress, CompressingResult);
|
||||
if (Result == S_OK && Progress)
|
||||
@@ -289,7 +296,7 @@ HRESULT CMtProgressMixer2::SetRatioInfo(int index, const UInt64 *inSize, const U
|
||||
InSizes[index] = *inSize;
|
||||
if (outSize != 0)
|
||||
OutSizes[index] = *outSize;
|
||||
UInt64 v = ProgressOffset + (_inSizeIsMain ?
|
||||
UInt64 v = ProgressOffset + (_inSizeIsMain ?
|
||||
(InSizes[0] + InSizes[1]) :
|
||||
(OutSizes[0] + OutSizes[1]));
|
||||
return Progress->SetCompleted(&v);
|
||||
@@ -300,7 +307,7 @@ STDMETHODIMP CMtProgressMixer2::SetRatioInfo(const UInt64 *inSize, const UInt64
|
||||
return SetRatioInfo(0, inSize, outSize);
|
||||
}
|
||||
|
||||
class CMtProgressMixer:
|
||||
class CMtProgressMixer:
|
||||
public ICompressProgressInfo,
|
||||
public CMyUnknownImp
|
||||
{
|
||||
@@ -328,14 +335,14 @@ STDMETHODIMP CMtProgressMixer::SetRatioInfo(const UInt64 *inSize, const UInt64 *
|
||||
#endif
|
||||
|
||||
|
||||
static HRESULT UpdateItemOldData(COutArchive &archive,
|
||||
static HRESULT UpdateItemOldData(COutArchive &archive,
|
||||
IInStream *inStream,
|
||||
const CUpdateItem &updateItem, CItemEx &item,
|
||||
const CUpdateItem &ui, CItemEx &item,
|
||||
/* bool izZip64, */
|
||||
ICompressProgressInfo *progress,
|
||||
UInt64 &complexity)
|
||||
{
|
||||
if (updateItem.NewProperties)
|
||||
if (ui.NewProperties)
|
||||
{
|
||||
if (item.HasDescriptor())
|
||||
return E_NOTIMPL;
|
||||
@@ -344,15 +351,15 @@ static HRESULT UpdateItemOldData(COutArchive &archive,
|
||||
// CUpdateRange range(item.GetLocalExtraPosition(), item.LocalExtraSize + item.PackSize);
|
||||
CUpdateRange range(item.GetDataPosition(), item.PackSize);
|
||||
|
||||
// item.ExternalAttributes = updateItem.Attributes;
|
||||
// item.ExternalAttributes = ui.Attributes;
|
||||
// Test it
|
||||
item.Name = updateItem.Name;
|
||||
item.SetUtf8(updateItem.IsUtf8);
|
||||
item.Time = updateItem.Time;
|
||||
item.NtfsMTime = updateItem.NtfsMTime;
|
||||
item.NtfsATime = updateItem.NtfsATime;
|
||||
item.NtfsCTime = updateItem.NtfsCTime;
|
||||
item.NtfsTimeIsDefined = updateItem.NtfsTimeIsDefined;
|
||||
item.Name = ui.Name;
|
||||
item.SetUtf8(ui.IsUtf8);
|
||||
item.Time = ui.Time;
|
||||
item.NtfsMTime = ui.NtfsMTime;
|
||||
item.NtfsATime = ui.NtfsATime;
|
||||
item.NtfsCTime = ui.NtfsCTime;
|
||||
item.NtfsTimeIsDefined = ui.NtfsTimeIsDefined;
|
||||
|
||||
item.CentralExtra.RemoveUnknownSubBlocks();
|
||||
item.LocalExtra.RemoveUnknownSubBlocks();
|
||||
@@ -369,7 +376,7 @@ static HRESULT UpdateItemOldData(COutArchive &archive,
|
||||
CUpdateRange range(item.LocalHeaderPosition, item.GetLocalFullSize());
|
||||
|
||||
// set new header position
|
||||
item.LocalHeaderPosition = archive.GetCurrentPosition();
|
||||
item.LocalHeaderPosition = archive.GetCurrentPosition();
|
||||
|
||||
RINOK(WriteRange(inStream, archive, range, progress));
|
||||
complexity += range.Size;
|
||||
@@ -379,21 +386,21 @@ static HRESULT UpdateItemOldData(COutArchive &archive,
|
||||
}
|
||||
|
||||
static void WriteDirHeader(COutArchive &archive, const CCompressionMethodMode *options,
|
||||
const CUpdateItem &updateItem, CItemEx &item)
|
||||
const CUpdateItem &ui, CItemEx &item)
|
||||
{
|
||||
SetFileHeader(archive, *options, updateItem, item);
|
||||
archive.PrepareWriteCompressedData((UInt16)item.Name.Length(), updateItem.Size, options->IsAesMode);
|
||||
SetFileHeader(archive, *options, ui, item);
|
||||
archive.PrepareWriteCompressedData((UInt16)item.Name.Length(), ui.Size, options->IsAesMode);
|
||||
archive.WriteLocalHeader(item);
|
||||
}
|
||||
|
||||
static HRESULT Update2St(
|
||||
DECL_EXTERNAL_CODECS_LOC_VARS
|
||||
COutArchive &archive,
|
||||
COutArchive &archive,
|
||||
CInArchive *inArchive,
|
||||
IInStream *inStream,
|
||||
const CObjectVector<CItemEx> &inputItems,
|
||||
const CObjectVector<CUpdateItem> &updateItems,
|
||||
const CCompressionMethodMode *options,
|
||||
const CCompressionMethodMode *options,
|
||||
const CByteBuffer &comment,
|
||||
IArchiveUpdateCallback *updateCallback)
|
||||
{
|
||||
@@ -411,37 +418,37 @@ static HRESULT Update2St(
|
||||
lps->InSize = unpackSizeTotal;
|
||||
lps->OutSize = packSizeTotal;
|
||||
RINOK(lps->SetCur());
|
||||
const CUpdateItem &updateItem = updateItems[itemIndex];
|
||||
const CUpdateItem &ui = updateItems[itemIndex];
|
||||
CItemEx item;
|
||||
if (!updateItem.NewProperties || !updateItem.NewData)
|
||||
if (!ui.NewProperties || !ui.NewData)
|
||||
{
|
||||
item = inputItems[updateItem.IndexInArchive];
|
||||
item = inputItems[ui.IndexInArchive];
|
||||
if (inArchive->ReadLocalItemAfterCdItemFull(item) != S_OK)
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
if (updateItem.NewData)
|
||||
if (ui.NewData)
|
||||
{
|
||||
bool isDirectory = ((updateItem.NewProperties) ? updateItem.IsDirectory : item.IsDirectory());
|
||||
if (isDirectory)
|
||||
bool isDir = ((ui.NewProperties) ? ui.IsDir : item.IsDir());
|
||||
if (isDir)
|
||||
{
|
||||
WriteDirHeader(archive, options, updateItem, item);
|
||||
WriteDirHeader(archive, options, ui, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
CMyComPtr<ISequentialInStream> fileInStream;
|
||||
HRESULT res = updateCallback->GetStream(updateItem.IndexInClient, &fileInStream);
|
||||
HRESULT res = updateCallback->GetStream(ui.IndexInClient, &fileInStream);
|
||||
if (res == S_FALSE)
|
||||
{
|
||||
lps->ProgressOffset += updateItem.Size;
|
||||
lps->ProgressOffset += ui.Size;
|
||||
RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK));
|
||||
continue;
|
||||
}
|
||||
RINOK(res);
|
||||
|
||||
// file Size can be 64-bit !!!
|
||||
SetFileHeader(archive, *options, updateItem, item);
|
||||
archive.PrepareWriteCompressedData((UInt16)item.Name.Length(), updateItem.Size, options->IsAesMode);
|
||||
SetFileHeader(archive, *options, ui, item);
|
||||
archive.PrepareWriteCompressedData((UInt16)item.Name.Length(), ui.Size, options->IsAesMode);
|
||||
CCompressingResult compressingResult;
|
||||
CMyComPtr<IOutStream> outStream;
|
||||
archive.CreateStreamForCompressing(&outStream);
|
||||
@@ -459,7 +466,7 @@ static HRESULT Update2St(
|
||||
{
|
||||
UInt64 complexity = 0;
|
||||
lps->SendRatio = false;
|
||||
RINOK(UpdateItemOldData(archive, inStream, updateItem, item, progress, complexity));
|
||||
RINOK(UpdateItemOldData(archive, inStream, ui, item, progress, complexity));
|
||||
lps->SendRatio = true;
|
||||
lps->ProgressOffset += complexity;
|
||||
}
|
||||
@@ -472,12 +479,12 @@ static HRESULT Update2St(
|
||||
|
||||
static HRESULT Update2(
|
||||
DECL_EXTERNAL_CODECS_LOC_VARS
|
||||
COutArchive &archive,
|
||||
COutArchive &archive,
|
||||
CInArchive *inArchive,
|
||||
IInStream *inStream,
|
||||
const CObjectVector<CItemEx> &inputItems,
|
||||
const CObjectVector<CUpdateItem> &updateItems,
|
||||
const CCompressionMethodMode *options,
|
||||
const CCompressionMethodMode *options,
|
||||
const CByteBuffer &comment,
|
||||
IArchiveUpdateCallback *updateCallback)
|
||||
{
|
||||
@@ -488,20 +495,20 @@ static HRESULT Update2(
|
||||
int i;
|
||||
for(i = 0; i < updateItems.Size(); i++)
|
||||
{
|
||||
const CUpdateItem &updateItem = updateItems[i];
|
||||
if (updateItem.NewData)
|
||||
const CUpdateItem &ui = updateItems[i];
|
||||
if (ui.NewData)
|
||||
{
|
||||
complexity += updateItem.Size;
|
||||
numBytesToCompress += updateItem.Size;
|
||||
complexity += ui.Size;
|
||||
numBytesToCompress += ui.Size;
|
||||
numFilesToCompress++;
|
||||
/*
|
||||
if (updateItem.Commented)
|
||||
complexity += updateItem.CommentRange.Size;
|
||||
if (ui.Commented)
|
||||
complexity += ui.CommentRange.Size;
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
CItemEx inputItem = inputItems[updateItem.IndexInArchive];
|
||||
CItemEx inputItem = inputItems[ui.IndexInArchive];
|
||||
if (inArchive->ReadLocalItemAfterCdItemFull(inputItem) != S_OK)
|
||||
return E_NOTIMPL;
|
||||
complexity += inputItem.GetLocalFullSize();
|
||||
@@ -580,7 +587,7 @@ static HRESULT Update2(
|
||||
mtProgressMixerSpec->Create(updateCallback, true);
|
||||
|
||||
CMtCompressProgressMixer mtCompressProgressMixer;
|
||||
mtCompressProgressMixer.Init(numThreads, mtProgressMixerSpec->RatioProgress);
|
||||
mtCompressProgressMixer.Init(numThreads, mtProgressMixerSpec->RatioProgress);
|
||||
|
||||
CMemBlockManagerMt memManager(kBlockSize);
|
||||
CMemRefs refs(&memManager);
|
||||
@@ -625,30 +632,30 @@ static HRESULT Update2(
|
||||
{
|
||||
if ((UInt32)threadIndices.Size() < numThreads && mtItemIndex < updateItems.Size())
|
||||
{
|
||||
const CUpdateItem &updateItem = updateItems[mtItemIndex++];
|
||||
if (!updateItem.NewData)
|
||||
const CUpdateItem &ui = updateItems[mtItemIndex++];
|
||||
if (!ui.NewData)
|
||||
continue;
|
||||
CItemEx item;
|
||||
if (updateItem.NewProperties)
|
||||
if (ui.NewProperties)
|
||||
{
|
||||
if (updateItem.IsDirectory)
|
||||
if (ui.IsDir)
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
item = inputItems[updateItem.IndexInArchive];
|
||||
item = inputItems[ui.IndexInArchive];
|
||||
if (inArchive->ReadLocalItemAfterCdItemFull(item) != S_OK)
|
||||
return E_NOTIMPL;
|
||||
if (item.IsDirectory())
|
||||
if (item.IsDir())
|
||||
continue;
|
||||
}
|
||||
CMyComPtr<ISequentialInStream> fileInStream;
|
||||
{
|
||||
NWindows::NSynchronization::CCriticalSectionLock lock(mtProgressMixerSpec->Mixer2->CriticalSection);
|
||||
HRESULT res = updateCallback->GetStream(updateItem.IndexInClient, &fileInStream);
|
||||
HRESULT res = updateCallback->GetStream(ui.IndexInClient, &fileInStream);
|
||||
if (res == S_FALSE)
|
||||
{
|
||||
complexity += updateItem.Size;
|
||||
complexity += ui.Size;
|
||||
complexity += NFileHeader::kLocalBlockSize;
|
||||
mtProgressMixerSpec->Mixer2->SetProgressOffset(complexity);
|
||||
RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK));
|
||||
@@ -667,9 +674,9 @@ static HRESULT Update2(
|
||||
threadInfo.IsFree = false;
|
||||
threadInfo.InStream = fileInStream;
|
||||
|
||||
// !!!!! we must release ref before sending event
|
||||
// !!!!! we must release ref before sending event
|
||||
// BUG was here in v4.43 and v4.44. It could change ref counter in two threads in same time
|
||||
fileInStream.Release();
|
||||
fileInStream.Release();
|
||||
|
||||
threadInfo.OutStreamSpec->Init();
|
||||
threadInfo.ProgressSpec->Reinit();
|
||||
@@ -690,31 +697,31 @@ static HRESULT Update2(
|
||||
continue;
|
||||
}
|
||||
|
||||
const CUpdateItem &updateItem = updateItems[itemIndex];
|
||||
const CUpdateItem &ui = updateItems[itemIndex];
|
||||
|
||||
CItemEx item;
|
||||
if (!updateItem.NewProperties || !updateItem.NewData)
|
||||
if (!ui.NewProperties || !ui.NewData)
|
||||
{
|
||||
item = inputItems[updateItem.IndexInArchive];
|
||||
item = inputItems[ui.IndexInArchive];
|
||||
if (inArchive->ReadLocalItemAfterCdItemFull(item) != S_OK)
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
if (updateItem.NewData)
|
||||
if (ui.NewData)
|
||||
{
|
||||
bool isDirectory = ((updateItem.NewProperties) ? updateItem.IsDirectory : item.IsDirectory());
|
||||
if (isDirectory)
|
||||
bool isDir = ((ui.NewProperties) ? ui.IsDir : item.IsDir());
|
||||
if (isDir)
|
||||
{
|
||||
WriteDirHeader(archive, options, updateItem, item);
|
||||
WriteDirHeader(archive, options, ui, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastRealStreamItemIndex < itemIndex)
|
||||
{
|
||||
lastRealStreamItemIndex = itemIndex;
|
||||
SetFileHeader(archive, *options, updateItem, item);
|
||||
SetFileHeader(archive, *options, ui, item);
|
||||
// file Size can be 64-bit !!!
|
||||
archive.PrepareWriteCompressedData((UInt16)item.Name.Length(), updateItem.Size, options->IsAesMode);
|
||||
archive.PrepareWriteCompressedData((UInt16)item.Name.Length(), ui.Size, options->IsAesMode);
|
||||
}
|
||||
|
||||
CMemBlocks2 &memRef = refs.Refs[itemIndex];
|
||||
@@ -725,7 +732,7 @@ static HRESULT Update2(
|
||||
memRef.WriteToStream(memManager.GetBlockSize(), outStream);
|
||||
SetItemInfoFromCompressingResult(memRef.CompressingResult,
|
||||
options->IsAesMode, options->AesKeyMode, item);
|
||||
SetFileHeader(archive, *options, updateItem, item);
|
||||
SetFileHeader(archive, *options, ui, item);
|
||||
archive.WriteLocalHeader(item);
|
||||
// RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK));
|
||||
memRef.FreeOpt(&memManager);
|
||||
@@ -743,7 +750,7 @@ static HRESULT Update2(
|
||||
}
|
||||
}
|
||||
|
||||
DWORD result = ::WaitForMultipleObjects(compressingCompletedEvents.Size(),
|
||||
DWORD result = ::WaitForMultipleObjects(compressingCompletedEvents.Size(),
|
||||
&compressingCompletedEvents.Front(), FALSE, INFINITE);
|
||||
int t = (int)(result - WAIT_OBJECT_0);
|
||||
CThreadInfo &threadInfo = threads.Threads[threadIndices[t]];
|
||||
@@ -756,9 +763,9 @@ static HRESULT Update2(
|
||||
{
|
||||
RINOK(threadInfo.OutStreamSpec->WriteToRealStream());
|
||||
threadInfo.OutStreamSpec->ReleaseOutStream();
|
||||
SetItemInfoFromCompressingResult(threadInfo.CompressingResult,
|
||||
SetItemInfoFromCompressingResult(threadInfo.CompressingResult,
|
||||
options->IsAesMode, options->AesKeyMode, item);
|
||||
SetFileHeader(archive, *options, updateItem, item);
|
||||
SetFileHeader(archive, *options, ui, item);
|
||||
archive.WriteLocalHeader(item);
|
||||
}
|
||||
else
|
||||
@@ -774,7 +781,7 @@ static HRESULT Update2(
|
||||
}
|
||||
else
|
||||
{
|
||||
RINOK(UpdateItemOldData(archive, inStream, updateItem, item, progress, complexity));
|
||||
RINOK(UpdateItemOldData(archive, inStream, ui, item, progress, complexity));
|
||||
}
|
||||
items.Add(item);
|
||||
complexity += NFileHeader::kLocalBlockSize;
|
||||
@@ -783,10 +790,10 @@ static HRESULT Update2(
|
||||
}
|
||||
archive.WriteCentralDir(items, comment);
|
||||
return S_OK;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
HRESULT Update(
|
||||
HRESULT Update(
|
||||
DECL_EXTERNAL_CODECS_LOC_VARS
|
||||
const CObjectVector<CItemEx> &inputItems,
|
||||
const CObjectVector<CUpdateItem> &updateItems,
|
||||
@@ -825,9 +832,9 @@ HRESULT Update(
|
||||
|
||||
return Update2(
|
||||
EXTERNAL_CODECS_LOC_VARS
|
||||
outArchive, inArchive, inStream,
|
||||
inputItems, updateItems,
|
||||
compressionMethodMode,
|
||||
outArchive, inArchive, inStream,
|
||||
inputItems, updateItems,
|
||||
compressionMethodMode,
|
||||
archiveInfo.Comment, updateCallback);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user