Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,7 @@ endif()

if(WIN32)
target_link_libraries(mmapper PRIVATE ws2_32)
target_link_libraries(mm_global PUBLIC ws2_32)
endif()

if(EMSCRIPTEN)
Expand Down
126 changes: 122 additions & 4 deletions src/global/io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

#ifdef Q_OS_WIN
#include "WinSock.h"

#include <io.h>
#include <windows.h>
#endif

namespace io {
Expand All @@ -29,7 +32,37 @@ ErrorNumberMessage::ErrorNumberMessage(const int error_number) noexcept
: m_error_number{error_number}
{
#ifdef Q_OS_WIN
/* nop */
LPWSTR messageBuffer = nullptr;
const DWORD size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
static_cast<DWORD>(error_number),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&messageBuffer),
0,
nullptr);
if (size > 0) {
const int res = WideCharToMultiByte(CP_UTF8,
0,
messageBuffer,
static_cast<int>(size),
m_buf,
static_cast<int>(sizeof(m_buf)) - 1,
nullptr,
nullptr);
if (res > 0) {
m_buf[res] = '\0';
// Trim trailing newlines and spaces
int len = res;
while (len > 0
&& (m_buf[len - 1] == '\r' || m_buf[len - 1] == '\n' || m_buf[len - 1] == ' '
|| m_buf[len - 1] == '\t' || m_buf[len - 1] == '.')) {
m_buf[--len] = '\0';
}
m_str = m_buf;
}
LocalFree(messageBuffer);
}
#elif defined(__GLIBC__)
/* GNU/Linux version can return a pointer to a static string */
m_str = ::strerror_r(error_number, m_buf, sizeof(m_buf));
Expand All @@ -41,13 +74,63 @@ ErrorNumberMessage::ErrorNumberMessage(const int error_number) noexcept
#endif
}

namespace { // anonymous
NODISCARD std::string getFriendlyMessage(const int error_number)
{
#ifdef Q_OS_WIN
switch (error_number) {
case ERROR_ACCESS_DENIED:
return "Access denied. You might not have permission to write to this location.";
case ERROR_SHARING_VIOLATION:
return "The file is being used by another process.";
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
return "The system cannot find the file or path specified.";
case ERROR_DISK_FULL:
return "The disk is full.";
case ERROR_WRITE_FAULT:
return "The system could not write to the specified device.";
default:
return "";
}
#else
switch (error_number) {
case EACCES:
return "Permission denied.";
case ENOENT:
return "No such file or directory.";
case ENOSPC:
return "No space left on device.";
case EBUSY:
return "Resource busy.";
case EROFS:
return "Read-only file system.";
default:
return "";
}
#endif
}
} // namespace

IOException IOException::withErrorNumber(const int error_number)
{
const std::string friendly = getFriendlyMessage(error_number);
if (const auto msg = ErrorNumberMessage{error_number}) {
return IOException{msg.getErrorMessage()};
const std::string sysMsg = msg.getErrorMessage();
if (friendly.empty()) {
return IOException{sysMsg};
}
if (friendly == sysMsg) {
return IOException{friendly};
}
return IOException{friendly + " (" + sysMsg + ")"};
}

return IOException{"unknown error_number: " + std::to_string(error_number)};
const std::string unknown = "unknown error code " + std::to_string(error_number);
if (!friendly.empty()) {
return IOException{friendly + " (" + unknown + ")"};
}
return IOException{unknown};
}

IOException IOException::withCurrentErrno()
Expand All @@ -61,7 +144,9 @@ bool fsync(QFile &file) CAN_THROW
{
const int handle = file.handle();
#ifdef Q_OS_WIN
return false;
if (::FlushFileBuffers(reinterpret_cast<HANDLE>(::_get_osfhandle(handle))) == 0) {
throw IOException::withErrorNumber(static_cast<int>(::GetLastError()));
}
#elif defined(Q_OS_MAC)
if (::fcntl(handle, F_FULLFSYNC) == -1) {
throw IOException::withCurrentErrno();
Expand All @@ -74,6 +159,39 @@ bool fsync(QFile &file) CAN_THROW
return true;
}

void rename(const QString &from, const QString &to) CAN_THROW
{
#ifdef Q_OS_WIN
const std::wstring fromW = from.toStdWString();
const std::wstring toW = to.toStdWString();
if (::ReplaceFileW(toW.c_str(),
fromW.c_str(),
nullptr,
REPLACEFILE_IGNORE_MERGE_ERRORS,
nullptr,
nullptr)
== 0) {
const auto err = ::GetLastError();
if (err == ERROR_FILE_NOT_FOUND) {
if (::MoveFileExW(fromW.c_str(),
toW.c_str(),
MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)
== 0) {
throw IOException::withErrorNumber(static_cast<int>(::GetLastError()));
}
} else {
throw IOException::withErrorNumber(static_cast<int>(err));
}
}
#else
const auto fromEncoded = QFile::encodeName(from);
const auto toEncoded = QFile::encodeName(to);
if (::rename(fromEncoded.data(), toEncoded.data()) == -1) {
throw IOException::withCurrentErrno();
}
#endif
}

IOResultEnum fsyncNoexcept(QFile &file) noexcept
{
try {
Expand Down
2 changes: 2 additions & 0 deletions src/global/io.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ static_assert(sizeof(ErrorNumberMessage) == 1024);

NODISCARD extern bool fsync(QFile &) CAN_THROW;

extern void rename(const QString &from, const QString &to) CAN_THROW;

NODISCARD extern IOResultEnum fsyncNoexcept(QFile &) noexcept;

NODISCARD extern bool tuneKeepAlive(qintptr socketDescriptor,
Expand Down
4 changes: 2 additions & 2 deletions src/global/mm_source_location.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

#include <cstdint>

#if __cplusplus >= 202000L \
&& __has_builtin(__builtin_source_location) // && __cpp_lib_source_location >= 201907L
#if __cplusplus >= 202000L && __has_builtin(__builtin_source_location)
// && __cpp_lib_source_location >= 201907L
#include <source_location>
namespace mm {
using source_location = std::source_location;
Expand Down
36 changes: 17 additions & 19 deletions src/group/groupwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -829,14 +829,7 @@ GroupWidget::GroupWidget(Mmapper2Group *const group, MapData *const md, QWidget
: QWidget(parent)
, m_group(group)
, m_map(md)
, m_model(this)
{
if (m_group) {
m_model.setCharacters(m_group->selectAll());
} else {
m_model.setCharacters({});
}

auto *layout = new QVBoxLayout(this);
layout->setAlignment(Qt::AlignTop);
layout->setContentsMargins(0, 0, 0, 0);
Expand All @@ -849,8 +842,15 @@ GroupWidget::GroupWidget(Mmapper2Group *const group, MapData *const md, QWidget
m_table->horizontalHeader()->setStretchLastSection(true);
m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);

m_proxyModel = new GroupProxyModel(this);
m_proxyModel->setSourceModel(&m_model);
m_model = new GroupModel(m_table);
if (m_group) {
m_model->setCharacters(m_group->selectAll());
} else {
m_model->setCharacters({});
}

m_proxyModel = new GroupProxyModel(m_table);
m_proxyModel->setSourceModel(m_model);
m_table->setModel(m_proxyModel);

m_table->setDragEnabled(true);
Expand All @@ -859,7 +859,7 @@ GroupWidget::GroupWidget(Mmapper2Group *const group, MapData *const md, QWidget
m_table->setDefaultDropAction(Qt::MoveAction);
m_table->setDropIndicatorShown(true);

m_table->setItemDelegate(new GroupDelegate(this));
m_table->setItemDelegate(new GroupDelegate(m_table));
layout->addWidget(m_table);

m_pulseTimer = new QTimer(this);
Expand Down Expand Up @@ -912,7 +912,7 @@ GroupWidget::GroupWidget(Mmapper2Group *const group, MapData *const md, QWidget
return;
}

selectedCharacter = m_model.getCharacter(sourceIndex.row());
selectedCharacter = deref(m_model).getCharacter(sourceIndex.row());
if (selectedCharacter) {
// Build Context menu
m_center->setText(
Expand Down Expand Up @@ -947,8 +947,6 @@ GroupWidget::GroupWidget(Mmapper2Group *const group, MapData *const md, QWidget
GroupWidget::~GroupWidget()
{
m_pulseTimer->stop();
delete m_table;
delete m_recolor;
}

QSize GroupWidget::sizeHint() const
Expand All @@ -964,7 +962,7 @@ void GroupWidget::updateColumnVisibility()
{
// Hide unnecessary columns like mana if everyone is a zorc/troll
const auto one_character_had_mana = [this]() -> bool {
for (const auto &character : m_model.getCharacters()) {
for (const auto &character : deref(m_model).getCharacters()) {
if (character && (character->getMana() > 0 || character->getMaxMana() > 0)) {
return true;
}
Expand All @@ -978,7 +976,7 @@ void GroupWidget::updateColumnVisibility()
void GroupWidget::updatePulseTimer()
{
const auto needs_pulse = [this]() -> bool {
for (const auto &character : m_model.getCharacters()) {
for (const auto &character : deref(m_model).getCharacters()) {
if (!character) {
continue;
}
Expand Down Expand Up @@ -1012,29 +1010,29 @@ void GroupWidget::updatePulseTimer()
void GroupWidget::slot_onCharacterAdded(SharedGroupChar character)
{
assert(character);
m_model.insertCharacter(character);
deref(m_model).insertCharacter(character);
updateColumnVisibility();
updatePulseTimer();
}

void GroupWidget::slot_onCharacterRemoved(const GroupId characterId)
{
assert(characterId != INVALID_GROUPID);
m_model.removeCharacterById(characterId);
deref(m_model).removeCharacterById(characterId);
updateColumnVisibility();
updatePulseTimer();
}

void GroupWidget::slot_onCharacterUpdated(SharedGroupChar character)
{
assert(character);
m_model.updateCharacter(character);
deref(m_model).updateCharacter(character);
updatePulseTimer();
}

void GroupWidget::slot_onGroupReset(const GroupVector &newCharacterList)
{
m_model.setCharacters(newCharacterList);
deref(m_model).setCharacters(newCharacterList);
updateColumnVisibility();
updatePulseTimer();
}
6 changes: 3 additions & 3 deletions src/group/groupwidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ class NODISCARD_QOBJECT GroupWidget final : public QWidget
Mmapper2Group *m_group = nullptr;
MapData *m_map = nullptr;
GroupProxyModel *m_proxyModel = nullptr;
GroupModel m_model;
GroupModel *m_model = nullptr;
QTimer *m_pulseTimer = nullptr;

void updateColumnVisibility();
Expand All @@ -174,8 +174,8 @@ class NODISCARD_QOBJECT GroupWidget final : public QWidget
void sig_center(glm::vec2);

public slots:
void slot_mapUnloaded() { m_model.setMapLoaded(false); }
void slot_mapLoaded() { m_model.setMapLoaded(true); }
void slot_mapUnloaded() { deref(m_model).setMapLoaded(false); }
void slot_mapLoaded() { deref(m_model).setMapLoaded(true); }

private slots:
void slot_onCharacterAdded(SharedGroupChar character);
Expand Down
Loading
Loading