Expand description
Writes tag SQLite logs through the C++ writer.
LogWriter is a safe wrapper over the C interface in
host/libraries/tagcore-c/taglogwriter_c.h, which wraps the C++
SqliteTagLogWriter that tag-dwnld, tag-log-import and the Qt
applications use. Each message crosses as its protobuf wire encoding, so
the database is exactly the one the C++ tools write: one writer, one
schema, and no log format interpreted on the Rust side.
The sequence follows a download:
use taglogwriter::{LogHeader, LogWriter};
let mut writer = LogWriter::create("tag.db3".as_ref(), &header.config, true)?;
writer.write_header(&header)?;
writer.begin_log()?;
let mut index = 0;
loop {
// The writer says how far each page advances the download, and
// where it ends: both depend on the tag type.
let consumed = writer.write_log(&read_page(index))?;
if consumed == 0 {
break;
}
index += consumed;
}
writer.end_log()?;§Linking
The crate links libtagcore_c, which CMake builds with
-DBUILD_TAGCORE_C=ON, through tagcore-c-sys, whose build script finds it
through TAGCORE_C_LIB_DIR and which the other wrappers over the library
share, so a program may use them together.
On macOS it links the static archive (tagcore_c_static), since each
application is its own bundle, and the executable needs nothing at run
time. On Linux and Windows it links the shared library, one copy of which
serves every tool in the install directory, and a binary must find it at
run time: on Linux its package’s build script sets an rpath from
DEP_TAGCORE_C_LIB_DIR, which a direct dependent of tagcore-c-sys reads
(see commandline-rs/build.rs); on Windows the
DLL must sit beside the executable or on PATH. TAGCORE_C_LINK=dylib
forces the shared library on macOS too.
§Safety model
All unsafe is in this file, and every call states why it is sound. Three
rules carry the whole argument:
- Ownership. A
LogWriterowns exactly one C handle, never null, closed exactly once inDrop. Nothing else holds the pointer. - Borrowed buffers. Every pointer passed in (a path, an encoded message, an info string) points at Rust memory that lives until the call returns. The library copies what it keeps and holds no pointer after the call.
- Returned strings.
tlw_last_error()returns memory owned by the handle, valid only until the next call on it, so it is copied out at once, by the privateLogWriter::last_error, which is why it is not public.
A handle may move between threads but must not be used from two at once:
LogWriter is Send and not Sync, and every writing method takes
&mut self.
§Maintaining
The declarations, imported here as ffi, are tagcore_c_sys::tlw
(host/libraries/tagcore-c-sys/src/tlw.rs), written by hand from
host/libraries/tagcore-c/taglogwriter_c.h; with a dozen functions,
bindgen would cost more than it saves. When the header changes:
- Change the matching declaration in
tlw.rs, and the safe method over it. - If a function’s signature or meaning changed, the header raises
TLW_ABI_VERSION; raiseffi::TLW_ABI_VERSIONto match.LogWriter::createrefuses a library whose version differs, so a stale library fails with a message instead of misbehaving. - Keep
ffi::TLW_CONFIG…TLW_STATEequal to the header’stlw_header_kindvalues.
The messages themselves need no such care: both sides compile them from
the same proto/ files (prost here, protoc for the library), and protobuf
wire encoding is the contract between them. A field added to proto/
reaches the C++ writer once both are rebuilt.
The design, including how the library is built and why, is in
host/libraries/tagcore-c/design/architecture.md.
Structs§
- LogHeader
- Everything a log records about the tag before its data: the C++
TagLogHeader. - LogWriter
- One SQLite log being written by the C++ writer.
- Writer
Error - Why a writer call failed: the C++ writer’s own message, or the interface’s (a message that did not parse, a version mismatch).
Functions§
- set_
debug - Sets how much the C++ writer logs to stderr: everything at DEBUG when
enabled, errors only otherwise, as the C++ tools’-ddoes. It logs at DEBUG until this is called. Process-wide.