mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-28 07:49:59 +00:00
Merge remote-tracking branch 'origin/v0.8.0' into ifcviewer-wgpu
This commit is contained in:
@@ -112,6 +112,47 @@ PyObject* get_feature(const std::string& x) {
|
||||
|
||||
%{
|
||||
|
||||
#include <fstream>
|
||||
#include <random>
|
||||
|
||||
// Atomic IFC/STEP write (issue #4797): serialize to a temporary file next to
|
||||
// the destination, then atomically rename it onto the destination. If the
|
||||
// process is interrupted mid-write, the destination is never truncated or
|
||||
// left with dangling STEP references; at most a stray temp file remains, which
|
||||
// the caller can safely ignore. Keeping the temp in the same directory means
|
||||
// the rename stays on a single filesystem and is therefore atomic. The temp
|
||||
// path never leaks into the FILE_NAME header, which is derived from the model
|
||||
// header, not the output path.
|
||||
template <typename T>
|
||||
static void helper_fn_atomic_write(T& file_obj, const std::string& fn) {
|
||||
std::random_device rd;
|
||||
const std::string temp_fn = fn + "." + std::to_string(rd()) + ".tmp";
|
||||
{
|
||||
// Same open mode as a plain write so the bytes are identical.
|
||||
std::ofstream f(IfcUtil::path::from_utf8(temp_fn).c_str());
|
||||
if (!f.good()) {
|
||||
// The temp file could not be created (e.g. directory not
|
||||
// writable). Nothing was touched; report as a normal write error.
|
||||
throw std::runtime_error("Failed to write to path: '" + fn + "', check folder and file permissions.");
|
||||
}
|
||||
f << file_obj;
|
||||
f.flush();
|
||||
if (!f.good()) {
|
||||
// Serialization failed (e.g. disk full). Clean up the partial temp
|
||||
// and abort. The existing destination is left intact.
|
||||
f.close();
|
||||
IfcUtil::path::delete_file(temp_fn);
|
||||
throw std::runtime_error("Failed to write to path: '" + fn + "', the file may be incomplete.");
|
||||
}
|
||||
// The ofstream destructor at the end of this scope closes the stream.
|
||||
// On Windows the file must be closed before it can be renamed.
|
||||
}
|
||||
if (!IfcUtil::path::atomic_rename_file(temp_fn, fn)) {
|
||||
IfcUtil::path::delete_file(temp_fn);
|
||||
throw std::runtime_error("Failed to write to path: '" + fn + "', could not replace the existing file.");
|
||||
}
|
||||
}
|
||||
|
||||
static const std::string& helper_fn_declaration_get_name(const ifcopenshell::declaration* decl) {
|
||||
return decl->name();
|
||||
}
|
||||
@@ -221,11 +262,10 @@ private:
|
||||
}
|
||||
|
||||
void _write(const std::string& fn) {
|
||||
std::ofstream f(ifcopenshell::path::from_utf8(fn).c_str());
|
||||
if (!f.good()) {
|
||||
throw std::runtime_error("Failed to write to path: '" + fn + "', check folder and file permissions.");
|
||||
}
|
||||
f << (*$self);
|
||||
// Atomic write: serialize to a temp file next to the target, then
|
||||
// atomically rename it into place, so an interrupted write can never
|
||||
// corrupt the destination (issue #4797).
|
||||
helper_fn_atomic_write(*$self, fn);
|
||||
}
|
||||
|
||||
std::string to_string() {
|
||||
@@ -947,6 +987,7 @@ object = _old_object
|
||||
|
||||
%include "../ifcparse/schema.h"
|
||||
%include "../serializers/RocksDbSerializer.h"
|
||||
%include "../ifcparse/IfcLogger.h"
|
||||
|
||||
// The file* returned by open() is to be freed by SWIG/Python
|
||||
%newobject open;
|
||||
@@ -954,10 +995,10 @@ object = _old_object
|
||||
%newobject stream_from_string;
|
||||
|
||||
%inline %{
|
||||
ifcopenshell::file* open(const std::string& fn, bool readonly=false) {
|
||||
ifcopenshell::file* open(const std::string& fn, bool readonly=false, Logger& logger=Logger::Root()) {
|
||||
ifcopenshell::file* f;
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
f = new ifcopenshell::file(fn, ifcopenshell::FT_AUTODETECT, readonly);
|
||||
f = new ifcopenshell::file(fn, ifcopenshell::FT_AUTODETECT, readonly, logger);
|
||||
Py_END_ALLOW_THREADS;
|
||||
return f;
|
||||
}
|
||||
@@ -1115,7 +1156,7 @@ object = _old_object
|
||||
static std::stringstream ifcopenshell_log_stream;
|
||||
%}
|
||||
%init %{
|
||||
logger::set_output(0, &ifcopenshell_log_stream);
|
||||
Logger::Root().SetOutput(0, &ifcopenshell_log_stream);
|
||||
%}
|
||||
%inline %{
|
||||
std::string get_log() {
|
||||
@@ -1124,20 +1165,20 @@ object = _old_object
|
||||
return log;
|
||||
}
|
||||
void turn_on_detailed_logging() {
|
||||
logger::set_output(&std::cout, &std::cout);
|
||||
logger::verbosity(logger::LOG_DEBUG);
|
||||
Logger::Root().SetOutput(&std::cout, &std::cout);
|
||||
Logger::Root().Verbosity(Logger::LOG_DEBUG);
|
||||
}
|
||||
void turn_off_detailed_logging() {
|
||||
logger::set_output(0, &ifcopenshell_log_stream);
|
||||
logger::verbosity(logger::LOG_WARNING);
|
||||
Logger::Root().SetOutput(0, &ifcopenshell_log_stream);
|
||||
Logger::Root().Verbosity(Logger::LOG_WARNING);
|
||||
}
|
||||
void set_log_format_json() {
|
||||
ifcopenshell_log_stream.str("");
|
||||
logger::output_format(logger::FMT_JSON);
|
||||
Logger::Root().OutputFormat(Logger::FMT_JSON);
|
||||
}
|
||||
void set_log_format_text() {
|
||||
ifcopenshell_log_stream.str("");
|
||||
logger::output_format(logger::FMT_PLAIN);
|
||||
Logger::Root().OutputFormat(Logger::FMT_PLAIN);
|
||||
}
|
||||
%}
|
||||
|
||||
@@ -1447,4 +1488,31 @@ object = _old_object
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
%extend Logger {
|
||||
%pythoncode %{
|
||||
def __iter__(self):
|
||||
return iter(self.log_messages())
|
||||
%}
|
||||
}
|
||||
|
||||
%extend log_message {
|
||||
std::string severity_string() const {
|
||||
static const char* const severity_strings[] = {"PERF", "DEBUG", "NOTICE", "WARNING", "ERROR"};
|
||||
return severity_strings[(int)$self->severity];
|
||||
}
|
||||
%pythoncode %{
|
||||
severity_string = property(severity_string)
|
||||
def to_dict(self):
|
||||
keys = ("timestamp", "severity", "code", "message", "instance", "product")
|
||||
return dict(zip(keys, self.to_tuple()))
|
||||
def to_tuple(self):
|
||||
return self.timestamp, self.severity_string, self.code, self.message, self.instance, self.product
|
||||
def __eq__(self, other):
|
||||
return type(self) == type(other) and self.to_tuple() == other.to_tuple()
|
||||
def __hash__(self):
|
||||
return hash(self.to_tuple())
|
||||
def __repr__(self):
|
||||
return "<log_message '[%s] [%s] %s'>" % (self.severity_string, self.code, self.message)
|
||||
%}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user