monitor texture and shader file writes using inotify: improved resource management
/**
* ShaderShark
* Copyright © 2023 František Kučera (Frantovo.cz, GlobalCode.info)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string>
#include <stdexcept>
#include <map>
#include <sys/inotify.h>
#include <string.h>
#include <unistd.h>
#include "FileMonitor.h"
void checkError(bool hasError, const std::string& message) {
if (hasError) throw std::logic_error(message + strerror(errno));
}
class WatchedFile::Impl {
public:
int fd;
int wd;
std::string fileName;
uint32_t mask;
std::shared_ptr<FileMonitor::Impl> fileMonitorImpl;
virtual ~Impl();
};
WatchedFile::WatchedFile() {
impl = std::make_shared<Impl>();
}
WatchedFile::~WatchedFile() {
}
class FileMonitor::Impl {
public:
int fd;
std::map<int, std::string> fileNames;
void unwatch(int wd) {
fileNames.erase(wd);
int result = inotify_rm_watch(fd, wd);
checkError(result < 0, "unable to stop monitoring a file: ");
}
virtual ~Impl() {
close(fd);
}
};
FileMonitor::FileMonitor() : FileMonitor(std::make_shared<Impl>()) {
}
FileMonitor::FileMonitor(std::shared_ptr<Impl> impl) {
this->impl = impl;
impl->fd = inotify_init1(IN_NONBLOCK);
checkError(impl->fd < 0, "unable to initialize FileMonitor: ");
}
FileMonitor::~FileMonitor() {
}
int FileMonitor::getFD() const {
return impl->fd;
}
WatchedFile FileMonitor::watch(const std::string& fileName) {
return watch(fileName, IN_CLOSE_WRITE);
}
WatchedFile FileMonitor::watch(const std::string& fileName, uint32_t mask) {
int wd = inotify_add_watch(impl->fd, fileName.c_str(), mask);
checkError(wd < 0, "unable to monitor a file: ");
impl->fileNames[wd] = fileName;
WatchedFile wf;
wf.impl->fd = impl->fd;
wf.impl->wd = wd;
wf.impl->fileMonitorImpl = impl;
wf.impl->fileName = fileName;
wf.impl->mask = mask;
return wf;
}
bool FileMonitor::readEvent(FileEvent& event) {
struct inotify_event rawEvent;
int result = ::read(impl->fd, &rawEvent, sizeof (rawEvent));
if (result == sizeof (rawEvent)) {
event.fileName = impl->fileNames[rawEvent.wd];
event.mask = rawEvent.mask;
return true;
} else {
return false;
}
}
WatchedFile::Impl::~Impl() {
try {
fileMonitorImpl->unwatch(wd);
} catch (const std::exception& e) {
// ignored, fails after file deletion
// std::cerr << "unwatch failed: " << e.what() << std::endl;
}
}
FileMonitor WatchedFile::getFileMonitor() const {
return FileMonitor(impl->fileMonitorImpl);
}
std::string WatchedFile::getFileName() const {
return impl->fileName;
}
uint32_t WatchedFile::getMask() const {
return impl->mask;
}