LogIt++
Header-only, macro-first C++ logging library
Loading...
Searching...
No Matches
CompressionWorker.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_DETAIL_COMPRESSIONWORKER_HPP_INCLUDED
3#define LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_DETAIL_COMPRESSIONWORKER_HPP_INCLUDED
4
7
8#include <string>
9#include <queue>
10#include <thread>
11#include <mutex>
12#include <condition_variable>
13#include <fstream>
14#include <vector>
15#include <cstdio>
16
17#if defined(LOGIT_HAS_ZLIB)
18# include <zlib.h>
19#endif
20#if defined(LOGIT_HAS_ZSTD)
21# include <zstd.h>
22#endif
23
24namespace logit { namespace detail {
25
33 const std::string& src,
34 int level,
35 const std::string& external_cmd);
36
40 public:
46 int level,
47 std::string external_cmd);
48
51
54 void enqueue(std::string path);
55
57 void wait();
58
59 private:
61 void run();
62
65 std::string m_external_cmd;
66 std::queue<std::string> m_q;
67 std::thread m_thread;
68 std::mutex m_mx;
69 std::condition_variable m_cv;
70 std::condition_variable m_cv_idle;
71 bool m_stop = false;
72 bool m_busy = false;
73 };
74
80 inline int clamp_level(int v, int lo, int hi) {
81 if (v < lo) return lo;
82 if (v > hi) return hi;
83 return v;
84 }
85
91 inline bool compress_file_gzip(const std::string& src,
92 const std::string& dst_tmp,
93 int level) {
94# if defined(LOGIT_HAS_ZLIB)
95 std::ifstream in(src.c_str(), std::ios::binary);
96 if (!in) return false;
97
98 char mode[8] = "wb6";
99 mode[2] = char('0' + clamp_level(level, 1, 9));
100 gzFile out = gzopen(dst_tmp.c_str(), mode);
101 if (!out) return false;
102 gzbuffer(out, 256 * 1024);
103
104 std::vector<char> buf(256 * 1024);
105 while (in) {
106 in.read(&buf[0], buf.size());
107 std::streamsize got = in.gcount();
108 if (got > 0) {
109 int written = gzwrite(out, &buf[0], static_cast<unsigned>(got));
110 if (written == 0) { gzclose(out); return false; }
111 }
112 }
113 return gzclose(out) == Z_OK;
114# else
115 (void)src; (void)dst_tmp; (void)level; return false;
116# endif
117 }
118
124 inline bool compress_file_zstd(const std::string& src,
125 const std::string& dst_tmp,
126 int level) {
127# if defined(LOGIT_HAS_ZSTD)
128 std::ifstream in(src.c_str(), std::ios::binary);
129 std::ofstream out(dst_tmp.c_str(), std::ios::binary | std::ios::trunc);
130 if (!in || !out) return false;
131
132 ZSTD_CCtx* cctx = ZSTD_createCCtx();
133 if (!cctx) return false;
134 ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, clamp_level(level, 1, 19));
135
136 const size_t inChunk = ZSTD_CStreamInSize();
137 const size_t outChunk = ZSTD_CStreamOutSize();
138 std::vector<char> inBuf(inChunk), outBuf(outChunk);
139
140 for (;;) {
141 in.read(&inBuf[0], inBuf.size());
142 size_t rd = static_cast<size_t>(in.gcount());
143 ZSTD_inBuffer zin = { &inBuf[0], rd, 0 };
144 const int last = in.eof() ? 1 : 0;
145
146 while (zin.pos < zin.size || last) {
147 ZSTD_outBuffer zout = { &outBuf[0], outBuf.size(), 0 };
148 size_t ret = last
149 ? ZSTD_compressStream2(cctx, &zout, &zin, ZSTD_e_end)
150 : ZSTD_compressStream2(cctx, &zout, &zin, ZSTD_e_continue);
151 if (ZSTD_isError(ret)) { ZSTD_freeCCtx(cctx); return false; }
152 out.write(&outBuf[0], static_cast<std::streamsize>(zout.pos));
153 if (!out) { ZSTD_freeCCtx(cctx); return false; }
154 if (last && ret == 0 && zin.pos == zin.size) {
155 ZSTD_freeCCtx(cctx);
156 out.flush();
157 return out.good();
158 }
159 if (!last && zin.pos == zin.size) break;
160 }
161 }
162# else
163 (void)src; (void)dst_tmp; (void)level; return false;
164# endif
165 }
166
172 inline bool compress_file_external(const std::string& src,
173 const std::string& cmd_tpl,
174 int level) {
175 if (cmd_tpl.empty()) return false;
176 std::string cmd = cmd_tpl;
177 size_t pos = cmd.find("{file}");
178 if (pos != std::string::npos) cmd.replace(pos, 6, "\"" + src + "\"");
179 pos = cmd.find("{level}");
180 if (pos != std::string::npos) cmd.replace(pos, 7, std::to_string(level));
181 return std::system(cmd.c_str()) == 0;
182 }
183
184 inline bool compress_file(CompressType type,
185 const std::string& src,
186 int level,
187 const std::string& external_cmd) {
188 if (type == CompressType::NONE) return true;
189 if (type == CompressType::EXTERNAL_CMD) {
190 return compress_file_external(src, external_cmd, level);
191 }
192 std::string dst = src + (type == CompressType::GZIP ? ".gz" : ".zst");
193 std::string tmp = dst + ".tmp";
194 bool ok = false;
195 if (type == CompressType::GZIP) ok = compress_file_gzip(src, tmp, level);
196 else ok = compress_file_zstd(src, tmp, level);
197 if (!ok) { std::remove(tmp.c_str()); return false; }
198 if (std::rename(tmp.c_str(), dst.c_str()) != 0) { std::remove(tmp.c_str()); return false; }
199 std::remove(src.c_str());
200 return true;
201 }
202
204 int level,
205 std::string external_cmd)
206 : m_type(type), m_level(level), m_external_cmd(std::move(external_cmd)) {
207 if (m_type != CompressType::NONE) {
208 m_thread = std::thread(&CompressionWorker::run, this);
209 }
210 }
211
213 {
214 std::lock_guard<std::mutex> lk(m_mx);
215 m_stop = true;
216 m_cv.notify_all();
217 }
218 if (m_thread.joinable()) m_thread.join();
219 }
220
221 inline void CompressionWorker::enqueue(std::string path) {
222 std::lock_guard<std::mutex> lk(m_mx);
223 m_q.push(std::move(path));
224 m_cv.notify_one();
225 }
226
228 std::unique_lock<std::mutex> lk(m_mx);
229 m_cv_idle.wait(lk, [this]{ return m_q.empty() && !m_busy; });
230 }
231
233 for (;;) {
234 std::string src;
235 {
236 std::unique_lock<std::mutex> lk(m_mx);
237 m_cv.wait(lk, [this]{ return m_stop || !m_q.empty(); });
238 if (m_stop && m_q.empty()) break;
239 src = std::move(m_q.front());
240 m_q.pop();
241 m_busy = true;
242 }
243
245
246 {
247 std::lock_guard<std::mutex> lk(m_mx);
248 m_busy = false;
249 if (m_q.empty()) m_cv_idle.notify_all();
250 }
251 }
252 }
253
254}} // namespace logit::detail
255
256#endif // LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_DETAIL_COMPRESSIONWORKER_HPP_INCLUDED
Background worker performing asynchronous compression.
CompressionWorker(CompressType type, int level, std::string external_cmd)
Create worker thread.
void enqueue(std::string path)
Enqueue a file for compression.
void run()
Worker loop processing queued files.
void wait()
Wait until all queued files are processed.
~CompressionWorker()
Stop worker thread and finish pending tasks.
bool compress_file(CompressType type, const std::string &src, int level, const std::string &external_cmd)
Compress a file using the specified compression type.
bool compress_file_external(const std::string &src, const std::string &cmd_tpl, int level)
Compress file using external command.
bool compress_file_zstd(const std::string &src, const std::string &dst_tmp, int level)
Compress file using Zstandard.
bool compress_file_gzip(const std::string &src, const std::string &dst_tmp, int level)
Compress file using gzip.
int clamp_level(int v, int lo, int hi)
Clamp value to inclusive range.
The primary namespace for the LogIt++ library.
CompressType
Supported compression algorithms for rotated files.
Definition enums.hpp:58
@ NONE
Do not compress rotated files.
@ GZIP
Compress using gzip (zlib).
@ EXTERNAL_CMD
Use an external command for compression.