- •1. Анализ задания и описание методов решения
- •2 Проектирование программного обеспечения
- •2.1 Проектирование графического интерфейса
- •2.2 Проектирование модуля сбора данных
- •3 Реализация программного обеспечения
- •3.1 Реализация структур данных
- •3.2 Реализация графического интерфейса
- •3.3 Реализация модуля сбора данных
- •4 Тестирование программного обеспечения
- •Список использованных источников
- •Приложение а Листинг программы
Список использованных источников
1. Павловская, Татьяна. С/С++. Программирование на языке высокого уровня. – СПб.: Питер, 2019. – 460 с.
2. Щупак Ю. А. Win32 API. Разработка приложения для Windows. – СПб.: Питер, 2010. – 590 с.
3. SetupDiGetClassDevs function (setupapi.h) - Win32 apps | Microsoft Docs. Режим доступа: https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetclassdevsw. Дата доступа: 03.05.2021 г.
4. SetupDiEnumDeviceInfo function (setupapi.h) - Win32 apps | Microsoft Docs. Режим доступа: https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdienumdeviceinfo. Дата доступа: 03.05.2021 г.
5. SetupDiGetClassDescription function (setupapi.h) - Win32 apps | Microsoft Docs. Режим доступа: https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetclassdescription. Дата доступа: 03.05.2021 г.
6. SetupDiGetDeviceProperty function (setupapi.h) - Win32 apps | Microsoft Docs. Режим доступа: https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceproperty. Дата доступа: 03.05.2021 г.
Приложение а Листинг программы
Листинг реализации основного модуля программы (main.cpp)
#include <QList>
#include <QTextStream>
#include <QtWidgets>
#include <iostream>
#include <unistd.h>
#include <widget.hpp>
const size_t SLEEP_DUR = 10; // ms
int main(int argc, char **argv) {
QApplication app(argc, argv);
GUI window;
window.setWindowTitle("Отслеживание директории");
window.resize(800, 400);
window.show();
int i = 0;
while (true && window.isVisible()) {
app.processEvents();
if (i % 50 == 0) { // Раз в полсекунды
window.render();
i = 0;
}
i++;
usleep(SLEEP_DUR * 1000);
}
app.exit();
return 0;
}
Листинг реализации модуля программы (monitor.hpp)
#pragma once
#include <files_monitor.hpp>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <sys/inotify.h>
#include <unordered_map>
#include <unordered_set>
using std::string;
using table_record = std::vector<QString>;
using table_records = std::vector<table_record>;
table_record gen_item(string path, string event) {
table_record rec;
rec.push_back(QString::fromStdString(path));
rec.push_back(QString::fromStdString(event));
return rec;
}
Syslog<> logger;
/// Этот класс отвечает
class Tracker {
public:
table_records get_records() {
table_records events;
while (true) {
try {
const inotify_event *eventp = inotify->read(1);
if (eventp == nullptr) {
break;
}
if (eventp->name != NULL && eventp->len > 1) { // Убрать метаобращения
events.push_back(gen_item(eventp->name, decode_event(eventp->mask)));
}
} catch (std::system_error &error) {
std::cout << "Error: " << error.code() << " - " << error.what() << '\n';
}
}
reverse(
events.begin(),
events.end()); // Последние события должны быть вверху таблицы (первые)
return events;
}
void change_dir(std::string path) {
inotify.reset(new Inotify<>(logger));
inotify->add_watch(path);
}
private:
string decode_event(uint32_t mask) {
if (IN_ACCESS & mask)
return "К файлу получили доступ";
if (IN_MODIFY & mask)
return "Файл был изменён";
if (IN_ATTRIB & mask)
return "Метадата файла была изменена";
if (IN_CLOSE & mask)
return "Файл был закрыт";
if (IN_OPEN & mask)
return "Файл был открыт";
if (IN_MOVE & mask)
return "Файл был перемещён";
if (IN_CREATE & mask)
return "Файл был создан";
if (IN_DELETE & mask)
return "Файл был удалён";
std::stringstream stream;
stream << "Неизвестное событие. Маска: ";
stream << std::hex << mask;
return stream.str();
}
std::unique_ptr<Inotify<>> inotify;
};
Листинг реализации модуля программы (widget.hpp)
#pragma once
#include <QAbstractTableModel>
#include <QFont>
#include <QLabel>
#include <QTableView>
#include <QVBoxLayout>
#include <QWidget>
#include <memory>
#include <monitor.hpp>
#include <sys/resource.h>
#include <sys/time.h>
class MyTable : public QAbstractTableModel {
public:
MyTable(QObject *parent = nullptr) : QAbstractTableModel(parent) {}
void add_events(table_records &events) {
beginInsertRows(QModelIndex(), recs.size(),
recs.size() + events.size() - 1);
recs.insert(recs.begin(), events.begin(), events.end());
endInsertRows();
}
void reset() {
beginResetModel();
this->recs.clear();
endResetModel();
}
private:
int rowCount(const QModelIndex &parent) const {
if (parent.isValid())
return 0;
return recs.size();
}
int columnCount(const QModelIndex &parent) const {
if (parent.isValid())
return 0;
return 2;
}
QVariant data(const QModelIndex &index, int role) const {
if (!index.isValid() || recs.empty())
return QVariant();
if (role == Qt::DisplayRole)
return recs.at(index.row()).at(index.column());
return QVariant();
}
QVariant headerData(int section, Qt::Orientation orientation,
int role) const {
if ((role == Qt::DisplayRole || role == Qt::ToolTipRole)) {
if (orientation == Qt::Horizontal) {
switch (section) {
case 0:
return (QString("FILE"));
case 1:
return (QString("EVENT"));
}
}
}
return QAbstractItemModel::headerData(section, orientation, role);
}
table_records recs;
};
class GUI : public QWidget {
public:
GUI() {
choose_dir_button =
std::make_unique<QPushButton>("Выбрать директорию", this);
connect(choose_dir_button.get(), &QPushButton::released, this,
&GUI::change_dir_click);
cur_dir = std::make_unique<QLineEdit>(this);
cur_dir->setReadOnly(true);
cur_dir->setPlaceholderText({"Выберите директорию для отслеживания"});
myModel = std::make_unique<MyTable>();
table = std::make_unique<QTableView>(this);
table->setModel(myModel.get());
table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
table->show();
vbox = std::make_unique<QVBoxLayout>(this);
vbox->addWidget(table.get());
vbox->addWidget(choose_dir_button.get());
vbox->addWidget(cur_dir.get());
setLayout(vbox.get());
}
void render() {
if (!dir.empty()) {
auto recs = tracker.get_records();
myModel->add_events(recs);
}
}
private:
void change_dir_click() {
QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::Directory);
dialog.setOption(QFileDialog::ShowDirsOnly, true);
int result = dialog.exec();
QString directory;
if (result) {
myModel->reset();
directory = dialog.selectedFiles()[0];
cur_dir->setText(directory);
dir = directory.toUtf8().constData();
tracker.change_dir(dir);
}
}
Tracker tracker;
std::unique_ptr<MyTable> myModel;
std::unique_ptr<QTableView> table;
std::unique_ptr<QPushButton> choose_dir_button;
std::unique_ptr<QVBoxLayout> vbox;
std::unique_ptr<QLineEdit> cur_dir;
std::string dir = "";
};
Листинг реализации модуля программы (syslog.hpp)
// Wrapper for syslog() system call in C++
// 01/20/18, dzchoi, feature complete
// How to use:
// - Define a global (or local) instance of Syslog object:
// Syslog<LOG_INFO> log;
// Syslog<> log; (setting LOG_ERR as a default priority)
// Syslog<> log("backupd", LOG_PID, LOG_DAEMON);
// - Write a log:
// log(LOG_WARNING, "errno=%d, strerror(errno)=%m", errno);
// log("..."); (using the default priority and the default
// facility) log(LOG_DAEMON, "..."); (using the default priority)
// log(LOG_INFO|LOG_DAEMON, "...");
// See http://hant.ask.helplib.com/c++/post_707188 for wrapping with the error
// stream
#ifndef SYSLOG_HPP
#define SYSLOG_HPP
#include <cstdarg> // va_list, va_start(), va_end()
extern "C" {
#include <syslog.h> // LOG_*, openlog(), closelog(), vsyslog(), setlogmask()
}
template <int Priority = LOG_ERR>
// Priority is the default priority to log with.
class Syslog { // being declared as a function object
// Should be used to define a single global object (though not forced), since
// the C openlog() keeps all its settings (ident, option, and facility, but
// not Priority) globally.
public:
Syslog(const char *ident, int option = LOG_ODELAY, int facility = LOG_USER) {
// The contents pointed to by ident is not backed up, so assumed to be
// unchanged across the entire program.
openlog(ident, option, facility);
}
Syslog() {}
// does not call openlog(), retaining any previous settings of openlog() from
// when constructing the last Syslog<> object.
~Syslog() { closelog(); }
void operator()(int priority, const char *format...) const;
// Note, the priority argument is formed by ORing together a facility value
// (like LOG_AUTH) and a level value (like LOG_ERR). If no facility value is
// ORed into the priority, then the default value set by openlog() is used,
// or, if there was no preceding openlog() call, a default of LOG_USER is
// employed.
template <typename... Args> // uses the default Priority
void operator()(const char *format, Args... args) const {
operator()(Priority, format, args...);
}
int setlogmask(int mask) { return ::setlogmask(mask); }
};
template <int Priority>
void Syslog<Priority>::operator()(int priority, const char *format...) const {
if (LOG_PRI(priority) == 0)
priority |= LOG_PRI(Priority);
va_list ap; // arg startup
va_start(ap, format);
vsyslog(priority, format, ap);
va_end(ap); // arg cleanup
}
#endif /* SYSLOG_HPP */
Листинг реализации модуля программы (files_monitor.hpp)
#ifndef INOTIFY_HPP
#define INOTIFY_HPP
#include "syslog.hpp"
#include <algorithm>
#include <chrono>
#include <cstring>
#include <experimental/filesystem>
#include <string>
#include <system_error>
#include <unordered_map>
#ifdef DEBUG
#include <cstdio>
#endif
extern "C" {
#include <poll.h>
#include <sys/inotify.h>
#include <unistd.h>
}
namespace fs = std::experimental::filesystem;
#ifdef DEBUG
#define printf(...) std::printf(__VA_ARGS__)
#else
#define printf(...)
#endif
template <typename CharT, typename Traits, typename Alloc>
inline std::basic_string<CharT, Traits, Alloc>
operator/(const std::basic_string<CharT, Traits, Alloc> &path1,
const CharT *path2)
{
return (fs::path(path1) / fs::path(path2)).string();
}
template <typename Log = Syslog<LOG_ERR>>
class Inotify {
const Log &log;
const int fd;
pollfd fds;
uint32_t mask;
struct Watch {
std::string path;
bool in_move;
};
std::unordered_map<int, Watch> watches;
inotify_event
buffer[(4 * 1024 + sizeof(inotify_event) - 1) / sizeof(inotify_event)];
int bytes_in_buffer = 0;
int bytes_handled = 0;
public:
Inotify(const Log &log, uint32_t mask = IN_ALL_EVENTS)
: log{log}, fd{inotify_init1(IN_NONBLOCK)}, fds{fd, POLLIN}, mask{mask} {
if (fd == -1)
throw std::system_error(errno, std::system_category());
}
~Inotify() { close(fd); }
const std::string &path(int wd) const { return watches.at(wd).path; }
int add_watch(const std::string &, bool = true);
void rm_watch(int wd) noexcept;
void rm_all_watches() noexcept;
const inotify_event *read(int timeout = (-1), int read_delay = 0);
};
template <typename Log>
int Inotify<Log>::add_watch(const std::string &path, bool in_move)
{
const bool recursive = path.back() != '/';
const int wd =
inotify_add_watch(fd, path.c_str(),
mask | IN_ONLYDIR | IN_MOVE_SELF |
(recursive ? IN_CREATE | IN_MOVED_TO : 0));
if (wd == -1) {
log("Warning: Cannot watch \"%s\": %s", path.c_str(), std::strerror(errno));
return wd;
}
const auto it = watches.find(wd);
if (it == watches.end()) {
printf("[%d] %s created\n", wd, path.c_str());
watches.emplace(wd, Watch{path, false});
}
else {
const std::string &path0 = it->second.path;
const auto &diff = std::mismatch(path0.begin(), path0.end(), path.begin());
if (diff.first == path0.end() &&
(diff.second == path.end() ||
!recursive && diff.second + 1 == path.end())) {
printf("[%d] %s ignored as a duplicate\n", wd, path.c_str());
return wd;
}
printf("[%d] %s %s\n", wd, path.c_str(),
diff.second == path.end() && diff.first + 1 == path0.end() &&
*diff.first == '/'
? "changed to recursive"
: "moved");
it->second.path = path;
}
if (in_move) {
if (recursive)
for (const fs::path &subdir : fs::directory_iterator(path))
if (fs::is_directory(subdir))
add_watch(subdir.string(), true);
}
else {
for (const fs::path &path : fs::directory_iterator(path))
if (!fs::is_other(path)) {
const bool isdir = fs::is_directory(path);
if (mask & IN_CREATE || isdir && recursive) {
const std::string &name = path.filename().string();
const uint32_t len =
(name.size() + sizeof(int)) / sizeof(int) * sizeof(int);
inotify_event &event =
*(inotify_event *)((char *)buffer + bytes_in_buffer);
bytes_in_buffer += sizeof(inotify_event) + len;
if (bytes_in_buffer > sizeof(buffer)) {
log("Error: add_watch() - Buffer underruns");
throw std::system_error(EINVAL, std::system_category());
}
event.wd = wd;
event.mask = isdir ? IN_ISDIR | IN_CREATE : IN_CREATE;
event.cookie = 0;
event.len = len;
name.copy(event.name, len);
event.name[name.size()] = '\0';
}
}
}
return wd;
}
template <typename Log>
void Inotify<Log>::rm_watch(int wd) noexcept
{
if (inotify_rm_watch(fd, wd))
log("Warning: inotify_rm_watch():%d - %s", errno, std::strerror(errno));
}
template <typename Log>
void Inotify<Log>::rm_all_watches() noexcept
{
for (const auto &it : watches)
rm_watch(it.first);
}
template <typename Log>
const inotify_event *Inotify<Log>::read(int timeout, int read_delay)
{
const auto then = std::chrono::system_clock::now();
if (bytes_in_buffer == 0) {
const char *where;
where = "poll()";
switch (poll(&fds, 1, timeout)) {
case 0:
return nullptr;
default:
where = "usleep()";
if (usleep(read_delay * 1000u) != -1) {
where = "read()";
bytes_in_buffer = ::read(fd, buffer, sizeof(buffer));
if (bytes_in_buffer > 0)
break;
if (bytes_in_buffer == 0)
errno = EIO;
}
case -1:
log("Error: %s:%d - %s", where, errno, std::strerror(errno));
throw std::system_error(errno, std::system_category());
}
}
do {
const inotify_event &event =
*(inotify_event *)((char *)buffer + bytes_handled);
bytes_handled += sizeof(inotify_event) + event.len;
if (bytes_handled >= bytes_in_buffer) {
if (bytes_handled > bytes_in_buffer) {
log("Error: read() - Incomplete event returned");
throw std::system_error(EINVAL, std::system_category());
}
bytes_handled = bytes_in_buffer = 0;
}
const auto it = watches.find(event.wd);
if (it == watches.end()) {
log("Error: read() - Event for unknown wd [%d] possibly due to "
"IN_Q_OVERFLOW",
event.wd);
throw std::system_error(EINVAL, std::system_category());
}
Watch &watch = it->second;
printf("- [%d] %s (%#x)\n", event.wd,
(event.len ? watch.path / event.name : watch.path).c_str(),
event.mask);
if (event.mask & (IN_CREATE | IN_MOVED_TO) && event.mask & IN_ISDIR &&
watch.path.back() != '/') {
const bool in_move = event.mask & IN_MOVED_TO;
const int wd = add_watch(watch.path / event.name, in_move);
if (in_move && wd >= 0 /* != -1 */)
watches.at(wd).in_move = true;
}
if (event.mask & IN_MOVE_SELF) {
if (watch.in_move)
watch.in_move = false;
else if (watch.path.back() == '/')
rm_watch(event.wd);
else
for (const auto &it : watches)
if (it.second.path.compare(0, watch.path.size(), watch.path) == 0)
rm_watch(it.first);
}
if (event.mask & IN_IGNORED) {
printf("[%d] %s deleted\n", event.wd, watch.path.c_str());
watches.erase(event.wd);
}
if (event.mask & mask)
return &event;
} while (bytes_handled > 0);
const int time_elapsed =
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now() - then)
.count();
if (timeout < 0 || (timeout -= time_elapsed) > 0)
return read(timeout, read_delay);
return nullptr;
}
#endif