Переключение между сжатыми и несжатыми данными - PullRequest
3 голосов
/ 05 июля 2019

Я написал простой фильтр для Boost iostreams, который определяет, является ли файл сжатым или простым текстом, и делегировал бы gzip_decompressor, если это так.

Проблема в том, что я возвращаюсь в свой входной поток, чтобы снова подать данные внутри декомпрессора. Только некоторые потоки не поддерживают это и прерываются с помощью резкого исключения потока.

Вместо этого я подумал: хорошо, давайте использовать basic_array_source для подачи двух символов, но этот источник не поддерживает вызов read!

Так что это работает постоянно:

struct gz_decompressor {
    typedef char                                          char_type;
    typedef boost::iostreams::multichar_input_filter_tag  category;

    boost::iostreams::gzip_decompressor m_decompressor{15, backtest::GzReader::GZIP_BUFFER_SIZE};
    bool m_initialized{false};
    bool m_is_compressed{false};

    template<typename Source>
    std::streamsize read(Source& src, char* s, std::streamsize n) {
        if (!m_initialized) {
            init(src, s, n);
        }

        if (m_is_compressed) {
            return m_decompressor.read(src, s, n);
        }

        return boost::iostreams::read(src, s, n);
    }
};

Часть, которую я не могу понять:

    template<typename Source>
    void init(Source& src, char* s, std::streamsize n) {
        char header[2];
        header[0] = boost::iostreams::get(src);
        header[1] = boost::iostreams::get(src);
        m_is_compressed = header[0] == static_cast<char>(0x1f) && header[2] == static_cast<char>(0x8b);
        m_initialized = true;

        boost::iostreams::basic_array_source<char> source(header);

        if (m_is_compressed) {
            m_decompressor.read(source, s, n); // Nope, is not allowed!
        }
        else {
            boost::iostreams::read(source, s, n);
        }
    }

Любая подсказка о том, как сделать это правильно, то есть без поиска назад?

1 Ответ

0 голосов
/ 05 июля 2019

У меня есть несовершенное решение путем повторного использования кода, который gzip_decompressor использует (peekable_source):

using namespace boost::iostreams;

template<typename Source>
struct PeekableSource {
    typedef char char_type;
    struct category : source_tag, peekable_tag { };
    explicit PeekableSource(Source& src, const std::string& putback = "")
            : src_(src), putback_(putback), offset_(0)
    { }
    std::streamsize read(char* s, std::streamsize n)
    {
        std::streamsize result = 0;

        // Copy characters from putback buffer
        std::streamsize pbsize =
                static_cast<std::streamsize>(putback_.size());
        if (offset_ < pbsize) {
            result = (std::min)(n, pbsize - offset_);
            BOOST_IOSTREAMS_CHAR_TRAITS(char)::copy(
                    s, putback_.data() + offset_, result);
            offset_ += result;
            if (result == n)
                return result;
        }

        // Read characters from src_
        std::streamsize amt =
                boost::iostreams::read(src_, s + result, n - result);
        return amt != -1 ?
               result + amt :
               result ? result : -1;
    }
    bool putback(char c)
    {
        if (offset_) {
            putback_[--offset_] = c;
        } else {
            boost::throw_exception(
                    boost::iostreams::detail::bad_putback());
        }
        return true;
    }
    void putback(const std::string& s)
    {
        putback_.replace(0, offset_, s);
        offset_ = 0;
    }

    // Returns true if some characters have been putback but not re-read.
    bool has_unconsumed_input() const
    {
        return offset_ < static_cast<std::streamsize>(putback_.size());
    }

    // Returns the sequence of characters that have been put back but not re-read.
    std::string unconsumed_input() const
    {
        return std::string(putback_, offset_, putback_.size() - offset_);
    }
    Source&          src_;
    std::string      putback_;
    std::streamsize  offset_;
};

struct gzDecompressor {
    typedef char              char_type;
    typedef multichar_input_filter_tag  category;

    gzip_decompressor m_decompressor;
    bool m_initialized{false};
    bool m_is_compressed{false};
    std::string m_putback;

    template<typename Source>
    void init(Source& src) {
        std::string data;
        data.push_back(get(src));
        data.push_back(get(src));
        m_is_compressed = data[0] == static_cast<char>(0x1f) && data[1] == static_cast<char>(0x8b);
        src.putback(data);
        m_initialized = true;
    }

    template<typename Source>
    std::streamsize read(Source& src, char* s, std::streamsize n) {
        PeekableSource<Source> peek(src, m_putback);
        if (!m_initialized) {
            init(peek);
        }

        if (m_is_compressed) {
            return m_decompressor.read(peek, s, n);
        }

        return boost::iostreams::read(peek, s, n);
    }
};

Это не так уж и здорово, потому что теперь есть два промежуточных источника, которые могут кэшировать данные, нопо крайней мере, основная часть работы должна выполняться через интерфейс read, а не побайтно, так что это должно уменьшить проблемы с производительностью.

...