Logging to with a ringbuffer. Much faster.
This commit is contained in:
@@ -34,12 +34,12 @@ std::string CanonicalizePath(const std::string& original_path) {
|
||||
pos_n = std::string::npos;
|
||||
break;
|
||||
case 1:
|
||||
// Duplicate separators
|
||||
// Duplicate separators.
|
||||
path.erase(pos, 1);
|
||||
pos_n -= 1;
|
||||
break;
|
||||
case 2:
|
||||
// Potential marker for current directory
|
||||
// Potential marker for current directory.
|
||||
if (path[pos + 1] == '.') {
|
||||
path.erase(pos, 2);
|
||||
pos_n -= 2;
|
||||
@@ -48,10 +48,10 @@ std::string CanonicalizePath(const std::string& original_path) {
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
// Potential marker for parent directory
|
||||
// Potential marker for parent directory.
|
||||
if (path[pos + 1] == '.' && path[pos + 2] == '.') {
|
||||
if (path_breaks.empty()) {
|
||||
// Ensure we don't override the device name
|
||||
// Ensure we don't override the device name.
|
||||
std::string::size_type loc(path.find_first_of(':'));
|
||||
auto req(pos + 3);
|
||||
if (loc == std::string::npos || loc > req) {
|
||||
@@ -66,7 +66,7 @@ std::string CanonicalizePath(const std::string& original_path) {
|
||||
auto last_diff((pos + 3) - last);
|
||||
path.erase(last, last_diff);
|
||||
pos_n = last;
|
||||
// Also remove path reference
|
||||
// Also remove path reference.
|
||||
path_breaks.erase(path_breaks.end() - 1);
|
||||
}
|
||||
} else {
|
||||
@@ -82,12 +82,12 @@ std::string CanonicalizePath(const std::string& original_path) {
|
||||
pos = pos_n;
|
||||
}
|
||||
|
||||
// Remove trailing seperator
|
||||
// Remove trailing seperator.
|
||||
if (!path.empty() && path.back() == path_sep) {
|
||||
path.erase(path.size() - 1);
|
||||
}
|
||||
|
||||
// Final sanity check for dead paths
|
||||
// Final sanity check for dead paths.
|
||||
if ((path.size() == 1 && (path[0] == '.' || path[0] == path_sep)) ||
|
||||
(path.size() == 2 && path[0] == '.' && path[1] == '.')) {
|
||||
return "";
|
||||
@@ -96,6 +96,16 @@ std::string CanonicalizePath(const std::string& original_path) {
|
||||
return path;
|
||||
}
|
||||
|
||||
bool CreateParentFolder(const std::wstring& path) {
|
||||
auto fixed_path = xe::fix_path_separators(path, '/');
|
||||
auto base_path = xe::find_base_path(fixed_path, '/');
|
||||
if (!PathExists(base_path)) {
|
||||
return CreateFolder(base_path);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
WildcardFlags WildcardFlags::FIRST(true, false);
|
||||
WildcardFlags WildcardFlags::LAST(false, true);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ std::string CanonicalizePath(const std::string& original_path);
|
||||
|
||||
bool PathExists(const std::wstring& path);
|
||||
|
||||
bool CreateParentFolder(const std::wstring& path);
|
||||
bool CreateFolder(const std::wstring& path);
|
||||
bool DeleteFolder(const std::wstring& path);
|
||||
bool IsFolder(const std::wstring& path);
|
||||
|
||||
@@ -11,12 +11,15 @@
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdarg>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/ring_buffer.h"
|
||||
#include "xenia/base/threading.h"
|
||||
|
||||
// For MessageBox:
|
||||
@@ -25,91 +28,150 @@
|
||||
#include "xenia/base/platform_win.h"
|
||||
#endif // XE_PLATFORM_WIN32
|
||||
|
||||
DEFINE_bool(fast_stdout, false,
|
||||
"Don't lock around stdout/stderr. May introduce weirdness.");
|
||||
DEFINE_bool(flush_stdout, true, "Flush stdout after each log line.");
|
||||
DEFINE_string(log_file, "",
|
||||
"Logs are written to the given file instead of the default.");
|
||||
DEFINE_bool(flush_log, true, "Flush log file after each log line batch.");
|
||||
|
||||
namespace xe {
|
||||
|
||||
std::mutex log_lock;
|
||||
thread_local std::vector<char> log_format_buffer_(64 * 1024);
|
||||
|
||||
thread_local std::vector<char> log_buffer(16 * 1024);
|
||||
|
||||
void format_log_line(char* buffer, size_t buffer_capacity,
|
||||
const char level_char, const char* fmt, va_list args) {
|
||||
char* buffer_ptr;
|
||||
buffer_ptr = buffer;
|
||||
*(buffer_ptr++) = level_char;
|
||||
*(buffer_ptr++) = '>';
|
||||
*(buffer_ptr++) = ' ';
|
||||
buffer_ptr +=
|
||||
std::snprintf(buffer_ptr, buffer_capacity - (buffer_ptr - buffer), "%.4X",
|
||||
xe::threading::current_thread_id());
|
||||
*(buffer_ptr++) = ' ';
|
||||
|
||||
// Scribble args into the print buffer.
|
||||
size_t remaining_capacity = buffer_capacity - (buffer_ptr - buffer) - 3;
|
||||
size_t chars_written = vsnprintf(buffer_ptr, remaining_capacity, fmt, args);
|
||||
if (chars_written >= remaining_capacity) {
|
||||
buffer_ptr += remaining_capacity - 1;
|
||||
} else {
|
||||
buffer_ptr += chars_written;
|
||||
class Logger {
|
||||
public:
|
||||
Logger() : ring_buffer_(buffer_, kBufferSize), running_(true) {
|
||||
flush_event_ = xe::threading::Event::CreateAutoResetEvent(false);
|
||||
write_thread_ =
|
||||
xe::threading::Thread::Create({}, [this]() { WriteThread(); });
|
||||
write_thread_->set_name("xe::FileLogSink Writer");
|
||||
}
|
||||
|
||||
// Add a trailing newline.
|
||||
if (buffer_ptr[-1] != '\n') {
|
||||
buffer_ptr[0] = '\n';
|
||||
buffer_ptr[1] = 0;
|
||||
~Logger() {
|
||||
running_ = false;
|
||||
flush_event_->Set();
|
||||
xe::threading::Wait(write_thread_.get(), true);
|
||||
fflush(file_);
|
||||
fclose(file_);
|
||||
}
|
||||
|
||||
void Initialize(const std::wstring& app_name) {
|
||||
if (!FLAGS_log_file.empty()) {
|
||||
auto file_path = xe::to_wstring(FLAGS_log_file.c_str());
|
||||
xe::filesystem::CreateParentFolder(file_path);
|
||||
file_ = xe::filesystem::OpenFile(file_path, "wt");
|
||||
} else {
|
||||
auto file_path = app_name + L".log";
|
||||
file_ = xe::filesystem::OpenFile(file_path, "wt");
|
||||
}
|
||||
}
|
||||
|
||||
void AppendLine(uint32_t thread_id, const char level_char, const char* buffer,
|
||||
size_t buffer_length) {
|
||||
LogLine line;
|
||||
line.thread_id = thread_id;
|
||||
line.level_char = level_char;
|
||||
line.buffer_length = buffer_length;
|
||||
while (true) {
|
||||
mutex_.lock();
|
||||
if (ring_buffer_.write_count() < sizeof(line) + buffer_length) {
|
||||
// Buffer is full. Stall.
|
||||
mutex_.unlock();
|
||||
xe::threading::MaybeYield();
|
||||
continue;
|
||||
}
|
||||
ring_buffer_.Write(&line, sizeof(LogLine));
|
||||
ring_buffer_.Write(buffer, buffer_length);
|
||||
mutex_.unlock();
|
||||
break;
|
||||
}
|
||||
flush_event_->Set();
|
||||
}
|
||||
|
||||
private:
|
||||
static const size_t kBufferSize = 32 * 1024 * 1024;
|
||||
|
||||
struct LogLine {
|
||||
uint32_t thread_id;
|
||||
char level_char;
|
||||
size_t buffer_length;
|
||||
};
|
||||
|
||||
void WriteThread() {
|
||||
while (running_) {
|
||||
mutex_.lock();
|
||||
bool did_write = false;
|
||||
while (!ring_buffer_.empty()) {
|
||||
did_write = true;
|
||||
LogLine line;
|
||||
ring_buffer_.Read(&line, sizeof(line));
|
||||
ring_buffer_.Read(log_format_buffer_.data(), line.buffer_length);
|
||||
const char prefix[3] = {line.level_char, '>', ' '};
|
||||
fwrite(prefix, 1, sizeof(prefix), file_);
|
||||
fwrite(log_format_buffer_.data(), 1, line.buffer_length, file_);
|
||||
if (log_format_buffer_[line.buffer_length - 1] != '\n') {
|
||||
const char suffix[1] = {'\n'};
|
||||
fwrite(suffix, 1, sizeof(suffix), file_);
|
||||
}
|
||||
}
|
||||
mutex_.unlock();
|
||||
if (did_write) {
|
||||
if (FLAGS_flush_log) {
|
||||
fflush(file_);
|
||||
}
|
||||
}
|
||||
xe::threading::Wait(flush_event_.get(), true);
|
||||
}
|
||||
}
|
||||
|
||||
FILE* file_ = nullptr;
|
||||
uint8_t buffer_[kBufferSize];
|
||||
RingBuffer ring_buffer_;
|
||||
std::mutex mutex_;
|
||||
std::atomic<bool> running_;
|
||||
std::unique_ptr<xe::threading::Event> flush_event_;
|
||||
std::unique_ptr<xe::threading::Thread> write_thread_;
|
||||
};
|
||||
|
||||
Logger logger_;
|
||||
|
||||
void InitializeLogging(const std::wstring& app_name) {
|
||||
logger_.Initialize(app_name);
|
||||
}
|
||||
|
||||
void log_line(const char level_char, const char* fmt, ...) {
|
||||
// SCOPE_profile_cpu_i("emu", "log_line");
|
||||
|
||||
void LogLineFormat(const char level_char, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
format_log_line(log_buffer.data(), log_buffer.capacity(), level_char, fmt,
|
||||
args);
|
||||
size_t chars_written = vsnprintf(log_format_buffer_.data(),
|
||||
log_format_buffer_.capacity(), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (!FLAGS_fast_stdout) {
|
||||
log_lock.lock();
|
||||
}
|
||||
#if 0 // defined(OutputDebugString)
|
||||
OutputDebugStringA(log_buffer.data());
|
||||
#else
|
||||
fprintf(stdout, "%s", log_buffer.data());
|
||||
if (FLAGS_flush_stdout) {
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif // OutputDebugString
|
||||
if (!FLAGS_fast_stdout) {
|
||||
log_lock.unlock();
|
||||
}
|
||||
logger_.AppendLine(xe::threading::current_thread_id(), level_char,
|
||||
log_format_buffer_.data(), chars_written);
|
||||
}
|
||||
|
||||
void handle_fatal(const char* fmt, ...) {
|
||||
void LogLineVarargs(const char level_char, const char* fmt, va_list args) {
|
||||
size_t chars_written = vsnprintf(log_format_buffer_.data(),
|
||||
log_format_buffer_.capacity(), fmt, args);
|
||||
logger_.AppendLine(xe::threading::current_thread_id(), level_char,
|
||||
log_format_buffer_.data(), chars_written);
|
||||
}
|
||||
|
||||
void LogLine(const char level_char, const std::string& str) {
|
||||
logger_.AppendLine(xe::threading::current_thread_id(), level_char,
|
||||
str.c_str(), str.length());
|
||||
}
|
||||
|
||||
void FatalError(const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
format_log_line(log_buffer.data(), log_buffer.capacity(), 'X', fmt, args);
|
||||
LogLineVarargs('X', fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (!FLAGS_fast_stdout) {
|
||||
log_lock.lock();
|
||||
}
|
||||
#if defined(OutputDebugString)
|
||||
OutputDebugStringA(log_buffer.data());
|
||||
#else
|
||||
fprintf(stderr, "%s", log_buffer.data());
|
||||
fflush(stderr);
|
||||
#endif // OutputDebugString
|
||||
if (!FLAGS_fast_stdout) {
|
||||
log_lock.unlock();
|
||||
}
|
||||
|
||||
#if XE_PLATFORM_WIN32
|
||||
if (!xe::has_console_attached()) {
|
||||
MessageBoxA(NULL, log_buffer.data(), "Xenia Error",
|
||||
va_start(args, fmt);
|
||||
vsnprintf(log_format_buffer_.data(), log_format_buffer_.capacity(), fmt,
|
||||
args);
|
||||
va_end(args);
|
||||
MessageBoxA(NULL, log_format_buffer_.data(), "Xenia Error",
|
||||
MB_OK | MB_ICONERROR | MB_APPLMODAL | MB_SETFOREGROUND);
|
||||
}
|
||||
#endif // WIN32
|
||||
@@ -117,4 +179,6 @@ void handle_fatal(const char* fmt, ...) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void FatalError(const std::string& str) { FatalError(str.c_str()); }
|
||||
|
||||
} // namespace xe
|
||||
|
||||
@@ -11,86 +11,47 @@
|
||||
#define XENIA_BASE_LOGGING_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/base/string.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
#define XE_OPTION_ENABLE_LOGGING 1
|
||||
#define XE_OPTION_LOG_ERROR 1
|
||||
#define XE_OPTION_LOG_WARNING 1
|
||||
#define XE_OPTION_LOG_INFO 1
|
||||
#define XE_OPTION_LOG_DEBUG 1
|
||||
#define XE_OPTION_LOG_CPU 1
|
||||
#define XE_OPTION_LOG_APU 1
|
||||
#define XE_OPTION_LOG_GPU 1
|
||||
#define XE_OPTION_LOG_KERNEL 1
|
||||
#define XE_OPTION_LOG_FS 1
|
||||
|
||||
#define XE_EMPTY_MACRO \
|
||||
do { \
|
||||
} while (false)
|
||||
// Initializes the logging system and any outputs requested.
|
||||
// Must be called on startup.
|
||||
void InitializeLogging(const std::wstring& app_name);
|
||||
|
||||
void log_line(const char level_char, const char* fmt, ...);
|
||||
void handle_fatal(const char* fmt, ...);
|
||||
// Appends a line to the log with printf-style formatting.
|
||||
void LogLineFormat(const char level_char, const char* fmt, ...);
|
||||
void LogLineVarargs(const char level_char, const char* fmt, va_list args);
|
||||
// Appends a line to the log.
|
||||
void LogLine(const char level_char, const std::string& str);
|
||||
|
||||
// Logs a fatal error with printf-style formatting and aborts the program.
|
||||
void FatalError(const char* fmt, ...);
|
||||
// Logs a fatal error and aborts the program.
|
||||
void FatalError(const std::string& str);
|
||||
|
||||
#if XE_OPTION_ENABLE_LOGGING
|
||||
#define XELOGCORE(level, fmt, ...) xe::log_line(level, fmt, ##__VA_ARGS__)
|
||||
#define XELOGCORE(level, fmt, ...) xe::LogLineFormat(level, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define XELOGCORE(level, fmt, ...) XE_EMPTY_MACRO
|
||||
#define XELOGCORE(level, fmt, ...) \
|
||||
do { \
|
||||
} while (false)
|
||||
#endif // ENABLE_LOGGING
|
||||
|
||||
#define XEFATAL(fmt, ...) \
|
||||
do { \
|
||||
xe::handle_fatal(fmt, ##__VA_ARGS__); \
|
||||
} while (false)
|
||||
|
||||
#if XE_OPTION_LOG_ERROR
|
||||
#define XELOGE(fmt, ...) XELOGCORE('!', fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define XELOGE(fmt, ...) XE_EMPTY_MACRO
|
||||
#endif
|
||||
#if XE_OPTION_LOG_WARNING
|
||||
#define XELOGW(fmt, ...) XELOGCORE('w', fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define XELOGW(fmt, ...) XE_EMPTY_MACRO
|
||||
#endif
|
||||
#if XE_OPTION_LOG_INFO
|
||||
#define XELOGI(fmt, ...) XELOGCORE('i', fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define XELOGI(fmt, ...) XE_EMPTY_MACRO
|
||||
#endif
|
||||
#if XE_OPTION_LOG_DEBUG
|
||||
#define XELOGD(fmt, ...) XELOGCORE('d', fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define XELOGD(fmt, ...) XE_EMPTY_MACRO
|
||||
#endif
|
||||
|
||||
#if XE_OPTION_LOG_CPU
|
||||
#define XELOGCPU(fmt, ...) XELOGCORE('C', fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define XELOGCPU(fmt, ...) XE_EMPTY_MACRO
|
||||
#endif
|
||||
#if XE_OPTION_LOG_APU
|
||||
#define XELOGAPU(fmt, ...) XELOGCORE('A', fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define XELOGAPU(fmt, ...) XE_EMPTY_MACRO
|
||||
#endif
|
||||
#if XE_OPTION_LOG_GPU
|
||||
#define XELOGGPU(fmt, ...) XELOGCORE('G', fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define XELOGGPU(fmt, ...) XE_EMPTY_MACRO
|
||||
#endif
|
||||
#if XE_OPTION_LOG_KERNEL
|
||||
#define XELOGKERNEL(fmt, ...) XELOGCORE('K', fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define XELOGKERNEL(fmt, ...) XE_EMPTY_MACRO
|
||||
#endif
|
||||
#if XE_OPTION_LOG_FS
|
||||
#define XELOGFS(fmt, ...) XELOGCORE('F', fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define XELOGFS(fmt, ...) XE_EMPTY_MACRO
|
||||
#endif
|
||||
|
||||
} // namespace xe
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/string.h"
|
||||
|
||||
namespace xe {
|
||||
@@ -32,6 +33,9 @@ extern "C" int main(int argc, char** argv) {
|
||||
args.push_back(xe::to_wstring(argv[n]));
|
||||
}
|
||||
|
||||
// Initialize logging. Needs parsed FLAGS.
|
||||
xe::InitializeLogging(entry_info.name);
|
||||
|
||||
// Call app-provided entry point.
|
||||
int result = entry_info.entry_point(args);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <gflags/gflags.h>
|
||||
#include <io.h>
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/platform_win.h"
|
||||
#include "xenia/base/string.h"
|
||||
|
||||
@@ -84,6 +85,9 @@ int Main() {
|
||||
// NOTE: this may fail if COM has already been initialized - that's OK.
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
|
||||
// Initialize logging. Needs parsed FLAGS.
|
||||
xe::InitializeLogging(entry_info.name);
|
||||
|
||||
// Call app-provided entry point.
|
||||
int result = entry_info.entry_point(args);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
namespace xe {
|
||||
|
||||
RingBuffer::RingBuffer(uint8_t* buffer, size_t capacity)
|
||||
: buffer_(buffer), capacity_(capacity), read_offset_(0), write_offset_(0) {}
|
||||
: buffer_(buffer), capacity_(capacity) {}
|
||||
|
||||
size_t RingBuffer::Read(uint8_t* buffer, size_t count) {
|
||||
count = std::min(count, capacity_);
|
||||
@@ -37,7 +37,7 @@ size_t RingBuffer::Read(uint8_t* buffer, size_t count) {
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t RingBuffer::Write(uint8_t* buffer, size_t count) {
|
||||
size_t RingBuffer::Write(const uint8_t* buffer, size_t count) {
|
||||
count = std::min(count, capacity_);
|
||||
if (!count) {
|
||||
return 0;
|
||||
|
||||
@@ -20,14 +20,13 @@ class RingBuffer {
|
||||
public:
|
||||
RingBuffer(uint8_t* buffer, size_t capacity);
|
||||
|
||||
size_t Read(uint8_t* buffer, size_t count);
|
||||
size_t Write(uint8_t* buffer, size_t count);
|
||||
uint8_t* buffer() const { return buffer_; }
|
||||
size_t capacity() const { return capacity_; }
|
||||
bool empty() const { return read_offset_ == write_offset_; }
|
||||
|
||||
uint8_t* buffer() { return buffer_; }
|
||||
size_t capacity() { return capacity_; }
|
||||
|
||||
size_t read_offset() { return read_offset_; }
|
||||
size_t read_count() {
|
||||
size_t read_offset() const { return read_offset_; }
|
||||
void set_read_offset(size_t offset) { read_offset_ = offset % capacity_; }
|
||||
size_t read_count() const {
|
||||
if (read_offset_ == write_offset_) {
|
||||
return 0;
|
||||
} else if (read_offset_ < write_offset_) {
|
||||
@@ -37,8 +36,9 @@ class RingBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
size_t write_offset() { return write_offset_; }
|
||||
size_t write_count() {
|
||||
size_t write_offset() const { return write_offset_; }
|
||||
void set_write_offset(size_t offset) { write_offset_ = offset % capacity_; }
|
||||
size_t write_count() const {
|
||||
if (read_offset_ == write_offset_) {
|
||||
return capacity_;
|
||||
} else if (write_offset_ < read_offset_) {
|
||||
@@ -48,15 +48,23 @@ class RingBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
void set_read_offset(size_t offset) { read_offset_ = offset % capacity_; }
|
||||
size_t Read(uint8_t* buffer, size_t count);
|
||||
template <typename T>
|
||||
size_t Read(T* buffer, size_t count) {
|
||||
return Read(reinterpret_cast<uint8_t*>(buffer), count);
|
||||
}
|
||||
|
||||
void set_write_offset(size_t offset) { write_offset_ = offset % capacity_; }
|
||||
size_t Write(const uint8_t* buffer, size_t count);
|
||||
template <typename T>
|
||||
size_t Write(const T* buffer, size_t count) {
|
||||
return Write(reinterpret_cast<const uint8_t*>(buffer), count);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t* buffer_;
|
||||
size_t capacity_;
|
||||
size_t read_offset_;
|
||||
size_t write_offset_;
|
||||
uint8_t* buffer_ = nullptr;
|
||||
size_t capacity_ = 0;
|
||||
size_t read_offset_ = 0;
|
||||
size_t write_offset_ = 0;
|
||||
};
|
||||
|
||||
} // namespace xe
|
||||
|
||||
@@ -155,14 +155,14 @@ std::string fix_path_separators(const std::string& source, char new_sep) {
|
||||
return dest;
|
||||
}
|
||||
|
||||
std::string find_name_from_path(const std::string& path) {
|
||||
std::string find_name_from_path(const std::string& path, char sep) {
|
||||
std::string name(path);
|
||||
if (!path.empty()) {
|
||||
std::string::size_type from(std::string::npos);
|
||||
if (path.back() == '\\') {
|
||||
if (path.back() == sep) {
|
||||
from = path.size() - 2;
|
||||
}
|
||||
auto pos(path.find_last_of('\\', from));
|
||||
auto pos(path.find_last_of(sep, from));
|
||||
if (pos != std::string::npos) {
|
||||
if (from == std::string::npos) {
|
||||
name = path.substr(pos + 1);
|
||||
@@ -175,14 +175,14 @@ std::string find_name_from_path(const std::string& path) {
|
||||
return name;
|
||||
}
|
||||
|
||||
std::wstring find_name_from_path(const std::wstring& path) {
|
||||
std::wstring find_name_from_path(const std::wstring& path, wchar_t sep) {
|
||||
std::wstring name(path);
|
||||
if (!path.empty()) {
|
||||
std::wstring::size_type from(std::wstring::npos);
|
||||
if (path.back() == '\\') {
|
||||
if (path.back() == sep) {
|
||||
from = path.size() - 2;
|
||||
}
|
||||
auto pos(path.find_last_of('\\', from));
|
||||
auto pos(path.find_last_of(sep, from));
|
||||
if (pos != std::wstring::npos) {
|
||||
if (from == std::wstring::npos) {
|
||||
name = path.substr(pos + 1);
|
||||
@@ -195,12 +195,12 @@ std::wstring find_name_from_path(const std::wstring& path) {
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string find_base_path(const std::string& path) {
|
||||
auto last_slash = path.find_last_of('\\');
|
||||
std::string find_base_path(const std::string& path, char sep) {
|
||||
auto last_slash = path.find_last_of(sep);
|
||||
if (last_slash == std::string::npos) {
|
||||
return path;
|
||||
} else if (last_slash == path.length() - 1) {
|
||||
auto prev_slash = path.find_last_of('\\', last_slash - 1);
|
||||
auto prev_slash = path.find_last_of(sep, last_slash - 1);
|
||||
if (prev_slash == std::string::npos) {
|
||||
return "";
|
||||
} else {
|
||||
@@ -211,12 +211,12 @@ std::string find_base_path(const std::string& path) {
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring find_base_path(const std::wstring& path) {
|
||||
auto last_slash = path.find_last_of('\\');
|
||||
std::wstring find_base_path(const std::wstring& path, wchar_t sep) {
|
||||
auto last_slash = path.find_last_of(sep);
|
||||
if (last_slash == std::wstring::npos) {
|
||||
return path;
|
||||
} else if (last_slash == path.length() - 1) {
|
||||
auto prev_slash = path.find_last_of('\\', last_slash - 1);
|
||||
auto prev_slash = path.find_last_of(sep, last_slash - 1);
|
||||
if (prev_slash == std::wstring::npos) {
|
||||
return L"";
|
||||
} else {
|
||||
|
||||
@@ -45,12 +45,16 @@ std::string fix_path_separators(const std::string& source,
|
||||
char new_sep = xe::kPathSeparator);
|
||||
|
||||
// Find the top directory name or filename from a path.
|
||||
std::string find_name_from_path(const std::string& path);
|
||||
std::wstring find_name_from_path(const std::wstring& path);
|
||||
std::string find_name_from_path(const std::string& path,
|
||||
char sep = xe::kPathSeparator);
|
||||
std::wstring find_name_from_path(const std::wstring& path,
|
||||
wchar_t sep = xe::kPathSeparator);
|
||||
|
||||
// Get parent path of the given directory or filename.
|
||||
std::string find_base_path(const std::string& path);
|
||||
std::wstring find_base_path(const std::wstring& path);
|
||||
std::string find_base_path(const std::string& path,
|
||||
char sep = xe::kPathSeparator);
|
||||
std::wstring find_base_path(const std::wstring& path,
|
||||
wchar_t sep = xe::kPathSeparator);
|
||||
|
||||
} // namespace xe
|
||||
|
||||
|
||||
Reference in New Issue
Block a user