Skeleton of application, at least compiles
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
build/
|
||||||
|
defrost.cfg
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
#include "BaseListeningThread.h"
|
||||||
|
|
||||||
|
#include <typeinfo>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <sys/file.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "libdiff.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
BaseListeningThread::BaseListeningThread(LogFile *log, int port) : log(log), port(port)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseListeningThread::Run()
|
||||||
|
{
|
||||||
|
string name = typeid(*this).name();
|
||||||
|
bool first_time = true;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (first_time) first_time = false;
|
||||||
|
else sleep(5);
|
||||||
|
try {
|
||||||
|
// сокет, на котором мы слушаем соединения
|
||||||
|
int listensock;
|
||||||
|
// создаем сокет для приема соединений
|
||||||
|
if ((listensock = socket(PF_INET, SOCK_STREAM, 0)) == -1)
|
||||||
|
{
|
||||||
|
log->LogString("Could not create socket for listening...");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// задаем опцию REUSEADDR на сокете. Это нужно для его пересозданияв случае, когда
|
||||||
|
// прием соединений временно прекращается
|
||||||
|
if (setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR, (&SOCKET_OPTION_TRUE), sizeof(SOCKET_OPTION_TRUE)) == -1)
|
||||||
|
{
|
||||||
|
log->LogString("Could not set socket option...");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in listenaddr;
|
||||||
|
|
||||||
|
// Заполнить структуру данных с адресом сокета
|
||||||
|
memset(&listenaddr, 0, sizeof(listenaddr));
|
||||||
|
listenaddr.sin_family = AF_INET;
|
||||||
|
listenaddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
listenaddr.sin_port = htons(port);
|
||||||
|
|
||||||
|
// привязываем сокет к адресу
|
||||||
|
if (bind(listensock, (struct sockaddr *)(&listenaddr), sizeof(struct sockaddr_in)) == -1)
|
||||||
|
{
|
||||||
|
log->LogString("Could not bind listening socket to port " + ToString(port));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(listen(listensock, LISTEN_BACKLOG) == -1)
|
||||||
|
{
|
||||||
|
log->LogString("Could not set socket into listening mode...");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in peeraddr;
|
||||||
|
socklen_t paddrlen;
|
||||||
|
paddrlen = sizeof(peeraddr);
|
||||||
|
int recsock;
|
||||||
|
log->LogString(name + " accepting connections on port " + ToString(port));
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
(recsock = accept(listensock, (struct sockaddr *)(&peeraddr), &paddrlen));
|
||||||
|
if (recsock == -1)
|
||||||
|
{
|
||||||
|
log->LogString("Error accepting connection, errno = " + ToString(errno));
|
||||||
|
// поспим немного, чтобы не ломать ничего лишнего и не заспамливать логи
|
||||||
|
sleep(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// напишем чего-нибудь про пришедшее соединение
|
||||||
|
log->LogString(name + " accepted connection from " + ToString(inet_ntoa(peeraddr.sin_addr)) + ":" + ToString(ntohs(peeraddr.sin_port)));
|
||||||
|
OnNewClient(recsock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (string s)
|
||||||
|
{
|
||||||
|
log->LogString("Exception in " + name + ": " + s);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
log->LogString("Unknown exception in " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef DATA_PROCESSOR_BASE_LISTENING_THREAD_H
|
||||||
|
#define DATA_PROCESSOR_BASE_LISTENING_THREAD_H
|
||||||
|
|
||||||
|
#include "Thread.h"
|
||||||
|
#include "liblog.h"
|
||||||
|
|
||||||
|
class BaseListeningThread : public ftc::Thread
|
||||||
|
{
|
||||||
|
const int SOCKET_OPTION_TRUE = 1;
|
||||||
|
const int LISTEN_BACKLOG = 100;
|
||||||
|
|
||||||
|
int port;
|
||||||
|
protected:
|
||||||
|
LogFile *log;
|
||||||
|
virtual void OnNewClient(int socket) = 0;
|
||||||
|
virtual void Run() override;
|
||||||
|
public:
|
||||||
|
BaseListeningThread(LogFile *log, int port);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.0)
|
||||||
|
project(defrost)
|
||||||
|
|
||||||
|
set(SOURCE_FILES
|
||||||
|
BaseListeningThread.h
|
||||||
|
BaseListeningThread.cpp
|
||||||
|
|
||||||
|
ModbusServiceListenThread.h
|
||||||
|
ModbusServiceListenThread.cpp
|
||||||
|
ModbusServiceWorker.h
|
||||||
|
ModbusServiceWorker.cpp
|
||||||
|
|
||||||
|
SynchronousSocket.h
|
||||||
|
SynchronousSocket.cpp
|
||||||
|
|
||||||
|
Thread.h
|
||||||
|
Thread.cpp
|
||||||
|
|
||||||
|
configparser.h
|
||||||
|
configparser.cpp
|
||||||
|
globals.h
|
||||||
|
globals.cpp
|
||||||
|
|
||||||
|
defrost.cpp
|
||||||
|
|
||||||
|
libdiff.h
|
||||||
|
libdiff.cpp
|
||||||
|
liblog.h
|
||||||
|
liblog.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
link_libraries(pthread)
|
||||||
|
add_executable(defrost ${SOURCE_FILES} ./defrost.cpp)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#include "ModbusServiceListenThread.h"
|
||||||
|
#include "ModbusServiceWorker.h"
|
||||||
|
|
||||||
|
ModbusServiceListenThread::ModbusServiceListenThread(LogFile *mainlog, int port)
|
||||||
|
: BaseListeningThread(mainlog, port)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusServiceListenThread::OnNewClient(int socket)
|
||||||
|
{
|
||||||
|
ModbusServiceWorker* worker = new ModbusServiceWorker(socket, log);
|
||||||
|
// worker удалится внутри класса Thread, так что можем на него с чистой совестью забить
|
||||||
|
Thread::Perform(worker);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef MODBUSSERVICELISTENTHREAD_H
|
||||||
|
#define MODBUSSERVICELISTENTHREAD_H
|
||||||
|
|
||||||
|
#include "liblog.h"
|
||||||
|
#include "BaseListeningThread.h"
|
||||||
|
|
||||||
|
using namespace ftc;
|
||||||
|
|
||||||
|
class ModbusServiceListenThread : public BaseListeningThread
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
virtual void OnNewClient(int socket) override;
|
||||||
|
public:
|
||||||
|
ModbusServiceListenThread(LogFile *log, int port);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FORMULASERVICELISTENTHREAD_H
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
#include "ModbusServiceWorker.h"
|
||||||
|
#include "libdiff.h"
|
||||||
|
#include "globals.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
ModbusServiceWorker::ModbusServiceWorker(int socket, LogFile *log)
|
||||||
|
: socket(socket), log(log)
|
||||||
|
{
|
||||||
|
s.Init(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusServiceWorker::Run()
|
||||||
|
{
|
||||||
|
log->LogString("Started ModbusServiceWorker");
|
||||||
|
try {
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
string header = s.Receive(7);
|
||||||
|
string transaction_id = header.substr(0, 2);
|
||||||
|
string protocol_id = header.substr(2, 2);
|
||||||
|
int length = ((int)(unsigned char)header[5]) | (((int)(unsigned char)header[4]) << 8);
|
||||||
|
string unit_identifier = header.substr(6, 1);
|
||||||
|
log->LogString("Receiving modbus frame of length " + ToString(length));
|
||||||
|
string data = s.Receive(length - 1);
|
||||||
|
log->LogString("Received modbus frame of length " + ToString(length));
|
||||||
|
string answer_data = "";
|
||||||
|
int function = data[0];
|
||||||
|
log->LogString("Processing function " + ToString(function));
|
||||||
|
if (function == 3)
|
||||||
|
{
|
||||||
|
LockMutex(register_mutex);
|
||||||
|
int address = GetInt(data.substr(1, 2));
|
||||||
|
int amount = GetInt(data.substr(3, 2));
|
||||||
|
string result = "";
|
||||||
|
for (int a = address; a < address + amount; a++)
|
||||||
|
{
|
||||||
|
int val = 0;
|
||||||
|
if (a >= 0 && a <= 65535) val = registers[a];
|
||||||
|
result += (char)((val >> 8) & 0xFF);
|
||||||
|
result += (char)(val & 0xFF);
|
||||||
|
}
|
||||||
|
ReleaseMutex(register_mutex);
|
||||||
|
answer_data += (char)3;
|
||||||
|
answer_data += (char)result.length();
|
||||||
|
answer_data += result;
|
||||||
|
}
|
||||||
|
else if (function == 6)
|
||||||
|
{
|
||||||
|
LockMutex(register_mutex);
|
||||||
|
int address = GetInt(data.substr(1, 2));
|
||||||
|
int value = GetInt(data.substr(3, 2));
|
||||||
|
if (address >= 0 && address <= 65535)
|
||||||
|
{
|
||||||
|
registers[address] = value;
|
||||||
|
}
|
||||||
|
ReleaseMutex(register_mutex);
|
||||||
|
answer_data = data;
|
||||||
|
}
|
||||||
|
else throw (string)("Unknown function " + ToString(function));
|
||||||
|
answer_data = unit_identifier + answer_data;
|
||||||
|
string length_str = "";
|
||||||
|
length_str += (char)((answer_data.length() >> 8) & 0xFF);
|
||||||
|
length_str += (char)((answer_data.length()) & 0xFF);
|
||||||
|
log->LogString("Sending answer");
|
||||||
|
s.Send(transaction_id + protocol_id + length_str + answer_data);
|
||||||
|
log->LogString("Answer sent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (string s)
|
||||||
|
{
|
||||||
|
log->LogString("Exception in ModbusServiceWorker: " + s);
|
||||||
|
}
|
||||||
|
catch (std::exception e)
|
||||||
|
{
|
||||||
|
log->LogString("Exception in ModbusServiceWorker: " + ToString(e.what()));
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
log->LogString("Unknown exception in ModbusServiceWorker");
|
||||||
|
// непонятно, что случилось, закрываем сокет и все дела
|
||||||
|
}
|
||||||
|
s.Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ModbusServiceWorker::GetInt(const string &s)
|
||||||
|
{
|
||||||
|
int res = 0;
|
||||||
|
for (int i = 0; i < (int)s.length(); i++)
|
||||||
|
{
|
||||||
|
res <<= 8;
|
||||||
|
res += (int)(unsigned char)s[i];
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModbusServiceWorker::~ModbusServiceWorker()
|
||||||
|
{
|
||||||
|
s.Disconnect();
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef MODBUS_SERVICE_WORKER_H
|
||||||
|
#define MODBUS_SERVICE_WORKER_H
|
||||||
|
|
||||||
|
#include "Thread.h"
|
||||||
|
#include "SynchronousSocket.h"
|
||||||
|
#include "liblog.h"
|
||||||
|
|
||||||
|
using namespace ftc;
|
||||||
|
|
||||||
|
class ModbusServiceWorker : public Thread
|
||||||
|
{
|
||||||
|
int socket;
|
||||||
|
SynchronousSocket s;
|
||||||
|
LogFile *log;
|
||||||
|
|
||||||
|
int GetInt(const std::string &s);
|
||||||
|
protected:
|
||||||
|
virtual void Run();
|
||||||
|
public:
|
||||||
|
ModbusServiceWorker(int socket, LogFile *log);
|
||||||
|
virtual ~ModbusServiceWorker();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,3 +1,17 @@
|
|||||||
# defrost_v2
|
Простенькое управляемое реле.
|
||||||
|
|
||||||
|
При запуске поднимает modbus tcp сервер, с которым можно общаться и управлять (а также читать текущие показания).
|
||||||
|
|
||||||
|
Список регистров:
|
||||||
|
|
||||||
|
* 0 - Температура включения реле (если ниже, включится) (в 0.01 градуса)
|
||||||
|
* 1 - Температура выключения реле (если выше, выключится) (в 0.01 градуса)
|
||||||
|
* 2 - Режим работы (0 - авто, 1 - вручную)
|
||||||
|
* 3 - Текущая температура (в 0.01 градуса)
|
||||||
|
* 4 - Текущее состояние реле (0 - выключено, 1 - включено)
|
||||||
|
* 5 - Дополнительная температура (второй датчик)
|
||||||
|
|
||||||
|
Используется вот такой термистор
|
||||||
|
https://files.amperka.ru/datasheets/B57164.pdf
|
||||||
|
Из них тот у которого 10кОм при 25градусах
|
||||||
|
|
||||||
Новая версия размораживалки (для работы на обычном ПК)
|
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
#include "SynchronousSocket.h"
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
SynchronousSocket::SynchronousSocket()
|
||||||
|
{
|
||||||
|
socket = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynchronousSocket::Init(int s)
|
||||||
|
{
|
||||||
|
socket = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SynchronousSocket::IsConnected()
|
||||||
|
{
|
||||||
|
return (socket != -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynchronousSocket::Connect(string host, int port)
|
||||||
|
{
|
||||||
|
// уже подключены?
|
||||||
|
if (socket != -1) return;
|
||||||
|
|
||||||
|
struct sockaddr_in remoteaddr;
|
||||||
|
|
||||||
|
struct addrinfo *addresses;
|
||||||
|
struct addrinfo hints;
|
||||||
|
memset(&hints, 0, sizeof(hints));
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
if (getaddrinfo(host.c_str(), NULL, NULL, &addresses) != 0)
|
||||||
|
{
|
||||||
|
throw (string)("Unable to getaddrinfo");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// копипастим себе самый первый адрес (в принципе, нам любой норм)
|
||||||
|
memcpy(&remoteaddr, addresses[0].ai_addr, sizeof(remoteaddr));
|
||||||
|
remoteaddr.sin_port = htons(port);
|
||||||
|
freeaddrinfo(addresses);
|
||||||
|
// создаем сокет
|
||||||
|
if ((socket = ::socket(PF_INET, SOCK_STREAM, 0)) == -1)
|
||||||
|
{
|
||||||
|
throw (string)("Unable to create socket");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (connect(socket, (struct sockaddr *)(&remoteaddr), sizeof(struct sockaddr_in)) == -1)
|
||||||
|
{
|
||||||
|
if (errno != EINPROGRESS)
|
||||||
|
{
|
||||||
|
socket = -1;
|
||||||
|
throw (string)("Unable to connect");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynchronousSocket::Send(const string & data)
|
||||||
|
{
|
||||||
|
int pos = 0;
|
||||||
|
time_t start = time(NULL);
|
||||||
|
while (pos < (int)data.length())
|
||||||
|
{
|
||||||
|
int res = send(socket, &((data.data())[pos]), data.length() - pos, 0);
|
||||||
|
if (res == -1)
|
||||||
|
{
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||||
|
{
|
||||||
|
if (time(NULL) - start > TIMEOUT)
|
||||||
|
{
|
||||||
|
throw (string)("Timeout");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// сокет поломался - закрываем его
|
||||||
|
throw (string)("Socket closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pos += res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string SynchronousSocket::Receive(int amount)
|
||||||
|
{
|
||||||
|
string r = "";
|
||||||
|
time_t start = time(NULL);
|
||||||
|
while ((int)r.length() < amount)
|
||||||
|
{
|
||||||
|
char buf[1024];
|
||||||
|
int to_recv = min((int)sizeof(buf), amount - (int)r.length());
|
||||||
|
int res = recv(socket, buf, to_recv, 0);
|
||||||
|
if (res == 0)
|
||||||
|
{
|
||||||
|
// бросаем специфический exception, означающий, что сокет закрыт
|
||||||
|
throw (string)("Socket closed");
|
||||||
|
}
|
||||||
|
else if (res == -1)
|
||||||
|
{
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||||
|
{
|
||||||
|
if (time(NULL) - start > TIMEOUT)
|
||||||
|
{
|
||||||
|
throw (string)("Timeout");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// что-то сломалось, надо закрыть сокет на всякий случай
|
||||||
|
// бросаем специфический exception, означающий, что сокет закрыт
|
||||||
|
throw (string)("Socket closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < res; i++) r += buf[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynchronousSocket::Disconnect()
|
||||||
|
{
|
||||||
|
if (socket == -1) return;
|
||||||
|
close(socket);
|
||||||
|
socket = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
string SynchronousSocket::ReceiveMessage()
|
||||||
|
{
|
||||||
|
string len_s = Receive(4);
|
||||||
|
int len = 0;
|
||||||
|
for (size_t i = 0; i < len_s.length(); i++)
|
||||||
|
{
|
||||||
|
len |= ((unsigned char)len_s[i]) << (8 * i);
|
||||||
|
}
|
||||||
|
return Receive(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SynchronousSocket::SendMessage(const string & m)
|
||||||
|
{
|
||||||
|
int len = m.length();
|
||||||
|
string len_s = "";
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
len_s += (char)((len >> (8 * i)) & 0xFF);
|
||||||
|
}
|
||||||
|
Send(len_s);
|
||||||
|
Send(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef SYNCHRONOUS_SOCKET_HEADER_FILE_INCLUDED
|
||||||
|
#define SYNCHRONOUS_SOCKET_HEADER_FILE_INCLUDED
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
class SynchronousSocket
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
const int TIMEOUT = 10; // in seconds
|
||||||
|
protected:
|
||||||
|
// собственно сокет
|
||||||
|
int socket;
|
||||||
|
public:
|
||||||
|
SynchronousSocket();
|
||||||
|
// Используется для проброса уже готового сокета и работы с ним
|
||||||
|
void Init(int s);
|
||||||
|
bool IsConnected();
|
||||||
|
virtual void Connect(string host, int port);
|
||||||
|
virtual void Send(const string &data);
|
||||||
|
virtual string Receive(int amount);
|
||||||
|
void Disconnect();
|
||||||
|
string ReceiveMessage();
|
||||||
|
void SendMessage(const string &m);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
#include "Thread.h"
|
||||||
|
|
||||||
|
void * ftc::Thread::DoRun(void * arg)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
((Thread*)arg)->is_running = true;
|
||||||
|
((Thread*)arg)->Run();
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
|
||||||
|
}
|
||||||
|
((Thread*)arg)->is_running = false;
|
||||||
|
if (((Thread*)arg)->delete_after_finish)
|
||||||
|
{
|
||||||
|
delete ((Thread*)arg);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftc::Thread::Run()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ftc::Thread::Start()
|
||||||
|
{
|
||||||
|
pthread_attr_t attr;
|
||||||
|
if (pthread_attr_init(&attr) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
is_running = true;
|
||||||
|
if (pthread_create(&thread, &attr, &Thread::DoRun, this) != 0)
|
||||||
|
{
|
||||||
|
is_running = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftc::Thread::Perform(Thread *work)
|
||||||
|
{
|
||||||
|
work->delete_after_finish = true;
|
||||||
|
work->Start();
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef CORE_THREAD_HEADER_FILE_INCLUDED
|
||||||
|
#define CORE_THREAD_HEADER_FILE_INCLUDED
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
namespace ftc
|
||||||
|
{
|
||||||
|
class Thread
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
pthread_t thread;
|
||||||
|
bool is_running = false;
|
||||||
|
bool delete_after_finish = false;
|
||||||
|
static void* DoRun(void *arg);
|
||||||
|
protected:
|
||||||
|
// Основная функция потока, переопределите в дочерних классах
|
||||||
|
virtual void Run();
|
||||||
|
public:
|
||||||
|
bool IsRunning() { return is_running; }
|
||||||
|
virtual ~Thread() {}
|
||||||
|
bool Start();
|
||||||
|
// Запускает поток, удаляя его после завершения
|
||||||
|
static void Perform(Thread *work);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,463 @@
|
|||||||
|
#include "configparser.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
const int OPERATIONSET = 0;
|
||||||
|
const int OPERATIONADD = 1;
|
||||||
|
const int OPERATION_MERGE = 2;
|
||||||
|
|
||||||
|
// конструктор ConfigParam
|
||||||
|
ConfigParam::ConfigParam()
|
||||||
|
{
|
||||||
|
name = "";
|
||||||
|
this->val.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// создаем ConfigParam из строки
|
||||||
|
ConfigParam::ConfigParam(string s)
|
||||||
|
{
|
||||||
|
name = s;
|
||||||
|
this->val.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// создаем ConfigParam по имени и значению
|
||||||
|
ConfigParam::ConfigParam(string name, string value)
|
||||||
|
{
|
||||||
|
this->name = name;
|
||||||
|
this->val.clear();
|
||||||
|
ConfigValue v;
|
||||||
|
v.type = CONFIGVALUETYPESTRING;
|
||||||
|
v.sval = value;
|
||||||
|
this->val.push_back(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfigParser::IsDelim(char c)
|
||||||
|
{
|
||||||
|
if (IsWS(c)) return true;
|
||||||
|
if ((c == '{') || (c == '}')) return true;
|
||||||
|
if ((c == '[') || (c == ']')) return true;
|
||||||
|
if (c == ',') return true;
|
||||||
|
if (c == ';') return true;
|
||||||
|
if (c == '#') return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const ConfigParam &a, const ConfigParam &b)
|
||||||
|
{
|
||||||
|
return a.name < b.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
string ConfigParser::i2s(int a)
|
||||||
|
{
|
||||||
|
ostringstream oss;
|
||||||
|
oss << a;
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Является ли символ whitespace-ом
|
||||||
|
bool ConfigParser::IsWS(char c)
|
||||||
|
{
|
||||||
|
return ((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Можно ли такие символы использовать в названии параметров
|
||||||
|
bool ConfigParser::GoodForName(char c)
|
||||||
|
{
|
||||||
|
if (IsWS(c)) return false;
|
||||||
|
if ((c == '[') || (c == ']')) return false;
|
||||||
|
if ((c == '{') || (c == '}')) return false;
|
||||||
|
if ((c == '(') || (c == ')')) return false;
|
||||||
|
if (c == '"') return false;
|
||||||
|
if (c == '\\') return false;
|
||||||
|
if (c == '+') return false;
|
||||||
|
if (c == '=') return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// возвращает символ, соответствующий escape-последовательности "\c"
|
||||||
|
char ConfigParser::UnSlash(char c)
|
||||||
|
{
|
||||||
|
if (c == 'n') return '\n';
|
||||||
|
if (c == '\\') return '\\';
|
||||||
|
if (c == '\"') return '"';
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// разбирает s с l по r, считая, что она должна быть ConfigParam - ом
|
||||||
|
// возвращает, какая операция была применена
|
||||||
|
int ConfigParser::ParseConfigParam(int &l, int &r, ConfigParam &p)
|
||||||
|
{
|
||||||
|
while ((l <= r) && (IsWS(s[l])))
|
||||||
|
{
|
||||||
|
if (s[l] == '\n') cline++;
|
||||||
|
l++;
|
||||||
|
}
|
||||||
|
if (l > r)
|
||||||
|
throw "Expected parameter at line " + i2s(cline);
|
||||||
|
if (!GoodForName(s[l]))
|
||||||
|
{
|
||||||
|
throw "Parameter name expected at line " + i2s(cline);
|
||||||
|
}
|
||||||
|
p.name = "";
|
||||||
|
while (GoodForName(s[l]))
|
||||||
|
{
|
||||||
|
if (l == r) throw "Unfinished param at line " + i2s(cline);
|
||||||
|
p.name += s[l];
|
||||||
|
l++;
|
||||||
|
}
|
||||||
|
while ((l <= r) && (IsWS(s[l])))
|
||||||
|
{
|
||||||
|
if (s[l] == '\n') cline++;
|
||||||
|
l++;
|
||||||
|
}
|
||||||
|
if (l > r) throw "Unfinished param at line " + i2s(cline);
|
||||||
|
int op;
|
||||||
|
if (s[l] == '+')
|
||||||
|
{
|
||||||
|
if ((l == r) || (s[l + 1] != '=')) throw "Unrecognized operation: + or += expected at line " + i2s(cline);
|
||||||
|
op = OPERATIONADD;
|
||||||
|
l += 2;
|
||||||
|
}
|
||||||
|
else if (s[l] == '=')
|
||||||
|
{
|
||||||
|
op = OPERATIONSET;
|
||||||
|
l++;
|
||||||
|
}
|
||||||
|
else if (s[l] == '(')
|
||||||
|
{
|
||||||
|
// this is a function call
|
||||||
|
l++;
|
||||||
|
string args = "";
|
||||||
|
while (l <= r && s[l] != ')')
|
||||||
|
{
|
||||||
|
args += s[l];
|
||||||
|
if (s[l] == '\n') cline++;
|
||||||
|
l++;
|
||||||
|
}
|
||||||
|
if (l > r) throw (string)("Unfinished function args at line " + i2s(cline));
|
||||||
|
l++;
|
||||||
|
if (p.name == "include")
|
||||||
|
{
|
||||||
|
// пока так костыльно, годный парсинг аргументов надо вместе с глобальным
|
||||||
|
// рефакторингом парсера делать
|
||||||
|
TrimSpaces(args);
|
||||||
|
TrimQuotes(args);
|
||||||
|
ConfigParser t;
|
||||||
|
t.Load(args);
|
||||||
|
p.val.push_back(t.cfg);
|
||||||
|
// в этом случае значение имеет тип "множество"
|
||||||
|
p.val[0].type = CONFIGVALUETYPESET;
|
||||||
|
return OPERATION_MERGE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw "Unknown function " + p.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else throw "Unrecognized operation: + or += expected at line " + i2s(cline);
|
||||||
|
while ((l <= r) && (IsWS(s[l])))
|
||||||
|
{
|
||||||
|
if (s[l] == '\n') cline++;
|
||||||
|
l++;
|
||||||
|
}
|
||||||
|
if (l > r) throw "Expected parameter value " + i2s(cline);
|
||||||
|
if (s[l] == '{')
|
||||||
|
{
|
||||||
|
ConfigValue a;
|
||||||
|
p.val.push_back(a);
|
||||||
|
l++;
|
||||||
|
// в этом случае значение имеет тип "множество"
|
||||||
|
p.val[0].type = CONFIGVALUETYPESET;
|
||||||
|
ParseConfigValue(l, r, p.val[0]);
|
||||||
|
}
|
||||||
|
else if (s[l] == '[')
|
||||||
|
{
|
||||||
|
l++;
|
||||||
|
ParseConfigParamValues(l, r, p);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ConfigValue a;
|
||||||
|
p.val.push_back(a);
|
||||||
|
// в этом случае значение имеет тип "строка"
|
||||||
|
p.val[0].type = CONFIGVALUETYPESTRING;
|
||||||
|
ParseConfigValue(l, r, p.val[0]);
|
||||||
|
}
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Разбирает несколько ConfigValues, заданных через запятую
|
||||||
|
void ConfigParser::ParseConfigParamValues(int &l, int &r, ConfigParam &b)
|
||||||
|
{
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
while ((l <= r) && (IsWS(s[l])))
|
||||||
|
{
|
||||||
|
if (s[l] == '\n') cline++;
|
||||||
|
l++;
|
||||||
|
}
|
||||||
|
if (l > r) throw "Unfinished array at line " + i2s(cline);
|
||||||
|
if (s[l] == ']')
|
||||||
|
{
|
||||||
|
l++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ConfigValue a;
|
||||||
|
b.val.push_back(a);
|
||||||
|
if (s[l] == '{')
|
||||||
|
{
|
||||||
|
l++;
|
||||||
|
b.val[b.val.size() - 1].type = CONFIGVALUETYPESET;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
b.val[b.val.size() - 1].type = CONFIGVALUETYPESTRING;
|
||||||
|
}
|
||||||
|
ParseConfigValue(l, r, b.val[b.val.size() - 1]);
|
||||||
|
if (l > r) throw "Unfinished array at line " + i2s(cline);
|
||||||
|
if (s[l] == ']')
|
||||||
|
{
|
||||||
|
l++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
l++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// разбирает строку s с l по r, считая, что она - корректный ConfigValue
|
||||||
|
void ConfigParser::ParseConfigValue(int &l, int &r, ConfigValue &vl)
|
||||||
|
{
|
||||||
|
while ((l <= r) && (IsWS(s[l])))
|
||||||
|
{
|
||||||
|
if (s[l] == '\n') cline++;
|
||||||
|
l++;
|
||||||
|
}
|
||||||
|
if (l > r) return;
|
||||||
|
// отсечем комментарии
|
||||||
|
if ((s[l] == ';') || (s[l] == '#'))
|
||||||
|
{
|
||||||
|
while (s[l] != '\n') l++;
|
||||||
|
ParseConfigValue(l, r, vl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// если в точке l наличествует символ-разделитель, значит
|
||||||
|
// наше Value закончилось
|
||||||
|
if ((s[l] == ',') || (s[l] == ']'))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (vl.type == CONFIGVALUETYPESET)
|
||||||
|
{
|
||||||
|
// нашли скобочку - set закрылся
|
||||||
|
if (s[l] == '}')
|
||||||
|
{
|
||||||
|
l++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!GoodForName(s[l]))
|
||||||
|
{
|
||||||
|
throw "Parameter name expected at line " + i2s(cline);
|
||||||
|
}
|
||||||
|
ConfigParam p;
|
||||||
|
int oper = ParseConfigParam(l, r, p);
|
||||||
|
if (oper == OPERATION_MERGE)
|
||||||
|
{
|
||||||
|
for (set<ConfigParam>::iterator t = p.val[0].pval.begin(); t != p.val[0].pval.end(); t++)
|
||||||
|
{
|
||||||
|
vl.pval.insert(*t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
set<ConfigParam>::iterator it = vl.pval.find(p);
|
||||||
|
if (it == vl.pval.end())
|
||||||
|
{
|
||||||
|
vl.pval.insert(p);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
if (oper == OPERATIONADD)
|
||||||
|
{
|
||||||
|
reverse(p.val.begin(), p.val.end());
|
||||||
|
for (i = ((int)it->val.size() - 1); i >= 0; i--)
|
||||||
|
{
|
||||||
|
p.val.push_back(it->val[i]);
|
||||||
|
}
|
||||||
|
reverse(p.val.begin(), p.val.end());
|
||||||
|
vl.pval.erase(it);
|
||||||
|
vl.pval.insert(p);
|
||||||
|
}
|
||||||
|
else if (oper == OPERATIONSET)
|
||||||
|
{
|
||||||
|
vl.pval.erase(it);
|
||||||
|
vl.pval.insert(p);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw (string)("Internal error (wrong operation returned) at line " + i2s(cline));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParseConfigValue(l, r, vl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vl.sval = "";
|
||||||
|
int isslashed = 0;
|
||||||
|
int isquoted = 0;
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
if (l > r) throw "Unfinished value at line " + i2s(cline);
|
||||||
|
if (isslashed)
|
||||||
|
{
|
||||||
|
vl.sval += UnSlash(s[l]);
|
||||||
|
isslashed = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (s[l] == '\\') isslashed = 1;
|
||||||
|
else if (s[l] == '"') isquoted = 1 - isquoted;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (IsDelim(s[l]))
|
||||||
|
{
|
||||||
|
if (isquoted == 0) break;
|
||||||
|
if (s[l] == '\n') throw "Unexpected end of line at line " + i2s(cline);
|
||||||
|
}
|
||||||
|
vl.sval += s[l];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// убирает двойные кавычки в начале/конце строки
|
||||||
|
void ConfigParser::TrimQuotes(string &s)
|
||||||
|
{
|
||||||
|
if (((int)s.length()) < 2) return;
|
||||||
|
if ((s[0] == '\"') && (s[((int)s.length()) - 1] == '\"'))
|
||||||
|
{
|
||||||
|
s = s.substr(1, s.length() - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigParser::Load(string fname)
|
||||||
|
{
|
||||||
|
// Загружаем весь конфиг в одну строку, теперь будем ее разбирать
|
||||||
|
FILE *fin = fopen(fname.c_str(), "rt");
|
||||||
|
if (!fin) throw "Config file " + fname + " not found!";
|
||||||
|
int c = getc(fin);
|
||||||
|
slen = 0;
|
||||||
|
while (c != EOF)
|
||||||
|
{
|
||||||
|
slen++;
|
||||||
|
c = getc(fin);
|
||||||
|
}
|
||||||
|
s = new char[slen + 1];
|
||||||
|
int i;
|
||||||
|
fclose(fin);
|
||||||
|
fin = fopen(fname.c_str(), "rt");
|
||||||
|
for (i = 0; i < slen; i++) s[i] = getc(fin);
|
||||||
|
fclose(fin);
|
||||||
|
s[slen] = '\n';
|
||||||
|
slen++;
|
||||||
|
// Теперь считаем всю строку конфиг-значением (так оно и должно быть)
|
||||||
|
cline = 1;
|
||||||
|
cfg.type = CONFIGVALUETYPESET;
|
||||||
|
cfg.sval = "";
|
||||||
|
cfg.pval.clear();
|
||||||
|
try{
|
||||||
|
int l = 0;
|
||||||
|
int r = slen - 1;
|
||||||
|
ParseConfigValue(l, r, cfg);
|
||||||
|
delete []s;
|
||||||
|
}
|
||||||
|
catch(string ss)
|
||||||
|
{
|
||||||
|
delete []s;
|
||||||
|
throw "Error parsing " + fname + ": " + ss;
|
||||||
|
}
|
||||||
|
catch(...)
|
||||||
|
{
|
||||||
|
delete []s;
|
||||||
|
throw "Unrecognized error parsing " + fname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// возвращает значение параметра (в старом стиле)
|
||||||
|
string ConfigParser::GetParam(string name)
|
||||||
|
{
|
||||||
|
set<ConfigParam>::iterator p;
|
||||||
|
p = this->cfg.pval.find(ConfigParam(name));
|
||||||
|
if (p != this->cfg.pval.end())
|
||||||
|
{
|
||||||
|
if (p->val.size() == 0) throw "There are no values for parameter " + name;
|
||||||
|
return p->val[0].sval;
|
||||||
|
}
|
||||||
|
throw ((string)"No param \"" + name + "\" found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// возвращает значение параметра (в старом стиле)
|
||||||
|
string ConfigParser::GetParamSilent(string name)
|
||||||
|
{
|
||||||
|
set<ConfigParam>::iterator p;
|
||||||
|
p = this->cfg.pval.find(ConfigParam(name));
|
||||||
|
if (p != this->cfg.pval.end())
|
||||||
|
{
|
||||||
|
if (p->val.size() == 0) return "";
|
||||||
|
return p->val[0].sval;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// возвращает какое-то одно значение параметра
|
||||||
|
// генерирует исключение при ошибке
|
||||||
|
string ConfigParam::GetSingleVal() const
|
||||||
|
{
|
||||||
|
if (val.size() == 0) throw ((string)"No value available");
|
||||||
|
if (val[0].type == CONFIGVALUETYPESTRING) return val[0].sval;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw "Wrong parameter value type (string expected)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// убивает в строке пробелы в начале и в конце
|
||||||
|
void ConfigParser::TrimSpaces(string &s)
|
||||||
|
{
|
||||||
|
string tmp = s;
|
||||||
|
s = "";
|
||||||
|
int wf = 0, i;
|
||||||
|
for (i = 0; i < ((int)tmp.length()); i++)
|
||||||
|
{
|
||||||
|
if (! IsWS(tmp[i]) )
|
||||||
|
{
|
||||||
|
wf = 1;
|
||||||
|
}
|
||||||
|
if (wf == 1) s += tmp[i];
|
||||||
|
}
|
||||||
|
while ((s.length() > 0) && (IsWS(s[s.length() - 1])))
|
||||||
|
{
|
||||||
|
s.erase(--s.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ищет в этом value заданный параметр. Возвращает пустую строку, если такого нет.
|
||||||
|
string ConfigValue::GetParamVal(string pname) const
|
||||||
|
{
|
||||||
|
if (this->type == CONFIGVALUETYPESTRING) throw ((string)"Attempted to get param from value, which is not a set");
|
||||||
|
set<ConfigParam>::iterator p = this->pval.find((ConfigParam)(pname));
|
||||||
|
if (p == pval.end()) return "";
|
||||||
|
return p->GetSingleVal();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
#ifndef FELIX_THE_CAT_FSYSTEM_CONFIGPARSER_HEADER_FILE_INCLUDED_KHKLJWGNBBVKJGLKXBVMZVEKLEKFJKJSAGSKAJGHJKBHQRPOTU
|
||||||
|
#define FELIX_THE_CAT_FSYSTEM_CONFIGPARSER_HEADER_FILE_INCLUDED_KHKLJWGNBBVKJGLKXBVMZVEKLEKFJKJSAGSKAJGHJKBHQRPOTU
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
const char CONFIGVALUETYPESTRING = 0;
|
||||||
|
const char CONFIGVALUETYPESET = 1;
|
||||||
|
|
||||||
|
class ConfigValue;
|
||||||
|
|
||||||
|
// Класс описывающий параметр из конфигурационного файла
|
||||||
|
class ConfigParam
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
string name;
|
||||||
|
vector<ConfigValue> val;
|
||||||
|
// возвращает первое значение параметра (если их несколько)
|
||||||
|
string GetSingleVal() const;
|
||||||
|
// создаем из строки
|
||||||
|
ConfigParam(string name);
|
||||||
|
// создаем просто так
|
||||||
|
ConfigParam();
|
||||||
|
// создаем параметр с единственным значением
|
||||||
|
ConfigParam(string name, string value);
|
||||||
|
};
|
||||||
|
|
||||||
|
bool operator<(const ConfigParam &a, const ConfigParam &b);
|
||||||
|
|
||||||
|
|
||||||
|
// Класс, описывающий значение параметра в конфигурационном файле
|
||||||
|
class ConfigValue
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
char type;
|
||||||
|
string sval;
|
||||||
|
set<ConfigParam> pval;
|
||||||
|
string GetParamVal(string pname) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Класс, разбирающий конфигурационный файл
|
||||||
|
class ConfigParser
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
char *s;
|
||||||
|
int slen;
|
||||||
|
int mode;
|
||||||
|
int cline;
|
||||||
|
// переделывает число в строку
|
||||||
|
string i2s(int a);
|
||||||
|
// разбирает s с l по r, считая, что она должна быть ConfigValue - ом
|
||||||
|
void ParseConfigValue(int &l, int &r, ConfigValue &vl);
|
||||||
|
// возвращает, является ли символ whitespace-ом
|
||||||
|
bool IsWS(char c);
|
||||||
|
// возвращает, может ли такой символ быть в имени параметра
|
||||||
|
bool GoodForName(char c);
|
||||||
|
// убирает двойные кавычки в начале/конце строки (если, конечно, таковые имеются)
|
||||||
|
void TrimQuotes(string &s);
|
||||||
|
// возвращает символ, соответствующий escape-последовательности "\c"
|
||||||
|
char UnSlash(char c);
|
||||||
|
// разбирает s с l по r, считая, что она должна быть ConfigParam - ом
|
||||||
|
// возвращает, какая операция была применена
|
||||||
|
int ParseConfigParam(int &l, int &r, ConfigParam &p);
|
||||||
|
// возращает, считается ли такой символ разделителем
|
||||||
|
// разделителями считаются ws-ы, скобки и запятые
|
||||||
|
bool IsDelim(char c);
|
||||||
|
// Разбирает несколько ConfigValues, заданных через запятую
|
||||||
|
void ParseConfigParamValues(int &l, int &r, ConfigParam &b);
|
||||||
|
void TrimSpaces(string &s);
|
||||||
|
public:
|
||||||
|
ConfigValue cfg;
|
||||||
|
// загружает конфигурационный файл
|
||||||
|
void Load(string fname);
|
||||||
|
// возвращает значение параметра (в старом стиле)
|
||||||
|
// генерирует исключение, если такого нет
|
||||||
|
string GetParam(string name);
|
||||||
|
// возвращает значение параметра (в старом стиле)
|
||||||
|
// возвращает пустую строку, если такого нет
|
||||||
|
string GetParamSilent(string name);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ListenPort = 15000
|
||||||
|
LogPath = "/var/log/defrost.log"
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
#include "configparser.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "ModbusServiceListenThread.h"
|
||||||
|
#include "libdiff.h"
|
||||||
|
|
||||||
|
#include <climits>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/file.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
|
||||||
|
void temperature_update()
|
||||||
|
{
|
||||||
|
// other helper variables
|
||||||
|
float temperature = 1.5;
|
||||||
|
|
||||||
|
registers[REGISTER_CUR_TEMP] = (int)(temperature * 100);
|
||||||
|
float additional_temperature = 2.5;
|
||||||
|
registers[REGISTER_SECONDARY_TEMP] = (int)(additional_temperature * 100);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// ����� lock-file, ����� ���������� ������ ���� ��������� �������
|
||||||
|
int lockfile = open("/tmp/defrost.lock", O_RDWR | O_CREAT, 0777);
|
||||||
|
if (lockfile == -1)
|
||||||
|
{
|
||||||
|
cout << "Unable to open file for locking, errno = " << errno << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (flock(lockfile, LOCK_EX | LOCK_NB) == -1)
|
||||||
|
{
|
||||||
|
cout << "Unable to lock file, errno = " << errno << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// load config
|
||||||
|
ConfigParser cfg;
|
||||||
|
cfg.Load(argv[1] );
|
||||||
|
mainlog.InitLogging(cfg.GetParam("LogPath"), LIBLOGSINGLEFILE, "defrost", true);
|
||||||
|
mainlog.LogString("Defrost service started");
|
||||||
|
|
||||||
|
if (pthread_mutex_init(®ister_mutex, NULL) != 0)
|
||||||
|
{
|
||||||
|
mainlog.LogString("Unable to create registers_mutex");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
registers[REGISTER_ON_TEMP] = 100;
|
||||||
|
registers[REGISTER_OFF_TEMP] = 500;
|
||||||
|
registers[REGISTER_MODE] = 0;
|
||||||
|
|
||||||
|
// run modbus service listen thread
|
||||||
|
ModbusServiceListenThread *modbus_service = new ModbusServiceListenThread(&mainlog, atoi(cfg.GetParam("ListenPort").c_str()));
|
||||||
|
modbus_service->Start();
|
||||||
|
|
||||||
|
int io_value = 0;
|
||||||
|
// loop forever updating the temperature values every second
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
LockMutex(register_mutex);
|
||||||
|
temperature_update();
|
||||||
|
int mode = registers[REGISTER_MODE];
|
||||||
|
if (mode == 0)
|
||||||
|
{
|
||||||
|
if (registers[REGISTER_CUR_TEMP] < registers[REGISTER_ON_TEMP]) io_value = 1;
|
||||||
|
else if (registers[REGISTER_CUR_TEMP] > registers[REGISTER_OFF_TEMP]) io_value = 0;
|
||||||
|
}
|
||||||
|
else if (mode == 1)
|
||||||
|
{
|
||||||
|
io_value = registers[REGISTER_CUR_STATE];
|
||||||
|
}
|
||||||
|
registers[REGISTER_CUR_STATE] = io_value;
|
||||||
|
//gpio.write(io_value);
|
||||||
|
ReleaseMutex(register_mutex);
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (string s)
|
||||||
|
{
|
||||||
|
cerr << "Exception: " << s << "\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Defrost service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/home/root/bin/defrost /home/root/bin/defrost.cfg
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
RuntimeDirectory=/home/root/bin
|
||||||
|
RuntimeDirectoryMode=0755
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#include "globals.h"
|
||||||
|
|
||||||
|
int registers[65536];
|
||||||
|
pthread_mutex_t register_mutex;
|
||||||
|
LogFile mainlog;
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef GLOBALS_HEADER_FILE_INCLUDED
|
||||||
|
#define GLOBALS_HEADER_FILE_INCLUDED
|
||||||
|
|
||||||
|
#include "liblog.h"
|
||||||
|
|
||||||
|
const int REGISTER_ON_TEMP = 0;
|
||||||
|
const int REGISTER_OFF_TEMP = 1;
|
||||||
|
const int REGISTER_MODE = 2;
|
||||||
|
const int REGISTER_CUR_TEMP = 3;
|
||||||
|
const int REGISTER_CUR_STATE = 4;
|
||||||
|
const int REGISTER_SECONDARY_TEMP = 5;
|
||||||
|
|
||||||
|
extern int registers[65536];
|
||||||
|
extern pthread_mutex_t register_mutex;
|
||||||
|
extern LogFile mainlog;
|
||||||
|
|
||||||
|
#endif
|
||||||
+177
@@ -0,0 +1,177 @@
|
|||||||
|
#include "libdiff.h"
|
||||||
|
|
||||||
|
#include <sys/timeb.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <iconv.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
ostream& operator<<(ostream &out, const pthread_mutex_t m)
|
||||||
|
{
|
||||||
|
out << &m;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LockMutex(pthread_mutex_t &m)
|
||||||
|
{
|
||||||
|
int mres;
|
||||||
|
while ((mres = pthread_mutex_lock(&m)) != 0)
|
||||||
|
{
|
||||||
|
if (mres != EAGAIN)
|
||||||
|
{
|
||||||
|
throw (string)("Fatal error acquiring mutex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReleaseMutex(pthread_mutex_t &m)
|
||||||
|
{
|
||||||
|
int mres = pthread_mutex_unlock(&m);
|
||||||
|
if (mres != 0)
|
||||||
|
{
|
||||||
|
throw (string)("Error releasing mutex " + ToString(m) + " res = " + ToString(mres));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Round(double v)
|
||||||
|
{
|
||||||
|
if (v < -1e-4) return -(int)(-v + 0.5);
|
||||||
|
else return (int)(v + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CP866ToUTF8(unsigned char *src, unsigned char *dest, int destlen)
|
||||||
|
{
|
||||||
|
iconv_t icd = iconv_open("UTF-8", "CP866");
|
||||||
|
if (icd == (iconv_t)-1)
|
||||||
|
{
|
||||||
|
throw (string)("Unable to open iconv descriptor, errno = " + ToString(errno));
|
||||||
|
}
|
||||||
|
size_t slen = strlen((char*)src);
|
||||||
|
destlen--;
|
||||||
|
// странные временные переменные, потому что иначе компилятор ругается, что
|
||||||
|
// результат приведения типов - не lvalue
|
||||||
|
char *s = (char*)src;
|
||||||
|
char *d = (char*)dest;
|
||||||
|
size_t dl = destlen;
|
||||||
|
size_t t = iconv(icd, &s, &slen, &d, &dl);
|
||||||
|
iconv_close(icd);
|
||||||
|
if (t == (size_t)-1)
|
||||||
|
{
|
||||||
|
throw (string)("Error during string conversion, errno = " + ToString(errno));
|
||||||
|
}
|
||||||
|
// терминируем строку, iconv сам этого не сделает
|
||||||
|
d[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
string CP1251ToUTF8(const string &src)
|
||||||
|
{
|
||||||
|
unsigned char *dest = new unsigned char[src.length() * 4];
|
||||||
|
CP1251ToUTF8((const unsigned char *)src.c_str(), dest, src.length() * 4);
|
||||||
|
string res((char*)dest);
|
||||||
|
delete[] dest;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CP1251ToUTF8(const unsigned char *src, unsigned char *dest, int destlen)
|
||||||
|
{
|
||||||
|
iconv_t icd = iconv_open("UTF-8", "CP1251");
|
||||||
|
if (icd == (iconv_t)-1)
|
||||||
|
{
|
||||||
|
throw (string)("Unable to open iconv descriptor, errno = " + ToString(errno));
|
||||||
|
}
|
||||||
|
size_t slen = strlen((char*)src);
|
||||||
|
destlen--;
|
||||||
|
// странные временные переменные, потому что иначе компилятор ругается, что
|
||||||
|
// результат приведения типов - не lvalue
|
||||||
|
char *s = (char*)src;
|
||||||
|
char *d = (char*)dest;
|
||||||
|
size_t dl = destlen;
|
||||||
|
size_t t = iconv(icd, &s, &slen, &d, &dl);
|
||||||
|
iconv_close(icd);
|
||||||
|
if (t == (size_t)-1)
|
||||||
|
{
|
||||||
|
throw (string)("Error during string conversion, errno = " + ToString(errno));
|
||||||
|
}
|
||||||
|
// терминируем строку, iconv сам этого не сделает
|
||||||
|
d[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UnpackBCD(int a)
|
||||||
|
{
|
||||||
|
if ((a & 0xF) > 9 ||
|
||||||
|
((a >> 4) & 0xF) > 9) throw (string)("Wrong BCD number: " + ToString(a));
|
||||||
|
return (((a >> 4) & 0xF) * 10) + (a & 0xF);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckBCD(int a)
|
||||||
|
{
|
||||||
|
if ((a & 0xF) > 9 ||
|
||||||
|
((a >> 4) & 0xF) > 9) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PackBCD(int a)
|
||||||
|
{
|
||||||
|
return (((a / 10) % 10) << 4) | (a % 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long GetMilliseconds()
|
||||||
|
{
|
||||||
|
struct timeb tm;
|
||||||
|
if (ftime(&tm) == -1)
|
||||||
|
{
|
||||||
|
// should never happen in fact
|
||||||
|
throw (string)("ftime() failed somehow");
|
||||||
|
}
|
||||||
|
unsigned long long res = tm.time;
|
||||||
|
res *= 1000;
|
||||||
|
res += tm.millitm;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
string ReplaceAll(string text, string pattern, string replacement)
|
||||||
|
{
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
size_t pos = text.find(pattern);
|
||||||
|
if (pos == string::npos) return text;
|
||||||
|
text = text.replace(pos, pattern.length(), replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string IntToString(int a, int base)
|
||||||
|
{
|
||||||
|
// no itoa in modern C++, what a pity
|
||||||
|
int sign = a < 0 ? -1 : 1;
|
||||||
|
if (a < 0) a = -a;
|
||||||
|
string res;
|
||||||
|
while (a > 0)
|
||||||
|
{
|
||||||
|
int cur = a % base;
|
||||||
|
char c = cur + '0';
|
||||||
|
if (cur > 9) c = cur + 'A' - 10;
|
||||||
|
res += c;
|
||||||
|
a /= base;
|
||||||
|
}
|
||||||
|
if (sign < 0) res += '-';
|
||||||
|
reverse(res.begin(), res.end());
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RaiseFDLimit()
|
||||||
|
{
|
||||||
|
struct rlimit rl;
|
||||||
|
if (getrlimit(RLIMIT_NOFILE, &rl) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
rl.rlim_cur = rl.rlim_max;
|
||||||
|
if (setrlimit(RLIMIT_NOFILE, &rl) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return rl.rlim_cur;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#ifndef FELIX_THE_CAT_DIFFERENT_LIBRARY_INCLUDED_AKJGNKJABVKALJGLKAJGLAKJG
|
||||||
|
#define FELIX_THE_CAT_DIFFERENT_LIBRARY_INCLUDED_AKJGNKJABVKALJGLKAJGLAKJG
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
string ToString(const T& a)
|
||||||
|
{
|
||||||
|
ostringstream oss;
|
||||||
|
oss.setf(ios::fixed | ios::showpoint);
|
||||||
|
oss.precision(10);
|
||||||
|
oss << a;
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
ostream& operator<<(ostream &out, const pthread_mutex_t m);
|
||||||
|
|
||||||
|
void LockMutex(pthread_mutex_t &m);
|
||||||
|
void ReleaseMutex(pthread_mutex_t &m);
|
||||||
|
|
||||||
|
|
||||||
|
int Round(double v);
|
||||||
|
|
||||||
|
// конвертирует из CP866 (в котором часто девайсы шлют всякую фигню)
|
||||||
|
// в UTF-8 (в котором весь наш код), чтобы можно было сравнивать строки
|
||||||
|
// и не иметь в исходниках две кодировки
|
||||||
|
// src предполагается нуль-терминированным
|
||||||
|
void CP866ToUTF8(unsigned char *src, unsigned char *dest, int destlen);
|
||||||
|
// То же самое для CP1251
|
||||||
|
void CP1251ToUTF8(const unsigned char *src, unsigned char *dest, int destlen);
|
||||||
|
|
||||||
|
string CP1251ToUTF8(const string &src);
|
||||||
|
|
||||||
|
// распаковывает BCD-число. Сделано в виде функции, а не макроса, чтобы
|
||||||
|
// можно было подставлять в нее функции, изменяющие состояние параметров
|
||||||
|
int UnpackBCD(int a);
|
||||||
|
// аналогично, упаковывает BCD-число
|
||||||
|
int PackBCD(int a);
|
||||||
|
// проверяет, является ли a валидным BCD-байтом
|
||||||
|
bool CheckBCD(int a);
|
||||||
|
|
||||||
|
|
||||||
|
// получает количество миллисекунд с начала эпохи (иногда нужно для)
|
||||||
|
// всяких точных таймаутов при приеме/отправке
|
||||||
|
unsigned long long GetMilliseconds();
|
||||||
|
|
||||||
|
// заменяет в text все вхождения pattern на replacement
|
||||||
|
// аккуратно со случаем, когда replacement содержит pattern - может повиснуть (!)
|
||||||
|
string ReplaceAll(string text, string pattern, string replacement);
|
||||||
|
|
||||||
|
// конвертирует число в строку в заданной base
|
||||||
|
string IntToString(int a, int base = 10);
|
||||||
|
|
||||||
|
// поднимает до упора лимит на количество открытых дескрипторов
|
||||||
|
int RaiseFDLimit();
|
||||||
|
|
||||||
|
#endif
|
||||||
+230
@@ -0,0 +1,230 @@
|
|||||||
|
#include "liblog.h"
|
||||||
|
#include "libdiff.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
LogFile::LogFile()
|
||||||
|
{
|
||||||
|
logmethod = -1;
|
||||||
|
logpath = "";
|
||||||
|
file = NULL;
|
||||||
|
initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string IntToLength(int a, int len)
|
||||||
|
{
|
||||||
|
string s = ToString(a);
|
||||||
|
while ((int)s.length() < len) s = '0' + s;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LogFile::InitLogging(string logpath, int method, string prefix, bool reopen)
|
||||||
|
{
|
||||||
|
this->logmethod = method;
|
||||||
|
this->logpath = logpath;
|
||||||
|
this->fileprefix = prefix;
|
||||||
|
this->reopen = reopen;
|
||||||
|
// инициализируем mutex
|
||||||
|
if (pthread_mutex_init(&filemutex, NULL) != 0)
|
||||||
|
{
|
||||||
|
throw (string)("Unable to create mutex");
|
||||||
|
}
|
||||||
|
if (!reopen)
|
||||||
|
{
|
||||||
|
// открываем файл
|
||||||
|
if (method == LIBLOGSINGLEFILE)
|
||||||
|
{
|
||||||
|
// формируем имя файла
|
||||||
|
ostringstream filename;
|
||||||
|
filename << logpath << "/" << fileprefix << ".log";
|
||||||
|
file = OpenFileWithRetries(filename.str(), "at");
|
||||||
|
if (file == NULL)
|
||||||
|
{
|
||||||
|
throw (string)("Unable to open file for logging");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
struct tm curtime;
|
||||||
|
time_t ct = time(NULL);
|
||||||
|
localtime_r(&ct, &curtime);
|
||||||
|
// теперь имя файла выглядит намного забавнее
|
||||||
|
ostringstream filename;
|
||||||
|
filename << logpath << "/" << fileprefix << curtime.tm_year + 1900 << IntToLength(curtime.tm_mon + 1, 2)
|
||||||
|
<< IntToLength(curtime.tm_mday, 2) << ".log";
|
||||||
|
file = OpenFileWithRetries(filename.str(), "at");
|
||||||
|
lastname = filename.str();
|
||||||
|
if (file == NULL)
|
||||||
|
{
|
||||||
|
throw (string)("Unable to open file for logging");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// записывает в лог-файл строку
|
||||||
|
// файл определяется исходя из метода
|
||||||
|
// строка предваряется текущим временем
|
||||||
|
// в некотором формате
|
||||||
|
void LogFile::LogString(string s, string pfx)
|
||||||
|
{
|
||||||
|
// Если лог еще не инициализирован, ничего не пишем (штатная ситуация, если,
|
||||||
|
// например, лог пришлось заглушить).
|
||||||
|
if (!initialized) return;
|
||||||
|
#ifdef FTC_DEBUG
|
||||||
|
cout << s << endl;
|
||||||
|
#endif
|
||||||
|
int res;
|
||||||
|
while ((res = pthread_mutex_lock(&filemutex)) != 0)
|
||||||
|
{
|
||||||
|
if (res != EAGAIN)
|
||||||
|
{
|
||||||
|
throw (string)("Fatal error acquiring mutex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct tm curtime;
|
||||||
|
time_t ct = time(NULL);
|
||||||
|
localtime_r(&ct, &curtime);
|
||||||
|
if (this->logmethod == LIBLOGSINGLEFILE)
|
||||||
|
{
|
||||||
|
if (reopen)
|
||||||
|
{
|
||||||
|
// формируем имя файла
|
||||||
|
ostringstream filename;
|
||||||
|
filename << logpath << "/" << fileprefix << ".log";
|
||||||
|
file = OpenFileWithRetries(filename.str(), "at");
|
||||||
|
if (file == NULL)
|
||||||
|
{
|
||||||
|
throw (string)("Unable to open file for logging");
|
||||||
|
pthread_mutex_unlock(&filemutex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// теперь сгенерить строку
|
||||||
|
ostringstream oss;
|
||||||
|
oss << pfx << " " << IntToLength(curtime.tm_year + 1900, 4) << "-" << IntToLength(curtime.tm_mon + 1, 2) << "-"
|
||||||
|
<< IntToLength(curtime.tm_mday, 2) << " " << IntToLength(curtime.tm_hour, 2)
|
||||||
|
<< ":" << IntToLength(curtime.tm_min, 2) << ":" << IntToLength(curtime.tm_sec, 2);
|
||||||
|
oss << " " << s + "\n";
|
||||||
|
// и записать ее куда следует
|
||||||
|
fprintf(file, "%s", oss.str().c_str());
|
||||||
|
if (reopen)
|
||||||
|
{
|
||||||
|
if (file != NULL)
|
||||||
|
{
|
||||||
|
fclose(file);
|
||||||
|
file = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this->logmethod == LIBLOGDAYFILES)
|
||||||
|
{
|
||||||
|
// переоткрываем файл если нужно
|
||||||
|
ostringstream filename;
|
||||||
|
filename << logpath << "/" << fileprefix << curtime.tm_year + 1900 << IntToLength(curtime.tm_mon + 1, 2)
|
||||||
|
<< IntToLength(curtime.tm_mday, 2) << ".log";
|
||||||
|
if (!reopen)
|
||||||
|
{
|
||||||
|
if (filename.str() != lastname)
|
||||||
|
{
|
||||||
|
if (file != NULL)
|
||||||
|
{
|
||||||
|
cerr << "LIBLOG, fclose " << file << "\n";
|
||||||
|
cerr << "LIBLOG, lastname = " << lastname << " newname = " << filename.str() << "\n";
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
file = OpenFileWithRetries(filename.str(), "at");
|
||||||
|
if (file == NULL)
|
||||||
|
{
|
||||||
|
throw (string)("Unable to open file for logging");
|
||||||
|
pthread_mutex_unlock(&filemutex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastname = filename.str();
|
||||||
|
cerr << "LIBLOG, reopened file = " << file << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
file = OpenFileWithRetries(filename.str(), "at");
|
||||||
|
if (file == NULL)
|
||||||
|
{
|
||||||
|
throw (string)("Unable to open file for logging");
|
||||||
|
pthread_mutex_unlock(&filemutex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// теперь сгенерить строку
|
||||||
|
ostringstream oss;
|
||||||
|
oss << pfx << " " << IntToLength(curtime.tm_hour, 2)
|
||||||
|
<< ":" << IntToLength(curtime.tm_min, 2) << ":" << IntToLength(curtime.tm_sec, 2);
|
||||||
|
oss << " " << s << "\n";
|
||||||
|
// и записать ее куда следует
|
||||||
|
if (file != NULL) fprintf(file, "%s", oss.str().c_str());
|
||||||
|
if (reopen)
|
||||||
|
{
|
||||||
|
if (file != NULL)
|
||||||
|
{
|
||||||
|
fclose(file);
|
||||||
|
file = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// ничего не делаем - неинициализированный лог - не проблема
|
||||||
|
}
|
||||||
|
if (!reopen)
|
||||||
|
{
|
||||||
|
if (file != NULL) fflush(file);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&filemutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
char GHC(char c)
|
||||||
|
{
|
||||||
|
if (c < 10) return (c + '0');
|
||||||
|
else return (c - 10 + 'A');
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetHex(unsigned char c)
|
||||||
|
{
|
||||||
|
string res;
|
||||||
|
int l, h;
|
||||||
|
h = (c >> 4) & 15;
|
||||||
|
l = c & 15;
|
||||||
|
res += GHC(h);
|
||||||
|
res += GHC(l);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FILE* LogFile::OpenFileWithRetries(string fname, const char *mode)
|
||||||
|
{
|
||||||
|
FILE *res = fopen(fname.c_str(), mode);
|
||||||
|
int rcnt = 0;
|
||||||
|
while (res == NULL)
|
||||||
|
{
|
||||||
|
rcnt++;
|
||||||
|
if (rcnt > LIBLOGNUMRETRIESTOOPEN) break;
|
||||||
|
sleep(1);
|
||||||
|
res = fopen(fname.c_str(), mode);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetHexString(unsigned int a)
|
||||||
|
{
|
||||||
|
return GetHex((a >> 24) & 0xFF) + GetHex((a >> 16) & 0xFF) + GetHex((a >> 8) & 0xFF) + GetHex(a & 0xFF);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#ifndef FELIX_THE_CAT_LOGGING_LIBRARY_HEADER_FILE_INCLUDED_EJGHJWVNKJNWKJGNKJWGJKGKJHKJH
|
||||||
|
#define FELIX_THE_CAT_LOGGING_LIBRARY_HEADER_FILE_INCLUDED_EJGHJWVNKJNWKJGNKJWGJKGKJHKJH
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// библиотечка, выполняющая запись в логи
|
||||||
|
// два метода - один мегафайл или много
|
||||||
|
// файлов по одному на каждый день
|
||||||
|
const int LIBLOGSINGLEFILE = 1;
|
||||||
|
const int LIBLOGDAYFILES = 2;
|
||||||
|
const int LIBLOGNUMRETRIESTOOPEN = 1;
|
||||||
|
|
||||||
|
|
||||||
|
class LogFile
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// запрещаем копирование и присваивание лог-файла (чтобы FILE* не копипастился почем зря)
|
||||||
|
// Передаем всегда указатели на лог
|
||||||
|
LogFile(const LogFile &l);
|
||||||
|
void operator=(const LogFile &l);
|
||||||
|
bool initialized;
|
||||||
|
public:
|
||||||
|
// mutex для блокирования дескриптора файла (чтобы толпой в один дескриптор не писать)
|
||||||
|
pthread_mutex_t filemutex;
|
||||||
|
// файл для записи
|
||||||
|
FILE *file;
|
||||||
|
// и его имя
|
||||||
|
string lastname;
|
||||||
|
int logmethod; // метод записи (смтор константы)
|
||||||
|
string logpath; // куда писать
|
||||||
|
string fileprefix; // префикс, который перед файлами генерировать
|
||||||
|
bool reopen; // нужно ли каждый раз переоткрывать лог-файл
|
||||||
|
LogFile();
|
||||||
|
void InitLogging(string logpath, int method, string prefix = "", bool reopen = false);
|
||||||
|
void LogString(string s, string pfx = "");
|
||||||
|
FILE *OpenFileWithRetries(string fname, const char *mode);
|
||||||
|
};
|
||||||
|
|
||||||
|
string GetHex(unsigned char c);
|
||||||
|
string GetHexString(unsigned int a);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user