From a98326eb2954ac1e79f5eac28dbeab3ec15e047f Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Sat, 30 Jun 2018 12:43:08 +0200 Subject: 10.1 --- wx+/async_task.h | 6 +- wx+/grid.cpp | 4 +- wx+/http.cpp | 279 ------------------------------------------------ wx+/http.h | 49 --------- wx+/image_holder.h | 10 +- wx+/image_resources.cpp | 129 +++++----------------- wx+/image_tools.cpp | 2 +- wx+/zlib_wrap.h | 6 +- 8 files changed, 43 insertions(+), 442 deletions(-) delete mode 100755 wx+/http.cpp delete mode 100755 wx+/http.h (limited to 'wx+') diff --git a/wx+/async_task.h b/wx+/async_task.h index 1d64e262..8c2602ca 100755 --- a/wx+/async_task.h +++ b/wx+/async_task.h @@ -49,12 +49,12 @@ public: bool resultReady () const override { return isReady(asyncResult_); } void evaluateResult() override { - evalResult(IsSameType()); + evalResult(std::is_same()); } private: - void evalResult(FalseType /*void result type*/) { evalOnGui_(asyncResult_.get()); } - void evalResult(TrueType /*void result type*/) { asyncResult_.get(); evalOnGui_(); } + void evalResult(std::false_type /*void result type*/) { evalOnGui_(asyncResult_.get()); } + void evalResult(std::true_type /*void result type*/) { asyncResult_.get(); evalOnGui_(); } std::future asyncResult_; Fun evalOnGui_; //keep "evalOnGui" strictly separated from async thread: in particular do not copy in thread! diff --git a/wx+/grid.cpp b/wx+/grid.cpp index a1beee01..adc0615c 100755 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -169,12 +169,12 @@ wxSize GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring& if (alignment & wxALIGN_RIGHT) //note: wxALIGN_LEFT == 0! pt.x += rect.width - extentTrunc.GetWidth(); else if (alignment & wxALIGN_CENTER_HORIZONTAL) - pt.x += (rect.width - extentTrunc.GetWidth()) / 2; + pt.x += static_cast(std::floor((rect.width - extentTrunc.GetWidth()) / 2.0)); //round down negative values, too! if (alignment & wxALIGN_BOTTOM) //note: wxALIGN_TOP == 0! pt.y += rect.height - extentTrunc.GetHeight(); else if (alignment & wxALIGN_CENTER_VERTICAL) - pt.y += (rect.height - extentTrunc.GetHeight()) / 2; + pt.y += static_cast(std::floor((rect.height - extentTrunc.GetHeight()) / 2.0)); //round down negative values, too! RecursiveDcClipper clip(dc, rect); dc.DrawText(textTrunc, pt); diff --git a/wx+/http.cpp b/wx+/http.cpp deleted file mode 100755 index 1526d30f..00000000 --- a/wx+/http.cpp +++ /dev/null @@ -1,279 +0,0 @@ -// ***************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * -// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * -// ***************************************************************************** - -#include "http.h" - - #include - #include //std::thread::id - #include - -using namespace zen; - - -namespace -{ - -struct UrlRedirectError -{ - UrlRedirectError(const std::wstring& url) : newUrl(url) {} - std::wstring newUrl; -}; -} - - -class HttpInputStream::Impl -{ -public: - Impl(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO, //throw SysError, UrlRedirectError - const std::string* postParams) : //issue POST if bound, GET otherwise - notifyUnbufferedIO_(notifyUnbufferedIO) - { - ZEN_ON_SCOPE_FAIL( cleanup(); /*destructor call would lead to member double clean-up!!!*/ ); - - //assert(!startsWith(url, L"https:", CmpAsciiNoCase())); //not supported by wxHTTP! - - const std::wstring urlFmt = afterFirst(url, L"://", IF_MISSING_RETURN_NONE); - const std::wstring server = beforeFirst(urlFmt, L'/', IF_MISSING_RETURN_ALL); - const std::wstring page = L'/' + afterFirst(urlFmt, L'/', IF_MISSING_RETURN_NONE); - - assert(std::this_thread::get_id() == mainThreadId); - assert(wxApp::IsMainLoopRunning()); - - webAccess_.SetHeader(L"User-Agent", userAgent); - webAccess_.SetTimeout(10 /*[s]*/); //default: 10 minutes: WTF are these wxWidgets people thinking??? - - if (!webAccess_.Connect(server)) //will *not* fail for non-reachable url here! - throw SysError(L"wxHTTP::Connect"); - - if (postParams) - if (!webAccess_.SetPostText(L"application/x-www-form-urlencoded", utfTo(*postParams))) - throw SysError(L"wxHTTP::SetPostText"); - - httpStream_.reset(webAccess_.GetInputStream(page)); //pass ownership - const int sc = webAccess_.GetResponse(); - - //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection - if (sc / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too! - { - const std::wstring newUrl(webAccess_.GetHeader(L"Location")); - if (newUrl.empty()) - throw SysError(L"Unresolvable redirect. Empty target Location."); - - throw UrlRedirectError(newUrl); - } - - if (sc != 200) //HTTP_STATUS_OK - throw SysError(replaceCpy(L"HTTP status code %x.", L"%x", numberTo(sc))); - - if (!httpStream_ || webAccess_.GetError() != wxPROTO_NOERR) - throw SysError(L"wxHTTP::GetError (" + numberTo(webAccess_.GetError()) + L")"); - } - - ~Impl() { cleanup(); } - - size_t read(void* buffer, size_t bytesToRead) //throw SysError, X; return "bytesToRead" bytes unless end of stream! - { - const size_t blockSize = getBlockSize(); - assert(memBuf_.size() >= blockSize); - assert(bufPos_ <= bufPosEnd_ && bufPosEnd_ <= memBuf_.size()); - - char* it = static_cast(buffer); - char* const itEnd = it + bytesToRead; - for (;;) - { - const size_t junkSize = std::min(static_cast(itEnd - it), bufPosEnd_ - bufPos_); - std::memcpy(it, &memBuf_[0] + bufPos_, junkSize); - bufPos_ += junkSize; - it += junkSize; - - if (it == itEnd) - break; - //-------------------------------------------------------------------- - const size_t bytesRead = tryRead(&memBuf_[0], blockSize); //throw SysError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0 - bufPos_ = 0; - bufPosEnd_ = bytesRead; - - if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesRead); //throw X - - if (bytesRead == 0) //end of file - break; - } - return it - static_cast(buffer); - } - - size_t getBlockSize() const { return 64 * 1024; } - -private: - size_t tryRead(void* buffer, size_t bytesToRead) //throw SysError; may return short, only 0 means EOF! - { - if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check! - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); - assert(bytesToRead == getBlockSize()); - - httpStream_->Read(buffer, bytesToRead); - - const wxStreamError ec = httpStream_->GetLastError(); - if (ec != wxSTREAM_NO_ERROR && ec != wxSTREAM_EOF) - throw SysError(L"wxInputStream::GetLastError (" + numberTo(httpStream_->GetLastError()) + L")"); - - const size_t bytesRead = httpStream_->LastRead(); - //"if there are not enough bytes in the stream right now, LastRead() value will be - // less than size but greater than 0. If it is 0, it means that EOF has been reached." - assert(bytesRead > 0 || ec == wxSTREAM_EOF); - if (bytesRead > bytesToRead) //better safe than sorry - throw SysError(L"InternetReadFile: buffer overflow."); - - return bytesRead; //"zero indicates end of file" - } - - Impl (const Impl&) = delete; - Impl& operator=(const Impl&) = delete; - - void cleanup() - { - } - - wxHTTP webAccess_; - std::unique_ptr httpStream_; //must be deleted BEFORE webAccess is closed - - const IOCallback notifyUnbufferedIO_; //throw X - - std::vector memBuf_ = std::vector(getBlockSize()); - size_t bufPos_ = 0; //buffered I/O; see file_io.cpp - size_t bufPosEnd_ = 0; // -}; - - -HttpInputStream::HttpInputStream(std::unique_ptr&& pimpl) : pimpl_(std::move(pimpl)) {} - -HttpInputStream::~HttpInputStream() {} - -size_t HttpInputStream::read(void* buffer, size_t bytesToRead) { return pimpl_->read(buffer, bytesToRead); } //throw SysError, X; return "bytesToRead" bytes unless end of stream! - -size_t HttpInputStream::getBlockSize() const { return pimpl_->getBlockSize(); } - -std::string HttpInputStream::readAll() { return bufferedLoad(*pimpl_); } //throw SysError, X; - -namespace -{ -std::unique_ptr sendHttpRequestImpl(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO, //throw SysError - const std::string* postParams) //issue POST if bound, GET otherwise -{ - std::wstring urlRed = url; - //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop." - for (int redirects = 0; redirects < 6; ++redirects) - try - { - return std::make_unique(urlRed, userAgent, notifyUnbufferedIO, postParams); //throw SysError, UrlRedirectError - } - catch (const UrlRedirectError& e) { urlRed = e.newUrl; } - throw SysError(L"Too many redirects."); -} - - -//encode into "application/x-www-form-urlencoded" -std::string urlencode(const std::string& str) -{ - std::string out; - for (const char c : str) //follow PHP spec: https://github.com/php/php-src/blob/master/ext/standard/url.c#L500 - if (c == ' ') - out += '+'; - else if (('0' <= c && c <= '9') || - ('A' <= c && c <= 'Z') || - ('a' <= c && c <= 'z') || - c == '-' || c == '.' || c == '_') //note: "~" is encoded by PHP! - out += c; - else - { - const std::pair hex = hexify(c); - - out += '%'; - out += hex.first; - out += hex.second; - } - return out; -} - - -std::string urldecode(const std::string& str) -{ - std::string out; - for (size_t i = 0; i < str.size(); ++i) - { - const char c = str[i]; - if (c == '+') - out += ' '; - else if (c == '%' && str.size() - i >= 3 && - isHexDigit(str[i + 1]) && - isHexDigit(str[i + 2])) - { - out += unhexify(str[i + 1], str[i + 2]); - i += 2; - } - else - out += c; - } - return out; -} -} - - -std::string zen::xWwwFormUrlEncode(const std::vector>& paramPairs) -{ - std::string output; - for (const auto& pair : paramPairs) - output += urlencode(pair.first) + '=' + urlencode(pair.second) + '&'; - //encode both key and value: https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 - if (!output.empty()) - output.pop_back(); - return output; -} - - -std::vector> zen::xWwwFormUrlDecode(const std::string& str) -{ - std::vector> output; - - for (const std::string& nvPair : split(str, '&', SplitType::SKIP_EMPTY)) - output.emplace_back(urldecode(beforeFirst(nvPair, '=', IF_MISSING_RETURN_ALL)), - urldecode(afterFirst (nvPair, '=', IF_MISSING_RETURN_NONE))); - return output; -} - - -HttpInputStream zen::sendHttpPost(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO, - const std::vector>& postParams) //throw SysError -{ - const std::string encodedParams = xWwwFormUrlEncode(postParams); - return sendHttpRequestImpl(url, userAgent, notifyUnbufferedIO, &encodedParams); //throw SysError -} - - -HttpInputStream zen::sendHttpGet(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO) //throw SysError -{ - return sendHttpRequestImpl(url, userAgent, notifyUnbufferedIO, nullptr); //throw SysError -} - - -bool zen::internetIsAlive() //noexcept -{ - assert(std::this_thread::get_id() == mainThreadId); - - const wxString server = L"www.google.com"; - const wxString page = L"/"; - - wxHTTP webAccess; - webAccess.SetTimeout(10 /*[s]*/); //default: 10 minutes: WTF are these wxWidgets people thinking??? - - if (!webAccess.Connect(server)) //will *not* fail for non-reachable url here! - return false; - - std::unique_ptr httpStream(webAccess.GetInputStream(page)); //call before checking wxHTTP::GetResponse() - const int sc = webAccess.GetResponse(); - //attention: http://www.google.com/ might redirect to "https" => don't follow, just return "true"!!! - return sc / 100 == 2 || //e.g. 200 - sc / 100 == 3; //e.g. 301, 302, 303, 307... when in doubt, consider internet alive! -} diff --git a/wx+/http.h b/wx+/http.h deleted file mode 100755 index bbfa3a74..00000000 --- a/wx+/http.h +++ /dev/null @@ -1,49 +0,0 @@ -// ***************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * -// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * -// ***************************************************************************** - -#ifndef HTTP_H_879083425703425702 -#define HTTP_H_879083425703425702 - -#include -#include - -namespace zen -{ -/* - TREAD-SAFETY - ------------ - Windows: WinInet-based => may be called from worker thread, supports HTTPS - Linux: wxWidgets-based => don't call from worker thread -*/ -class HttpInputStream -{ -public: - //support zen/serialize.h buffered input stream concept - size_t read(void* buffer, size_t bytesToRead); //throw SysError, X; return "bytesToRead" bytes unless end of stream! - std::string readAll(); //throw SysError, X - - size_t getBlockSize() const; - - class Impl; - HttpInputStream(std::unique_ptr&& pimpl); - HttpInputStream(HttpInputStream&&) = default; - ~HttpInputStream(); - -private: - std::unique_ptr pimpl_; -}; - - -HttpInputStream sendHttpGet (const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO); //throw SysError -HttpInputStream sendHttpPost(const std::wstring& url, const std::wstring& userAgent, const IOCallback& notifyUnbufferedIO, - const std::vector>& postParams); //throw SysError -bool internetIsAlive(); //noexcept - -std::string xWwwFormUrlEncode(const std::vector>& paramPairs); -std::vector> xWwwFormUrlDecode(const std::string& str); -} - -#endif //HTTP_H_879083425703425702 diff --git a/wx+/image_holder.h b/wx+/image_holder.h index 6804d5fc..f4bf3c8a 100755 --- a/wx+/image_holder.h +++ b/wx+/image_holder.h @@ -18,15 +18,15 @@ struct ImageHolder //prepare conversion to wxImage as much as possible while sta { ImageHolder() {} - ImageHolder(int w, int h, bool withAlpha) : //init with allocated memory + ImageHolder(int w, int h, bool withAlpha) : //init with memory allocated width_(w), height_(h), rgb_(static_cast(::malloc(w * h * 3))), alpha_(withAlpha ? static_cast(::malloc(w * h)) : nullptr) {} - ImageHolder (ImageHolder&& tmp) = default; // - ImageHolder& operator=(ImageHolder&& tmp) = default; //move semantics only! - ImageHolder (const ImageHolder&) = delete; // - ImageHolder& operator=(const ImageHolder&) = delete; // + ImageHolder (ImageHolder&&) noexcept = default; // + ImageHolder& operator=(ImageHolder&&) noexcept = default; //move semantics only! + ImageHolder (const ImageHolder&) = delete; // + ImageHolder& operator=(const ImageHolder&) = delete; // explicit operator bool() const { return rgb_.get() != nullptr; } diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index e361515a..bbeeff8b 100755 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -26,8 +26,6 @@ using namespace zen; namespace { - - ImageHolder dpiScale(int width, int height, int dpiWidth, int dpiHeight, const unsigned char* imageRgb, const unsigned char* imageAlpha, int hqScale) { assert(imageRgb && imageAlpha); //see convertToVanillaImage() @@ -84,113 +82,41 @@ ImageHolder dpiScale(int width, int height, int dpiWidth, int dpiHeight, const u } -struct WorkItem +auto getScalerTask(const wxString& name, const wxImage& img, int hqScale, Protected>>& result) { - std::wstring name; //don't trust wxString to be thread-safe like an int - int width = 0; - int height = 0; - int dpiWidth = 0; - int dpiHeight = 0; - const unsigned char* rgb = nullptr; - const unsigned char* alpha = nullptr; -}; - - -class WorkLoad -{ -public: - void add(const WorkItem& wi) //context of main thread - { - assert(std::this_thread::get_id() == mainThreadId); - { - std::lock_guard dummy(lockWork_); - workLoad_.push_back(wi); - } - conditionNewWork_.notify_all(); - } - - void noMoreWork() + return [name = copyStringTo(name), //don't trust wxString to be thread-safe like an int + width = img.GetWidth(), + height = img.GetHeight(), + dpiWidth = fastFromDIP(img.GetWidth()), + dpiHeight = fastFromDIP(img.GetHeight()), //don't call fastFromDIP() from worker thread (wxWidgets function!) + rgb = img.GetData(), + alpha = img.GetAlpha(), + hqScale, &result] { - assert(std::this_thread::get_id() == mainThreadId); - { - std::lock_guard dummy(lockWork_); - expectMoreWork_ = false; - } - conditionNewWork_.notify_all(); - } - - //context of worker thread, blocking: - Opt extractNext() //throw ThreadInterruption - { - assert(std::this_thread::get_id() != mainThreadId); - std::unique_lock dummy(lockWork_); - - interruptibleWait(conditionNewWork_, dummy, [this] { return !workLoad_.empty() || !expectMoreWork_; }); //throw ThreadInterruption - - if (!workLoad_.empty()) - { - WorkItem wi = std::move(workLoad_. back()); // - /**/ workLoad_.pop_back(); //yes, no strong exception guarantee (std::bad_alloc) - return std::move(wi); // - } - return NoValue(); - } - -private: - std::mutex lockWork_; - std::vector workLoad_; - std::condition_variable conditionNewWork_; //signal event: data for processing available - bool expectMoreWork_ = true; -}; + ImageHolder ih = dpiScale(width, height, + dpiWidth, dpiHeight, + rgb, alpha, hqScale); + result.access([&](std::vector>& r) { r.emplace_back(name, std::move(ih)); }); + }; +} class DpiParallelScaler { public: - DpiParallelScaler(int hqScale) - { - assert(hqScale > 1); - const int threadCount = std::max(std::thread::hardware_concurrency(), 1); //hardware_concurrency() == 0 if "not computable or well defined" - - for (int i = 0; i < threadCount; ++i) - worker_.push_back([hqScale, &workload = workload_, &result = result_] - { - setCurrentThreadName("xBRZ Scaler"); - while (Opt wi = workload.extractNext()) //throw ThreadInterruption - { - ImageHolder ih = dpiScale(wi->width, wi->height, - wi->dpiWidth, wi->dpiHeight, - wi->rgb, wi->alpha, hqScale); - result.access([&](std::vector>& r) { r.emplace_back(wi->name, std::move(ih)); }); - } - }); - } + DpiParallelScaler(int hqScale) : hqScale_(hqScale) { assert(hqScale > 1); } - ~DpiParallelScaler() - { - for (InterruptibleThread& w : worker_) - w.interrupt(); - - for (InterruptibleThread& w : worker_) - if (w.joinable()) - w.join(); - } + ~DpiParallelScaler() { threadGroup_ = zen::NoValue(); } //DpiParallelScaler must out-live threadGroup!!! void add(const wxString& name, const wxImage& img) { imgKeeper_.push_back(img); //retain (ref-counted) wxImage so that the rgb/alpha pointers remain valid after passed to threads - workload_.add({ copyStringTo(name), - img.GetWidth(), img.GetHeight(), - fastFromDIP(img.GetWidth()), fastFromDIP(img.GetHeight()), //don't call fastFromDIP() from worker thread (wxWidgets function!) - img.GetData(), img.GetAlpha() }); + threadGroup_->run(getScalerTask(name, img, hqScale_, result_)); } std::map waitAndGetResult() { - workload_.noMoreWork(); - - for (InterruptibleThread& w : worker_) - w.join(); + threadGroup_->wait(); std::map output; @@ -203,17 +129,20 @@ public: wxImage img(ih.getWidth(), ih.getHeight(), ih.releaseRgb(), false /*static_data*/); //pass ownership img.SetAlpha(ih.releaseAlpha(), false /*static_data*/); - output[item.first] = wxBitmap(img); + output.emplace(item.first, std::move(img)); } }); return output; } private: - std::vector worker_; - WorkLoad workload_; - Protected>> result_; + const int hqScale_; std::vector imgKeeper_; + Protected>> result_; + + using TaskType = FunctionReturnTypeT; + Opt> threadGroup_{ ThreadGroup(std::max(std::thread::hardware_concurrency(), 1), "xBRZ Scaler") }; + //hardware_concurrency() == 0 if "not computable or well defined" }; @@ -222,12 +151,12 @@ void loadAnimFromZip(wxZipInputStream& zipInput, wxAnimation& anim) //work around wxWidgets bug: //construct seekable input stream (zip-input stream is not seekable) for wxAnimation::Load() //luckily this method call is very fast: below measurement precision! - std::vector data; + std::vector data; data.reserve(10000); int newValue = 0; while ((newValue = zipInput.GetC()) != wxEOF) - data.push_back(newValue); + data.push_back(static_cast(newValue)); wxMemoryInputStream seekAbleStream(&data.front(), data.size()); //stream does not take ownership of data @@ -243,7 +172,7 @@ public: static std::shared_ptr instance() { static Global inst(std::make_unique()); - assert(std::this_thread::get_id() == mainThreadId); //wxWidgets is not thread-safe! + assert(runningMainThread()); //wxWidgets is not thread-safe! return inst.get(); } diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp index 26a5b37c..88a78b21 100755 --- a/wx+/image_tools.cpp +++ b/wx+/image_tools.cpp @@ -83,7 +83,7 @@ wxImage zen::stackImages(const wxImage& img1, const wxImage& img2, ImageStackLay switch (align) { case ImageStackAlignment::CENTER: - return (totalExtent - imageExtent) / 2; + return static_cast(std::floor((totalExtent - imageExtent) / 2.0)); //consistency: round down negative values, too! case ImageStackAlignment::LEFT: return 0; case ImageStackAlignment::RIGHT: diff --git a/wx+/zlib_wrap.h b/wx+/zlib_wrap.h index 7265331e..d3bb017b 100755 --- a/wx+/zlib_wrap.h +++ b/wx+/zlib_wrap.h @@ -52,8 +52,8 @@ BinContainer compress(const BinContainer& stream, int level) //throw ZlibInterna //save uncompressed stream size for decompression const uint64_t uncompressedSize = stream.size(); //use portable number type! contOut.resize(sizeof(uncompressedSize)); - std::copy(reinterpret_cast(&uncompressedSize), - reinterpret_cast(&uncompressedSize) + sizeof(uncompressedSize), + std::copy(reinterpret_cast(&uncompressedSize), + reinterpret_cast(&uncompressedSize) + sizeof(uncompressedSize), &*contOut.begin()); const size_t bufferEstimate = impl::zlib_compressBound(stream.size()); //upper limit for buffer size, larger than input size!!! @@ -85,7 +85,7 @@ BinContainer decompress(const BinContainer& stream) //throw ZlibInternalError throw ZlibInternalError(); std::copy(&*stream.begin(), &*stream.begin() + sizeof(uncompressedSize), - reinterpret_cast(&uncompressedSize)); + reinterpret_cast(&uncompressedSize)); //attention: contOut MUST NOT be empty! Else it will pass a nullptr to zlib_decompress() => Z_STREAM_ERROR although "uncompressedSize == 0"!!! //secondary bug: don't dereference iterator into empty container! if (uncompressedSize == 0) //cannot be 0: compress() directly maps empty -> empty container skipping zlib! -- cgit