| // MultiOutStream.cpp |
| |
| #include "StdAfx.h" |
| |
| // #define DEBUG_VOLUMES |
| |
| #ifdef DEBUG_VOLUMES |
| #include <stdio.h> |
| #define PRF(x) x; |
| #else |
| #define PRF(x) |
| #endif |
| |
| #include "../../Common/ComTry.h" |
| |
| #include "../../Windows/FileDir.h" |
| #include "../../Windows/FileFind.h" |
| #include "../../Windows/System.h" |
| |
| #include "MultiOutStream.h" |
| |
| using namespace NWindows; |
| using namespace NFile; |
| using namespace NDir; |
| |
| static const unsigned k_NumVols_MAX = k_VectorSizeMax - 1; |
| // 2; // for debug |
| |
| /* |
| #define UPDATE_HRES(hres, x) \ |
| { const HRESULT res2 = (x); if (hres == SZ_OK) hres = res2; } |
| */ |
| |
| HRESULT CMultiOutStream::Destruct() |
| { |
| COM_TRY_BEGIN |
| HRESULT hres = S_OK; |
| HRESULT hres3 = S_OK; |
| |
| while (!Streams.IsEmpty()) |
| { |
| try |
| { |
| HRESULT hres2; |
| if (NeedDelete) |
| { |
| /* we could call OptReOpen_and_SetSize() to test that we try to delete correct file, |
| but we cannot guarantee that (RealSize) will be correct after Write() or another failures. |
| And we still want to delete files even for such cases. |
| So we don't check for OptReOpen_and_SetSize() here: */ |
| // if (OptReOpen_and_SetSize(Streams.Size() - 1, 0) == S_OK) |
| hres2 = CloseStream_and_DeleteFile(Streams.Size() - 1); |
| } |
| else |
| { |
| hres2 = CloseStream(Streams.Size() - 1); |
| } |
| if (hres == S_OK) |
| hres = hres2; |
| } |
| catch(...) |
| { |
| hres3 = E_OUTOFMEMORY; |
| } |
| |
| { |
| /* Stream was released in CloseStream_*() above already, and it was removed from linked list |
| it's some unexpected case, if Stream is still attached here. |
| So the following code is optional: */ |
| CVolStream &s = Streams.Back(); |
| if (s.Stream) |
| { |
| if (hres3 == S_OK) |
| hres3 = E_FAIL; |
| s.Stream.Detach(); |
| /* it will be not failure, even if we call RemoveFromLinkedList() |
| twice for same CVolStream in this Destruct() function */ |
| RemoveFromLinkedList(Streams.Size() - 1); |
| } |
| } |
| Streams.DeleteBack(); |
| // Delete_LastStream_Records(); |
| } |
| |
| if (hres == S_OK) |
| hres = hres3; |
| if (hres == S_OK && NumListItems != 0) |
| hres = E_FAIL; |
| return hres; |
| COM_TRY_END |
| } |
| |
| |
| CMultiOutStream::~CMultiOutStream() |
| { |
| // we try to avoid exception in destructors |
| Destruct(); |
| } |
| |
| |
| void CMultiOutStream::Init(const CRecordVector<UInt64> &sizes) |
| { |
| Streams.Clear(); |
| InitLinkedList(); |
| Sizes = sizes; |
| NeedDelete = true; |
| MTime_Defined = false; |
| FinalVol_WasReopen = false; |
| NumOpenFiles_AllowedMax = NSystem::Get_File_OPEN_MAX_Reduced_for_3_tasks(); |
| |
| _streamIndex = 0; |
| _offsetPos = 0; |
| _absPos = 0; |
| _length = 0; |
| _absLimit = (UInt64)(Int64)-1; |
| |
| _restrict_Begin = 0; |
| _restrict_End = (UInt64)(Int64)-1; |
| _restrict_Global = 0; |
| |
| UInt64 sum = 0; |
| unsigned i = 0; |
| for (i = 0; i < Sizes.Size(); i++) |
| { |
| if (i >= k_NumVols_MAX) |
| { |
| _absLimit = sum; |
| break; |
| } |
| const UInt64 size = Sizes[i]; |
| const UInt64 next = sum + size; |
| if (next < sum) |
| break; |
| sum = next; |
| } |
| |
| // if (Sizes.IsEmpty()) throw "no volume sizes"; |
| const UInt64 size = Sizes.Back(); |
| if (size == 0) |
| throw "zero size last volume"; |
| |
| if (i == Sizes.Size()) |
| if ((_absLimit - sum) / size >= (k_NumVols_MAX - i)) |
| _absLimit = sum + (k_NumVols_MAX - i) * size; |
| } |
| |
| |
| /* IsRestricted(): |
| we must call only if volume is full (s.RealSize==VolSize) or finished. |
| the function doesn't use VolSize and it uses s.RealSize instead. |
| it returns true : if stream is restricted, and we can't close that stream |
| it returns false : if there is no restriction, and we can close that stream |
| Note: (RealSize == 0) (empty volume) on restriction bounds are supposed as non-restricted |
| */ |
| bool CMultiOutStream::IsRestricted(const CVolStream &s) const |
| { |
| if (s.Start < _restrict_Global) |
| return true; |
| if (_restrict_Begin == _restrict_End) |
| return false; |
| if (_restrict_Begin <= s.Start) |
| return _restrict_End > s.Start; |
| return _restrict_Begin < s.Start + s.RealSize; |
| } |
| |
| /* |
| // this function check also _length and volSize |
| bool CMultiOutStream::IsRestricted_for_Close(unsigned index) const |
| { |
| const CVolStream &s = Streams[index]; |
| if (_length <= s.Start) // we don't close streams after the end, because we still can write them later |
| return true; |
| // (_length > s.Start) |
| const UInt64 volSize = GetVolSize_for_Stream(index); |
| if (volSize == 0) |
| return IsRestricted_Empty(s); |
| if (_length - s.Start < volSize) |
| return true; |
| return IsRestricted(s); |
| } |
| */ |
| |
| FString CMultiOutStream::GetFilePath(unsigned index) |
| { |
| FString name; |
| name.Add_UInt32(index + 1); |
| while (name.Len() < 3) |
| name.InsertAtFront(FTEXT('0')); |
| name.Insert(0, Prefix); |
| return name; |
| } |
| |
| |
| // we close stream, but we still keep item in Streams[] vector |
| HRESULT CMultiOutStream::CloseStream(unsigned index) |
| { |
| CVolStream &s = Streams[index]; |
| if (s.Stream) |
| { |
| RINOK(s.StreamSpec->Close()) |
| // the following two commands must be called together: |
| s.Stream.Release(); |
| RemoveFromLinkedList(index); |
| } |
| return S_OK; |
| } |
| |
| |
| // we close stream and delete file, but we still keep item in Streams[] vector |
| HRESULT CMultiOutStream::CloseStream_and_DeleteFile(unsigned index) |
| { |
| PRF(printf("\n====== %u, CloseStream_AndDelete \n", index)); |
| RINOK(CloseStream(index)) |
| FString path = GetFilePath(index); |
| path += Streams[index].Postfix; |
| // we can checki that file exist |
| // if (NFind::DoesFileExist_Raw(path)) |
| if (!DeleteFileAlways(path)) |
| return GetLastError_noZero_HRESULT(); |
| return S_OK; |
| } |
| |
| |
| HRESULT CMultiOutStream::CloseStream_and_FinalRename(unsigned index) |
| { |
| PRF(printf("\n====== %u, CloseStream_and_FinalRename \n", index)); |
| CVolStream &s = Streams[index]; |
| // HRESULT res = S_OK; |
| bool mtime_WasSet = false; |
| if (MTime_Defined && s.Stream) |
| { |
| if (s.StreamSpec->SetMTime(&MTime)) |
| mtime_WasSet = true; |
| // else res = GetLastError_noZero_HRESULT(); |
| } |
| |
| RINOK(CloseStream(index)) |
| if (s.Postfix.IsEmpty()) // if Postfix is empty, the path is already final |
| return S_OK; |
| const FString path = GetFilePath(index); |
| FString tempPath = path; |
| tempPath += s.Postfix; |
| |
| if (MTime_Defined && !mtime_WasSet) |
| { |
| if (!SetDirTime(tempPath, NULL, NULL, &MTime)) |
| { |
| // res = GetLastError_noZero_HRESULT(); |
| } |
| } |
| if (!MyMoveFile(tempPath, path)) |
| return GetLastError_noZero_HRESULT(); |
| /* we clear CVolStream::Postfix. So we will not use Temp path |
| anymore for this stream, and we will work only with final path */ |
| s.Postfix.Empty(); |
| // we can ignore set_mtime error or we can return it |
| return S_OK; |
| // return res; |
| } |
| |
| |
| HRESULT CMultiOutStream::PrepareToOpenNew() |
| { |
| if (NumListItems < NumOpenFiles_AllowedMax) |
| return S_OK; |
| /* when we create zip archive: in most cases we need only starting |
| data of restricted region for rewriting zip's local header. |
| So here we close latest created volume (from Head), and we try to |
| keep oldest volumes that will be used for header rewriting later. */ |
| const int index = Head; |
| if (index == -1) |
| return E_FAIL; |
| PRF(printf("\n== %u, PrepareToOpenNew::CloseStream, NumListItems =%u \n", index, NumListItems)); |
| /* we don't expect non-restricted stream here in normal cases (if _restrict_Global was not changed). |
| if there was non-restricted stream, it should be closed before */ |
| // if (!IsRestricted_for_Close(index)) return CloseStream_and_FinalRename(index); |
| return CloseStream((unsigned)index); |
| } |
| |
| |
| HRESULT CMultiOutStream::CreateNewStream(UInt64 newSize) |
| { |
| PRF(printf("\n== %u, CreateNewStream, size =%u \n", Streams.Size(), (unsigned)newSize)); |
| |
| if (Streams.Size() >= k_NumVols_MAX) |
| return E_INVALIDARG; // E_OUTOFMEMORY |
| |
| RINOK(PrepareToOpenNew()) |
| CVolStream s; |
| s.StreamSpec = new COutFileStream; |
| s.Stream = s.StreamSpec; |
| const FString path = GetFilePath(Streams.Size()); |
| |
| if (NFind::DoesFileExist_Raw(path)) |
| return HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); |
| if (!CreateTempFile2(path, false, s.Postfix, &s.StreamSpec->File)) |
| return GetLastError_noZero_HRESULT(); |
| |
| s.Start = GetGlobalOffset_for_NewStream(); |
| s.Pos = 0; |
| s.RealSize = 0; |
| |
| const unsigned index = Streams.Add(s); |
| InsertToLinkedList(index); |
| |
| if (newSize != 0) |
| return s.SetSize2(newSize); |
| return S_OK; |
| } |
| |
| |
| HRESULT CMultiOutStream::CreateStreams_If_Required(unsigned streamIndex) |
| { |
| // UInt64 lastStreamSize = 0; |
| for (;;) |
| { |
| const unsigned numStreamsBefore = Streams.Size(); |
| if (streamIndex < numStreamsBefore) |
| return S_OK; |
| UInt64 newSize; |
| if (streamIndex == numStreamsBefore) |
| { |
| // it's final volume that will be used for real writing. |
| /* SetSize(_offsetPos) is not required, |
| because the file Size will be set later by calling Seek() with Write() */ |
| newSize = 0; // lastStreamSize; |
| } |
| else |
| { |
| // it's intermediate volume. So we need full volume size |
| newSize = GetVolSize_for_Stream(numStreamsBefore); |
| } |
| |
| RINOK(CreateNewStream(newSize)) |
| |
| // optional check |
| if (numStreamsBefore + 1 != Streams.Size()) return E_FAIL; |
| |
| if (streamIndex != numStreamsBefore) |
| { |
| // it's intermediate volume. So we can close it, if it's non-restricted |
| bool isRestricted; |
| { |
| const CVolStream &s = Streams[numStreamsBefore]; |
| if (newSize == 0) |
| isRestricted = IsRestricted_Empty(s); |
| else |
| isRestricted = IsRestricted(s); |
| } |
| if (!isRestricted) |
| { |
| RINOK(CloseStream_and_FinalRename(numStreamsBefore)) |
| } |
| } |
| } |
| } |
| |
| |
| HRESULT CMultiOutStream::ReOpenStream(unsigned streamIndex) |
| { |
| PRF(printf("\n====== %u, ReOpenStream \n", streamIndex)); |
| RINOK(PrepareToOpenNew()) |
| CVolStream &s = Streams[streamIndex]; |
| |
| FString path = GetFilePath(streamIndex); |
| path += s.Postfix; |
| |
| s.StreamSpec = new COutFileStream; |
| s.Stream = s.StreamSpec; |
| s.Pos = 0; |
| |
| HRESULT hres; |
| if (s.StreamSpec->Open(path, OPEN_EXISTING)) |
| { |
| if (s.Postfix.IsEmpty()) |
| { |
| /* it's unexpected case that we open finished volume. |
| It can mean that the code for restriction is incorrect */ |
| FinalVol_WasReopen = true; |
| } |
| UInt64 realSize = 0; |
| hres = s.StreamSpec->GetSize(&realSize); |
| if (hres == S_OK) |
| { |
| if (realSize == s.RealSize) |
| { |
| InsertToLinkedList(streamIndex); |
| return S_OK; |
| } |
| // file size was changed between Close() and ReOpen() |
| // we must release Stream to be consistent with linked list |
| hres = E_FAIL; |
| } |
| } |
| else |
| hres = GetLastError_noZero_HRESULT(); |
| s.Stream.Release(); |
| s.StreamSpec = NULL; |
| return hres; |
| } |
| |
| |
| /* Sets size of stream, if new size is not equal to old size (RealSize). |
| If stream was closed and size change is required, it reopens the stream. */ |
| |
| HRESULT CMultiOutStream::OptReOpen_and_SetSize(unsigned index, UInt64 size) |
| { |
| CVolStream &s = Streams[index]; |
| if (size == s.RealSize) |
| return S_OK; |
| if (!s.Stream) |
| { |
| RINOK(ReOpenStream(index)) |
| } |
| PRF(printf("\n== %u, OptReOpen_and_SetSize, size =%u RealSize = %u\n", index, (unsigned)size, (unsigned)s.RealSize)); |
| // comment it to debug tail after data |
| return s.SetSize2(size); |
| } |
| |
| |
| /* |
| call Normalize_finalMode(false), if _length was changed. |
| for all streams starting after _length: |
| - it sets zero size |
| - it still keeps file open |
| Note: after _length reducing with CMultiOutStream::SetSize() we can |
| have very big number of empty streams at the end of Streams[] list. |
| And Normalize_finalMode() will runs all these empty streams of Streams[] vector. |
| So it can be ineffective, if we call Normalize_finalMode() many |
| times after big reducing of (_length). |
| |
| call Normalize_finalMode(true) to set final presentations of all streams |
| for all streams starting after _length: |
| - it sets zero size |
| - it removes file |
| - it removes CVolStream object from Streams[] vector |
| |
| Note: we don't remove zero sized first volume, if (_length == 0) |
| */ |
| |
| HRESULT CMultiOutStream::Normalize_finalMode(bool finalMode) |
| { |
| PRF(printf("\n== Normalize_finalMode: _length =%d \n", (unsigned)_length)); |
| |
| unsigned i = Streams.Size(); |
| |
| UInt64 offset = 0; |
| |
| /* At first we normalize (reduce or increase) the sizes of all existing |
| streams in Streams[] that can be affected by changed _length. |
| And we remove tailing zero-size streams, if (finalMode == true) */ |
| while (i != 0) |
| { |
| offset = Streams[--i].Start; // it's last item in Streams[] |
| // we don't want to remove first volume |
| if (offset < _length || i == 0) |
| { |
| const UInt64 volSize = GetVolSize_for_Stream(i); |
| UInt64 size = _length - offset; // (size != 0) here |
| if (size > volSize) |
| size = volSize; |
| RINOK(OptReOpen_and_SetSize(i, size)) |
| if (_length - offset <= volSize) |
| return S_OK; |
| // _length - offset > volSize |
| offset += volSize; |
| // _length > offset |
| break; |
| // UPDATE_HRES(res, OptReOpen_and_SetSize(i, size)); |
| } |
| |
| /* we Set Size of stream to zero even for (finalMode==true), although |
| that stream will be deleted in next commands */ |
| // UPDATE_HRES(res, OptReOpen_and_SetSize(i, 0)); |
| RINOK(OptReOpen_and_SetSize(i, 0)) |
| if (finalMode) |
| { |
| RINOK(CloseStream_and_DeleteFile(i)) |
| /* CVolStream::Stream was released above already, and it was |
| removed from linked list. So we don't need to update linked list |
| structure, when we delete last item in Streams[] */ |
| Streams.DeleteBack(); |
| // Delete_LastStream_Records(); |
| } |
| } |
| |
| /* now we create new zero-filled streams to cover all data up to _length */ |
| |
| if (_length == 0) |
| return S_OK; |
| |
| // (offset) is start offset of next stream after existing Streams[] |
| |
| for (;;) |
| { |
| // _length > offset |
| const UInt64 volSize = GetVolSize_for_Stream(Streams.Size()); |
| UInt64 size = _length - offset; // (size != 0) here |
| if (size > volSize) |
| size = volSize; |
| RINOK(CreateNewStream(size)) |
| if (_length - offset <= volSize) |
| return S_OK; |
| // _length - offset > volSize) |
| offset += volSize; |
| // _length > offset |
| } |
| } |
| |
| |
| HRESULT CMultiOutStream::FinalFlush_and_CloseFiles(unsigned &numTotalVolumesRes) |
| { |
| // at first we remove unused zero-sized streams after _length |
| HRESULT res = Normalize_finalMode(true); |
| numTotalVolumesRes = Streams.Size(); |
| FOR_VECTOR (i, Streams) |
| { |
| const HRESULT res2 = CloseStream_and_FinalRename(i); |
| if (res == S_OK) |
| res = res2; |
| } |
| if (NumListItems != 0 && res == S_OK) |
| res = E_FAIL; |
| return res; |
| } |
| |
| |
| bool CMultiOutStream::SetMTime_Final(const CFiTime &mTime) |
| { |
| // we will set mtime only if new value differs from previous |
| if (!FinalVol_WasReopen && MTime_Defined && Compare_FiTime(&MTime, &mTime) == 0) |
| return true; |
| bool res = true; |
| FOR_VECTOR (i, Streams) |
| { |
| CVolStream &s = Streams[i]; |
| if (s.Stream) |
| { |
| if (!s.StreamSpec->SetMTime(&mTime)) |
| res = false; |
| } |
| else |
| { |
| if (!SetDirTime(GetFilePath(i), NULL, NULL, &mTime)) |
| res = false; |
| } |
| } |
| return res; |
| } |
| |
| |
| Z7_COM7F_IMF(CMultiOutStream::SetSize(UInt64 newSize)) |
| { |
| COM_TRY_BEGIN |
| if ((Int64)newSize < 0) |
| return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; |
| if (newSize > _absLimit) |
| { |
| /* big seek value was sent to SetSize() or to Seek()+Write(). |
| It can mean one of two situations: |
| 1) some incorrect code called it with big seek value. |
| 2) volume size was small, and we have too big number of volumes |
| */ |
| /* in Windows SetEndOfFile() can return: |
| ERROR_NEGATIVE_SEEK: for >= (1 << 63) |
| ERROR_INVALID_PARAMETER: for > (16 TiB - 64 KiB) |
| ERROR_DISK_FULL: for <= (16 TiB - 64 KiB) |
| */ |
| // return E_FAIL; |
| // return E_OUTOFMEMORY; |
| return E_INVALIDARG; |
| } |
| |
| if (newSize > _length) |
| { |
| // we don't expect such case. So we just define global restriction */ |
| _restrict_Global = newSize; |
| } |
| else if (newSize < _restrict_Global) |
| _restrict_Global = newSize; |
| |
| PRF(printf("\n== SetSize, size =%u \n", (unsigned)newSize)); |
| |
| _length = newSize; |
| return Normalize_finalMode(false); |
| |
| COM_TRY_END |
| } |
| |
| |
| Z7_COM7F_IMF(CMultiOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) |
| { |
| COM_TRY_BEGIN |
| if (processedSize) |
| *processedSize = 0; |
| if (size == 0) |
| return S_OK; |
| |
| if (_absPos > _length) |
| { |
| // it create data only up to _absPos. |
| // but we still can need additional new streams, if _absPos at range of volume |
| RINOK(SetSize(_absPos)) |
| } |
| |
| while (size != 0) |
| { |
| UInt64 volSize; |
| { |
| if (_streamIndex < Sizes.Size() - 1) |
| { |
| volSize = Sizes[_streamIndex]; |
| if (_offsetPos >= volSize) |
| { |
| _offsetPos -= volSize; |
| _streamIndex++; |
| continue; |
| } |
| } |
| else |
| { |
| volSize = Sizes[Sizes.Size() - 1]; |
| if (_offsetPos >= volSize) |
| { |
| const UInt64 v = _offsetPos / volSize; |
| if (v >= ((UInt32)(Int32)-1) - _streamIndex) |
| return E_INVALIDARG; |
| // throw 202208; |
| _streamIndex += (unsigned)v; |
| _offsetPos -= (unsigned)v * volSize; |
| } |
| if (_streamIndex >= k_NumVols_MAX) |
| return E_INVALIDARG; |
| } |
| } |
| |
| // (_offsetPos < volSize) here |
| |
| /* we can need to create one or more streams here, |
| vol_size for some streams is allowed to be 0. |
| Also we close some new created streams, if they are non-restricted */ |
| // file Size will be set later by calling Seek() with Write() |
| |
| /* the case (_absPos > _length) was processed above with SetSize(_absPos), |
| so here it's expected. that we can create optional zero-size streams and then _streamIndex */ |
| RINOK(CreateStreams_If_Required(_streamIndex)) |
| |
| CVolStream &s = Streams[_streamIndex]; |
| |
| PRF(printf("\n%d, == Write : Pos = %u, RealSize = %u size =%u \n", |
| _streamIndex, (unsigned)s.Pos, (unsigned)s.RealSize, size)); |
| |
| if (!s.Stream) |
| { |
| RINOK(ReOpenStream(_streamIndex)) |
| } |
| if (_offsetPos != s.Pos) |
| { |
| RINOK(s.Stream->Seek((Int64)_offsetPos, STREAM_SEEK_SET, NULL)) |
| s.Pos = _offsetPos; |
| } |
| |
| UInt32 curSize = size; |
| { |
| const UInt64 rem = volSize - _offsetPos; |
| if (curSize > rem) |
| curSize = (UInt32)rem; |
| } |
| // curSize != 0 |
| UInt32 realProcessed = 0; |
| |
| HRESULT hres = s.Stream->Write(data, curSize, &realProcessed); |
| |
| data = (const void *)((const Byte *)data + realProcessed); |
| size -= realProcessed; |
| s.Pos += realProcessed; |
| _offsetPos += realProcessed; |
| _absPos += realProcessed; |
| if (_length < _absPos) |
| _length = _absPos; |
| if (s.RealSize < _offsetPos) |
| s.RealSize = _offsetPos; |
| if (processedSize) |
| *processedSize += realProcessed; |
| |
| if (s.Pos == volSize) |
| { |
| bool isRestricted; |
| if (volSize == 0) |
| isRestricted = IsRestricted_Empty(s); |
| else |
| isRestricted = IsRestricted(s); |
| if (!isRestricted) |
| { |
| const HRESULT res2 = CloseStream_and_FinalRename(_streamIndex); |
| if (hres == S_OK) |
| hres = res2; |
| } |
| _streamIndex++; |
| _offsetPos = 0; |
| } |
| |
| RINOK(hres) |
| if (realProcessed == 0 && curSize != 0) |
| return E_FAIL; |
| // break; |
| } |
| return S_OK; |
| COM_TRY_END |
| } |
| |
| |
| Z7_COM7F_IMF(CMultiOutStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) |
| { |
| PRF(printf("\n-- Seek seekOrigin=%u Seek =%u\n", seekOrigin, (unsigned)offset)); |
| |
| switch (seekOrigin) |
| { |
| case STREAM_SEEK_SET: break; |
| case STREAM_SEEK_CUR: offset += _absPos; break; |
| case STREAM_SEEK_END: offset += _length; break; |
| default: return STG_E_INVALIDFUNCTION; |
| } |
| if (offset < 0) |
| return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; |
| if ((UInt64)offset != _absPos) |
| { |
| _absPos = (UInt64)offset; |
| _offsetPos = (UInt64)offset; |
| _streamIndex = 0; |
| } |
| if (newPosition) |
| *newPosition = (UInt64)offset; |
| return S_OK; |
| } |
| |
| |
| // result value will be saturated to (UInt32)(Int32)-1 |
| |
| unsigned CMultiOutStream::GetStreamIndex_for_Offset(UInt64 offset, UInt64 &relOffset) const |
| { |
| const unsigned last = Sizes.Size() - 1; |
| for (unsigned i = 0; i < last; i++) |
| { |
| const UInt64 size = Sizes[i]; |
| if (offset < size) |
| { |
| relOffset = offset; |
| return i; |
| } |
| offset -= size; |
| } |
| const UInt64 size = Sizes[last]; |
| const UInt64 v = offset / size; |
| if (v >= ((UInt32)(Int32)-1) - last) |
| return (UInt32)(Int32)-1; // saturation |
| relOffset = offset - (unsigned)v * size; |
| return last + (unsigned)(v); |
| } |
| |
| |
| Z7_COM7F_IMF(CMultiOutStream::SetRestriction(UInt64 begin, UInt64 end)) |
| { |
| COM_TRY_BEGIN |
| |
| // begin = end = 0; // for debug |
| |
| PRF(printf("\n==================== CMultiOutStream::SetRestriction %u, %u\n", (unsigned)begin, (unsigned)end)); |
| if (begin > end) |
| { |
| // these value are FAILED values. |
| return E_FAIL; |
| // return E_INVALIDARG; |
| /* |
| // or we can ignore error with 3 ways: no change, non-restricted, saturation: |
| end = begin; // non-restricted |
| end = (UInt64)(Int64)-1; // saturation: |
| return S_OK; |
| */ |
| } |
| UInt64 b = _restrict_Begin; |
| UInt64 e = _restrict_End; |
| _restrict_Begin = begin; |
| _restrict_End = end; |
| |
| if (b == e) // if there were no restriction before |
| return S_OK; // no work to derestrict now. |
| |
| /* [b, e) is previous restricted region. So all volumes that |
| intersect that [b, e) region are candidats for derestriction */ |
| |
| if (begin != end) // if there is new non-empty restricted region |
| { |
| /* Now we will try to reduce or change (b) and (e) bounds |
| to reduce main loop that checks volumes for derestriction. |
| We still use one big derestriction region in main loop, although |
| in some cases we could have two smaller derestriction regions. |
| Also usually restriction region cannot move back from previous start position, |
| so (b <= begin) is expected here for normal cases */ |
| if (b == begin) // if same low bounds |
| b = end; // we need to derestrict only after the end of new restricted region |
| if (e == end) // if same high bounds |
| e = begin; // we need to derestrict only before the begin of new restricted region |
| } |
| |
| if (b > e) // || b == (UInt64)(Int64)-1 |
| return S_OK; |
| |
| /* Here we close finished volumes that are not restricted anymore. |
| We close (low number) volumes at first. */ |
| |
| UInt64 offset; |
| unsigned index = GetStreamIndex_for_Offset(b, offset); |
| |
| for (; index < Streams.Size(); index++) |
| { |
| { |
| const CVolStream &s = Streams[index]; |
| if (_length <= s.Start) |
| break; // we don't close streams after _length |
| // (_length > s.Start) |
| const UInt64 volSize = GetVolSize_for_Stream(index); |
| if (volSize == 0) |
| { |
| if (e < s.Start) |
| break; |
| // we don't close empty stream, if next byte [s.Start, s.Start] is restricted |
| if (IsRestricted_Empty(s)) |
| continue; |
| } |
| else |
| { |
| if (e <= s.Start) |
| break; |
| // we don't close non full streams |
| if (_length - s.Start < volSize) |
| break; |
| // (volSize == s.RealSize) is expected here. So no need to check it |
| // if (volSize != s.RealSize) break; |
| if (IsRestricted(s)) |
| continue; |
| } |
| } |
| RINOK(CloseStream_and_FinalRename(index)) |
| } |
| |
| return S_OK; |
| COM_TRY_END |
| } |