git-svn-id: svn://db.shs.com.ru/pip@400 12ceb7fc-bf1f-11e4-8940-5bc7170c53b5

This commit is contained in:
2017-04-18 20:49:45 +00:00
parent b1b23bf89c
commit 95c1f34b45
192 changed files with 291 additions and 187 deletions

View File

@@ -0,0 +1,559 @@
#include "pibasetransfer.h"
const uint PIBaseTransfer::signature = 0x54424950;
PIBaseTransfer::PIBaseTransfer(): crc(standardCRC_16()), diag(false) {
header.sig = signature;
header.session_id = 0;
packet_header_size = sizeof(PacketHeader) + customHeader().size();
part_header_size = sizeof(Part) + sizeof(int);
is_sending = is_receiving = is_pause = false;
break_ = true;
bytes_all = bytes_cur = 0;
send_queue = 0;
timeout_ = 10.;
diag.setDisconnectTimeout(timeout_);
//CONNECTU(&diag, qualityChanged, this, diagChanged);
diag.setName("PIBaseTransfer");
diag.start(50);
packets_count = 32;
setPacketSize(4096);
randomize();
}
PIBaseTransfer::~PIBaseTransfer() {
diag.stop();
break_ = true;
}
void PIBaseTransfer::stopSend() {
if (!is_sending) return;
break_ = true;
}
void PIBaseTransfer::stopReceive() {
if (!is_receiving) return;
break_ = true;
//piCoutObj << "stopReceive()";
finish_receive(false);
}
void PIBaseTransfer::setPause(bool pause_) {
if (is_pause == pause_) return;
pause_tm.reset();
is_pause = pause_;
if (pause_) paused();
else resumed();
}
void PIBaseTransfer::received(PIByteArray data) {
packet_header_size = sizeof(PacketHeader) + customHeader().size();
if (data.size() < sizeof(PacketHeader)) {
diag.received(data.size(), false);
return;
}
PacketHeader h;
data >> h;
PacketType pt = (PacketType)h.type;
if (!h.check_sig()) {
diag.received(data.size(), false);
return;
} else diag.received(data.size(), true);
//piCoutObj << "receive" << h.session_id << h.type << h.id;
switch (pt) {
case pt_Unknown: break;
case pt_Data:
mutex_header.lock();
if (h.session_id != header.session_id || !is_receiving) {
sendBreak(h.session_id);
mutex_header.unlock();
return;
} else {
uint rcrc = h.crc;
uint ccrc = crc.calculate(data.data(), data.size_s());
if (rcrc != ccrc) {
header.id = h.id;
sendReply(pt_ReplyInvalid);
piCoutObj << "invalid CRC";
} else {
mutex_session.lock();
processData(h.id, data);
mutex_session.unlock();
}
if (is_pause) sendReply(pt_Pause);
}
mutex_header.unlock();
break;
case pt_ReplySuccess:
case pt_ReplyInvalid:
mutex_header.lock();
if (h.session_id != header.session_id) {
mutex_header.unlock();
return;
}
if (is_pause) sendReply(pt_Pause);
mutex_header.unlock();
if (pt == pt_ReplySuccess) {
mutex_send.lock();
send_queue--;
if (send_queue < 0) send_queue = 0;
send_tm.reset();
mutex_send.unlock();
}
if (is_sending) {
mutex_session.lock();
if (h.id < replies.size())
replies[h.id] = pt;
mutex_session.unlock();
// piCoutObj << "Done Packet" << h.id;
}
if (is_receiving && h.id == 0) {
// if (checkSession() == 0 && pt == pt_ReplySuccess) finish_receive(true);
if (pt == pt_ReplySuccess) {
mutex_session.lock();
int cs = checkSession();
mutex_session.unlock();
//piCoutObj << "Success receive";
if (cs == 0) finish_receive(true);
}
}
break;
case pt_Break:
break_ = true;
//piCoutObj << "BREAK";
if (is_receiving) {
stopReceive();
return;
}
if (is_sending) {
stopSend();
return;
}
break;
case pt_Start:
mutex_header.lock();
if (is_pause && (is_sending || is_receiving)) {
if (header.session_id == h.session_id) {
is_pause = false;
mutex_header.unlock();
resumed();
return;
}
}
if (is_sending && header.session_id != h.session_id) {
sendBreak(h.session_id);
mutex_header.unlock();
return;
}
if (is_receiving) {
if (header.session_id != h.session_id) {
//sendBreak(h.session_id);
//return;
//piCoutObj << "restart receive";
mutex_header.unlock();
finish_receive(false, true);
} else {
mutex_header.unlock();
return;
}
}
mutex_header.unlock();
if (data.size() == sizeof(StartRequest)) {
StartRequest sr;
data >> sr;
mutex_session.lock();
mutex_header.lock();
bytes_cur = 0;
state_string = "start request";
bytes_all = sr.size;
header.session_id = h.session_id;
header.id = 0;
session.clear();
replies.clear();
session.resize(sr.packets);
replies.resize(sr.packets + 1);
replies.fill(pt_Unknown);
diag.reset();
// diag.start(100);
//piCoutObj << "receiveStarted()";
is_receiving = true;
break_ = false;
mutex_send.lock();
send_queue = 0;
mutex_send.unlock();
receiveStarted();
state_string = "receiving";
replies[0] = pt_ReplySuccess;
mutex_session.unlock();
sendReply(pt_ReplySuccess);
mutex_header.unlock();
}
break;
case pt_Pause:
mutex_header.lock();
if (header.session_id == h.session_id) {
//piCout << "receive pause";
if (!is_pause && pause_tm.elapsed_s() < timeout_/10) {
//piCout << "resume";
sendReply(pt_Start);
mutex_header.unlock();
return;
}
if (!is_pause) paused();
is_pause = true;
if (is_receiving && pause_tm.elapsed_m() > 40) {
//piCout << "send pause";
sendReply(pt_Pause);
}
if (is_sending) send_tm.reset();
pause_tm.reset();
}
mutex_header.unlock();
break;
default: break;
}
}
bool PIBaseTransfer::send_process() {
mutex_session.lock();
packet_header_size = sizeof(PacketHeader) + customHeader().size();
break_ = false;
diag.reset();
// diag.start(100);
sendStarted();
is_sending = true;
int session_size = session.size();
replies.resize(session_size + 1);
replies.fill(pt_Unknown);
mutex_session.unlock();
PIByteArray ba;
if (!getStartRequest()) return finish_send(false);
state_string = "sending";
PITimeMeasurer stm;
mutex_send.lock();
send_queue = 0;
send_tm.reset();
mutex_send.unlock();
//int ltm = 0;
for (int i = 0; i < session_size; i++) {
mutex_send.lock();
int sq = send_queue;
mutex_send.unlock();
if (sq >= packets_count || is_pause) {
--i;
//piMSleep(1);
if (is_pause) {
piMSleep(40);
//piCout << "send pause";
mutex_header.lock();
sendReply(pt_Pause);
mutex_header.unlock();
if (pause_tm.elapsed_s() > timeout())return finish_send(false);
}
mutex_send.lock();
if (send_tm.elapsed_s() > timeout_) {
mutex_send.unlock();
return finish_send(false);
}
if (stm.elapsed_s() > timeout_ / 10.) {
send_queue = 0;
}
mutex_send.unlock();
continue;
}
stm.reset();
ba = build_packet(i);
diag.sended(ba.size_s());
sendRequest(ba);
mutex_send.lock();
send_queue++;
mutex_send.unlock();
if (break_) return finish_send(false);
}
// piCoutObj << "send done, checking";
PITimeMeasurer tm;
int prev_chk = 0;
mutex_send.lock();
send_queue = 0;
mutex_send.unlock();
piMSleep(10);
while (tm.elapsed_s() < timeout_) {
//piCoutObj << "recovering...";
mutex_session.lock();
int chk = checkSession();
mutex_session.unlock();
if (chk == 0) return finish_send(true);
if (chk != prev_chk) tm.reset();
else if (tm.elapsed_m() < 100) {
piMSleep(1);
continue;
}
if (is_pause) {
piMSleep(40);
//piCout << "send pause";
mutex_header.lock();
sendReply(pt_Pause);
mutex_header.unlock();
if (pause_tm.elapsed_s() > timeout())return finish_send(false);
else continue;
}
prev_chk = chk;
if (chk > 0) {
//piCoutObj << "recovery packet" << chk;
mutex_send.lock();
int sq = send_queue;
mutex_send.unlock();
if (sq >= packets_count) {
piMSleep(10);
continue;
}
ba = build_packet(chk - 1);
diag.sended(ba.size_s());
sendRequest(ba);
mutex_send.lock();
send_queue++;
mutex_send.unlock();
}
// if (chk == -1) return finish_send(false);
if (break_) return finish_send(false);
//piMSleep(1);
}
return finish_send(false);
}
int PIBaseTransfer::checkSession() {
if (!(is_receiving || is_sending)) return -1;
int miss = 0;
for (int i = 1; i < replies.size_s(); i++) {
if (replies[i] != pt_ReplySuccess) miss++;
if (replies[i] == pt_ReplyInvalid) return i;
}
for (int i = 1; i < replies.size_s(); i++) {
if (replies[i] != pt_ReplySuccess) return i;
}
if (miss > 0) {
//piCoutObj << "missing" << miss << "packets";
return -miss;
} else return 0;
}
void PIBaseTransfer::buildSession(PIVector<Part> parts) {
mutex_session.lock();
mutex_header.lock();
state_string = "calculating parts ... ";
session.clear();
header.session_id = randomi();
bytes_all = 0;
Part fi;
int fi_index, fi_prts;
PIVector<Part> lfi;
int min_size = packet_header_size + part_header_size;
int cur_size = min_size;
for (int i = 0; i < parts.size_s(); i++) {
state_string = "calculating parts ... " + PIString::fromNumber(i) + " of " + PIString::fromNumber(parts.size());
fi.id = parts[i].id;
//piCout << fi.id << state_string;
bytes_all += parts[i].size;
//fi.size = fi.entry.size;
fi.start = 0;
llong rest = parts[i].size - (packet_size - cur_size);
//piCout << i << fi.size << rest << bytes_all;
if (rest <= 0) {
fi.size = parts[i].size;
lfi << fi;
cur_size += fi.size + part_header_size;
} else {
fi.size = parts[i].size - rest;
fi_index = 1;
fi_prts = 1 + 1 + piMaxi(1, rest / (packet_size - min_size));
//piCout << fi_prts;
lfi << fi;
session << lfi;
lfi.clear();
cur_size = min_size;
llong fs = fi.size;
for (int j = 1; j < fi_prts; j++) {
fi_index++;
fi.start = fs;
fi.size = piMin<ullong>(parts[i].size - fs, packet_size - min_size);
lfi << fi;
cur_size += fi.size + part_header_size;
if (fi_index != fi_prts) {
session << lfi;
lfi.clear();
cur_size = min_size;
fs += fi.size;
}
}
}
if (packet_size - cur_size < min_size) {
session << lfi;
lfi.clear();
cur_size = min_size;
}
}
if (cur_size > min_size) session << lfi;
mutex_header.unlock();
mutex_session.unlock();
}
void PIBaseTransfer::sendBreak(int session_id) {
//piCoutObj << "sendBreak";
uint psid = header.session_id;
header.session_id = session_id;
sendReply(pt_Break);
header.session_id = psid;
}
void PIBaseTransfer::sendReply(PacketType reply) {
//piCoutObj << "sendReply" << reply;
header.type = reply;
PIByteArray ba;
ba << header;
if (is_sending || is_receiving) diag.sended(ba.size_s());
sendRequest(ba);
}
bool PIBaseTransfer::getStartRequest() {
PITimeMeasurer tm;
mutex_header.lock();
header.type = pt_Start;
header.id = 0;
PIByteArray ba;
StartRequest st;
st.packets = (uint)session.size();
st.size = bytes_all;
ba << header;
mutex_header.unlock();
ba << st;
state_string = "send request";
while (tm.elapsed_s() < timeout_) {
diag.sended(ba.size_s());
sendRequest(ba);
if (break_) return false;
// piCoutObj << replies[0];
mutex_session.lock();
if (replies[0] == pt_ReplySuccess) {
state_string = "send permited!";
//piCoutObj << "ping " << tm.elapsed_m();
mutex_session.unlock();
return true;
}
mutex_session.unlock();
piMSleep(1);
}
return false;
}
void PIBaseTransfer::processData(int id, PIByteArray & data) {
// piCoutObj << "received packet" << id << ", size" << data.size();
if (id < 1 || id > replies.size_s()) return;
if (!session[id - 1].isEmpty()) {
header.id = id;
replies[id] = pt_ReplySuccess;
sendReply(pt_ReplySuccess);
if (replies[replies.size()-1] == pt_ReplySuccess)
if (checkSession() == 0) state_string = "receive ok";
return;
}
Part fi;
PIByteArray ba, pheader;
pheader.resize(packet_header_size - sizeof(PacketHeader));
if (!pheader.isEmpty()) {
memcpy(pheader.data(), data.data(), pheader.size());
data.remove(0, pheader.size_s());
}
while (!data.isEmpty()) {
ba.clear();
data >> fi;
//if (fi.size > 0)
data >> ba;
//fi.fsize = ba.size();
bytes_cur += fi.size;
//piCoutObj << "recv" << fi;
session[id - 1] << fi;
state_string = "receiving...";
receivePart(fi, ba, pheader);
}
header.id = id;
replies[id] = pt_ReplySuccess;
sendReply(pt_ReplySuccess);
if (checkSession() == 0) state_string = "receive ok";
}
PIByteArray PIBaseTransfer::build_packet(int id) {
PIByteArray ret;
PIByteArray ba;
//piCoutObj << "session id" << header.session_id;
//ret << header;
ret.append(customHeader());
mutex_session.lock();
for (int i = 0; i < session[id].size_s(); i++) {
Part fi = session[id][i];
ret << fi;
//piCout << "biuld" << fi;
ba = buildPacket(fi);
bytes_cur += ba.size();
if (ba.size() != fi.size) piCoutObj << "***error while build packet, wrong part size";
ret << ba;
}
mutex_session.unlock();
PIByteArray hdr;
mutex_header.lock();
header.id = id + 1;
header.type = pt_Data;
header.crc = crc.calculate(ret);
hdr << header;
mutex_header.unlock();
ret.insert(0, hdr);
// piCoutObj << "Send Packet" << header.id << ret.size();
return ret;
}
bool PIBaseTransfer::finish_send(bool ok) {
is_sending = false;
if (ok) state_string = "send done";
else state_string = "send failed";
//piCoutObj << state_string << PIString::readableSize(bytes_all);
mutex_header.lock();
header.id = 0;
if (!ok) sendBreak(header.session_id);
else sendReply(pt_ReplySuccess);
mutex_header.unlock();
sendFinished(ok);
// diag.stop();
bytes_all = bytes_cur = 0;
return ok;
}
void PIBaseTransfer::finish_receive(bool ok, bool quet) {
is_receiving = false;
if (ok) state_string = "receive done";
else state_string = "receive failed";
//piCoutObj << state_string << PIString::readableSize(bytes_all);
if (!ok && !quet) {
mutex_header.lock();
sendBreak(header.session_id);
mutex_header.unlock();
}
receiveFinished(ok);
// diag.stop();
bytes_all = bytes_cur = 0;
}

View File

@@ -0,0 +1,153 @@
/*! \file pibasetransfer.h
* \brief Base class for reliable send and receive data in fixed packets with error correction, pause and resume
*/
/*
PIP - Platform Independent Primitives
Base class for reliable send and receive data in fixed packets with error correction, pause and resume
Copyright (C) 2016 Andrey Bychkov work.a.b@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIBASETRANSFER_H
#define PIBASETRANSFER_H
#include "picrc.h"
#include "pidiagnostics.h"
class PIBaseTransfer: public PIObject
{
PIOBJECT_SUBCLASS(PIBaseTransfer, PIObject)
public:
PIBaseTransfer();
~PIBaseTransfer();
# pragma pack(push,1)
struct PacketHeader {
uint sig;
int type; // PacketType
int session_id;
uint id;
uint crc;
bool check_sig() {return (sig == signature);}
};
struct Part {
Part(uint id_ = 0, ullong size_ = 0, ullong start_ = 0) : id(id_), size(size_), start(start_) {}
uint id;
ullong size;
ullong start;
};
# pragma pack(pop)
void stopSend();
void stopReceive();
bool isSending() const {return is_sending;}
bool isReceiving() const {return is_receiving;}
bool isPause() const {return is_pause;}
void setPause(bool pause_);
void setPacketSize(int size) {packet_size = size;}
int packetSize() const {return packet_size;}
void setTimeout(double sec) {timeout_ = sec; diag.setDisconnectTimeout(sec);}
double timeout() const {return timeout_;}
const PIString & stateString() const {return state_string;}
llong bytesAll() const {return bytes_all;}
llong bytesCur() const {return bytes_cur;}
const PIString * stateString_ptr() const {return &state_string;}
const llong * bytesAll_ptr() const {return &bytes_all;}
const llong * bytesCur_ptr() const {return &bytes_cur;}
const PIDiagnostics &diagnostic() {return diag;}
static uint packetSignature() {return signature;}
EVENT_HANDLER1(void, received, PIByteArray, data);
EVENT_HANDLER(void, stop) {stopSend(); stopReceive();}
EVENT_HANDLER(void, pause) {setPause(true);}
EVENT_HANDLER(void, resume) {setPause(false);}
EVENT(receiveStarted)
EVENT(paused)
EVENT(resumed)
EVENT1(receiveFinished, bool, ok)
EVENT(sendStarted)
EVENT1(sendFinished, bool, ok)
EVENT1(sendRequest, PIByteArray &, data)
protected:
uint packet_header_size, part_header_size;
bool break_, is_sending, is_receiving, is_pause;
PIString state_string;
llong bytes_all, bytes_cur;
void buildSession(PIVector<Part> parts);
virtual PIByteArray buildPacket(Part fi) = 0;
virtual void receivePart(Part fi, PIByteArray ba, PIByteArray pheader) = 0;
virtual PIByteArray customHeader() {return PIByteArray();}
bool send_process();
private:
enum PacketType {pt_Unknown, pt_Data, pt_ReplySuccess, pt_ReplyInvalid, pt_Break, pt_Start, pt_Pause};
# pragma pack(push,1)
struct StartRequest {
uint packets;
ullong size;
};
# pragma pack(pop)
friend PIByteArray & operator >>(PIByteArray & s, PIBaseTransfer::StartRequest & v);
friend PIByteArray & operator <<(PIByteArray & s, const PIBaseTransfer::StartRequest & v);
static const uint signature;
int packet_size, packets_count;
double timeout_;
PIVector<PIVector<Part> > session;
PIVector<PacketType> replies;
PITimeMeasurer send_tm, pause_tm;
PacketHeader header;
CRC_16 crc;
int send_queue;
PIDiagnostics diag;
PIMutex mutex_session;
PIMutex mutex_send;
PIMutex mutex_header;
void processData(int id, PIByteArray &data);
PIByteArray build_packet(int id);
int checkSession();
void sendBreak(int session_id);
void sendReply(PacketType reply);
bool getStartRequest();
bool finish_send(bool ok);
void finish_receive(bool ok, bool quet = false);
};
inline PIByteArray & operator <<(PIByteArray & s, const PIBaseTransfer::PacketHeader & v) {s << v.sig << v.type << v.session_id << v.id << v.crc; return s;}
inline PIByteArray & operator >>(PIByteArray & s, PIBaseTransfer::PacketHeader & v) {s >> v.sig >> v.type >> v.session_id >> v.id >> v.crc; return s;}
inline PIByteArray & operator <<(PIByteArray & s, const PIBaseTransfer::Part & v) {s << v.id << v.size << v.start; return s;}
inline PIByteArray & operator >>(PIByteArray & s, PIBaseTransfer::Part & v) {s >> v.id >> v.size >> v.start; return s;}
inline PIByteArray & operator <<(PIByteArray & s, const PIBaseTransfer::StartRequest & v) {s << v.packets << v.size; return s;}
inline PIByteArray & operator >>(PIByteArray & s, PIBaseTransfer::StartRequest & v) {s >> v.packets >> v.size; return s;}
inline PICout operator <<(PICout s, const PIBaseTransfer::Part & v) {s.setControl(0, true); s << "Part(\"" << v.id << "\", " << PIString::readableSize(v.start) << " b | " << PIString::readableSize(v.size) << " b)"; s.restoreControl(); return s;}
#endif // PIBASETRANSFER_H

644
src_main/io/pibinarylog.cpp Normal file
View File

@@ -0,0 +1,644 @@
/*
PIP - Platform Independent Primitives
Class for write binary data to logfile, and read or playback this data
Copyright (C) 2016 Andrey Bychkov work.a.b@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 "pibinarylog.h"
#include "pidir.h"
/*! \class PIBinaryLog
* \brief Class for read and write binary data to logfile, and playback this data in realtime, or custom speed
*
* \section PIBinaryLog_sec0 Synopsis
* Binary Log is a file with simple header, where you can read and write some binary data.
* Any written data include special header with ID, size and timestamp.
* This header provides separation different messages from the one file by choosing different IDs.
* With \a filterID or special functions, like \a readBinLog() you can choose IDs what you want to read.
* With function \a writeBinLog() or \a setDefaultID() you can choose ID that mark you data.
* By default ID = 1, and \a filterID is empty, that mean you read any ID without filtering.
* ThreadedRead provide you playback data, with delay that you write data.
* You can choose different playbak modes by set \a PlayMode.
*
* \section PIBinaryLog_sec1 Basic usage
* This class provide all functions of \a PIIODevice, such \a open(), \a close(),
* \a read() ,\a write(), and threaded read/write.
* function \a setLogDir() need to set directory for BinLog files
* function \a createNewFile() need to create new binlog file
* function \a restart() need start from the begining of binlog file
*
*/
REGISTER_DEVICE(PIBinaryLog)
PIBinaryLog::PIBinaryLog() {
setThreadedReadBufferSize(65536);
is_started = is_indexed = is_pause = false;
current_index = -1;
setPlaySpeed(1.);
setDefaultID(1);
setPlaySpeed(1.0);
setPlayDelay(PISystemTime::fromSeconds(1.0));
setPlayRealTime();
setSplitTime(PISystemTime(600, 0));
setSplitRecordCount(1000);
setSplitFileSize(0xFFFFFF);
setSplitMode(SplitNone);
setLogDir(PIString());
setFilePrefix(PIString());
setRapidStart(false);
file.setName("__S__PIBinaryLog::file");
// piCoutObj << "created";
}
bool PIBinaryLog::openDevice() {
lastrecord.timestamp = PISystemTime();
lastrecord.id = 0;
write_count = 0;
is_started = false;
is_thread_ok = true;
is_indexed = false;
is_pause = false;
index.clear();
index_pos.clear();
if (mode_ == ReadWrite) {
piCoutObj << "Error: ReadWrite mode not supported, use WriteOnly or ReadOnly";
return false;
}
if (path().isEmpty() && mode_ == WriteOnly) {
setPath(getLogfilePath());
}
if (path().isEmpty() && mode_ == ReadOnly) {
PIDir ld(logDir());
if (ld.isExists()) {
PIVector<PIFile::FileInfo> es = ld.allEntries();
piForeachC(PIFile::FileInfo &i, es) {
if (i.extension() == "binlog" && i.isFile())
setPath(i.path);
}
}
}
if (!file.open(path(), mode_)) {
piCoutObj << "Error: Can't open file" << path();
return false;
}
setName(path());
if (mode_ == WriteOnly) {
file.clear();
if (!writeFileHeader()) {
piCoutObj << "Error: Can't write binlog file header" << path();
return false;
}
is_started = true;
}
if (mode_ == ReadOnly) {
if (file.isEmpty()) {
piCoutObj << "Error: File is null" << path();
fileError();
return false;
}
if (!checkFileHeader()) {
fileError();
return false;
}
if (isEmpty()) {
piCoutObj << "Warning: Empty BinLog file" << path();
fileEnd();
}
play_time = 0;
if (!rapidStart()) is_started = true;
}
startlogtime = PISystemTime::current();
pause_time = PISystemTime();
return true;
}
bool PIBinaryLog::closeDevice() {
moveIndex(-1);
is_indexed = false;
index.clear();
index_pos.clear();
if (canWrite() && isEmpty()) {
file.remove();
return true;
}
return file.close();
}
bool PIBinaryLog::threadedRead(uchar *readed, int size) {
// piCout << "binlog threaded read";
if (!canRead() || isEnd()) return PIIODevice::threadedRead(readed, size);
is_thread_ok = false;
PISystemTime pt;
double delay;
switch (play_mode) {
case PlayRealTime:
pausemutex.lock();
if (is_pause) {
piMSleep(100);
pausemutex.unlock();
return false;
} else if (pause_time > PISystemTime()) {
startlogtime += pause_time;
pause_time = PISystemTime();
}
pausemutex.unlock();
pt = PISystemTime::current() - startlogtime;
if (is_started) {
if (lastrecord.timestamp > pt)
(lastrecord.timestamp - pt).sleep();
} else {
startlogtime = PISystemTime::current() - lastrecord.timestamp;
is_started = true;
}
break;
case PlayVariableSpeed:
delay = lastrecord.timestamp.toMilliseconds() - play_time;
//piCoutObj << "delay" << delay;
double cdelay;
int dtc;
if (is_started) {
if (is_pause) {
piMSleep(100);
return false;
}
if (delay > 0) {
cdelay = delay * play_speed;
dtc = int(cdelay) /100;
if (play_speed <= 0.) dtc = 2;
//piCout << play_speed << dtc;
for (int j=0; j<dtc; j++) {
cdelay = delay * play_speed;
dtc = int(cdelay) /100;
piMSleep(100);
if (play_speed <= 0.) {dtc = 2; j = 0;}
//piCout << " " << play_speed << dtc << j;
}
cdelay = cdelay - dtc*100;
PISystemTime::fromMilliseconds(cdelay).sleep();
}
} else is_started = true;
play_time = lastrecord.timestamp.toMilliseconds();
break;
case PlayStaticDelay:
if (is_started) {
if (is_pause) {
piMSleep(100);
return false;
}
play_delay.sleep();
} else is_started = true;
break;
default:
return false;
}
bool res = PIIODevice::threadedRead(readed, size);
is_thread_ok = true;
return res;
}
PIString PIBinaryLog::getLogfilePath() const {
PIDir dir(logDir());
dir.setDir(dir.absolutePath());
if (!dir.isExists()) {
piCoutObj << "Creating directory" << dir.path();
dir.make(true);
}
PIString npath = logDir() + "/" + filePrefix() + PIDateTime::current().toString("yyyy_MM_dd__hh_mm_ss");
PIString cnpath = npath + ".binlog";
int i = 1;
while (PIFile::isExists(cnpath)) {
cnpath = npath + "_" + PIString::fromNumber(i) + ".binlog";
i++;
}
return cnpath;
}
PIString PIBinaryLog::createNewFile() {
if (!file.close()) return PIString();
PIString cnpath = getLogfilePath();
if (open(cnpath, PIIODevice::WriteOnly)) {
newFile(file.path());
return file.path();
}
piCoutObj << "Can't create new file, maybe LogDir is invalid.";
return PIString();
}
void PIBinaryLog::createNewFile(const PIString &path) {
if (open(path, PIIODevice::WriteOnly)) newFile(file.path());
else piCoutObj << "Can't create new file, maybe path is invalid.";
}
void PIBinaryLog::setPause(bool pause) {
pausemutex.lock();
is_pause = pause;
if (pause) pause_time = PISystemTime::current();
else pause_time = PISystemTime::current() - pause_time;
pausemutex.unlock();
}
int PIBinaryLog::writeBinLog(int id, const void *data, int size) {
if (size <= 0 || !canWrite()) return -1;
if (id == 0) {
piCoutObj << "Error: can`t write with id = 0!";
return -1;
}
switch (split_mode) {
case SplitSize:
if (logSize() > split_size) createNewFile();
break;
case SplitTime:
if ((PISystemTime::current() - startlogtime) > split_time) createNewFile();
break;
case SplitCount:
if (write_count > split_count) createNewFile();
break;
default: break;
}
if (is_pause) return 0;
PIByteArray logdata;
logdata << id << size << (PISystemTime::current() - startlogtime) << PIByteArray::RawData(data, size);
int res = file.write(logdata.data(), logdata.size());
file.flush();
write_count++;
if (res > 0) return size;
else return res;
}
int PIBinaryLog::writeBinLog_raw(int id, const PISystemTime &time, const void *data, int size) {
if (size <= 0 || !canWrite()) return -1;
PIByteArray logdata;
logdata << id << size << time << PIByteArray::RawData(data, size);
int res = file.write(logdata.data(), logdata.size());
file.flush();
write_count++;
if (res > 0) return size;
else return res;
}
PIByteArray PIBinaryLog::readBinLog(int id, PISystemTime * time) {
if (!canRead()) return PIByteArray();
BinLogRecord br = readRecord();
if (br.id == -1) {
piCoutObj << "End of BinLog file";
fileEnd();
return PIByteArray();
}
if (id == 0 && br.id > 0) return br.data;
while (br.id != id && !isEnd()) br = readRecord();
if (br.id == -1) {
piCoutObj << "End of BinLog file";
fileEnd();
return PIByteArray();
}
if (br.id == id) {
if (time)
*time = br.timestamp;
return br.data;
}
piCoutObj << "Can't find record with id =" << id;
return PIByteArray();
}
int PIBinaryLog::readBinLog(int id, void *read_to, int max_size, PISystemTime * time) {
if (max_size <= 0 || read_to == 0) return -1;
PIByteArray ba = readBinLog(id, time);
if (ba.isEmpty()) return -1;
int sz = piMini(max_size, ba.size());
memcpy(read_to, ba.data(), sz);
return sz;
}
int PIBinaryLog::readDevice(void *read_to, int max_size) {
if (lastrecord.id == -1 || isEnd()) return 0;
if(!is_thread_ok && lastrecord.id > 0) return lastrecord.data.size();
if (!canRead()) return -1;
if (max_size <= 0 || read_to == 0) return -1;
BinLogRecord br;
br.id = 0;
if (filterID.isEmpty()) br = readRecord();
else {
while (!filterID.contains(br.id) && !isEnd()) br = readRecord();
}
if (br.id == -1) {
fileEnd();
piCoutObj << "End of BinLog file";
return 0;
}
if (br.id <= 0) {
piCoutObj << "Read record error";
return -1;
}
int sz = piMini(max_size, br.data.size());
memcpy(read_to, br.data.data(), sz);
return sz;
}
void PIBinaryLog::restart() {
bool th = isRunning();
if (th) stopThreadedRead();
if (!canRead()) return;
lastrecord.timestamp = PISystemTime();
lastrecord.id = 0;
is_thread_ok = true;
is_started = !rapidStart();
play_time = 0;
file.seekToBegin();
checkFileHeader();
moveIndex(0);
startlogtime = PISystemTime::current();
if (th) startThreadedRead();
}
bool PIBinaryLog::writeFileHeader() {
if (file.write(&__S__PIBinaryLog::binlog_sig, PIBINARYLOG_SIGNATURE_SIZE) <= 0) return false;
uchar version = PIBINARYLOG_VERSION;
if (file.write(&version, 1) <= 0) return false;
file.flush();
return true;
}
bool PIBinaryLog::checkFileHeader() {
uchar read_sig[PIBINARYLOG_SIGNATURE_SIZE];
for (uint i=0; i<PIBINARYLOG_SIGNATURE_SIZE; i++) read_sig[i] = 0;
if (file.read(read_sig, PIBINARYLOG_SIGNATURE_SIZE) < 0) return false;
bool correct = true;
for (uint i=0; i<PIBINARYLOG_SIGNATURE_SIZE; i++)
if (read_sig[i] != __S__PIBinaryLog::binlog_sig[i]) correct = false;
if (!correct) {
piCoutObj << "BinLogFile signature is corrupted or invalid file";
return false;
}
uchar read_version = 0;
if (file.read(&read_version, 1) < 0) return false;
if (read_version == PIBINARYLOG_VERSION) return true;
if (read_version == 0)
piCoutObj << "BinLogFile has invalid version";
if (read_version < PIBINARYLOG_VERSION)
piCoutObj << "BinLogFile has too old verion";
if (read_version > PIBINARYLOG_VERSION)
piCoutObj << "BinLogFile has too newest version";
return false;
}
PIBinaryLog::BinLogRecord PIBinaryLog::readRecord() {
logmutex.lock();
PIByteArray ba;
BinLogRecord br;
lastrecord.id = 0;
lastrecord.data.clear();
lastrecord.timestamp = PISystemTime();
ba.resize(sizeof(BinLogRecord) - sizeof(PIByteArray));
if(file.read(ba.data(), ba.size_s()) > 0) {
ba >> br.id >> br.size >> br.timestamp;
} else {
br.id = -1;
logmutex.unlock();
return br;
}
if (br.id > 0 && br.size > 0) {
ba.resize(br.size);
if(file.read(ba.data(), ba.size_s()) > 0) br.data = ba;
else br.id = 0;
} else br.id = 0;
lastrecord = br;
if (br.id == 0) fileError();
moveIndex(index_pos.value(file.pos(), -1));
logmutex.unlock();
return br;
}
void PIBinaryLog::parseLog(PIFile * f, PIBinaryLog::BinLogInfo * info, PIVector<PIBinaryLog::BinLogIndex> * index) {
BinLogInfo * bi = info;
bool ginfo = info != 0;
bool gindex = index != 0;
if (!ginfo && !gindex) return;
if (ginfo) {
bi->log_size = -1;
bi->records_count = 0;
bi->records.clear();
}
if (gindex) index->clear();
if (f == 0) return;
if (!f->canRead()) return;
if (ginfo) {
bi->path = f->path();
bi->log_size = f->size();
}
uchar read_sig[PIBINARYLOG_SIGNATURE_SIZE];
for (uint i=0; i<PIBINARYLOG_SIGNATURE_SIZE; i++) read_sig[i] = 0;
bool ok = true;
if (f->read(read_sig, PIBINARYLOG_SIGNATURE_SIZE) < 0) {if (ginfo) bi->records_count = -1; ok = false;}
for (uint i=0; i<PIBINARYLOG_SIGNATURE_SIZE; i++)
if (read_sig[i] != __S__PIBinaryLog::binlog_sig[i]) {if (ginfo) bi->records_count = -2; ok = false;}
uchar read_version = 0;
if (f->read(&read_version, 1) < 0) {if (ginfo) bi->records_count = -3; ok = false;}
if (read_version == 0) {if (ginfo) bi->records_count = -4; ok = false;}
if (read_version < PIBINARYLOG_VERSION) {if (ginfo) bi->records_count = -5; ok = false;}
if (read_version > PIBINARYLOG_VERSION) {if (ginfo) bi->records_count = -6; ok = false;}
if (!ok) return;
PIByteArray ba;
BinLogRecord br;
bool first = true;
llong hdr_size = sizeof(BinLogRecord) - sizeof(PIByteArray);
ba.resize(hdr_size);
while (1) {
ba.resize(hdr_size);
if(f->read(ba.data(), ba.size_s()) > 0) {
ba >> br.id >> br.size >> br.timestamp;
} else break;
if (f->size() - f->pos() >= br.size) f->seek(f->pos() + br.size);
else break;
if (br.id > 0) {
if (gindex) {
BinLogIndex bl_ind;
bl_ind.id = br.id;
bl_ind.pos = f->pos() - br.size - hdr_size;
bl_ind.timestamp = br.timestamp;
index->append(bl_ind);
}
if (ginfo) {
bi->records_count++;
if (first) {
bi->start_time = br.timestamp;
first = false;
}
BinLogRecordInfo &bri(bi->records[br.id]);
bri.count++;
if (bri.id == 0) {
bri.id = br.id;
bri.minimum_size = bri.maximum_size = br.size;
bri.start_time = br.timestamp;
} else {
bri.end_time = br.timestamp;
if (bri.minimum_size > br.size) bri.minimum_size = br.size;
if (bri.maximum_size < br.size) bri.maximum_size = br.size;
}
}
}
}
if (ginfo) bi->end_time = br.timestamp;
}
void PIBinaryLog::moveIndex(int i) {
if (is_indexed) {
current_index = i;
posChanged(current_index);
}
}
PIBinaryLog::BinLogInfo PIBinaryLog::getLogInfo(const PIString & path) {
BinLogInfo bi;
bi.path = path;
bi.records_count = 0;
PIFile tfile;
if (!tfile.open(path, PIIODevice::ReadOnly)) return bi;
parseLog(&tfile, &bi, 0);
return bi;
}
PIString PIBinaryLog::constructFullPathDevice() const {
PIString ret;
ret << logDir() << ":" << filePrefix() << ":" << defaultID() << ":";
switch (play_mode) {
case PlayRealTime:
ret << "RT";
break;
case PlayVariableSpeed:
ret << PIString::fromNumber(playSpeed()) << "X";
break;
case PlayStaticDelay:
ret << PIString::fromNumber(playDelay().toMilliseconds()) << "M";
break;
default:
ret << "RT";
break;
}
return ret;
}
bool PIBinaryLog::createIndex() {
llong cp = file.pos();
file.seekToBegin();
index.clear();
index_pos.clear();
parseLog(&file, &binfo, &index);
file.seek(cp);
is_indexed = !index.isEmpty();
for (uint i=0; i<index.size(); i++) index_pos[index[i].pos] = i;
return is_indexed;
}
void PIBinaryLog::seekTo(int rindex) {
logmutex.lock();
if (rindex < index.size_s() && rindex >= 0) {
file.seek(index[rindex].pos);
moveIndex(index_pos.value(file.pos(), -1));
play_time = index[rindex].timestamp.toMilliseconds();
lastrecord.timestamp = index[rindex].timestamp;
}
logmutex.unlock();
}
bool PIBinaryLog::seek(const PISystemTime & time) {
int ci = -1;
for (uint i=0; i<index.size(); i++) {
if (time <= index[i].timestamp && (filterID.contains(index[i].id) || filterID.isEmpty())) {
ci = i;
break;
}
}
if (ci >= 0) {
seekTo(ci);
return true;
}
return false;
}
bool PIBinaryLog::seek(llong filepos) {
int ci = -1;
for (uint i=0; i<index.size(); i++) {
if (filepos <= index[i].pos && (filterID.contains(index[i].id) || filterID.isEmpty())) {
ci = i;
break;
}
}
if (ci >= 0) {
seekTo(ci);
return true;
}
return false;
}
void PIBinaryLog::configureFromFullPathDevice(const PIString & full_path) {
PIStringList pl = full_path.split(":");
for (int i = 0; i < pl.size_s(); ++i) {
PIString p(pl[i]);
switch (i) {
case 0: setLogDir(p); break;
case 1: setFilePrefix(p); break;
case 2: setDefaultID(p.toInt()); break;
case 3:
if (p.toUpperCase() == "RT") setPlayRealTime();
if (p.toUpperCase().right(1) == "X") setPlaySpeed((p.left(p.size() - 1)).toDouble());
if (p.toUpperCase().right(1) == "M") setPlayDelay(PISystemTime::fromMilliseconds((p.left(p.size() - 1)).toDouble()));
break;
}
}
// piCoutObj << "configured";
}
void PIBinaryLog::propertyChanged(const PIString &s) {
default_id = property("defaultID").toInt();
rapid_start = property("rapidStart").toBool();
play_mode = (PlayMode)property("playMode").toInt();
double ps = property("playSpeed").toDouble();
play_speed = ps > 0. ? 1. / ps : 0.;
play_delay = property("playDelay").toSystemTime();
split_mode = (SplitMode)property("splitMode").toInt();
split_time = property("splitTime").toSystemTime();
split_size = property("splitFileSize").toLLong();
split_count = property("splitRecordCount").toInt();
// piCoutObj << "propertyChanged" << s << play_mode;
}

349
src_main/io/pibinarylog.h Normal file
View File

@@ -0,0 +1,349 @@
/*! \file pibinarylog.h
* \brief Binary log
*/
/*
PIP - Platform Independent Primitives
Class for write binary data to logfile, and read or playback this data
Copyright (C) 2016 Andrey Bychkov work.a.b@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIBINARYLOG_H
#define PIBINARYLOG_H
#include "pifile.h"
#define PIBINARYLOG_VERSION 0x31
namespace __S__PIBinaryLog {
static const uchar binlog_sig[] = {'B','I','N','L','O','G'};
}
#define PIBINARYLOG_SIGNATURE_SIZE sizeof(__S__PIBinaryLog::binlog_sig)
/// TODO: Create static functions to split and join binlog files
/// TODO: Create functions to insert and delete records
class PIBinaryLog: public PIIODevice
{
PIIODEVICE(PIBinaryLog)
public:
explicit PIBinaryLog();
~PIBinaryLog() {closeDevice();}
//! \brief Play modes for \a PIBinaryLog
enum PlayMode {
PlayRealTime /*! Play in system realtime, default mode */ ,
PlayVariableSpeed /*! Play in software realtime with speed, set by \a setSpeed */ ,
PlayStaticDelay /*! Play with custom static delay, ignoring timestamp */
};
//! \brief Different split modes for writing \a PIBinaryLog, which can separate files by size, by time or by records count
enum SplitMode {
SplitNone /*! Without separate, default mode */ ,
SplitTime /*! Separate files by record time */ ,
SplitSize /*! Separate files by size */ ,
SplitCount /*! Separate files by records count */
};
//! \brief Struct contains information about all records with same ID
struct BinLogRecordInfo {
BinLogRecordInfo() {
id = count = 0;
minimum_size = maximum_size = 0;
}
int id;
int count;
int minimum_size;
int maximum_size;
PISystemTime start_time;
PISystemTime end_time;
};
//! \brief Struct contains full information about Binary Log file and about all Records using map of \a BinLogRecordInfo
struct BinLogInfo {
PIString path;
int records_count;
llong log_size;
PISystemTime start_time;
PISystemTime end_time;
PIMap<int, BinLogRecordInfo> records;
};
//! \brief Struct contains position, ID and timestamp of record in file
struct BinLogIndex {
int id;
llong pos;
PISystemTime timestamp;
};
//! Current \a PlayMode
PlayMode playMode() const {return play_mode;}
//! Current \a SplitMode
SplitMode splitMode() const {return split_mode;}
//! Current directory where billogs wiil be saved
PIString logDir() const {return property("logDir").toString();}
//! Returns current file prefix
PIString filePrefix() const {return property("filePrefix").toString();}
//! Default ID, used in \a write function
int defaultID() const {return default_id;}
//! Returns current play speed
double playSpeed() const {return play_speed > 0 ? 1. / play_speed : 0.;}
//! Returns current play delay
PISystemTime playDelay() const {return play_delay;}
//! Returns current binlog file split time
PISystemTime splitTime() const {return split_time;}
//! Returns current binlog file split size
llong splitFileSize() const {return split_size;}
//! Returns current binlog file split records count
int splitRecordCount() const {return split_count;}
//! Returns if rapid start enabled
bool rapidStart() const {return rapid_start;}
//! Create binlog file with Filename = path
void createNewFile(const PIString &path);
//! Set \a PlayMode
void setPlayMode(PlayMode mode) {setProperty("playMode", (int)mode);}
//! Set \a SplitMode
void setSplitMode(SplitMode mode) {setProperty("splitMode", (int)mode);}
//! Set path to directory where binlogs will be saved
void setLogDir(const PIString & path) {setProperty("logDir", path);}
//! Set file prefix, used to
void setFilePrefix(const PIString & prefix) {setProperty("filePrefix", prefix);}
//! Set defaultID, used in \a write function
void setDefaultID(int id) {setProperty("defaultID", id);}
//! If enabled BinLog \a ThreadedRead starts without delay for first record, i.e. first record will be readed immediately
void setRapidStart(bool enabled) {setProperty("rapidStart", enabled);}
//! Set play speed to "speed", default value is 1.0x
//! Also this function set \a playMode to \a PlayVariableSpeed
void setPlaySpeed(double speed) {setPlayMode(PlayVariableSpeed); setProperty("playSpeed", speed);}
//! Setting static delay between records, default value is 1 sec
//! Also this function set \a playMode to \a PlayStaticDelay
void setPlayDelay(const PISystemTime & delay) {setPlayMode(PlayStaticDelay); setProperty("playDelay", delay);}
//! Set \a playMode to \a PlayRealTime
void setPlayRealTime() {setPlayMode(PlayRealTime);}
//! Set binlog file split time
//! Also this function set \a splitMode to \a SplitTime
void setSplitTime(const PISystemTime & time) {setSplitMode(SplitTime); setProperty("splitTime", time);}
//! Set binlog file split size
//! Also this function set \a splitMode to \a SplitSize
void setSplitFileSize(llong size) {setSplitMode(SplitSize); setProperty("splitFileSize", size);}
//! Set binlog file split records count
//! Also this function set \a splitMode to \a SplitCount
void setSplitRecordCount(int count) {setSplitMode(SplitCount); setProperty("splitRecordCount", count);}
//! Set pause while playing via \a threadedRead or writing via write
void setPause(bool pause);
//! Write one record to BinLog file, with ID = id, id must be greather than 0
int writeBinLog(int id, PIByteArray data) {return writeBinLog(id, data.data(), data.size_s());}
//! Write one record to BinLog file, with ID = id, id must be greather than 0
int writeBinLog(int id, const void * data, int size);
//! Write one RAW record to BinLog file, with ID = id, Timestamp = time
int writeBinLog_raw(int id, const PISystemTime &time, const PIByteArray &data) {return writeBinLog_raw(id, time, data.data(), data.size_s());}
int writeBinLog_raw(int id, const PISystemTime &time, const void * data, int size);
//! Returns count of writed records
int writeCount() const {return write_count;}
//! Read one record from BinLog file, with ID = id, if id = 0 than any id will be readed
PIByteArray readBinLog(int id = 0, PISystemTime * time = 0);
//! Read one record from BinLog file, with ID = id, if id = 0 than any id will be readed
int readBinLog(int id, void * read_to, int max_size, PISystemTime * time = 0);
//! Returns binary log file size
llong logSize() const {return file.size();}
//! Return true, if position at the end of BinLog file
bool isEnd() const {if (isClosed()) return true; return file.isEnd();}
//! Returns if BinLog file is empty
bool isEmpty() const {return (file.size() <= llong(PIBINARYLOG_SIGNATURE_SIZE + 1));}
//! Returns BinLog pause status
bool isPause() const {return is_pause;}
//! Returns if BinLog file is empty
int lastReadedID() const {return lastrecord.id;}
#ifdef DOXYGEN
//! Read one message from binlog file, with ID contains in "filterID" or any ID, if "filterID" is empty
int read(void *read_to, int max_size);
//! Write one record to BinLog file, with ID = "defaultID"
int write(const void * data, int size);
#endif
//! Array of ID, that BinLog can read from binlog file, when use \a read function, or in \a ThreadedRead
PIVector<int> filterID;
//! Go to begin of BinLog file
void restart();
//! Get binlog info \a BinLogInfo
BinLogInfo logInfo() const {if (is_indexed) return binfo; return getLogInfo(path());}
//! Get binlog index \a BinLogIndex, need \a createIndex before getting index
const PIVector<BinLogIndex> & logIndex() const {return index;} /// TODO: Think about index positions
//! Create index of current binlog file
bool createIndex();
//! Go to record #index
void seekTo(int rindex);
//! Go to nearest record
bool seek(const PISystemTime & time);
//! Set position in file to reading/playing
bool seek(llong filepos);
//! Get current record index (position record in file)
int pos() const {if (is_indexed) return current_index; return -1;} /// TODO: Think about index positions
//! \handlers
//! \{
//! \fn PIString createNewFile()
//! \brief Create new binlog file in \a logDir, if successful returns filename, else returns empty string.
//! Filename is like \a filePrefix + "yyyy_MM_dd__hh_mm_ss.binlog"
//! \}
//! \events
//! \{
//! \fn void fileEnd()
//! \brief Raise on file end while reading
//! \fn void fileError()
//! \brief Raise on file creation error
//! \fn void newFile(const PIString & filename)
//! \brief Raise on new file created
//! \}
EVENT_HANDLER(PIString, createNewFile);
EVENT(fileEnd)
EVENT(fileError)
EVENT1(newFile, const PIString &, filename)
EVENT1(posChanged, int, pos) /// TODO: Think about index positions
//! Get binlog info and statistic
static BinLogInfo getLogInfo(const PIString & path);
protected:
PIString fullPathPrefix() const {return "binlog";}
PIString constructFullPathDevice() const;
void configureFromFullPathDevice(const PIString & full_path);
int readDevice(void *read_to, int max_size);
int writeDevice(const void * data, int size) {return writeBinLog(default_id, data, size);}
bool openDevice();
bool closeDevice();
void propertyChanged(const PIString &);
bool threadedRead(uchar *readed, int size);
private:
struct BinLogRecord {
int id;
int size;
PISystemTime timestamp;
PIByteArray data;
};
bool writeFileHeader();
bool checkFileHeader();
BinLogRecord readRecord();
static void parseLog(PIFile *f, BinLogInfo *info, PIVector<BinLogIndex> * index);
void moveIndex(int i);
PIString getLogfilePath() const;
PIVector<BinLogIndex> index;
PIMap<llong, int> index_pos;
BinLogInfo binfo;
PlayMode play_mode;
SplitMode split_mode;
PIFile file;
BinLogRecord lastrecord;
PISystemTime startlogtime, play_delay, split_time, pause_time;
PIMutex logmutex, pausemutex;
double play_time, play_speed;
llong split_size;
int write_count, split_count, default_id, current_index;
bool is_started, is_thread_ok, is_indexed, rapid_start, is_pause;
};
//! \relatesalso PICout \relatesalso PIBinaryLog::BinLogInfo \brief Output operator to PICout
inline PICout operator <<(PICout s, const PIBinaryLog::BinLogInfo & bi) {
s.space();
s.setControl(0, true);
s << "[PIBinaryLog] " << bi.path << "\n";
if (bi.log_size < 0) {
s << "invalid file path";
s.restoreControl();
return s;
}
if (bi.log_size == 0) {
s << "Invalid empty file";
s.restoreControl();
return s;
} if (bi.records_count < 0 && bi.records_count > -4) {
s << "Invalid file or corrupted signature";
s.restoreControl();
return s;
}
if (bi.records_count < -3) {
s << "Invalid binlog version";
s.restoreControl();
return s;
}
s << "read records " << bi.records_count << " in " << bi.records.size() << " types, log size " << bi.log_size;
s << "\nlog start " << bi.start_time << " , log end " << bi.end_time;
PIVector<int> keys = bi.records.keys();
piForeachC(int i, keys) {
const PIBinaryLog::BinLogRecordInfo &bri(bi.records[i]);
s << "\n record id " << bri.id << " , count " << bri.count;
s << "\n record start " << bri.start_time << " , end " << bri.end_time;
s << "\n record size " << bri.minimum_size << " - " << bri.maximum_size;
}
s.restoreControl();
return s;
}
#endif // PIBINARYLOG_H

827
src_main/io/piconfig.cpp Executable file
View File

@@ -0,0 +1,827 @@
/*
PIP - Platform Independent Primitives
Config parser
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 <iostream>
#include "piconfig.h"
#include "pifile.h"
#include "piiostring.h"
#include "pistring_std.h"
/*! \class PIConfig
* \brief Configuration file
* \details This class provide handle access to configuration file.
*
* \section PIConfig_sec0 Synopsis
* PIConfig reads configuration file and create internal dendritic
* representation of all entries of this file. You can easily read
* some values and write new.
* \image html piconfig.png
*
* %PIConfig supports also INI-style files with sections "[section]".
* In this case line with section name interpret as prefix to the next
* lines. For example, these configs are equal:
* \code
* ser.device = /dev/ttyS0
* ser.speed = 115200
* debug = true
* \endcode
* \code
* [ser]
* device = /dev/ttyS0
* speed = 115200
* []
* debug = true
* \endcode
*
* \section PIConfig_sec1 Concepts
* Each node of internal tree has type PIConfig::Entry. %PIConfig
* has one root element \a rootEntry(). Any entry of configuration file is a
* child of this element.
*
*/
/*! \class PIConfig::Entry
* \brief %Entry of configuration file
* \details This class is node of internal PIConfig tree.
* %Entry provide access to elements of PIConfig. Each entry has
* children or next properties:
* * name
* * value
* * type
* * comment
*
* Each property is a PIString. These properties forms from text line with
* format: \code{.cpp} <name> = <value> #<type> <comment> \endcode
* Type and comment are optional fields. Type is a single letter immediately
* after comment symbol "#". \n \n
* %Entry has many implicit convertions to common types: boolean, integers,
* float, double, PIString, PIStringList. \n \n
* Generally there is no need to create instance of %PIConfig::Entry manually,
* it returns by functions \a getValue() of \a PIConfig, \a PIConfig::Entry or
* \a PIConfig::Branch. If there is no suitable entry to return, reference to
* internal instance of %PIConfig::Entry with "default" value will be returned.
* \snippet piconfig.cpp PIConfig::Entry
*
*/
/*! \class PIConfig::Branch
* \brief %Branch is a list of entries of configuration file
* \details %Branch provides some features to get entries lists.
* \snippet piconfig.cpp PIConfig::Branch
*
*/
PIConfig::Entry PIConfig::Branch::_empty;
PIConfig::Entry PIConfig::Entry::_empty;
PIConfig::Branch PIConfig::Branch::allLeaves() {
Branch b;
b.delim = delim;
piForeach (Entry * i, *this) {
if (i->isLeaf()) b << i;
else allLeaves(b, i);
}
return b;
}
PIConfig::Entry & PIConfig::Branch::getValue(const PIString & vname, const PIString & def, bool * exist) {
if (vname.isEmpty()) {
_empty.clear();
_empty.delim = delim;
if (exist != 0) *exist = false;
return _empty;
}
PIStringList tree = vname.split(delim);
PIString name = tree.front();
tree.pop_front();
Entry * ce = 0;
piForeach (Entry * i, *this)
if (i->_name == name) {
ce = i;
break;
}
if (ce == 0) {
_empty._name = vname;
_empty._value = def;
_empty.delim = delim;
if (exist != 0) *exist = false;
return _empty;
}
piForeach (PIString & i, tree) {
ce = ce->findChild(i);
if (ce == 0) {
_empty._name = vname;
_empty._value = def;
_empty.delim = delim;
if (exist != 0) *exist = false;
return _empty;
}
}
if (exist != 0) *exist = true;
return *ce;
}
PIConfig::Branch PIConfig::Branch::getValues(const PIString & name) {
Branch b;
b.delim = delim;
piForeach (Entry * i, *this) {
if (i->isLeaf()) {
if (i->_name.find(name) >= 0)
b << i;
} else {
piForeach (Entry * j, i->_children)
if (j->_name.find(name) >= 0)
b << j;
}
}
return b;
}
PIConfig::Branch PIConfig::Branch::getLeaves() {
Branch b;
b.delim = delim;
piForeach (Entry * i, *this)
if (i->isLeaf())
b << i;
return b;
}
PIConfig::Branch PIConfig::Branch::getBranches() {
Branch b;
b.delim = delim;
piForeach (Entry * i, *this)
if (!i->isLeaf())
b << i;
return b;
}
PIConfig::Branch & PIConfig::Branch::filter(const PIString & f) {
for (int i = 0; i < size_s(); ++i) {
if (at(i)->_name.find(f) < 0) {
remove(i);
--i;
}
}
return *this;
}
bool PIConfig::Branch::entryExists(const Entry * e, const PIString & name) const {
if (e->_children.isEmpty()) {
return (e->_name == name);
}
piForeachC (Entry * i, e->_children)
if (entryExists(i, name)) return true;
return false;
}
PIConfig::Entry & PIConfig::Entry::getValue(const PIString & vname, const PIString & def, bool * exist) {
PIStringList tree = vname.split(delim);
Entry * ce = this;
piForeach (PIString & i, tree) {
ce = ce->findChild(i);
if (ce == 0) {
_empty._name = vname;
_empty._value = def;
_empty.delim = delim;
if (exist != 0) *exist = false;
return _empty;
}
}
if (exist != 0) *exist = true;
return *ce;
}
PIConfig::Branch PIConfig::Entry::getValues(const PIString & vname) {
Branch b;
b.delim = delim;
piForeach (Entry * i, _children)
if (i->_name.find(vname) >= 0)
b << i;
return b;
}
bool PIConfig::Entry::entryExists(const Entry * e, const PIString & name) const {
if (e->_children.isEmpty()) {
return (e->_name == name);
}
piForeachC (Entry * i, e->_children)
if (entryExists(i, name)) return true;
return false;
}
void PIConfig::Entry::coutt(std::ostream & s, const PIString & p) const {
PIString nl = p + " ";
if (!_value.isEmpty()) s << p << _name << " = " << _value << std::endl;
else std::cout << p << _name << std::endl;
piForeachC (Entry * i, _children) i->coutt(s, nl);
}
void PIConfig::Entry::piCoutt(PICout s, const PIString & p) const {
PIString nl = p + " ";
if (!_value.isEmpty()) s << p << _name << " = " << _value << PICoutManipulators::NewLine;
else std::cout << p << _name << std::endl;
piForeachC (Entry * i, _children) i->piCoutt(s, nl);
}
PIConfig::PIConfig(const PIString & path, PIIODevice::DeviceMode mode) {
_init();
own_dev = true;
dev = new PIFile(path, mode);
if (!dev->isOpened())
dev->open(path, mode);
parse();
}
PIConfig::PIConfig(PIString * string, PIIODevice::DeviceMode mode) {
_init();
own_dev = true;
dev = new PIIOString(string, mode);
parse();
}
PIConfig::PIConfig(PIIODevice * device, PIIODevice::DeviceMode mode) {
_init();
own_dev = false;
dev = device;
if (dev) dev->open(mode);
parse();
}
PIConfig::PIConfig(const PIString & path, PIStringList dirs) {
_init();
internal = true;
own_dev = true;
dev = new PIFile(path, PIIODevice::ReadOnly);
incdirs = dirs;
incdirs << PIFile::fileInfo(path).dir();
while (!dev->isOpened()) {
if (dirs.isEmpty()) break;
PIString cp = dirs.back();
if (cp.endsWith("/") || cp.endsWith("\\")) cp.pop_back();
cp += "/" + path;
dev->open(cp, PIIODevice::ReadOnly);
dirs.pop_back();
}
if (!dev->isOpened()) {
delete dev;
dev = 0;
return;
}
parse();
}
PIConfig::~PIConfig() {
root.deleteBranch();
if (own_dev && dev) delete dev;
dev = 0;
piForeach (PIConfig * c, inc_devs)
delete c;
inc_devs.clear();
includes.clear();
}
bool PIConfig::open(const PIString & path, PIIODevice::DeviceMode mode) {
if (own_dev && dev) delete dev;
own_dev = true;
dev = new PIFile(path, mode);
if (!dev->isOpened())
dev->open(path, mode);
parse();
return dev->isOpened();
}
bool PIConfig::open(PIString * string, PIIODevice::DeviceMode mode) {
if (own_dev && dev) delete dev;
own_dev = true;
dev = new PIIOString(string, mode);
parse();
return true;
}
void PIConfig::_init() {
internal = false;
delim = PIStringAscii(".");
root.delim = delim;
empty.delim = delim;
empty._parent = 0;
}
void PIConfig::_clearDev() {
if (!dev) return;
if (PIString(dev->className()) == "PIFile") {((PIFile*)dev)->clear(); return;}
if (PIString(dev->className()) == "PIIOString") {((PIIOString*)dev)->clear(); return;}
}
void PIConfig::_flushDev() {
if (!dev) return;
if (PIString(dev->className()) == "PIFile") {((PIFile*)dev)->flush();}
}
bool PIConfig::_isEndDev() {
if (!dev) return true;
if (PIString(dev->className()) == "PIFile") {return ((PIFile*)dev)->isEnd();}
if (PIString(dev->className()) == "PIIOString") {return ((PIIOString*)dev)->isEnd();}
return true;
}
void PIConfig::_seekToBeginDev() {
if (!dev) return;
if (PIString(dev->className()) == "PIFile") {((PIFile*)dev)->seekToBegin(); return;}
if (PIString(dev->className()) == "PIIOString") {((PIIOString*)dev)->seekToBegin(); return;}
}
PIString PIConfig::_readLineDev() {
if (!dev) return PIString();
if (PIString(dev->className()) == "PIFile") {return ((PIFile*)dev)->readLine();}
if (PIString(dev->className()) == "PIIOString") {return ((PIIOString*)dev)->readLine();}
return PIString();
}
void PIConfig::_writeDev(const PIString & l) {
//piCout << "write \"" << l << "\"";
if (!dev) return;
if (PIString(dev->className()) == "PIFile") {*((PIFile*)dev) << (l); return;}
if (PIString(dev->className()) == "PIIOString") {((PIIOString*)dev)->writeString(l); return;}
dev->write(l.toByteArray());
}
bool PIConfig::isOpened() const {
if (dev) return dev->isOpened();
return false;
}
PIConfig::Entry & PIConfig::getValue(const PIString & vname, const PIString & def, bool * exist) {
PIStringList tree = vname.split(delim);
Entry * ce = &root;
piForeach (PIString & i, tree) {
ce = ce->findChild(i);
if (ce == 0) {
if (exist != 0) *exist = false;
empty._name = vname;
empty._value = def;
empty.delim = delim;
return empty;
}
}
if (exist != 0) *exist = true;
return *ce;
}
PIConfig::Branch PIConfig::getValues(const PIString & vname) {
Branch b;
b.delim = delim;
piForeach (Entry * i, root._children)
if (i->_name.find(vname) >= 0)
b << i;
return b;
};
void PIConfig::addEntry(const PIString & name, const PIString & value, const PIString & type, bool write) {
if (getValue(name)._parent != 0)
return;
bool toRoot = false;
PIStringList tree = name.split(delim);
PIString ename = tree.back();
tree.pop_back();
Entry * te, * ce, * entry = &root;
if (tree.isEmpty()) toRoot = true;
piForeach (PIString & i, tree) {
te = entry->findChild(i);
if (te == 0) {
ce = new Entry();
ce->delim = delim;
ce->_tab = entry->_tab;
ce->_line = entry->_line;
ce->_name = i;
ce->_parent = entry;
entry->_children << ce;
entry = ce;
} else entry = te;
}
PIConfig::Branch ch = entry->_children;
ch.sort(PIConfig::Entry::compare);
te = (entry->isLeaf() ? 0 : ch.back());
ce = new Entry();
ce->delim = delim;
ce->_name = ename;
ce->_value = value;
ce->_type = type;
if (te == 0) {
ce->_tab = entry->_tab;
if (toRoot) ce->_line = other.size_s() - 1;
else ce->_line = entry->_line;
} else {
ce->_tab = te->_tab;
if (toRoot) ce->_line = other.size_s() - 1;
else {
ch = entry->_parent->_children;
ch.sort(PIConfig::Entry::compare);
ce->_line = ch.back()->_line + 1;
}
}
ce->_parent = entry;
entry->_children << ce;
other.insert(ce->_line, "");
Branch b = allLeaves();
bool found = false;
for (int i = 0; i < b.size_s(); ++i) {
if (found) {
b[i]->_line++;
continue;
}
if (b[i] == ce) {
found = true;
if (i > 0)
if (b[i - 1]->_line == b[i]->_line)
b[i - 1]->_line++;
}
}
if (write) writeAll();
}
void PIConfig::setValue(const PIString & name, const PIString & value, const PIString & type, bool write) {
Entry & e(getValue(name));
if (&e == &empty) {
addEntry(name, value, type, write);
return;
}
e._value = value;
e._type = type;
if (write) writeAll();
}
int PIConfig::entryIndex(const PIString & name) {
PIStringList tree = name.split(delim);
Entry * ce = &root;
piForeach (PIString & i, tree) {
ce = ce->findChild(i);
if (ce == 0)
return -1;
}
return allLeaves().indexOf(ce);
}
void PIConfig::setValue(uint number, const PIString & value, bool write) {
Entry & e(entryByIndex(number));
if (&e == &empty) return;
e._value = value;
if (write) writeAll();
}
void PIConfig::setName(uint number, const PIString & name, bool write) {
Entry & e(entryByIndex(number));
if (&e == &empty) return;
e._name = name;
if (write) writeAll();
}
void PIConfig::setType(uint number, const PIString & type, bool write) {
Entry & e(entryByIndex(number));
if (&e == &empty) return;
e._type = type;
if (write) writeAll();
}
void PIConfig::setComment(uint number, const PIString & comment, bool write) {
Entry & e(entryByIndex(number));
if (&e == &empty) return;
e._comment = comment;
if (write) writeAll();
}
void PIConfig::removeEntry(const PIString & name, bool write) {
Entry & e(getValue(name));
if (&e == &empty) return;
Branch b = allLeaves();
removeEntry(b, &e);
if (write) writeAll();
}
void PIConfig::removeEntry(uint number, bool write) {
Entry & e(entryByIndex(number));
if (&e == &empty) return;
Branch b = allLeaves();
removeEntry(b, &e);
if (write) writeAll();
}
void PIConfig::removeEntry(Branch & b, PIConfig::Entry * e) {
bool leaf = true;
if (e->isLeaf()) other.remove(e->_line);
if (!e->isLeaf() && !e->_value.isEmpty()) {
e->_value.clear();
leaf = false;
} else {
int cc = e->_children.size_s();
piForTimes (cc)
removeEntry(b, e->_children.back());
}
bool found = false;
for (int i = 0; i < b.size_s(); ++i) {
if (found) {
b[i]->_line--;
continue;
}
if (b[i] == e) found = true;
}
if (!leaf) return;
e->_parent->_children.removeOne(e);
b.removeOne(e);
delete e;
}
PIString PIConfig::getPrefixFromLine(PIString line, bool * exists) {
line.trim();
if (line.left(1) == "#") {if (exists) *exists = false; return PIString();}
int ci = line.find("#");
if (ci >= 0) line.cutRight(line.size() - ci);
if (line.find("=") >= 0) {if (exists) *exists = false; return PIString();}
if (line.find("[") >= 0 && line.find("]") >= 0) {
if (exists) *exists = true;
return line.takeRange('[', ']').trim();
}
if (exists) *exists = false;
return PIString();
}
void PIConfig::writeAll() {
//cout << this << " write < " << size() << endl;
_clearDev();
//*this << "1234567894132456798\n"; return;
//writeEntry(&root);
buildFullNames(&root);
Branch b = allLeaves();
PIString prefix, tprefix;
bool isPrefix;
//for (int i = 0; i < b.size_s(); ++i)
// cout << b[i]->_name << " = " << b[i]->_value << endl;
int j = 0;
for (int i = 0; i < other.size_s(); ++i) {
//cout << j << endl;
if (j >= 0 && j < b.size_s()) {
if (b[j]->_line == i) {
b[j]->buildLine();
_writeDev((b[j]->_all).cutLeft(prefix.size()) + "\n");
//cout << this << " " << b[j]->_all << endl;
++j;
} else {
_writeDev(other[i]);
tprefix = getPrefixFromLine(other[i], &isPrefix);
if (isPrefix) {
prefix = tprefix;
if (!prefix.isEmpty())
prefix += delim;
}
if (i < other.size_s() - 1)
_writeDev('\n');
//cout << this << " " << other[i] << endl;
}
} else {
_writeDev(other[i]);
tprefix = getPrefixFromLine(other[i], &isPrefix);
if (isPrefix) {
prefix = tprefix;
if (!prefix.isEmpty())
prefix += delim;
}
if (i < other.size_s() - 1)
_writeDev('\n');
//cout << this << " " << other[i] << endl;
}
}
_flushDev();
readAll();
//cout << this << " write > " << size() << endl;
}
void PIConfig::clear() {
_clearDev();
parse();
}
void PIConfig::readAll() {
root.deleteBranch();
root.clear();
parse();
}
bool PIConfig::entryExists(const Entry * e, const PIString & name) const {
if (e->_children.isEmpty()) {
return (e->_name == name);
}
piForeachC (Entry * i, e->_children)
if (entryExists(i, name)) return true;
return false;
}
void PIConfig::updateIncludes() {
if (internal) return;
all_includes.clear();
piForeach (PIConfig * c, includes)
all_includes << c->allLeaves();
}
PIString PIConfig::parseLine(PIString v) {
int i = -1, l = 0;
while (1) {
i = v.find("${");
if (i < 0) break;
PIString w = v.mid(i + 1).takeRange('{', '}'), r;
l = w.length() + 3;
w = parseLine(w);
w.trim();
bool ex = false;
PIConfig::Entry & me = getValue(w, "", &ex);
if (ex) {
r = me._value;
} else {
piForeachC (PIConfig::Entry * e, all_includes)
if (e->_full_name == w) {
r = e->_value;
break;
}
}
v.replace(i, l, r);
}
return v;
}
void PIConfig::parse() {
//piCout << "[PIConfig] charset" << PIFile::defaultCharset();
PIString src, str, tab, comm, all, name, type, prefix, tprefix;
PIStringList tree;
Entry * entry, * te, * ce;
int ind, sind;
bool isNew, isPrefix;
piForeach (PIConfig * c, inc_devs)
delete c;
inc_devs.clear();
includes.clear();
if (!isOpened()) return;
_seekToBeginDev();
other.clear();
lines = 0;
while (!_isEndDev()) {
other.push_back(PIString());
src = str = parseLine(_readLineDev());
tprefix = getPrefixFromLine(src, &isPrefix);
if (isPrefix) {
prefix = tprefix;
if (!prefix.isEmpty())
prefix += delim;
}
//piCout << "line \"" << str << "\"";
tab = str.left(str.find(str.trimmed().left(1)));
str.trim();
//cout << endl << str << endl << endl;
// piCout << "[PIConfig] str" << str.size() << str << str.toUTF8();
all = str;
ind = str.find('=');
if ((ind > 0) && (str[0] != '#')) {
sind = str.find('#');
if (sind > 0) {
comm = str.right(str.length() - sind - 1).trimmed();
if (comm.length() > 0) type = comm[0];
else type = "s";
comm = comm.right(comm.length() - 1).trimmed();
str = str.left(sind);
} else {
type = "s";
comm = "";
}
//name = str.left(ind).trimmed();
tree = (prefix + str.left(ind).trimmed()).split(delim);
if (tree.front() == "include") {
name = str.right(str.length() - ind - 1).trimmed();
PIConfig * iconf = new PIConfig(name, incdirs);
//piCout << "include" << name << iconf->dev;
if (!iconf->dev) {
delete iconf;
} else {
inc_devs << iconf;
includes << iconf << iconf->includes;
updateIncludes();
}
//piCout << "includes" << includes;
other.back() = src;
} else {
name = tree.back();
tree.pop_back();
entry = &root;
piForeachC (PIString & i, tree) {
te = entry->findChild(i);
if (te == 0) {
ce = new Entry();
ce->delim = delim;
ce->_tab = tab;
ce->_line = lines;
ce->_name = i;
ce->_parent = entry;
entry->_children << ce;
entry = ce;
} else entry = te;
}
isNew = false;
ce = entry->findChild(name);
if (ce == 0) {
ce = new Entry();
isNew = true;
}
ce->delim = delim;
ce->_tab = tab;
ce->_name = name;
ce->_value = str.right(str.length() - ind - 1).trimmed();
ce->_type = type;
ce->_comment = comm;
//piCout << "[PIConfig] comm" << comm.size() << comm << comm.toUTF8();
//piCout << "[PIConfig] type" << type.size() << type << type.toUTF8();
ce->_line = lines;
ce->_all = all;
if (isNew) {
ce->_parent = entry;
entry->_children << ce;
}
}
} else other.back() = src;
lines++;
}
setEntryDelim(&root, delim);
buildFullNames(&root);
}
std::ostream &operator <<(std::ostream & s, const PIConfig::Entry & v) {
s << v.value();
return s;
}
std::ostream &operator <<(std::ostream & s, const PIConfig::Branch & v) {
v.coutt(s, "");
return s;
}

530
src_main/io/piconfig.h Executable file
View File

@@ -0,0 +1,530 @@
/*! \file piconfig.h
* \brief Configuration parser and writer
*/
/*
PIP - Platform Independent Primitives
Configuration parser and writer
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PICONFIG_H
#define PICONFIG_H
#include "piiodevice.h"
#define PICONFIG_GET_VALUE \
Entry & getValue(const PIString & vname, const char * def, bool * exists = 0) {return getValue(vname, PIString(def), exists);} \
Entry & getValue(const PIString & vname, const PIStringList & def, bool * exists = 0) {return getValue(vname, def.join("%|%"), exists);} \
Entry & getValue(const PIString & vname, const bool def, bool * exists = 0) {return getValue(vname, PIString::fromBool(def), exists);} \
Entry & getValue(const PIString & vname, const short def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const int def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const long def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const uchar def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const ushort def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const uint def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const ulong def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const float def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const double def, bool * exists = 0) {return getValue(vname, PIString::fromNumber(def), exists);} \
\
Entry & getValue(const PIString & vname, const char * def, bool * exists = 0) const {return getValue(vname, PIString(def), exists);} \
Entry & getValue(const PIString & vname, const PIStringList & def, bool * exists = 0) const {return getValue(vname, def.join("%|%"), exists);} \
Entry & getValue(const PIString & vname, const bool def, bool * exists = 0) const {return getValue(vname, PIString::fromBool(def), exists);} \
Entry & getValue(const PIString & vname, const short def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const int def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const long def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const uchar def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const ushort def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const uint def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const ulong def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const float def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);} \
Entry & getValue(const PIString & vname, const double def, bool * exists = 0) const {return getValue(vname, PIString::fromNumber(def), exists);}
class PIP_EXPORT PIConfig
{
friend class Entry;
friend class Branch;
public:
//! Contructs and read configuration file at path "path" in mode "mode"
PIConfig(const PIString & path, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);
//! Contructs and read configuration string "string" in mode "mode"
PIConfig(PIString * string, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);
//! Contructs and read configuration from custom device "device" in mode "mode"
PIConfig(PIIODevice * device = 0, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);
~PIConfig();
class Entry;
class PIP_EXPORT Branch: public PIVector<Entry * > {
friend class PIConfig;
friend class Entry;
friend std::ostream & operator <<(std::ostream & s, const Branch & v);
friend PICout operator <<(PICout s, const Branch & v);
public:
Branch() {;}
Entry & getValue(const PIString & vname, const PIString & def = PIString(), bool * exists = 0);
Entry & getValue(const PIString & vname, const PIString & def = PIString(), bool * exists = 0) const {return const_cast<Branch * >(this)->getValue(vname, def, exists);}
PICONFIG_GET_VALUE
Branch allLeaves();
Branch getValues(const PIString & name);
Branch getLeaves();
Branch getBranches();
Branch & filter(const PIString & f);
bool isEntryExists(const PIString & name) const {piForeachC (Entry * i, *this) if (entryExists(i, name)) return true; return false;}
int indexOf(const Entry * e) {for (int i = 0; i < size_s(); ++i) if (at(i) == e) return i; return -1;}
//void clear() {piForeach (Entry * i, *this) delete i; PIVector<Entry * >::clear();}
private:
bool entryExists(const Entry * e, const PIString & name) const;
void allLeaves(Branch & b, Entry * e) {piForeach (Entry * i, e->_children) {if (i->isLeaf()) b << i; else allLeaves(b, i);}}
void coutt(std::ostream & s, const PIString & p) const {piForeachC (Entry * i, *this) i->coutt(s, p);}
void piCoutt(PICout s, const PIString & p) const {piForeachC (Entry * i, *this) i->piCoutt(s, p);}
static Entry _empty;
PIString delim;
};
class PIP_EXPORT Entry {
friend class PIConfig;
friend class Branch;
public:
Entry() {_parent = 0; _line = -1;}
//! Returns parent entry, or 0 if there is no parent (root of default value)
Entry * parent() const {return _parent;}
//! Returns children count
int childCount() const {return _children.size_s();}
//! Returns children as \a PIConfig::Branch
Branch & children() const {_children.delim = delim; return _children;}
//! Returns child at index "index"
Entry * child(const int index) const {return _children[index];}
//! Returns first child with name "name"
Entry * findChild(const PIString & name) {piForeach (Entry * i, _children) if (i->_name == name) return i; return 0;}
//! Returns first child with name "name"
const Entry * findChild(const PIString & name) const {piForeachC (Entry * i, _children) if (i->_name == name) return i; return 0;}
//! Returns \b true if there is no children
bool isLeaf() const {return _children.isEmpty();}
//! Returns name
const PIString & name() const {return _name;}
//! Returns value
const PIString & value() const {return _value;}
//! Returns type
const PIString & type() const {return _type;}
//! Returns comment
const PIString & comment() const {return _comment;}
/** \brief Returns full name, i.e. name as it looks in file
* \details In case of default entry full name always is empty
* \snippet piconfig.cpp fullName */
const PIString & fullName() const {return _full_name;}
//! Set name to "value" and returns this
Entry & setName(const PIString & value) {_name = value; return *this;}
//! Set type to "value" and returns this
Entry & setType(const PIString & value) {_type = value; return *this;}
//! Set comment to "value" and returns this
Entry & setComment(const PIString & value) {_comment = value; return *this;}
//! Set value to "value" and returns this
Entry & setValue(const PIString & value) {_value = value; return *this;}
//! Set value to "value" and returns this. Type is set to "l"
Entry & setValue(const PIStringList & value) {setValue(value.join("%|%")); setType("l"); return *this;}
//! Set value to "value" and returns this. Type is set to "s"
Entry & setValue(const char * value) {setValue(PIString(value)); setType("s"); return *this;}
//! Set value to "value" and returns this. Type is set to "b"
Entry & setValue(const bool value) {setValue(PIString::fromBool(value)); setType("b"); return *this;}
//! Set value to "value" and returns this. Type is set to "s"
Entry & setValue(const char value) {setValue(PIString(1, value)); setType("s"); return *this;}
//! Set value to "value" and returns this. Type is set to "n"
Entry & setValue(const short value) {setValue(PIString::fromNumber(value)); setType("n"); return *this;}
//! Set value to "value" and returns this. Type is set to "n"
Entry & setValue(const int value) {setValue(PIString::fromNumber(value)); setType("n"); return *this;}
//! Set value to "value" and returns this. Type is set to "n"
Entry & setValue(const long value) {setValue(PIString::fromNumber(value)); setType("n"); return *this;}
//! Set value to "value" and returns this. Type is set to "n"
Entry & setValue(const uchar value) {setValue(PIString::fromNumber(value)); setType("n"); return *this;}
//! Set value to "value" and returns this. Type is set to "n"
Entry & setValue(const ushort value) {setValue(PIString::fromNumber(value)); setType("n"); return *this;}
//! Set value to "value" and returns this. Type is set to "n"
Entry & setValue(const uint value) {setValue(PIString::fromNumber(value)); setType("n"); return *this;}
//! Set value to "value" and returns this. Type is set to "n"
Entry & setValue(const ulong value) {setValue(PIString::fromNumber(value)); setType("n"); return *this;}
//! Set value to "value" and returns this. Type is set to "f"
Entry & setValue(const float value) {setValue(PIString::fromNumber(value)); setType("f"); return *this;}
//! Set value to "value" and returns this. Type is set to "f"
Entry & setValue(const double value) {setValue(PIString::fromNumber(value)); setType("f"); return *this;}
/** \brief Returns entry with name "vname" and default value "def"
* \details If there is no suitable entry found, reference to default internal entry with
* value = "def" will be returned, and if "exists" not null it will be set to \b false */
Entry & getValue(const PIString & vname, const PIString & def = PIString(), bool * exists = 0);
Entry & getValue(const PIString & vname, const PIString & def = PIString(), bool * exists = 0) const {return const_cast<Entry * >(this)->getValue(vname, def, exists);}
PICONFIG_GET_VALUE
//! \fn Entry & getValue(const PIString & vname, const char * def, bool * exists = 0)
//! \brief Returns entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const char * def, bool * exists = 0)
//! \brief Returns entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const PIStringList & def, bool * exists = 0)
//! \brief Returns entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const bool def, bool * exists = 0)
//! \brief Returns entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const short def, bool * exists = 0)
//! \brief Returns entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const int def, bool * exists = 0)
//! \brief Returns entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const long def, bool * exists = 0)
//! \brief Returns entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const uchar def, bool * exists = 0)
//! \brief Returns entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const ushort def, bool * exists = 0)
//! \brief Returns entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const uint def, bool * exists = 0)
//! \brief Returns entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const ulong def, bool * exists = 0)
//! \brief Returns entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const float def, bool * exists = 0)
//! \brief Returns entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const double def, bool * exists = 0)
//! \brief Returns entry with name "vname" and default value "def"
//! Find all entries with names with substrings "vname" and returns them as \a PIConfig::Branch
Branch getValues(const PIString & vname);
//! If there is no children returns if name == "name". Else returns if any child has name == "name"
bool isEntryExists(const PIString & name) const {return entryExists(this, name);}
//! Convertion to boolean
operator bool() {return _value.toBool();}
//! Convertion to char
operator char() {return (_value.isEmpty() ? 0 : _value[0].toAscii());}
//! Convertion to short
operator short() {return _value.toShort();}
//! Convertion to int
operator int() {return _value.toInt();}
//! Convertion to long
operator long() {return _value.toLong();}
//! Convertion to uchar
operator uchar() {return _value.toInt();}
//! Convertion to ushort
operator ushort() {return _value.toShort();}
//! Convertion to uint
operator uint() {return _value.toInt();}
//! Convertion to ulong
operator ulong() {return _value.toLong();}
//! Convertion to float
operator float() {return _value.toFloat();}
//! Convertion to double
operator double() {return _value.toDouble();}
//! Convertion to PIString
operator PIString() {return _value;}
//! Convertion to PIStringList
operator PIStringList() {return _value.split("%|%");}
private:
typedef PIConfig::Entry * EntryPtr;
static int compare(const EntryPtr * f, const EntryPtr * s) {return (*f)->_line == (*s)->_line ? 0 : (*f)->_line < (*s)->_line ? -1 : 1;}
bool entryExists(const Entry * e, const PIString & name) const;
void buildLine() {_all = _tab + _full_name + " = " + _value + " #" + _type + " " + _comment;}
void clear() {_children.clear(); _name = _value = _type = _comment = _all = PIString(); _line = 0; _parent = 0;}
void coutt(std::ostream & s, const PIString & p) const;
void piCoutt(PICout s, const PIString & p) const;
void deleteBranch() {piForeach (Entry * i, _children) {i->deleteBranch(); delete i;}}
static Entry _empty;
Entry * _parent;
mutable Branch _children;
PIString _tab;
PIString _name;
PIString _value;
PIString _type;
PIString _comment;
PIString _all;
PIString _full_name;
PIString delim;
int _line;
};
//! Read configuration file at path "path" in mode "mode"
bool open(const PIString & path, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);
//! Read configuration string "string" in mode "mode"
bool open(PIString * string, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);\
bool isOpened() const;
//! Returns top-level entry with name "vname", if doesn`t exists return entry with value "def" and set *exist to false
Entry & getValue(const PIString & vname, const PIString & def = PIString(), bool * exists = 0);
Entry & getValue(const PIString & vname, const PIString & def = PIString(), bool * exists = 0) const {return const_cast<PIConfig * >(this)->getValue(vname, def, exists);}
PICONFIG_GET_VALUE
//! \fn Entry & getValue(const PIString & vname, const char * def, bool * exists = 0)
//! \brief Returns top-level entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const char * def, bool * exists = 0)
//! \brief Returns top-level entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const PIStringList & def, bool * exists = 0)
//! \brief Returns top-level entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const bool def, bool * exists = 0)
//! \brief Returns top-level entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const short def, bool * exists = 0)
//! \brief Returns top-level entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const int def, bool * exists = 0)
//! \brief Returns top-level entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const long def, bool * exists = 0)
//! \brief Returns top-level entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const uchar def, bool * exists = 0)
//! \brief Returns top-level entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const ushort def, bool * exists = 0)
//! \brief Returns top-level entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const uint def, bool * exists = 0)
//! \brief Returns top-level entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const ulong def, bool * exists = 0)
//! \brief Returns top-level entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const float def, bool * exists = 0)
//! \brief Returns top-level entry with name "vname" and default value "def"
//! \fn Entry & getValue(const PIString & vname, const double def, bool * exists = 0)
//! \brief Returns top-level entry with name "vname" and default value "def"
//! Returns top-level entries with names with substrings "vname"
Branch getValues(const PIString & vname);
//! Set top-level entry with name "name" value to "value", type to "type" and if "write" immediate write to file. Add new entry if there is no suitable exists
void setValue(const PIString & name, const PIString & value, const PIString & type = "s", bool write = true);
//! Set top-level entry with name "name" value to "value", type to "l" and if "write" immediate write to file. Add new entry if there is no suitable exists
void setValue(const PIString & name, const PIStringList & value, bool write = true) {setValue(name, value.join("%|%"), "l", write);}
//! Set top-level entry with name "name" value to "value", type to "s" and if "write" immediate write to file. Add new entry if there is no suitable exists
void setValue(const PIString & name, const char * value, bool write = true) {setValue(name, PIString(value), "s", write);}
//! Set top-level entry with name "name" value to "value", type to "b" and if "write" immediate write to file. Add new entry if there is no suitable exists
void setValue(const PIString & name, const bool value, bool write = true) {setValue(name, PIString::fromBool(value), "b", write);}
//! Set top-level entry with name "name" value to "value", type to "n" and if "write" immediate write to file. Add new entry if there is no suitable exists
void setValue(const PIString & name, const short value, bool write = true) {setValue(name, PIString::fromNumber(value), "n", write);}
//! Set top-level entry with name "name" value to "value", type to "n" and if "write" immediate write to file. Add new entry if there is no suitable exists
void setValue(const PIString & name, const int value, bool write = true) {setValue(name, PIString::fromNumber(value), "n", write);}
//! Set top-level entry with name "name" value to "value", type to "n" and if "write" immediate write to file. Add new entry if there is no suitable exists
void setValue(const PIString & name, const long value, bool write = true) {setValue(name, PIString::fromNumber(value), "n", write);}
//! Set top-level entry with name "name" value to "value", type to "n" and if "write" immediate write to file. Add new entry if there is no suitable exists
void setValue(const PIString & name, const uchar value, bool write = true) {setValue(name, PIString::fromNumber(value), "n", write);}
//! Set top-level entry with name "name" value to "value", type to "n" and if "write" immediate write to file. Add new entry if there is no suitable exists
void setValue(const PIString & name, const ushort value, bool write = true) {setValue(name, PIString::fromNumber(value), "n", write);}
//! Set top-level entry with name "name" value to "value", type to "n" and if "write" immediate write to file. Add new entry if there is no suitable exists
void setValue(const PIString & name, const uint value, bool write = true) {setValue(name, PIString::fromNumber(value), "n", write);}
//! Set top-level entry with name "name" value to "value", type to "n" and if "write" immediate write to file. Add new entry if there is no suitable exists
void setValue(const PIString & name, const ulong value, bool write = true) {setValue(name, PIString::fromNumber(value), "n", write);}
//! Set top-level entry with name "name" value to "value", type to "f" and if "write" immediate write to file. Add new entry if there is no suitable exists
void setValue(const PIString & name, const float value, bool write = true) {setValue(name, PIString::fromNumber(value), "f", write);}
//! Set top-level entry with name "name" value to "value", type to "f" and if "write" immediate write to file. Add new entry if there is no suitable exists
void setValue(const PIString & name, const double value, bool write = true) {setValue(name, PIString::fromNumber(value), "f", write);}
//! Returns root entry
Entry & rootEntry() {return root;}
//! Returns top-level entries count
int entriesCount() const {return childCount(&root);}
//! Returns if top-level entry with name "name" exists
bool isEntryExists(const PIString & name) const {return entryExists(&root, name);}
//! Returns all top-level entries
Branch allTree() {Branch b; piForeach (Entry * i, root._children) b << i; b.delim = delim; return b;}
//! Returns all entries without children
Branch allLeaves() {Branch b; allLeaves(b, &root); b.sort(Entry::compare); b.delim = delim; return b;}
int entryIndex(const PIString & name);
PIString getName(uint number) {return entryByIndex(number)._name;}
PIString getValue(uint number) {return entryByIndex(number)._value;}
PIChar getType(uint number) {return entryByIndex(number)._type[0];}
PIString getComment(uint number) {return entryByIndex(number)._comment;}
void addEntry(const PIString & name, const PIString & value, const PIString & type = "s", bool write = true);
void setName(uint number, const PIString & name, bool write = true);
void setValue(uint number, const PIString & value, bool write = true);
void setType(uint number, const PIString & type, bool write = true);
void setComment(uint number, const PIString & comment, bool write = true);
void removeEntry(const PIString & name, bool write = true);
void removeEntry(uint number, bool write = true);
//! Remove all tree and device content
void clear();
//! Parse device and build internal tree
void readAll();
//! Write all internal tree to device
void writeAll();
//! Returns current tree delimiter, default "."
const PIString & delimiter() const {return delim;}
//! Set current tree delimiter
void setDelimiter(const PIString & d) {delim = d; setEntryDelim(&root, d); readAll();}
private:
PIConfig(const PIString & path, PIStringList dirs);
void _init();
void _clearDev();
void _flushDev();
bool _isEndDev();
void _seekToBeginDev();
PIString _readLineDev();
void _writeDev(const PIString & l);
int childCount(const Entry * e) const {int c = 0; piForeachC (Entry * i, e->_children) c += childCount(i); c += e->_children.size_s(); return c;}
bool entryExists(const Entry * e, const PIString & name) const;
void buildFullNames(Entry * e) {piForeach (Entry * i, e->_children) {if (e != &root) i->_full_name = e->_full_name + delim + i->_name; else i->_full_name = i->_name; buildFullNames(i);}}
void allLeaves(Branch & b, Entry * e) {piForeach (Entry * i, e->_children) {if ((!i->_value.isEmpty() && !i->isLeaf()) || i->isLeaf()) b << i; allLeaves(b, i);}}
void setEntryDelim(Entry * e, const PIString & d) {piForeach (Entry * i, e->_children) setEntryDelim(i, d); e->delim = d;}
Entry & entryByIndex(const int index) {Branch b = allLeaves(); if (index < 0 || index >= b.size_s()) return empty; return *(b[index]);}
void removeEntry(Branch & b, Entry * e);
void deleteEntry(Entry * e) {piForeach (Entry * i, e->_children) deleteEntry(i); delete e;}
PIString getPrefixFromLine(PIString line, bool * exists);
void updateIncludes();
PIString parseLine(PIString v);
void parse();
bool own_dev, internal;
PIVector<PIConfig * > includes, inc_devs;
Branch all_includes;
PIIODevice * dev;
PIString delim;
PIStringList incdirs;
Entry root, empty;
uint lines;
PIStringList other;
};
std::ostream & operator <<(std::ostream & s, const PIConfig::Branch & v);
std::ostream & operator <<(std::ostream & s, const PIConfig::Entry & v);
inline PICout operator <<(PICout s, const PIConfig::Branch & v) {s.setControl(0, true); v.piCoutt(s, ""); s.restoreControl(); return s;}
inline PICout operator <<(PICout s, const PIConfig::Entry & v) {s << v.value(); return s;}
/** \relatesalso PIConfig \relatesalso PIIODevice
* \brief Service function. useful for configuring devices
* \details Function takes entry name "name", default value "def" and two
* \a PIConfig::Entry sections: "em" and their parent "ep". If there is no
* parent ep = 0. If "ep" is not null and entry "name" exists in "ep" function
* returns this value. Else returns value of entry "name" in section "em" or
* "def" if entry doesn`t exists. \n This function useful to read settings
* from configuration file in implementation \a PIIODevice::configureDevice() function */
template<typename T>
T readDeviceSetting(const PIString & name, const T & def, const PIConfig::Entry * em, const PIConfig::Entry * ep) {
if (ep != 0) {
T ret;
bool ex;
ret = ep->getValue(name, def, &ex);
if (!ex) ret = em->getValue(name, def);
return ret;
}
return em->getValue(name, def);
}
#endif // PICONFIG_H

1234
src_main/io/piconnection.cpp Executable file
View File

@@ -0,0 +1,1234 @@
/*
PIP - Platform Independent Primitives
Complex I/O point
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 "piconnection.h"
#include "piconfig.h"
/** \class PIConnection
* \brief Complex Input/Output point
*
* \section PIConnection_synopsis Synopsis
* %PIConnection provides abstract layer over physical devices,
* filtering and connecting data streams. Each %PIConnection
* works through Device Pool, so several %PIConnections can
* read from single physical device. General scheme:
* \image html piconnection.png
*
* \section PIConnection_pool Device pool concept
* Device pool is static object, single for each application, which
* contains unique devices. Each %PIConnection works with real devices
* through Device pool. Each device has assosiated thread for read
* and it can be started or stopped with %PIConnection functions
* \a startThreadedRead() and \a stopThreadedRead().
*
* \section PIConnection_filters Filters
* %PIConnection filter is a PIPacketExtractor and assosiated
* array of devices or other filters. When read thread is successfully read
* from device this data can be passed to one or more filters. Each filter
* has name and filter names should be unique. You can use this name for
* access to PIPacketExtractor* with function \a filter(), or get array of
* assosiated devices and filters with function \a filterBoundedDevices().
* One filter can receive data from several sources, and can be bounded to
* several filters.
* \image html piconnection_filters.png
*
* \section PIConnection_diag Diagnostics
* %PIConnection create PIDiagnostics for each device or filter. You can
* access to these objects with functions \a diagnostic().
*
* \section PIConnection_sender Senders
* %PIConnection can send data to devices with named timers ("senders").
* You can create sender or add device to sender with function \a addSender().
* Each sender has internal timer and every tick execute virtual function
* \a senderData(). Returns value of this function sended to bounded devices.
* You can assign fixed send data to sender with function \a setSenderFixedData().
* In this case sender will NOT execute \a senderData(), but send assigned data.
* \image html piconnection_senders.png
*
* \section PIConnection_config Configuration
* You can create %PIConnection from config file section or configure
* it later with function \a configureFromConfig(). Devices describes
* with its full pathes, for details see \ref PIIODevice_sec7. Example:
* \image html piconnection_conf.png
* Also %PIConnection can create PIString with its configuration with
* function \a makeConfig(). This string can be directly inserted into the
* config file.
*
*/
PIVector<PIConnection * > PIConnection::_connections;
PIConnection::PIConnection(const PIString & name): PIObject(name) {
_connections << this;
}
PIConnection::PIConnection(const PIString & config, const PIString & name_): PIObject(name_) {
_connections << this;
configureFromConfig(config, name_);
}
PIConnection::PIConnection(PIString * string, const PIString & name_): PIObject(name_) {
_connections << this;
configureFromString(string, name_);
}
PIConnection::~PIConnection() {
__device_pool__->unboundConnection(this);
removeAllFilters();
_connections.removeAll(this);
}
bool PIConnection::configureFromConfig(const PIString & conf_path, const PIString & name_) {
PIConfig conf(conf_path, PIIODevice::ReadOnly);
return configure(conf, name_);
}
bool PIConnection::configureFromString(PIString * string, const PIString & name_) {
PIConfig conf(string, PIIODevice::ReadOnly);
return configure(conf, name_);
}
bool PIConnection::configure(PIConfig & conf, const PIString & name_) {
if (!conf.isOpened()) return false;
__device_pool__->unboundConnection(this);
removeAllSenders();
removeAllChannels();
removeAllFilters();
removeAllDevices();
setName(name_);
if (name_.isEmpty()) piCoutObj << "Warning, can't configure connection with empty name";
PIConfig::Entry ce(conf.getValue(name_));
PIConfig::Branch db(ce.getValue("device").children()), fb(ce.getValue("filter").children()),
cb(ce.getValue("channel").children()), sb(ce.getValue("sender").children());
PIStringList dev_list(ce.getValue("device").value());
PIStringList name_list(ce.getValue("device").name());
piForeachC (PIConfig::Entry * e, db) {
dev_list << e->value();
name_list << e->name();
}
PIMap<PIString, PIString> dev_aliases;
for (int i = 0; i < dev_list.size_s(); ++i) {
PIString fn(dev_list[i]);
if (fn.isEmpty()) continue;
PIString & n(name_list[i]);
PIIODevice::DeviceMode dm = PIIODevice::ReadWrite;
PIIODevice::splitFullPath(fn, &fn, &dm);
//piCout << fn;
PIIODevice * dev = addDevice(fn, dm);
if (!dev) continue;
dev_aliases[n] = fn;
device_names[n] = dev;
setDeviceName(dev, n);
dev->setName(name_ + ".device." + dev_list[i]);
PIDiagnostics * diag = diags_.value(dev, 0);
if (diag != 0)
diag->setDisconnectTimeout(ce.getValue("device." + n + ".disconnectTimeout", diag->disconnectTimeout()));
}
int added(0), padded(-1), tries(0);
bool pdebug = debug();
setDebug(false);
PIStringList filter_fails;
while (added != padded && tries < 100) {
padded = added;
added = 0;
++tries;
piForeachC (PIConfig::Entry * e, fb) {
PIPacketExtractor::SplitMode sm = PIPacketExtractor::None;
PIString sms(e->getValue("splitMode").value());
int smi = sms.toInt();
if (smi >= 1 && smi <= 5) sm = (PIPacketExtractor::SplitMode)smi;
else {
sms = sms.trim().toLowerCase();
if (sms.find("header") >= 0 && sms.find("footer") >= 0)
sm = PIPacketExtractor::HeaderAndFooter;
else {
if (sms.find("header") >= 0)
sm = PIPacketExtractor::Header;
else {
if (sms.find("footer") >= 0)
sm = PIPacketExtractor::Footer;
else {
if (sms.find("time") >= 0)
sm = PIPacketExtractor::Timeout;
else {
if (sms.find("size") >= 0)
sm = PIPacketExtractor::Size;
}
}
}
}
}
PIStringList devs(e->value());
PIConfig::Branch db(e->getValue("device").children());
devs << e->getValue("device", "").value();
piForeachC (PIConfig::Entry * e2, db)
devs << e2->value();
devs.removeStrings("");
if (devs.isEmpty()) continue;
PIString dname = dev_aliases.value(devs.front(), devs.front());
PIPacketExtractor * pe = addFilter(e->name(), dname, sm);
if (pe == 0) {
if (!filter_fails.contains(dname))
filter_fails << dname;
continue;
} else {
filter_fails.removeAll(dname);
}
++added;
for (int i = 1; i < devs.size_s(); ++i) {
dname = dev_aliases.value(devs[i], devs[i]);
if (addFilter(e->name(), dname, sm) != 0) {
filter_fails.removeAll(dname);
++added;
} else {
if (!filter_fails.contains(dname))
filter_fails << dname;
}
}
PIDiagnostics * diag = diags_.value(pe, 0);
if (diag != 0)
diag->setDisconnectTimeout(e->getValue("disconnectTimeout", diag->disconnectTimeout()));
pe->setPayloadSize(e->getValue("payloadSize", pe->payloadSize()));
pe->setPacketSize(e->getValue("packetSize", pe->packetSize()));
pe->setTimeout(e->getValue("timeout", pe->timeout()));
pe->setHeader(PIByteArray::fromString(e->getValue("header", "").value()));
pe->setFooter(PIByteArray::fromString(e->getValue("footer", "").value()));
}
}
setDebug(pdebug);
piForeachC (PIString & f, filter_fails)
piCoutObj << "\"addFilter\" error: no such device \"" << f << "\"!";
piForeachC (PIConfig::Entry * e, cb) {
PIString f(e->getValue("from").value()), t(e->getValue("to").value());
addChannel(dev_aliases.value(f, f), dev_aliases.value(t, t));
}
piForeachC (PIConfig::Entry * e, sb) {
PIStringList devs(e->value());
PIConfig::Branch db(e->getValue("device").children());
devs << e->getValue("device", "").value();
piForeachC (PIConfig::Entry * e2, db)
devs << e2->value();
devs.removeStrings("");
if (devs.isEmpty()) continue;
float freq = e->getValue("frequency");
piForeachC (PIString & d, devs)
addSender(e->name(), dev_aliases.value(d, d), freq);
PIByteArray fd(PIByteArray::fromString(e->getValue("fixedData").value()));
setSenderFixedData(e->name(), fd);
}
return true;
}
PIString PIConnection::makeConfig() const {
PIString ret;
ret << "[" << name() << "]\n";
PIVector<PIIODevice * > devs(boundedDevices());
int dn(-1);
piForeachC (PIIODevice * d, devs) {
PIStringList dnl(deviceNames(d));
if (dnl.isEmpty()) dnl << PIString::fromNumber(++dn);
piForeachC (PIString & dname, dnl) {
ret << "device." << dname << " = " << d->constructFullPath() << " #s\n";
PIDiagnostics * diag = diags_.value(const_cast<PIIODevice * >(d), 0);
if (diag != 0)
ret << "device." << dname << ".disconnectTimeout = " << diag->disconnectTimeout() << " #f\n";
}
}
piForeachC (PEPair & f, extractors) {
if (f.second == 0) continue;
if (f.second->extractor == 0) continue;
PIString prefix = "filter." + f.first;
for (int i = 0; i < f.second->devices.size_s(); ++i)
ret << prefix << ".device." << i << " = " << device_names.key(f.second->devices[i]) << " #s\n";
PIDiagnostics * diag = diags_.value(f.second->extractor, 0);
if (diag != 0)
ret << prefix << ".disconnectTimeout = " << diag->disconnectTimeout() << " #f\n";
ret << prefix << ".splitMode = ";
switch (f.second->extractor->splitMode()) {
case PIPacketExtractor::None: ret << "none"; break;
case PIPacketExtractor::Header: ret << "header"; break;
case PIPacketExtractor::Footer: ret << "footer"; break;
case PIPacketExtractor::HeaderAndFooter: ret << "header & footer"; break;
case PIPacketExtractor::Size: ret << "size"; break;
case PIPacketExtractor::Timeout: ret << "timeout"; break;
}
ret << " #s\n";
ret << prefix << ".payloadSize = " << f.second->extractor->payloadSize() << " #n\n";
ret << prefix << ".packetSize = " << f.second->extractor->packetSize() << " #n\n";
ret << prefix << ".timeout = " << f.second->extractor->timeout() << " #f\n";
ret << prefix << ".header = " << f.second->extractor->header().toString() << " #s\n";
ret << prefix << ".footer = " << f.second->extractor->footer().toString() << " #s\n";
}
dn = 0;
piForeachC (CPair & c, channels_) {
piForeachC (PIIODevice * d, c.second) {
PIString prefix = "channel." + PIString::fromNumber(dn); ++dn;
ret << prefix << ".from = " << devPath(c.first) << " #s\n";
ret << prefix << ".to = " << devPath(d) << " #s\n";
}
}
piForeachC (SPair & s, senders) {
if (s.second == 0) continue;
PIString prefix = "sender." + s.second->name();
for (int i = 0; i < s.second->devices.size_s(); ++i)
ret << prefix << ".device." << i << " = " << devPath(s.second->devices[i]) << " #s\n";
double int_ = s.second->int_;
if (int_ > 0.)
ret << prefix << ".frequency = " << (1000. / int_) << " #f\n";
if (!s.second->sdata.isEmpty())
ret << prefix << ".fixedData = " << s.second->sdata.toString() << " #s\n";
}
ret << "[]\n";
return ret;
}
PIIODevice * PIConnection::addDevice(const PIString & full_path, PIIODevice::DeviceMode mode, bool start) {
PIString fp(PIIODevice::normalizeFullPath(full_path));
PIIODevice * dev = __device_pool__->addDevice(this, fp, mode, start);
if (dev) {
dev->setName(name() + ".device." + fp);
device_modes[dev] = mode;
__device_pool__->lock();
if (diags_.value(dev, 0) == 0) {
PIDiagnostics * d = new PIDiagnostics(false);
d->setInterval(10.);
diags_[dev] = d;
CONNECTU(d, qualityChanged, this, diagQualityChanged);
__device_pool__->init();
}
__device_pool__->unlock();
}
return dev;
}
PIStringList PIConnection::deviceNames(const PIIODevice *dev) const {
PIStringList ret;
piForeachC (DNPair & s, device_names)
if (s.second == dev)
ret << s.first;
return ret;
}
bool PIConnection::removeDevice(const PIString & full_path) {
PIString fp(PIIODevice::normalizeFullPath(full_path));
PIIODevice * dev = __device_pool__->device(fp);
if (dev == 0) return false;
PIStringList dntd(deviceNames(dev));
piForeachC (PIString & n, dntd)
device_names.removeOne(n);
piForeachC (SPair & s, senders) {
if (s.second == 0) continue;
s.second->lock();
s.second->devices.removeAll(dev);
s.second->unlock();
}
device_modes.remove(dev);
piForeachC (PEPair & i, extractors) {
if (i.second == 0) continue;
i.second->devices.removeAll(dev);
}
bounded_extractors.remove(dev);
channels_.remove(dev);
for (PIMap<PIIODevice * , PIVector<PIIODevice * > >::iterator it = channels_.begin(); it != channels_.end(); ++it)
it.value().removeAll(dev);
__device_pool__->lock();
if (diags_.value(dev, 0) != 0)
delete diags_.value(dev);
diags_.remove(dev);
__device_pool__->unlock();
return __device_pool__->removeDevice(this, fp);
}
void PIConnection::removeAllDevices() {
device_names.clear();
PIVector<PIIODevice * > bdevs(__device_pool__->boundedDevices(this));
__device_pool__->lock();
piForeach (PIIODevice * d, bdevs) {
piForeachC (SPair & s, senders) {
if (s.second == 0) continue;
s.second->lock();
s.second->devices.removeAll(d);
s.second->unlock();
}
channels_.remove(d);
for (PIMap<PIIODevice * , PIVector<PIIODevice * > >::iterator it = channels_.begin(); it != channels_.end(); ++it)
it.value().removeAll(d);
if (diags_.value(d, 0) != 0)
delete diags_.value(d);
diags_.remove(d);
}
__device_pool__->unboundConnection(this);
__device_pool__->unlock();
device_modes.clear();
bounded_extractors.clear();
piForeachC (PEPair & i, extractors) {
if (i.second == 0) continue;
i.second->devices.clear();
}
}
PIIODevice * PIConnection::deviceByFullPath(const PIString & full_path) const {
PIString fp(PIIODevice::normalizeFullPath(full_path));
DevicePool::DeviceData * dd = __device_pool__->devices.value(fp);
if (dd == 0) return 0;
if (dd->dev == 0) return 0;
if (!dd->listeners.contains(const_cast<PIConnection * >(this))) return 0;
return dd->dev;
}
PIIODevice * PIConnection::deviceByName(const PIString & name) const {
return device_names.value(name, 0);
}
PIVector<PIIODevice * > PIConnection::boundedDevices() const {
return __device_pool__->boundedDevices(this);
}
PIPacketExtractor * PIConnection::addFilter(const PIString & name_, const PIString & full_path, PIPacketExtractor::SplitMode mode) {
PIString fp(PIIODevice::normalizeFullPath(full_path));
PIString fname_ = name_.trimmed();
Extractor * e = extractors.value(fname_);
if (full_path.isEmpty()) return (e == 0 ? 0 : e->extractor);
PIIODevice * dev = deviceByFullPath(fp);
PIPacketExtractor * pe(0);
if (extractors.value(full_path) != 0) pe = extractors.value(full_path)->extractor;
if (pe != 0) dev = pe;
if (dev == 0) {
piCoutObj << "\"addFilter\" error: no such device \"" << full_path << "\"!";
return 0;
}
if (e == 0) {
e = new Extractor();
extractors[fname_] = e;
}
if (e->extractor == 0) {
e->extractor = new PIPacketExtractor(0, mode);
e->extractor->setName(fname_);
e->extractor->setThreadedReadData(new PIPair<PIConnection * , PIString>(this, fname_));
e->extractor->setHeaderCheckSlot(filterValidateHeaderS);
e->extractor->setFooterCheckSlot(filterValidateFooterS);
e->extractor->setPayloadCheckSlot(filterValidatePayloadS);
__device_pool__->lock();
if (diags_.value(e->extractor, 0) == 0) {
PIDiagnostics * d = new PIDiagnostics(false);
d->setInterval(10.);
diags_[e->extractor] = d;
CONNECTU(d, qualityChanged, this, diagQualityChanged);
}
__device_pool__->unlock();
CONNECT2(void, uchar * , int, e->extractor, packetReceived, this, packetExtractorReceived)
}
if (!e->devices.contains(dev)) {
bounded_extractors[dev] << e->extractor;
//if (PIString(dev->className()) == "PIPacketExtractor") dev->setThreadSafe(false);
e->devices << dev;
}
return e->extractor;
}
PIPacketExtractor * PIConnection::addFilter(PIPacketExtractor * filter, const PIString & full_path) {
PIString fp(PIIODevice::normalizeFullPath(full_path));
Extractor * e = 0;
if (full_path.isEmpty()) return (e == 0 ? 0 : e->extractor);
PIIODevice * dev = deviceByFullPath(fp);
PIPacketExtractor * pe(0);
if (extractors.value(full_path) != 0) pe = extractors.value(full_path)->extractor;
if (pe != 0) dev = pe;
if (dev == 0) {
piCoutObj << "\"addFilter\" error: no such device \"" << full_path << "\"!";
return 0;
}
if (e == 0) {
e = new Extractor();
extractors[filter->name()] = e;
}
if (e->extractor == 0) {
e->extractor = filter;
e->extractor->setThreadedReadData(new PIPair<PIConnection * , PIString>(this, filter->name()));
__device_pool__->lock();
if (diags_.value(e->extractor, 0) == 0) {
PIDiagnostics * d = new PIDiagnostics(false);
d->setInterval(10.);
diags_[e->extractor] = d;
CONNECTU(d, qualityChanged, this, diagQualityChanged);
}
__device_pool__->unlock();
CONNECT2(void, uchar * , int, e->extractor, packetReceived, this, packetExtractorReceived)
}
if (!e->devices.contains(dev)) {
bounded_extractors[dev] << e->extractor;
//if (PIString(dev->className()) == "PIPacketExtractor") dev->setThreadSafe(false);
e->devices << dev;
}
return e->extractor;
}
bool PIConnection::removeFilter(const PIString & name_, const PIString & full_path) {
PIString fp(PIIODevice::normalizeFullPath(full_path));
Extractor * p = extractors.value(name_.trimmed());
if (p == 0) return false;
bool ret = false;
for (int i = 0; i < p->devices.size_s(); ++i) {
if (devFPath(p->devices[i]) == fp || devFPath(p->devices[i]) == full_path) {
bounded_extractors[p->devices[i]].removeAll(p->extractor);
p->devices.remove(i);
--i;
ret = true;
}
}
if (p->devices.isEmpty()) {
unboundExtractor(p->extractor);
delete p;
}
return ret;
}
bool PIConnection::removeFilter(const PIString & name, const PIIODevice * dev) {
if (dev == 0) return false;
return removeFilter(name, devFPath(dev));
}
bool PIConnection::removeFilter(const PIString & name_) {
Extractor * p = extractors.value(name_.trimmed());
if (p == 0) return false;
unboundExtractor(p->extractor);
delete p;
return true;
}
void PIConnection::removeAllFilters() {
__device_pool__->lock();
piForeachC (PEPair & i, extractors) {
if (i.second == 0) continue;
channels_.remove(i.second->extractor);
for (PIMap<PIIODevice * , PIVector<PIIODevice * > >::iterator it = channels_.begin(); it != channels_.end(); ++it)
it.value().removeAll(i.second->extractor);
if (diags_.value(i.second->extractor, 0) != 0)
delete diags_.value(i.second->extractor);
diags_.remove(i.second->extractor);
delete i.second;
}
extractors.clear();
bounded_extractors.clear();
__device_pool__->unlock();
}
PIVector<PIPacketExtractor * > PIConnection::filters() const {
PIVector<PIPacketExtractor * > ret;
piForeachC (PEPair & i, extractors)
if (i.second != 0)
if (i.second->extractor != 0) ret << i.second->extractor;
return ret;
}
PIStringList PIConnection::filterNames() const {
PIStringList ret;
piForeachC (PEPair & i, extractors)
if (i.second != 0)
if (i.second->extractor != 0) ret << i.first;
return ret;
}
PIPacketExtractor * PIConnection::filter(const PIString & name_) const {
PIString fname_ = name_.trimmed();
piForeachC (PEPair & i, extractors)
if (i.second != 0)
if (i.second->extractor != 0 && i.first == fname_)
return i.second->extractor;
return 0;
}
PIVector<PIIODevice * > PIConnection::filterBoundedDevices(const PIString & name_) const {
PIVector<PIIODevice * > ret;
Extractor * p = extractors.value(name_.trimmed());
if (p == 0) return ret;
return p->devices;
}
bool PIConnection::addChannel(const PIString & name0, const PIString & name1) {
//piCout << "addChannel" << name0 << name1;
if (name0.isEmpty() || name1.isEmpty()) return false;
PIIODevice * dev0 = deviceByFullPath(name0), * dev1 = deviceByFullPath(name1);
PIPacketExtractor * pe0(0), * pe1(0);
if (extractors.value(name0) != 0) pe0 = extractors.value(name0)->extractor;
if (extractors.value(name1) != 0) pe1 = extractors.value(name1)->extractor;
if (pe0 != 0) dev0 = pe0;
if (pe1 != 0) dev1 = pe1;
if (dev0 == 0 || dev1 == 0) {
if (dev0 == 0) piCoutObj << "\"addChannel\" error: no such device \"" << name0 << "\"!";
if (dev1 == 0) piCoutObj << "\"addChannel\" error: no such device \"" << name1 << "\"!";
return false;
}
if (!channels_[dev0].contains(dev1))
channels_[dev0] << dev1;
return true;
}
bool PIConnection::removeChannel(const PIString & name0, const PIString & name1) {
PIIODevice * dev0 = deviceByFullPath(name0), * dev1 = deviceByFullPath(name1);
PIPacketExtractor * pe0(0), * pe1(0);
if (extractors.value(name0) != 0) pe0 = extractors.value(name0)->extractor;
if (extractors.value(name1) != 0) pe1 = extractors.value(name1)->extractor;
if (pe0 != 0) dev0 = pe0;
if (pe1 != 0) dev1 = pe1;
if (dev0 == 0 || dev1 == 0) return false;
channels_[dev0].removeAll(dev1);
return true;
}
bool PIConnection::removeChannel(const PIString & name0) {
PIIODevice * dev0 = deviceByFullPath(name0);
PIPacketExtractor * pe0(0);
if (extractors.value(name0) != 0) pe0 = extractors.value(name0)->extractor;
if (pe0 != 0) dev0 = pe0;
if (dev0 == 0) return false;
channels_.remove(dev0);
for (PIMap<PIIODevice * , PIVector<PIIODevice * > >::iterator it = channels_.begin(); it != channels_.end(); ++it)
it.value().removeAll(dev0);
return true;
}
void PIConnection::removeAllChannels() {
channels_.clear();
}
PIString PIConnection::devPath(const PIIODevice * d) const {
if (d == 0) return PIString();
if (strcmp(d->className(), "PIPacketExtractor") == 0) return d->name();
return d->constructFullPath();
}
PIString PIConnection::devFPath(const PIIODevice * d) const {
if (d == 0) return PIString();
if (d->isPropertyExists("__fullPath__")) return d->property("__fullPath__").toString();
return d->name();
}
PIVector<PIPair<PIString, PIString > > PIConnection::channels() const {
PIVector<PIPair<PIString, PIString > > ret;
piForeachC (CPair & i, channels_) {
PIString fp0(devFPath(i.first));
piForeachC (PIIODevice * d, i.second)
ret << PIPair<PIString, PIString>(fp0, devFPath(d));
}
return ret;
}
void PIConnection::addSender(const PIString & name_, const PIString & full_path, float frequency, bool start_) {
PIString fp(PIIODevice::normalizeFullPath(full_path));
PIString fname_ = name_.trimmed();
if (fp.isEmpty() || frequency <= 0.) return;
Sender * s = senders.value(fname_);
PIIODevice * dev = deviceByFullPath(fp);
if (s == 0) {
s = new Sender(this);
s->setName(fname_);
s->int_ = 1000. / frequency;
senders[fname_] = s;
}
if (dev == 0) {
piCoutObj << "\"addSender\" error: no such device \"" << full_path << "\"!";
return;
}
if (!s->isRunning() && start_) {
//piCoutObj << name_ << "start" << 1000. / frequency;
if (!__device_pool__->fake) s->start(s->int_);
}
s->lock();
if (!s->devices.contains(dev))
s->devices << dev;
s->unlock();
}
bool PIConnection::removeSender(const PIString & name, const PIString & full_path) {
PIString fp(PIIODevice::normalizeFullPath(full_path));
Sender * s = senders.value(name, 0);
PIIODevice * d = deviceByFullPath(fp);
if (s == 0 || d == 0) return false;
s->lock();
bool ret = s->devices.contains(d);
if (ret)
s->devices.removeAll(d);
s->unlock();
return ret;
}
bool PIConnection::removeSender(const PIString & name) {
Sender * s = senders.value(name, 0);
if (s == 0) return false;
delete s;
senders.remove(name);
return true;
}
bool PIConnection::setSenderFixedData(const PIString & name, const PIByteArray & data) {
Sender * s = senders.value(name, 0);
if (s == 0) return false;
s->lock();
s->sdata = data;
s->unlock();
return true;
}
bool PIConnection::clearSenderFixedData(const PIString & name) {
Sender * s = senders.value(name, 0);
if (s == 0) return false;
s->lock();
s->sdata.clear();
s->unlock();
return true;
}
PIByteArray PIConnection::senderFixedData(const PIString & name) const {
Sender * s = senders.value(name, 0);
if (s == 0) return PIByteArray();
return s->sdata;
}
float PIConnection::senderFrequency(const PIString & name) const {
Sender * s = senders.value(name, 0);
if (s == 0) return -1.f;
double i = s->interval();
if (i == 0.) return 0.f;
return 1000. / s->interval();
}
void PIConnection::removeAllSenders() {
piForeachC (SPair & s, senders)
if (s.second != 0)
delete s.second;
senders.clear();
}
void PIConnection::startThreadedRead(const PIString & full_path) {
PIString fp(PIIODevice::normalizeFullPath(full_path));
DevicePool::DeviceData * dd = __device_pool__->devices.value(fp, 0);
if (dd == 0) return;
if (dd->dev == 0) return;
if (dd->started || dd->dev->mode() == PIIODevice::WriteOnly) return;
if (!__device_pool__->fake) dd->rthread->start();
dd->started = true;
}
void PIConnection::startAllThreadedReads() {
piForeachC (DevicePool::DDPair & d, __device_pool__->devices)
startThreadedRead(d.first);
}
void PIConnection::startSender(const PIString & name) {
Sender * s = senders.value(name, 0);
if (s == 0) return;
if (!s->isRunning() && !__device_pool__->fake)
s->start(s->int_);
}
void PIConnection::startAllSenders() {
piForeachC (SPair & s, senders) {
if (s.second == 0) continue;
if (!s.second->isRunning() && !__device_pool__->fake)
s.second->start(s.second->int_);
}
}
void PIConnection::stopThreadedRead(const PIString & full_path) {
PIString fp(PIIODevice::normalizeFullPath(full_path));
DevicePool::DeviceData * dd = __device_pool__->devices.value(fp, 0);
if (dd == 0) return;
if (dd->dev == 0) return;
if (!dd->started || dd->dev->mode() == PIIODevice::WriteOnly) return;
dd->rthread->stop();
dd->started = false;
}
void PIConnection::stopAllThreadedReads() {
piForeachC (DevicePool::DDPair & d, __device_pool__->devices)
stopThreadedRead(d.first);
}
void PIConnection::stopSender(const PIString & name) {
Sender * s = senders.value(name, 0);
if (s == 0) return;
if (s->isRunning()) s->stop();
}
void PIConnection::stopAllSenders() {
piForeachC (SPair & s, senders) {
if (s.second == 0) continue;
if (s.second->isRunning())
s.second->stop();
}
}
PIDiagnostics * PIConnection::diagnostic(const PIString & full_path_name) const {
PIIODevice * dev = deviceByFullPath(full_path_name);
if (dev == 0) dev = device_names.value(full_path_name, 0);
PIPacketExtractor * pe(0);
if (extractors.value(full_path_name) != 0) pe = extractors.value(full_path_name)->extractor;
if (pe != 0) dev = pe;
if (dev == 0) return 0;
return diags_.value(dev, 0);
}
int PIConnection::writeByFullPath(const PIString & full_path, const PIByteArray & data) {
PIString fp = PIIODevice::normalizeFullPath(full_path);
PIIODevice * dev = __device_pool__->device(fp);
//piCout << "SEND" << full_path << fp;
return write(dev, data);
}
int PIConnection::writeByName(const PIString & name, const PIByteArray & data) {
PIIODevice * dev = deviceByName(name);
return write(dev, data);
}
int PIConnection::write(PIIODevice * dev, const PIByteArray & data) {
if (dev == 0) {
piCoutObj << "Null Device!";
return -1;
}
if (!dev->isOpened()) return -1;
if (!dev->canWrite()) {
piCoutObj << "Device \"" << dev->constructFullPath() << "\" can`t write!";
return -1;
}
int ret = dev->write(data);
PIDiagnostics * diag = diags_.value(dev);
if (diag != 0 && ret > 0) diag->sended(ret);
return ret;
}
PIVector< PIConnection * > PIConnection::allConnections() {
return _connections;
}
PIVector< PIIODevice * > PIConnection::allDevices() {
return __device_pool__->boundedDevices();
}
bool PIConnection::setFakeMode(bool yes) {
bool ret = isFakeMode();
__device_pool__->fake = yes;
return ret;
}
bool PIConnection::isFakeMode() {
return __device_pool__->fake;
}
PIConnection::DevicePool::DevicePool(): PIThread(false, 10) {
setName("PIConnection::DevicePool");
needLockRun(true);
fake = false;
}
void PIConnection::DevicePool::init() {
if (!isRunning())
start(10);
}
PIIODevice * PIConnection::DevicePool::addDevice(PIConnection * parent, const PIString & fp, PIIODevice::DeviceMode mode, bool start) {
DeviceData * dd = devices[fp];
int pmode(0);
bool need_start = false;
if (dd == 0) {
dd = new DeviceData();
devices[fp] = dd;
}
if (dd->dev == 0) {
//piCout << "new device" << fp;
dd->dev = PIIODevice::createFromFullPath(fp);
if (dd->dev == 0) {
piCoutObj << "Error: can`t create device \"" << fp << "\"!"; //:" << errorString();
return 0;
}
dd->dev->setProperty("__fullPath__", fp);
} else
pmode = dd->dev->mode();
if (!dd->listeners.contains(parent))
dd->listeners << parent;
if (pmode == mode || pmode == PIIODevice::ReadWrite)
return dd->dev;
if ((mode & PIIODevice::ReadOnly) > 0) {
if (dd->rthread != 0) {
delete dd->rthread;
dd->rthread = 0;
dd->started = false;
}
dd->rthread = new PIThread(dd, __DevicePool_threadReadDP);
dd->rthread->setName("__S__connection_" + fp + "_read_thread");
need_start = true;
pmode |= PIIODevice::ReadOnly;
}
if ((mode & PIIODevice::WriteOnly) > 0)
pmode |= PIIODevice::WriteOnly;
if (!fake) {
dd->dev->close();
dd->dev->open((PIIODevice::DeviceMode)pmode);
} else
dd->dev->setMode((PIIODevice::DeviceMode)pmode);
if (need_start && start) {
if (!fake) dd->rthread->start();
dd->started = true;
}
return dd->dev;
}
bool PIConnection::DevicePool::removeDevice(PIConnection * parent, const PIString & fp) {
DeviceData * dd = devices.value(fp);
if (dd == 0)
return false;
if (dd->dev == 0)
return false;
bool ok = dd->listeners.contains(parent);
dd->listeners.removeAll(parent);
if (dd->listeners.isEmpty()) {
delete dd;
devices.remove(fp);
}
return ok;
}
void PIConnection::DevicePool::unboundConnection(PIConnection * parent) {
PIStringList rem;
piForeachC (DDPair & i, devices) {
if (i.second == 0) {
rem << i.first;
continue;
}
i.second->listeners.removeAll(parent);
if (i.second->listeners.isEmpty())
rem << i.first;
}
piForeachC (PIString & i, rem) {
DeviceData * dd = devices.value(i);
if (dd == 0)
continue;
delete dd;
devices.remove(i);
}
}
PIIODevice * PIConnection::DevicePool::device(const PIString & fp) const {
DeviceData * dd = devices.value(fp);
if (dd == 0) return 0;
return dd->dev;
}
PIVector<PIConnection * > PIConnection::DevicePool::boundedConnections() const {
PIVector<PIConnection * > ret;
piForeachC (DDPair & i, devices) {
if (i.second == 0)
continue;
ret << i.second->listeners;
}
for (int i = 0; i < ret.size_s(); ++i)
for (int j = i + 1; j < ret.size_s(); ++j)
if (ret[i] == ret[j]) {
ret.remove(j);
--j;
}
return ret;
}
PIVector< PIIODevice * > PIConnection::DevicePool::boundedDevices() const {
PIVector<PIIODevice * > ret;
piForeachC (DDPair & i, devices) {
if (i.second == 0) continue;
if (i.second->dev == 0) continue;
ret << i.second->dev;
}
return ret;
}
PIVector<PIIODevice * > PIConnection::DevicePool::boundedDevices(const PIConnection * parent) const {
PIVector<PIIODevice * > ret;
piForeachC (DDPair & i, devices) {
if (i.second == 0) continue;
if (i.second->dev == 0) continue;
if (i.second->listeners.contains(const_cast<PIConnection*>(parent)))
ret << i.second->dev;
}
return ret;
}
PIConnection::DevicePool::DeviceData::~DeviceData() {
if (rthread != 0) {
rthread->stop();
if (!rthread->waitForFinish(1000))
rthread->terminate();
delete rthread;
rthread = 0;
}
if (dev != 0) {
dev->close();
delete dev;
dev = 0;
}
}
void PIConnection::DevicePool::run() {
PIVector<PIConnection * > conns(PIConnection::allConnections());
piForeach (PIConnection * c, conns) {
piForeachC (PIConnection::DPair & d, c->diags_) {
if (d.second == 0) continue;
d.second->tick(0, 1);
}
}
}
void __DevicePool_threadReadDP(void * ddp) {
PIConnection::DevicePool::DeviceData * dd((PIConnection::DevicePool::DeviceData * )ddp);
if (dd->dev == 0) {piMSleep(100); return;}
if (dd->dev->isClosed())
if (!dd->dev->open()) {piMSleep(dd->dev->reopenTimeout()); return;}
PIByteArray ba;
ba = dd->dev->read(dd->dev->threadedReadBufferSize());
// dd->dev->threadedRead(ba.data(), ba.size());
if (ba.isEmpty()) {piMSleep(10); return;}
dd->dev->threadedRead(ba.data(), ba.size_s());
//piCout << "Readed from" << dd->dev->path() << Hex << ba;
__device_pool__->deviceReaded(dd, ba);
}
void PIConnection::DevicePool::deviceReaded(PIConnection::DevicePool::DeviceData * dd, const PIByteArray & data) {
PIString from = dd->dev->property("__fullPath__").toString();
piForeach (PIConnection * ld, dd->listeners)
ld->rawReceived(dd->dev, from, data);
}
bool PIConnection::filterValidateHeaderS(void * c, uchar * src, uchar * rec, int size) {
PIPair<PIConnection * , PIString> * p((PIPair<PIConnection * , PIString> * )c);
return p->first->filterValidateHeader(p->second, src, rec, size);
}
bool PIConnection::filterValidateFooterS(void * c, uchar * src, uchar * rec, int size) {
PIPair<PIConnection * , PIString> * p((PIPair<PIConnection * , PIString> * )c);
return p->first->filterValidateFooter(p->second, src, rec, size);
}
bool PIConnection::filterValidatePayloadS(void * c, uchar * rec, int size) {
PIPair<PIConnection * , PIString> * p((PIPair<PIConnection * , PIString> * )c);
return p->first->filterValidatePayload(p->second, rec, size);
}
void PIConnection::rawReceived(PIIODevice * dev, const PIString & from, const PIByteArray & data) {
dataReceived(from, data);
dataReceivedEvent(from, data);
PIVector<PIPacketExtractor * > be(bounded_extractors.value(dev));
//piCout << be;
piForeach (PIPacketExtractor * i, be)
i->threadedRead(const_cast<uchar * >(data.data()), data.size_s());
PIVector<PIIODevice * > chd(channels_.value(dev));
piForeach (PIIODevice * d, chd) {
int ret = d->write(data);
PIDiagnostics * diag = diags_.value(d);
if (diag != 0 && ret > 0) diag->sended(ret);
}
PIDiagnostics * diag = diags_.value(dev);
if (diag != 0) diag->received(data.size_s());
}
bool PIConnection::filterValidateHeader(const PIString & filter_name, uchar * src, uchar * rec, int size) {
for (int i = 0; i < size; ++i)
if (src[i] != rec[i])
return false;
return true;
}
bool PIConnection::filterValidateFooter(const PIString & filter_name, uchar * src, uchar * rec, int size) {
for (int i = 0; i < size; ++i)
if (src[i] != rec[i])
return false;
return true;
}
bool PIConnection::filterValidatePayload(const PIString & filter_name, uchar * rec, int size) {
return true;
}
PIByteArray PIConnection::senderData(const PIString & sender_name) {
return PIByteArray();
}
PIConnection::Extractor::~Extractor() {
if (extractor != 0) {
if (extractor->threadedReadData() != 0)
delete (PIPair<PIConnection * , PIString> * )(extractor->threadedReadData());
delete extractor;
extractor = 0;
}
}
void PIConnection::Sender::tick(void * , int) {
if (parent == 0) return;
PIByteArray data;
if (!sdata.isEmpty()) data = sdata;
else data = parent->senderData(name());
if (data.isEmpty()) return;
//piCoutObj << "write"<<data.size()<<"bytes to"<<devices.size()<<"devices";
piForeach (PIIODevice * d, devices) {
int ret = d->write(data);
PIDiagnostics * diag = parent->diags_.value(d);
if (diag != 0 && ret > 0) diag->sended(ret);
}
}
void PIConnection::unboundExtractor(PIPacketExtractor * pe) {
if (pe == 0) return;
channels_.remove(pe);
for (PIMap<PIIODevice * , PIVector<PIIODevice * > >::iterator it = channels_.begin(); it != channels_.end(); ++it)
it.value().removeAll(pe);
bounded_extractors.remove(pe);
PIVector<PIIODevice * > k = bounded_extractors.keys();
piForeach (PIIODevice * i, k) {
PIVector<PIPacketExtractor * > & be(bounded_extractors[i]);
be.removeAll(pe);
if (be.isEmpty())
bounded_extractors.remove(i);
}
__device_pool__->lock();
if (diags_.value(pe, 0) != 0)
delete diags_.value(pe);
diags_.remove(pe);
extractors.remove(pe->name());
__device_pool__->unlock();
}
void PIConnection::packetExtractorReceived(uchar * data, int size) {
PIString from(emitter() == 0 ? "" : emitter()->name());
PIIODevice * cd = (PIIODevice * )emitter();
// piCout << "packetExtractorReceived" << from << cd;
if (cd != 0) {
PIVector<PIPacketExtractor * > be(bounded_extractors.value(cd));
//piCout << be << (void*)data << size;
piForeach (PIPacketExtractor * i, be)
i->threadedRead(data, size);
PIVector<PIIODevice * > chd(channels_.value(cd));
piForeach (PIIODevice * d, chd) {
int ret = d->write(data, size);
PIDiagnostics * diag = diags_.value(d);
if (diag != 0) diag->sended(ret);
}
PIDiagnostics * diag = diags_.value(cd);
if (diag != 0) diag->received(size);
}
packetReceived(from, PIByteArray(data, size));
packetReceivedEvent(from, PIByteArray(data, size));
}
void PIConnection::diagQualityChanged(PIDiagnostics::Quality new_quality, PIDiagnostics::Quality old_quality) {
qualityChanged(diags_.key((PIDiagnostics*)emitter()), new_quality, old_quality);
}
PIConnection::DevicePool * __device_pool__;
bool __DevicePoolContainer__::inited_(false);
__DevicePoolContainer__::__DevicePoolContainer__() {
if (inited_) return;
inited_ = true;
__device_pool__ = new PIConnection::DevicePool();
}

422
src_main/io/piconnection.h Executable file
View File

@@ -0,0 +1,422 @@
/*! \file piconnection.h
* \brief Complex I/O point
*/
/*
PIP - Platform Independent Primitives
Complex I/O point
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PICONNECTION_H
#define PICONNECTION_H
#include "pipacketextractor.h"
#include "pidiagnostics.h"
class PIConfig;
class PIP_EXPORT PIConnection: public PIObject
{
PIOBJECT_SUBCLASS(PIConnection, PIObject)
public:
//! Constructs connection with name "name", or with default name = "connection"
PIConnection(const PIString & name = PIStringAscii("connection"));
//! Constructs connection and configure it from config file "config" from section "name"
PIConnection(const PIString & config, const PIString & name);
//! Constructs connection and configure it from config content "string" from section "name"
PIConnection(PIString * string, const PIString & name);
~PIConnection();
/*! \brief Configure connection from config file "config" from section "name". Returns if configuration was successful
* \details \b Warning: all devices, filters and channels removed before configure! */
bool configureFromConfig(const PIString & config, const PIString & name = PIStringAscii("connection"));
/*! \brief Configure connection from config content "string" from section "name". Returns if configuration was successful
* \details \b Warning: all devices, filters and channels removed before configure! */
bool configureFromString(PIString * string, const PIString & name = PIStringAscii("connection"));
//! Returns config file section of current connection configuration
PIString makeConfig() const;
/*! \brief Add device with full path "full_path", open mode "mode" to Device pool and connection
* \details Returns pointer to device or null if device can not be created. If "start" is true,
* read thread is started immediately. Else, you can start read thread with functions \a startThreadedRead()
* or \a startAllThreadedReads(). By default, read thread doesn`t start */
PIIODevice * addDevice(const PIString & full_path, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite, bool start = false);
void setDeviceName(PIIODevice * dev, const PIString & name) {device_names[name] = dev;}
PIStringList deviceNames(const PIIODevice * dev) const;
/*! \brief Remove device with full path "full_path" from connection
* \details Returns if device was removed. If there is no connection bounded to this device,
* it will be removed from Device pool */
bool removeDevice(const PIString & full_path);
/*! \brief Remove all device from connection
* \details If there is no connection bounded to there devices, they removed from Device pool */
void removeAllDevices();
//! Returns device with full path "full_path" or null if there is no such device
PIIODevice * deviceByFullPath(const PIString & full_path) const;
//! Returns device with name "name" or null if there is no such device
PIIODevice * deviceByName(const PIString & name) const;
//! Returns all devices bounded to this connection
PIVector<PIIODevice * > boundedDevices() const;
/*! \brief Add filter with name "name" to device with full path "full_path_name" or filter "full_path_name"
* \details If there is no filter with name "name", connection create new with split mode "mode" and bound
* to it device "full_path_name" or filter "full_path_name". If filter with name "name" already exists,
* device "full_path_name" or filter "full_path_name" add to this filter.
* This function returns PIPacketExtractor * assosiated with this filter
* \n \b Attention! "mode" is altual olny if new filter was created! */
PIPacketExtractor * addFilter(const PIString & name, const PIString & full_path_name, PIPacketExtractor::SplitMode mode = PIPacketExtractor::None);
//! Add filter with name "name" to device "dev"
PIPacketExtractor * addFilter(const PIString & name, const PIIODevice * dev, PIPacketExtractor::SplitMode mode = PIPacketExtractor::None) {return addFilter(name, devFPath(dev), mode);}
//! Add filter with "filter" to device "dev"
PIPacketExtractor * addFilter(PIPacketExtractor * filter, const PIString & full_path_name);
//! Add filter with "filter" to device "dev"
PIPacketExtractor * addFilter(PIPacketExtractor * filter, const PIIODevice * dev) {return addFilter(filter, devFPath(dev));}
/*! \brief Remove from filter with name "name" device with full path "full_path_name" or filter "full_path_name"
* \details If there is no devices bounded to this filter, it will be removed. Returns if device was removed */
bool removeFilter(const PIString & name, const PIString & full_path_name);
//! Remove from filter with name "name" device or filter "dev"
bool removeFilter(const PIString & name, const PIIODevice * dev);
//! Remove filter with name "name". Returns if filter was removed
bool removeFilter(const PIString & name);
//! Remove all filters from connection
void removeAllFilters();
//! Returns all filters of connection
PIVector<PIPacketExtractor * > filters() const;
//! Returns all filter names of connection
PIStringList filterNames() const;
//! Returns PIPacketExtractor * assosiated with filter "name" or null if there is no such filter
PIPacketExtractor * filter(const PIString & name) const;
//! Returns all devices bounded to filter "name"
PIVector<PIIODevice * > filterBoundedDevices(const PIString & name) const;
/*! \brief Add to connection channel from "name_from" to "name_to"
* \details "name_from" and "name_to" can be full pathes of devices or filter names.
* Returns \b false if there if no such device or filter, else create channel and returns \b true */
bool addChannel(const PIString & name_from, const PIString & name_to);
//! Add to connection channel from "name_from" to "dev_to"
bool addChannel(const PIString & name_from, const PIIODevice * dev_to) {return addChannel(name_from, devFPath(dev_to));}
//! Add to connection channel from "dev_from" to "name_to"
bool addChannel(const PIIODevice * dev_from, const PIString & name_to) {return addChannel(devFPath(dev_from), name_to);}
//! Add to connection channel from "dev_from" to "dev_to"
bool addChannel(const PIIODevice * dev_from, const PIIODevice * dev_to) {return addChannel(devFPath(dev_from), devFPath(dev_to));}
/*! \brief Remove from connection channel from "name_from" to "name_to"
* \details "name_from" and "name_to" can be full pathes of devices or filter names.
* Returns \b false if there if no such device or filter, else remove channel and returns \b true */
bool removeChannel(const PIString & name_from, const PIString & name_to);
//! Remove from connection channel from "name_from" to "dev_to"
bool removeChannel(const PIString & name_from, const PIIODevice * dev_to) {return removeChannel(name_from, devFPath(dev_to));}
//! Remove from connection channel from "dev_from" to "name_to"
bool removeChannel(const PIIODevice * dev_from, const PIString & name_to) {return removeChannel(devFPath(dev_from), name_to);}
//! Remove from connection channel from "dev_from" to "dev_to"
bool removeChannel(const PIIODevice * dev_from, const PIIODevice * dev_to) {return removeChannel(devFPath(dev_from), devFPath(dev_to));}
/*! \brief Remove from connection all channels from "name_from"
* \details "name_from" can be full path of device or filter name.
* Returns \b false if there if no such device or filter, else remove channels and returns \b true */
bool removeChannel(const PIString & name_from);
//! Remove from connection all channels from "dev_from"
bool removeChannel(const PIIODevice * dev_from) {return removeChannel(devFPath(dev_from));}
//! Remove from connection all channels
void removeAllChannels();
//! Returns all channels of this connection as full pathes or filter names pair array (from, to)
PIVector<PIPair<PIString, PIString> > channels() const;
/*! \brief Add to connection sender with name "name" device with full path "full_path"
* \details If there is no sender with name "name", connection create new, bound
* to it device "full_path_name" and start sender timer with frequency "frequency".
* If sender with name "name" already exists, device "full_path_name" add to this sender
* If "start" is true, sender is started immediately. Else, you can start sender with
* functions \a startSender()
* \n \b Attention! "frequency" is actual olny if new sender was created! */
void addSender(const PIString & name, const PIString & full_path, float frequency, bool start = false);
//! Add to connection sender with name "name" device "dev"
void addSender(const PIString & name, const PIIODevice * dev, float frequency, bool start = false) {addSender(name, devFPath(dev), frequency, start);}
/*! \brief Remove from sender with name "name" device with full path "full_path_name"
* \details If there is no devices bounded to this sender, it will be removed. Returns if sender was removed */
bool removeSender(const PIString & name, const PIString & full_path);
//! Remove from sender with name "name" device "dev"
bool removeSender(const PIString & name, const PIIODevice * dev) {return removeSender(name, devFPath(dev));}
//! Remove sender with name "name", returns if sender was removed
bool removeSender(const PIString & name);
//! Set sender "name" fixed send data "data", returns if sender exists
bool setSenderFixedData(const PIString & name, const PIByteArray & data);
//! Remove sender "name" fixed send data, returns if sender exists
bool clearSenderFixedData(const PIString & name);
//! Returns sender "name" fixed send data
PIByteArray senderFixedData(const PIString & name) const;
//! Returns sender "name" timer frequency, -1 if there is no such sender, or 0 if sender is not started yet
float senderFrequency(const PIString & name) const;
//! Remove from connection all senders
void removeAllSenders();
//! Start read thread of device with full path "full_path"
void startThreadedRead(const PIString & full_path);
//! Start read thread of device "dev"
void startThreadedRead(const PIIODevice * dev) {startThreadedRead(devFPath(dev));}
//! Start read threads of all Device pool device
void startAllThreadedReads();
//! Start sender "name" timer
void startSender(const PIString & name);
//! Start all senders timers
void startAllSenders();
//! Start all read threads and senders
void start() {startAllThreadedReads(); startAllSenders();}
//! Stop read thread of device with full path "full_path"
void stopThreadedRead(const PIString & full_path);
//! Stop read thread of device "dev"
void stopThreadedRead(const PIIODevice * dev) {stopThreadedRead(devFPath(dev));}
//! Stop read threads of all Device pool device
void stopAllThreadedReads();
//! Stop sender "name" timer
void stopSender(const PIString & name);
//! Stop all senders timers
void stopAllSenders();
//! Stop all read threads and senders
void stop() {stopAllThreadedReads(); stopAllSenders();}
//! Stop connection and remove all devices
void destroy() {stop(); removeAllDevices();}
//! Returns if there are no devices in this connection
bool isEmpty() const {return device_modes.isEmpty();}
//! Returns PIDiagnostics * assosiated with device with full path "full_path_name", name "full_path_name" or filter "full_path_name"
PIDiagnostics * diagnostic(const PIString & full_path_name) const;
//! Returns PIDiagnostics * assosiated with device or filter "dev"
PIDiagnostics * diagnostic(const PIIODevice * dev) const {return diags_.value(const_cast<PIIODevice * >(dev), 0);}
//! Write data "data" to device with full path "full_path" and returns result of \a write() function of device
int writeByFullPath(const PIString & full_path, const PIByteArray & data);
//! Write data "data" to device with name "name" and returns result of \a write() function of device
int writeByName(const PIString & name, const PIByteArray & data);
//! Write data "data" to device "dev" and returns result of \a write() function of device
int write(PIIODevice * dev, const PIByteArray & data);
//! Returns all connections in application
static PIVector<PIConnection * > allConnections();
//! Returns all devices in Device pool
static PIVector<PIIODevice * > allDevices();
//! Set Device pool fake mode to \"yes\" and returns previous mode
static bool setFakeMode(bool yes);
//! Returns if Device pool works in fake mode
static bool isFakeMode();
class DevicePool: public PIThread {
PIOBJECT_SUBCLASS(DevicePool, PIThread)
friend void __DevicePool_threadReadDP(void * ddp);
friend class PIConnection;
public:
DevicePool();
void init();
PIIODevice * addDevice(PIConnection * parent, const PIString & fp, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite, bool start = true);
bool removeDevice(PIConnection * parent, const PIString & fp);
void unboundConnection(PIConnection * parent);
PIIODevice * device(const PIString & fp) const;
PIVector<PIConnection * > boundedConnections() const;
PIVector<PIIODevice * > boundedDevices() const;
PIVector<PIIODevice * > boundedDevices(const PIConnection * parent) const;
protected:
struct DeviceData {
DeviceData(): dev(0), rthread(0), started(false) {}
~DeviceData();
PIIODevice * dev;
PIThread * rthread;
bool started;
PIVector<PIConnection * > listeners;
};
void run();
void deviceReaded(DeviceData * dd, const PIByteArray & data);
typedef PIMap<PIString, DeviceData * >::value_type DDPair;
PIMap<PIString, DeviceData * > devices;
bool fake;
};
EVENT2(dataReceivedEvent, const PIString &, from, const PIByteArray &, data)
EVENT2(packetReceivedEvent, const PIString &, from, const PIByteArray &, data)
EVENT3(qualityChanged, const PIIODevice * , dev, PIDiagnostics::Quality, new_quality, PIDiagnostics::Quality, old_quality)
//! \events
//! \{
//! \fn void dataReceivedEvent(const PIString & from, const PIByteArray & data)
//! \brief Raise on data received from device with full path "from"
//! \fn void packetReceivedEvent(const PIString & from, const PIByteArray & data)
//! \brief Raise on packet received from filter with name "from"
//! \fn void qualityChanged(const PIIODevice * device, PIDiagnostics::Quality new_quality, PIDiagnostics::Quality old_quality)
//! \brief Raise on diagnostic quality of device "device" changed from "old_quality" to "new_quality"
//! \}
protected:
//! Executes on data received from device with full path "from"
virtual void dataReceived(const PIString & from, const PIByteArray & data) {}
//! Executes on packet received from filter with name "from"
virtual void packetReceived(const PIString & from, const PIByteArray & data) {}
//! Validate header "rec" with source header "src" and size "size", executes from filter "filter_name"
virtual bool filterValidateHeader(const PIString & filter_name, uchar * src, uchar * rec, int size);
//! Validate footer "rec" with source footer "src" and size "size", executes from filter "filter_name"
virtual bool filterValidateFooter(const PIString & filter_name, uchar * src, uchar * rec, int size);
//! Validate payload "rec" with size "size", executes from filter "filter_name"
virtual bool filterValidatePayload(const PIString & filter_name, uchar * rec, int size);
//! You should returns data for sender "sender_name"
virtual PIByteArray senderData(const PIString & sender_name);
private:
static bool filterValidateHeaderS(void * c, uchar * src, uchar * rec, int size);
static bool filterValidateFooterS(void * c, uchar * src, uchar * rec, int size);
static bool filterValidatePayloadS(void * c, uchar * rec, int size);
bool configure(PIConfig & conf, const PIString & name_);
void rawReceived(PIIODevice * dev, const PIString & from, const PIByteArray & data);
void unboundExtractor(PIPacketExtractor * pe);
EVENT_HANDLER2(void, packetExtractorReceived, uchar * , data, int, size);
EVENT_HANDLER2(void, diagQualityChanged, PIDiagnostics::Quality, new_quality, PIDiagnostics::Quality, old_quality);
PIString devPath(const PIIODevice * d) const;
PIString devFPath(const PIIODevice * d) const;
struct Extractor {
Extractor(): extractor(0) {}
~Extractor();
PIPacketExtractor * extractor;
PIVector<PIIODevice * > devices;
};
class Sender: public PITimer {
PIOBJECT_SUBCLASS(Sender, PIObject)
public:
Sender(PIConnection * parent_ = 0): parent(parent_), int_(0.f) {needLockRun(true);}
~Sender() {stop();}
PIConnection * parent;
PIVector<PIIODevice * > devices;
PIByteArray sdata;
float int_;
void tick(void * , int);
};
typedef PIMap<PIString, Extractor * >::value_type PEPair;
typedef PIMap<PIString, Sender * >::value_type SPair;
typedef PIMap<PIString, PIIODevice * >::value_type DNPair;
typedef PIMap<PIIODevice * , PIVector<PIPacketExtractor * > >::value_type BEPair;
typedef PIMap<PIIODevice * , PIVector<PIIODevice * > >::value_type CPair;
typedef PIMap<PIIODevice * , PIDiagnostics * >::value_type DPair;
PIMap<PIString, Extractor * > extractors;
PIMap<PIString, Sender * > senders;
PIMap<PIString, PIIODevice * > device_names;
PIMap<PIIODevice * , PIIODevice::DeviceMode> device_modes;
PIMap<PIIODevice * , PIVector<PIPacketExtractor * > > bounded_extractors;
PIMap<PIIODevice * , PIVector<PIIODevice * > > channels_;
PIMap<PIIODevice * , PIDiagnostics * > diags_;
static PIVector<PIConnection * > _connections;
};
void __DevicePool_threadReadDP(void * ddp);
extern PIConnection::DevicePool * __device_pool__;
class __DevicePoolContainer__ {
public:
__DevicePoolContainer__();
static bool inited_;
};
static __DevicePoolContainer__ __device_pool_container__;
#endif // PICONNECTION_H

View File

@@ -0,0 +1,24 @@
#include "pidatatransfer.h"
PIByteArray PIDataTransfer::buildPacket(Part fi) {
PIByteArray ba;
ba.resize(fi.size);
memcpy(ba.data(), data_.data(fi.start), fi.size);
return ba;
}
void PIDataTransfer::receivePart(Part fi, PIByteArray ba, PIByteArray pheader) {
if (data_.size() < fi.start + fi.size) data_.resize(fi.start + fi.size);
memcpy(data_.data(fi.start), ba.data(), ba.size_s());
}
bool PIDataTransfer::send(const PIByteArray& ba) {
data_ = ba;
buildSession(PIVector<Part>() << Part(0, data_.size()));
return send_process();
}

View File

@@ -0,0 +1,46 @@
/*! \file pidatatransfer.h
* \brief Class for send and receive PIByteArray via \a PIBaseTransfer
*/
/*
PIP - Platform Independent Primitives
Class for send and receive PIByteArray via PIBaseTransfer
Copyright (C) 2016 Andrey Bychkov work.a.b@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIDATATRANSFER_H
#define PIDATATRANSFER_H
#include "pibasetransfer.h"
class PIDataTransfer: public PIBaseTransfer
{
PIOBJECT_SUBCLASS(PIDataTransfer, PIBaseTransfer)
public:
PIDataTransfer() {;}
~PIDataTransfer() {;}
bool send(const PIByteArray &ba);
const PIByteArray & data() {return data_;}
private:
virtual PIByteArray buildPacket(Part p);
virtual void receivePart(PIBaseTransfer::Part fi, PIByteArray ba, PIByteArray pheader);
PIByteArray data_;
};
#endif // PIDATATRANSFER_H

188
src_main/io/pidiagnostics.cpp Executable file
View File

@@ -0,0 +1,188 @@
/*
PIP - Platform Independent Primitives
Speed and quality in/out diagnostics
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 "pidiagnostics.h"
/** \class PIDiagnostics
* \brief Connection quality diagnostics
* \details
* \section PIDiagnostics_sec0 Synopsis
* This class provide abstract connection quality diagnostics and
* counting. You should create instance of %PIDiagnostics and on
* packet receive call function \a received(), on packet send call
* function \a sended(). %PIDiagnostics calculates correct, wrong
* and sended counters, packets per second, bytes per seconds,
* immediate and integral receive frequencies and receive/send speeds
* in human readable representation. There statistics are calculates
* one time per period, by default 1 second. To calculate them you
* should start %PIDiagnostics with function \a start() or pass \b true
* to constructor.
* */
PIDiagnostics::PIDiagnostics(bool start_): PITimer(PITimer::Pool) {
disconn_ = 0.;
setInterval(100);
reset();
setDisconnectTimeout(3.);
changeDisconnectTimeout(3.);
if (start_) start(100);
}
void PIDiagnostics::reset() {
lock();
qual = PIDiagnostics::Unknown;
speedRecv = speedSend = PIString::readableSize(0) + "/s";
immediate_freq = integral_freq = 0.f;
count_wrong = count_recv = count_send = bytes_wrong = bytes_recv = bytes_send = 0;
packets_recv_sec = packets_send_sec = bytes_recv_sec = bytes_send_sec = 0;
if (disconn_ != 0.) {
int hist_size = history_rec.size();
history_rec.clear();
history_send.clear();
history_rec.resize(hist_size);
history_send.resize(hist_size);
}
unlock();
}
void PIDiagnostics::received(int size, bool correct) {
lock();
Entry & e(history_rec.front());
if (correct) {
e.cnt_ok++;
e.bytes_ok += size;
count_recv++;
bytes_recv += size;
} else {
e.cnt_fail++;
e.bytes_fail += size;
count_wrong++;
bytes_wrong += size;
}
e.empty = false;
unlock();
}
void PIDiagnostics::sended(int size) {
lock();
Entry & e(history_send.front());
e.cnt_ok++;
e.bytes_ok += size;
count_send++;
bytes_send += size;
e.empty = false;
unlock();
}
void PIDiagnostics::tick(void * , int ) {
lock();
int tcnt_recv = 0;
int tcnt_send = 0;
Entry send = calcHistory(history_send, tcnt_send);
Entry recv = calcHistory(history_rec, tcnt_recv);
float itr = disconn_ * (float(tcnt_recv) / history_rec.size());
float its = disconn_ * (float(tcnt_send) / history_send.size());
float hz = interval() / 1000.f;
if (tcnt_recv == 0) integral_freq = 0;
// piCoutObj << itr << tcnt_recv << history_rec.size();
else integral_freq = recv.cnt_ok / itr;
packets_recv_sec = ullong(float(recv.cnt_ok) / itr);
packets_send_sec = ullong(float(send.cnt_ok) / its);
bytes_recv_sec = ullong(double(recv.bytes_ok) / itr);
bytes_send_sec = ullong(double(send.bytes_ok) / its);
immediate_freq = double(history_rec.front().cnt_ok) / hz;
// piCoutObj << "tick" << recv.cnt_ok << send.cnt_ok;
// speedRecv = PIString::readableSize(ullong(double(history_rec.front().bytes_ok) / hz)) + "/s";
// speedSend = PIString::readableSize(ullong(double(history_send.front().bytes_ok) / hz)) + "/s";
speedRecv = PIString::readableSize(bytes_recv_sec) + "/s";
speedSend = PIString::readableSize(bytes_send_sec) + "/s";
int arc = recv.cnt_ok + recv.cnt_fail;
float good_percents = 0.f;
if (arc > 0) good_percents = (float)recv.cnt_ok / arc * 100.f;
PIDiagnostics::Quality diag;
if (tcnt_recv == 0) {
diag = PIDiagnostics::Unknown;
} else {
if (good_percents == 0.f) diag = PIDiagnostics::Failure;
else if (good_percents <= 20.f) diag = PIDiagnostics::Bad;
else if (good_percents > 20.f && good_percents <= 80.f) diag = PIDiagnostics::Average;
else diag = PIDiagnostics::Good;
}
if ((tcnt_send + tcnt_recv) != 0) {
// piCoutObj << tcnt_recv << tcnt_send;
history_rec.dequeue();
history_send.dequeue();
Entry e;
e.empty = false;
history_rec.enqueue(e);
history_send.enqueue(e);
}
if (diag != qual) {
qualityChanged(diag, qual);
qual = diag;
}
unlock();
}
PIDiagnostics::Entry PIDiagnostics::calcHistory(PIQueue<Entry> & hist, int & cnt) {
Entry e;
cnt = 0;
for (int i = 0; i < hist.size_s(); ++i) {
e.bytes_ok += hist[i].bytes_ok;
e.bytes_fail += hist[i].bytes_fail;
e.cnt_ok += hist[i].cnt_ok;
e.cnt_fail += hist[i].cnt_fail;
if (!hist[i].empty) cnt++;
}
e.empty = false;
// piCoutObj << hist.size() << cnt;
return e;
}
void PIDiagnostics::propertyChanged(const PIString &) {
float disct = property("disconnectTimeout").toFloat();
changeDisconnectTimeout(disct);
}
void PIDiagnostics::changeDisconnectTimeout(float disct) {
lock();
disconn_ = piMaxf(disct, interval() / 1000.f);
if (interval() > 0) {
int hist_size = piMaxi(int(disconn_ * 1000. / interval()), 1);
//piCoutObj << hist_size << interval();
history_rec.resize(hist_size);
history_send.resize(hist_size);
} else {
history_rec.resize(1);
history_send.resize(1);
}
//piCoutObj << hist_size << disconn_ << interval();
unlock();
}

208
src_main/io/pidiagnostics.h Executable file
View File

@@ -0,0 +1,208 @@
/*! \file pidiagnostics.h
* \brief Connection quality diagnostics
*/
/*
PIP - Platform Independent Primitives
Speed and quality in/out diagnostics
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru, Bychkov Andrey wapmobil@gmail.com
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIDIAGNOSTICS_H
#define PIDIAGNOSTICS_H
#include "pitimer.h"
#include "piqueue.h"
class PIP_EXPORT PIDiagnostics: public PITimer
{
PIOBJECT_SUBCLASS(PIDiagnostics, PITimer)
friend class PIConnection;
public:
//! Constructs an empty diagnostics and if "start_" start it
PIDiagnostics(bool start_ = true);
virtual ~PIDiagnostics() {;}
//! Connection quality
enum Quality {
Unknown /** Unknown, no one packet received yet */ = 1,
Failure /** No connection, no one correct packet received for last period */ = 2,
Bad /** Bad connection, correct packets received <= 20% */ = 3,
Average /** Average connection, correct packets received > 20% and <= 80% */ = 4,
Good /** Good connection, correct packets received > 80% */ = 5
};
//! Returns period of full disconnect in seconds and period of averaging frequency
float disconnectTimeout() const {return disconn_;}
//! Returns period of full disconnect in seconds and period of averaging frequency
void setDisconnectTimeout(float s) {setProperty("disconnectTimeout", s);}
//! Returns immediate receive frequency, packets/s
float immediateFrequency() const {return immediate_freq;}
//! Returns integral receive frequency for \a disconnectTimeout() seconds, packets/s
float integralFrequency() const {return integral_freq;}
//! Returns correct received packets per second
ullong receiveCountPerSec() const {return packets_recv_sec;}
//! Returns sended packets per second
ullong sendCountPerSec() const {return packets_send_sec;}
//! Returns correct received bytes per second
ullong receiveBytesPerSec() const {return bytes_recv_sec;}
//! Returns sended bytes per second
ullong sendBytesPerSec() const {return bytes_send_sec;}
//! Returns overall correct received bytes
ullong receiveBytes() const {return bytes_recv;}
//! Returns overall wrong received bytes
ullong wrongBytes() const {return bytes_wrong;}
//! Returns overall sended bytes
ullong sendBytes() const {return bytes_send;}
//! Returns overall correct received packets count
ullong receiveCount() const {return count_recv;}
//! Returns overall wrong received packets count
ullong wrongCount() const {return count_wrong;}
//! Returns overall sended packets count
ullong sendCount() const {return count_send;}
//! Returns connection quality
PIDiagnostics::Quality quality() const {return qual;}
//! Returns receive speed in format "n {B|kB|MB|GB|TB}/s"
PIString receiveSpeed() const {return speedRecv;}
//! Returns send speed in format "n {B|kB|MB|GB|TB}/s"
PIString sendSpeed() const {return speedSend;}
//! Returns immediate receive frequency pointer, packets/s. Useful for output to PIConsole
const float * immediateFrequency_ptr() const {return &immediate_freq;}
//! Returns integral receive frequency pointer for period, packets/s. Useful for output to PIConsole
const float * integralFrequency_ptr() const {return &integral_freq;}
//! Returns correct received packets per second pointer. Useful for output to PIConsole
const ullong * receiveCountPerSec_ptr() const {return &packets_recv_sec;}
//! Returns sended packets per second pointer. Useful for output to PIConsole
const ullong * sendCountPerSec_ptr() const {return &packets_send_sec;}
//! Returns correct received bytes per second pointer. Useful for output to PIConsole
const ullong * receiveBytesPerSec_ptr() const {return &bytes_recv_sec;}
//! Returns sended bytes per second pointer. Useful for output to PIConsole
const ullong * sendBytesPerSec_ptr() const {return &bytes_send_sec;}
//! Returns overall correct received bytes pointer. Useful for output to PIConsole
const ullong * receiveBytes_ptr() const {return &bytes_recv;}
//! Returns overall wrong received bytes pointer. Useful for output to PIConsole
const ullong * wrongBytes_ptr() const {return &bytes_wrong;}
//! Returns overall sended bytes pointer. Useful for output to PIConsole
const ullong * sendBytes_ptr() const {return &bytes_send;}
//! Returns overall correct received packets count pointer. Useful for output to PIConsole
const ullong * receiveCount_ptr() const {return &count_recv;}
//! Returns overall wrong received packets count pointer. Useful for output to PIConsole
const ullong * wrongCount_ptr() const {return &count_wrong;}
//! Returns overall sended packets count pointer. Useful for output to PIConsole
const ullong * sendCount_ptr() const {return &count_send;}
//! Returns connection quality pointer. Useful for output to PIConsole
const int * quality_ptr() const {return (int * )&qual;}
//! Returns receive speed pointer in format "n {B|kB|MB|GB|TB}/s". Useful for output to PIConsole
const PIString * receiveSpeed_ptr() const {return &speedRecv;}
//! Returns send speed pointer in format "n {B|kB|MB|GB|TB}/s". Useful for output to PIConsole
const PIString * sendSpeed_ptr() const {return &speedSend;}
EVENT_HANDLER0(void, start) {start(100.); changeDisconnectTimeout(disconn_);}
EVENT_HANDLER1(void, start, double, msecs) {if (msecs > 0.) {PITimer::start(msecs); changeDisconnectTimeout(disconn_);}}
EVENT_HANDLER0(void, stop) {PITimer::stop();}
EVENT_HANDLER0(void, reset);
EVENT_HANDLER1(void, received, int, size) {received(size, true);}
EVENT_HANDLER2(void, received, int, size, bool, correct);
EVENT_HANDLER1(void, sended, int, size);
EVENT2(qualityChanged, PIDiagnostics::Quality, new_quality, PIDiagnostics::Quality, old_quality)
//! \handlers
//! \{
//! \fn void start(double msecs = 1000.)
//! \brief Start diagnostics evaluations with period "msecs" milliseconds
//! \fn void reset()
//! \brief Reset diagnostics counters
//! \fn void received(int size, bool correct = true)
//! \brief Notify diagnostics about "correct" corected received packet
//! \fn void sended(int size)
//! \brief Notify diagnostics about sended packet
//! \}
//! \events
//! \{
//! \fn void qualityChanged(PIDiagnostics::Quality new_quality, PIDiagnostics::Quality old_quality)
//! \brief Raise on change receive quality from "old_quality" to "new_quality"
//! \}
private:
struct Entry {
Entry() {bytes_ok = bytes_fail = 0; cnt_ok = cnt_fail = 0; empty = true;}
ullong bytes_ok;
ullong bytes_fail;
uint cnt_ok;
uint cnt_fail;
bool empty;
};
void tick(void *, int);
Entry calcHistory(PIQueue<Entry> & hist, int & cnt);
void propertyChanged(const PIString &);
void changeDisconnectTimeout(float disct);
PIDiagnostics::Quality qual;
PIString speedRecv, speedSend;
float immediate_freq, integral_freq;
PIQueue<Entry> history_rec, history_send;
float disconn_;
ullong count_wrong, count_recv, count_send, bytes_wrong, bytes_recv, bytes_send;
ullong packets_recv_sec, packets_send_sec, bytes_recv_sec, bytes_send_sec;
};
#endif // PIDIAGNOSTICS_H

457
src_main/io/pidir.cpp Executable file
View File

@@ -0,0 +1,457 @@
/*
PIP - Platform Independent Primitives
Directory
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 "piincludes_p.h"
#include "pidir.h"
//#if !defined(ANDROID)
const PIChar PIDir::separator = '/';
#ifdef QNX
# define _stat_struct_ struct stat
# define _stat_call_ stat
# define _stat_link_ lstat
#else
# define _stat_struct_ struct stat64
# define _stat_call_ stat64
# define _stat_link_ lstat64
#endif
#ifndef WINDOWS
# ifdef ANDROID
# include <dirent.h>
# else
# include <sys/dir.h>
# endif
# include <sys/stat.h>
#endif
/*! \class PIDir
* \brief Local directory
*
* \section PIDir_sec0 Synopsis
* This class provide access to local file. You can manipulate
* binary content or use this class as text stream. To binary
* access there are function \a read(), \a write(), and many
* \a writeBinary() functions. For write variables to file in
* their text representation threr are many "<<" operators.
*
*/
PIDir::PIDir(const PIString & dir) {
setDir(dir);
}
PIDir::PIDir(const PIFile & file) {
setDir(file.path());
if (isExists()) return;
int pos = path_.findLast(separator);
path_.cutRight(path_.size_s() - pos);
}
bool PIDir::operator ==(const PIDir & d) const {
return d.absolutePath() == absolutePath();
}
bool PIDir::isAbsolute() const {
if (path_.isEmpty()) return false;
/*#ifdef WINDOWS
if (path_.size_s() < 2) return false;
return (path_.mid(1, 2).contains(":"));
#else
return (path_[0] == separator);
#endif*/
return (path_[0] == separator);
}
PIString PIDir::path() const {
#ifdef WINDOWS
if (path_.startsWith(separator)) {
if (path_.length() == 1)
return separator;
else
return path_.mid(1);
} else
#endif
return path_;
}
PIString PIDir::absolutePath() const {
if (isAbsolute()) {
#ifdef WINDOWS
if (path_.startsWith(separator)) {
if (path_.length() == 1)
return separator;
else
return path_.mid(1);
} else
#endif
return path_;
}
return PIDir(PIDir::current().path() + separator + path_).path();
}
PIDir & PIDir::cleanPath() {
PIString p(path_);
if (p.size() == 0) {
path_ = ".";
return *this;
}
PIString sep = PIString(separator);
path_.replaceAll(sep + sep, sep);
bool is_abs = isAbsolute();
PIStringList l = PIString(p).split(separator);
l.removeAll(".");
l.removeAll("");
for (int i = 0; i < l.size_s() - 1; ++i) {
if (l[i] != ".." && l[i + 1] == "..") {
l.remove(i, 2);
i -= 2;
if (i < -1) i = -1;
}
}
if (is_abs)
while (!l.isEmpty()) {
if (l.front() == "..")
l.pop_front();
else
break;
}
path_ = l.join(separator);
if (is_abs)
path_.prepend(separator);
if (path_.isEmpty()) path_ = ".";
return *this;
}
PIString PIDir::relative(const PIString & path) const {
PIDir td(path);
PIStringList dl(absolutePath().split(separator)), pl(td.absolutePath().split(separator)), rl;
//piCout << pl << "rel to" << dl;
while (!dl.isEmpty() && !pl.isEmpty()) {
if (dl.front() != pl.front()) break;
dl.pop_front();
pl.pop_front();
}
for (int i = 0; i < dl.size_s(); ++i)
rl << "..";
rl << pl;
if (rl.isEmpty()) return ".";
return rl.join(separator);
}
PIDir & PIDir::setDir(const PIString & path) {
path_ = path;
#ifdef WINDOWS
path_.replaceAll("\\", separator);
if (path_.length() > 2)
if (path_.mid(1, 2).contains(":"))
path_.prepend(separator);
#endif
cleanPath();
return *this;
}
PIDir & PIDir::cd(const PIString & path) {
if (path_.isEmpty()) return *this;
if (path_.back() != separator) path_ += separator;
path_ += path;
return cleanPath();
}
bool PIDir::make(bool withParents) {
PIDir d = cleanedPath();
PIString tp;
bool is_abs = isAbsolute();
if (withParents) {
PIStringList l = d.path().split(separator);
for (int i = l.size_s() - 1; i >= 0; --i) {
if (i > 1) tp = PIStringList(l).remove(i, l.size_s() - i).join(separator);
else {
tp = separator;
if (!is_abs) tp.push_front('.');
}
piCout << tp;
if (isExists(tp)) {
for (int j = i + 1; j <= l.size_s(); ++j) {
tp = PIStringList(l).remove(j, l.size_s() - j).join(separator);
piCout << tp;
if (makeDir(tp)) continue;
else return false;
}
break;
};
}
return true;
} else
if (makeDir(d.path())) return true;
return false;
}
#ifdef WINDOWS
int sort_compare(const PIFile::FileInfo * v0, const PIFile::FileInfo * v1) {
return strcoll(v0->path.data(), v1->path.data());
}
#endif
PIVector<PIFile::FileInfo> PIDir::entries() {
PIVector<PIFile::FileInfo> l;
if (!isExists()) return l;
PIString dp = absolutePath();
PIString p(dp);
if (dp == ".") dp.clear();
else if (!dp.endsWith(separator)) dp += separator;
// piCout << "start entries from" << p;
#ifdef WINDOWS
if (dp == separator) {
char letters[1024];
PIFile::FileInfo fi;
DWORD ll = GetLogicalDriveStrings(1023, letters);
PIString clet;
for (DWORD i = 0; i < ll; ++i) {
if (letters[i] == '\0') {
clet.resize(2);
fi.path = clet;
fi.flags = PIFile::FileInfo::Dir;
l << fi;
clet.clear();
} else
clet += PIChar(letters[i]);
}
} else {
WIN32_FIND_DATA fd; memset(&fd, 0, sizeof(fd));
p += "\\*";
void * hf = FindFirstFile((LPCTSTR)(p.data()), &fd);
if (!hf) return l;
bool hdd = false;
do {
PIString fn(fd.cFileName);
if (fn == "..") hdd = true;
l << PIFile::fileInfo(dp + fn);
memset(&fd, 0, sizeof(fd));
} while (FindNextFile(hf, &fd) != 0);
FindClose(hf);
l.sort(sort_compare);
if (!hdd) {
PIFile::FileInfo fi;
fi.path = "..";
fi.flags = PIFile::FileInfo::Dir | PIFile::FileInfo::DotDot;
l.push_front(fi);
}
}
#else
# ifdef QNX
struct dirent * de = 0;
DIR * dir = 0;
dir = opendir(p.data());
if (dir) {
for (;;) {
de = readdir(dir);
if (!de) break;
l << PIFile::fileInfo(dp + PIString(de->d_name));
}
closedir(dir);
}
# else
dirent ** list;
int cnt = scandir(p.data(), &list, 0,
# if defined(MAC_OS) || defined(ANDROID) || defined(BLACKBERRY) || defined(QNX)
alphasort);
# else
versionsort);
# endif
for (int i = 0; i < cnt; ++i) {
l << PIFile::fileInfo(dp + PIString(list[i]->d_name));
free(list[i]);
}
free(list);
# endif
#endif
// piCout << "end entries from" << p;
return l;
}
PIVector<PIFile::FileInfo> PIDir::allEntries() {
PIVector<PIFile::FileInfo> ret;
PIVector<PIFile::FileInfo> dirs;
PIStringList cdirs, ndirs;
cdirs << path();
while (!cdirs.isEmpty()) {
piForeachC (PIString & d, cdirs) {
scan_ = d;
PIVector<PIFile::FileInfo> el = PIDir(d).entries();
piForeachC (PIFile::FileInfo & de, el) {
if (de.name() == "." || de.name() == "..") continue;
if (de.isSymbolicLink()) continue; /// TODO: resolve symlinks
if (de.isDir()) {
dirs << de;
ndirs << de.path;
} else ret << de;
}
}
cdirs = ndirs;
ndirs.clear();
}
ret.insert(0, dirs);
scan_.clear();
return ret;
}
bool PIDir::isExists(const PIString & path) {
#ifdef WINDOWS
DWORD ret = GetFileAttributes((LPCTSTR)(path.data()));
return (ret != 0xFFFFFFFF) && (ret & FILE_ATTRIBUTE_DIRECTORY);
#else
DIR * dir_ = opendir(path.data());
if (dir_ == 0) return false;
closedir(dir_);
#endif
return true;
}
PIDir PIDir::current() {
char rc[1024];
#ifdef WINDOWS
memset(rc, 0, 1024);
if (GetCurrentDirectory(1024, (LPTSTR)rc) == 0) return PIString();
PIString ret(rc);
ret.replaceAll("\\", PIDir::separator);
ret.prepend(separator);
return PIDir(ret);
#else
if (getcwd(rc, 1024) == 0) return PIString();
#endif
return PIDir(rc);
}
PIDir PIDir::home() {
char * rc = 0;
#ifdef WINDOWS
rc = new char[1024];
memset(rc, 0, 1024);
if (ExpandEnvironmentStrings((LPCTSTR)"%HOMEPATH%", (LPTSTR)rc, 1024) == 0) {
delete[] rc;
return PIDir();
}
PIString s(rc);
s.replaceAll("\\", PIDir::separator);
delete[] rc;
s.prepend(separator);
return PIDir(s);
#else
rc = getenv("HOME");
if (rc == 0) return PIDir();
return PIDir(rc);
#endif
}
PIDir PIDir::temporary() {
char * rc = 0;
#ifdef WINDOWS
rc = new char[1024];
memset(rc, 0, 1024);
int ret = GetTempPath(1024, (LPTSTR)rc);
if (ret == 0) {
delete[] rc;
return PIDir();
}
PIString s(rc);
s.replaceAll("\\", PIDir::separator);
delete[] rc;
s.prepend(separator);
return PIDir(s);
#else
rc = tmpnam(0);
if (rc == 0) return PIDir();
PIString s(rc);
return PIDir(s.left(s.findLast(PIDir::separator)));
#endif
}
PIVector<PIFile::FileInfo> PIDir::allEntries(const PIString &path) {
return PIDir(path).allEntries();
}
bool PIDir::make(const PIString & path, bool withParents) {
PIDir d(path);
if (d.isExists()) return true;
return d.make(withParents);
}
bool PIDir::setCurrent(const PIString & path) {
#ifdef WINDOWS
if (SetCurrentDirectory((LPCTSTR)(path.data())) != 0) return true;
#else
if (chdir(path.data()) == 0) return true;
#endif
printf("[PIDir] setCurrent(\"%s\") error: %s\n", path.data(), errorString().data());
return false;
}
bool PIDir::makeDir(const PIString & path) {
#ifdef WINDOWS
if (CreateDirectory((LPCTSTR)(path.data()), NULL) != 0) return true;
#else
if (mkdir(path.data(), 16877) == 0) return true;
#endif
printf("[PIDir] makeDir(\"%s\") error: %s\n", path.data(), errorString().data());
return false;
}
bool PIDir::removeDir(const PIString & path) {
#ifdef WINDOWS
if (RemoveDirectory((LPCTSTR)(path.data())) != 0) return true;
#else
if (rmdir(path.data()) == 0) return true;
#endif
printf("[PIDir] removeDir(\"%s\") error: %s\n", path.data(), errorString().data());
return false;
}
bool PIDir::renameDir(const PIString & path, const PIString & new_name) {
if (::rename(path.data(), new_name.data()) == 0) return true;
printf("[PIDir] renameDir(\"%s\", \"%s\") error: %s\n", path.data(), new_name.data(), errorString().data());
return false;
}
//#endif

136
src_main/io/pidir.h Executable file
View File

@@ -0,0 +1,136 @@
/*! \file pidir.h
* \brief Local directory
*/
/*
PIP - Platform Independent Primitives
Directory
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIDIR_H
#define PIDIR_H
#include "pifile.h"
//#if !defined(ANDROID)
class PIP_EXPORT PIDir
{
public:
//! Constructs directory with path "path"
PIDir(const PIString & dir = PIString());
//! Constructs directory with "file" directory path "path"
PIDir(const PIFile & file);
//! Returns if this directory is exists
bool isExists() const {return PIDir::isExists(path());}
//! Returns if path of this directory is absolute
bool isAbsolute() const;
//! Returns if path of this directory is relative
bool isRelative() const {return !isAbsolute();}
//! Returns path of current reading directory. This path
//! valid only while \a allEntries functions
const PIString & scanDir() const {return scan_;}
//! Returns path of this directory
PIString path() const;
//! Returns absolute path of this directory
PIString absolutePath() const;
/** \brief Simplify path of this directory
* \details This function remove repeatedly separators and
* resolve ".." in path. E.g. "/home/.//peri4/src/../.." will
* become "/home" \n This function returns reference to this %PIDir */
PIDir & cleanPath();
//! Returns %PIDir with simplified path of this directory
PIDir cleanedPath() const {PIDir d(path()); d.cleanPath(); return d;}
//! Returns relative to this directory path "path"
PIString relative(const PIString & path) const;
//! Set this directory path to simplified "path"
PIDir & setDir(const PIString & path);
//! Set this directory path as current for application
bool setCurrent() {return PIDir::setCurrent(path());}
/** \brief Returns this directory content
* \details Scan this directory and returns all directories
* and files in one list, sorted alphabetically. This list
* contains also "." and ".." members. There are absolute
* pathes in returned list.
* \attention This function doesn`t scan content of inner
* directories! */
PIVector<PIFile::FileInfo> entries();
/** \brief Returns all this directory content
* \details Scan this directory recursively and returns all
* directories and files in one list, sorted alphabetically.
* This list doesn`t contains "." and ".." members. There
* are absolute pathes in returned list, and
* files placed after directories in this list */
PIVector<PIFile::FileInfo> allEntries();
bool make(bool withParents = true);
bool remove() {return PIDir::remove(path());}
bool rename(const PIString & new_name) {if (!PIDir::rename(path(), new_name)) return false; setDir(new_name); return true;}
PIDir & cd(const PIString & path);
PIDir & up() {return cd("..");}
bool operator ==(const PIDir & d) const;
bool operator !=(const PIDir & d) const {return !((*this) == d);}
static const PIChar separator;
static PIDir current();
static PIDir home();
static PIDir temporary();
static PIVector<PIFile::FileInfo> allEntries(const PIString & path);
static bool isExists(const PIString & path);
static bool make(const PIString & path, bool withParents = true);
static bool remove(const PIString & path) {return removeDir(path);}
static bool rename(const PIString & path, const PIString & new_name) {return PIDir::renameDir(path, new_name);}
static bool setCurrent(const PIString & path);
static bool setCurrent(const PIDir & dir) {return setCurrent(dir.path());}
private:
static bool makeDir(const PIString & path);
static bool removeDir(const PIString & path);
static bool renameDir(const PIString & path, const PIString & new_name);
PIString path_, scan_;
};
inline bool operator <(const PIFile::FileInfo & v0, const PIFile::FileInfo & v1) {return (v0.path < v1.path);}
inline bool operator >(const PIFile::FileInfo & v0, const PIFile::FileInfo & v1) {return (v0.path > v1.path);}
inline bool operator ==(const PIFile::FileInfo & v0, const PIFile::FileInfo & v1) {return (v0.path == v1.path);}
inline bool operator !=(const PIFile::FileInfo & v0, const PIFile::FileInfo & v1) {return (v0.path != v1.path);}
inline PICout operator <<(PICout s, const PIDir & v) {s.setControl(0, true); s << "PIDir(\"" << v.path() << "\")"; s.restoreControl(); return s;}
#endif // PIDIR_H

1167
src_main/io/piethernet.cpp Executable file
View File

@@ -0,0 +1,1167 @@
/*
PIP - Platform Independent Primitives
Ethernet, UDP/TCP Broadcast/Multicast
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 "piincludes_p.h"
#include "piethernet.h"
#include "piconfig.h"
#include "pisysteminfo.h"
#ifdef QNX
# include <net/if.h>
# include <net/if_dl.h>
# include <hw/nicinfo.h>
# include <netdb.h>
# include <sys/socket.h>
# include <sys/time.h>
# include <sys/types.h>
# include <sys/ioctl.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <ifaddrs.h>
# include <fcntl.h>
# ifdef BLACKBERRY
# include <netinet/in.h>
# else
# include <sys/dcmd_io-net.h>
# endif
# define ip_mreqn ip_mreq
# define imr_address imr_interface
#else
# ifdef WINDOWS
# include <io.h>
# include <winsock2.h>
# include <iphlpapi.h>
# include <psapi.h>
# include <ws2tcpip.h>
# define ip_mreqn ip_mreq
# define imr_address imr_interface
# else
# include <fcntl.h>
# include <sys/ioctl.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <sys/socket.h>
# include <net/if.h>
# include <netdb.h>
# ifndef ANDROID
# include <ifaddrs.h>
# endif
# endif
#endif
#include <errno.h>
/** \class PIEthernet
* \brief Ethernet device
* \details
* \section PIEthernet_sec0 Synopsis
* %PIEthernet designed to work with IPv4 network via two protocols:
* UDP and TCP. This class allow you send and receive packets to/from
* another computer through network. Also it supports broadcast and
* multicast extensions.
*
* \section PIEthernet_sec1 IPv4
*
*
* \section PIEthernet_sec2 UDP
* User Datagram Protocol
*
* \section PIEthernet_sec3 TCP
* Transmission Control Protocol
*
* */
#ifndef WINDOWS
PIString getSockAddr(sockaddr * s) {
return s == 0 ? PIString() : PIStringAscii(inet_ntoa(((sockaddr_in*)s)->sin_addr));
}
#endif
REGISTER_DEVICE(PIEthernet)
PRIVATE_DEFINITION_START(PIEthernet)
sockaddr_in addr_;
sockaddr_in saddr_;
sockaddr_in raddr_;
PRIVATE_DEFINITION_END(PIEthernet)
PIEthernet::PIEthernet(): PIIODevice("", ReadWrite) {
construct();
setType(UDP);
setParameters(PIEthernet::ReuseAddress | PIEthernet::MulticastLoop | PIEthernet::KeepConnection);
//if (type_ != UDP) init();
}
PIEthernet::PIEthernet(PIEthernet::Type type_, const PIString & ip_port, const PIFlags<PIEthernet::Parameters> params_): PIIODevice(ip_port, ReadWrite) {
construct();
parseAddress(ip_port, &ip_, &port_);
setType(type_);
setParameters(params_);
if (type_ != UDP) init();
}
PIEthernet::PIEthernet(int sock_, PIString ip_port): PIIODevice("", ReadWrite) {
construct();
parseAddress(ip_port, &ip_s, &port_s);
sock = sock_;
opened_ = connected_ = true;
init();
setParameters(PIEthernet::ReuseAddress | PIEthernet::MulticastLoop);
setType(TCP_Client, false);
setPath(ip_port);
//piCoutObj << "new tcp client" << sock_;
}
PIEthernet::~PIEthernet() {
//piCout << "~PIEthernet" << uint(this);
stop();
closeDevice();
piMonitor.ethernets--;
//piCoutObj << "~PIEthernet done";
}
void PIEthernet::construct() {
//piCout << " PIEthernet" << uint(this);
piMonitor.ethernets++;
connected_ = connecting_ = listen_threaded = server_bounded = false;
port_ = port_s = port_r = 0;
sock = sock_s = -1;
setReadTimeout(10000.);
setWriteTimeout(10000.);
setTTL(64);
setMulticastTTL(1);
server_thread_.setData(this);
server_thread_.setName("__S__server_thread");
setThreadedReadBufferSize(65536);
setPriority(piHigh);
}
bool PIEthernet::init() {
if (isOpened()) return true;
//cout << "init " << type_ << endl;
if (sock_s == sock)
sock_s = -1;
closeSocket(sock);
closeSocket(sock_s);
int st = 0, pr = 0;
if (type() == UDP) {
st = SOCK_DGRAM;
pr = IPPROTO_UDP;
} else {
st = SOCK_STREAM;
pr = IPPROTO_TCP;
}
PIFlags<Parameters> params = parameters();
sock = socket(AF_INET, st, pr);
if (params[SeparateSockets])
sock_s = socket(AF_INET, st, pr);
else
sock_s = sock;
if (sock == -1 || sock_s == -1) {
piCoutObj << "Can`t create socket, " << ethErrorString();
return false;
}
if (params[PIEthernet::ReuseAddress]) ethSetsockoptBool(sock, SOL_SOCKET, SO_REUSEADDR);
if (params[PIEthernet::Broadcast]) ethSetsockoptBool(sock, SOL_SOCKET, SO_BROADCAST);
//if (type() == PIEthernet::TCP_Client) ethSetsockoptBool(sock, SOL_SOCKET, SO_KEEPALIVE);
applyTimeouts();
applyOptInt(IPPROTO_IP, IP_TTL, TTL());
// piCoutObj << "inited" << path();
//cout << "inited " << sock << ": bc = " << params << endl;
//fcntl(sock, F_SETFL, 0/*O_NONBLOCK*/);
//piCoutObj << "init" << sock;
return true;
}
void PIEthernet::parseAddress(const PIString & ipp, PIString * ip, int * port) {
//piCout << "parse" << ipp;
if (ip != 0) *ip = ipp.left(ipp.find(":"));
if (port != 0) *port = ipp.right(ipp.length() - ipp.find(":") - 1).toInt();
}
PIString PIEthernet::macFromBytes(const PIByteArray & mac) {
PIString r;
for (int i = 0; i < mac.size_s(); ++i) {
r += PIString::fromNumber(mac[i], 16).expandLeftTo(2, '0');
if (i < mac.size_s() - 1) r += ":";
}
return r;
}
PIByteArray PIEthernet::macToBytes(const PIString & mac) {
PIByteArray r;
PIStringList sl = mac.split(PIStringAscii(":"));
piForeachC (PIString & i, sl)
r << uchar(i.toInt(16));
return r;
}
PIString PIEthernet::applyMask(const PIString & ip, const PIString & mask) {
struct in_addr ia;
ia.s_addr = inet_addr(ip.dataAscii()) & inet_addr(mask.dataAscii());
return PIStringAscii(inet_ntoa(ia));
}
PIString PIEthernet::getBroadcast(const PIString & ip, const PIString & mask) {
struct in_addr ia;
ia.s_addr = inet_addr(ip.dataAscii()) | ~inet_addr(mask.dataAscii());
return PIStringAscii(inet_ntoa(ia));
}
bool PIEthernet::openDevice() {
if (connected_) return true;
init();
if (sock == -1 || path().isEmpty()) return false;
parseAddress(path(), &ip_, &port_);
if (type() == TCP_Client)
connecting_ = true;
if (type() != UDP || mode() == PIIODevice::WriteOnly)
return true;
memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
PRIVATE->addr_.sin_family = AF_INET;
PRIVATE->addr_.sin_port = htons(port_);
PIFlags<Parameters> params = parameters();
if (params[PIEthernet::Broadcast]) PRIVATE->addr_.sin_addr.s_addr = INADDR_ANY;
else PRIVATE->addr_.sin_addr.s_addr = inet_addr(ip_.dataAscii());
#ifdef QNX
PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
//piCout << "bind to" << (params[PIEthernet::Broadcast] ? "255.255.255.255" : ip_) << ":" << port_ << " ...";
int tries = 0;
while ((bind(sock, (sockaddr * )&PRIVATE->addr_, sizeof(PRIVATE->addr_)) == -1) && (tries < 2)) {
init();
tries++;
}
if (tries == 2) {
piCoutObj << "Can`t bind to " << ip_ << ":" << port_ << ", " << ethErrorString();
return false;
}
opened_ = true;
while (!mcast_queue.isEmpty())
joinMulticastGroup(mcast_queue.dequeue());
//cout << "!" << endl;
applyTimeouts();
applyOptInt(IPPROTO_IP, IP_TTL, TTL());
port_r = 0;
ip_r.clear();
return true;
}
bool PIEthernet::closeDevice() {
//cout << "close\n";
if (server_thread_.isRunning()) {
server_thread_.stop();
if (!server_thread_.waitForFinish(100))
server_thread_.terminate();
}
if (sock_s == sock)
sock_s = -1;
closeSocket(sock);
closeSocket(sock_s);
while (!clients_.isEmpty())
delete clients_.back();
if (connected_) disconnected(false);
connected_ = connecting_ = false;
return true;
}
void PIEthernet::closeSocket(int & sd) {
if (sd != -1)
ethClosesocket(sd, type() != PIEthernet::UDP);
sd = -1;
}
void PIEthernet::applyTimeouts() {
if (sock < 0) return;
double rtm = readTimeout(), wtm = writeTimeout();
applyTimeout(sock, SO_RCVTIMEO, rtm);
applyTimeout(sock, SO_SNDTIMEO, wtm);
if (sock_s != sock && sock_s != -1) {
applyTimeout(sock_s, SO_RCVTIMEO, rtm);
applyTimeout(sock_s, SO_SNDTIMEO, wtm);
}
}
void PIEthernet::applyTimeout(int fd, int opt, double ms) {
if (fd == 0) return;
//piCoutObj << "setReadIsBlocking" << yes;
#ifdef WINDOWS
DWORD _tm = ms;
#else
double s = ms / 1000.;
timeval _tm;
_tm.tv_sec = piFloord(s); s -= _tm.tv_sec;
_tm.tv_usec = s * 1000000.;
#endif
ethSetsockopt(fd, SOL_SOCKET, opt, &_tm, sizeof(_tm));
}
void PIEthernet::applyOptInt(int level, int opt, int val) {
if (sock < 0) return;
ethSetsockoptInt(sock, level, opt, val);
if (sock_s != sock && sock_s != -1)
ethSetsockoptInt(sock_s, level, opt, val);
}
void PIEthernet::setParameter(PIEthernet::Parameters parameter, bool on) {
PIFlags<Parameters> cp = (PIFlags<Parameters>)(property("parameters").toInt());
cp.setFlag(parameter, on);
setParameters(cp);
}
bool PIEthernet::joinMulticastGroup(const PIString & group) {
if (sock == -1) init();
if (sock == -1) return false;
if (type() != UDP) {
piCoutObj << "Only UDP sockets can join multicast groups";
return false;
}
if (isClosed()) {
if (mcast_queue.contains(group))
return false;
mcast_queue.enqueue(group);
if (!mcast_groups.contains(group)) mcast_groups << group;
return true;
}
PIFlags<Parameters> params = parameters();
//#ifndef QNX
//if (!params[Broadcast])
//;piCoutObj << "Warning: \"Broadcast\" parameter not set, \"joinMulticastGroup(\"" << group << "\")\" may be useless!";
parseAddress(path(), &ip_, &port_);
struct ip_mreqn mreq;
memset(&mreq, 0, sizeof(mreq));
#ifdef LINUX
//mreq.imr_address.s_addr = INADDR_ANY;
/*PIEthernet::InterfaceList il = interfaces();
const PIEthernet::Interface * ci = il.getByAddress(ip_);
if (ci != 0) mreq.imr_ifindex = ci->index;*/
#endif
if (params[PIEthernet::Broadcast]) mreq.imr_address.s_addr = INADDR_ANY;
else mreq.imr_address.s_addr = inet_addr(ip_.dataAscii());
/*#ifndef WINDOWS
PIEthernet::InterfaceList il = interfaces();
const PIEthernet::Interface * ci = il.getByAddress(ip_);
if (ci != 0) mreq.imr_ifindex = ci->index;
#endif*/
//piCout << "join group" << group << "ip" << ip_ << "with index" << mreq.imr_ifindex << "socket" << sock;
mreq.imr_multiaddr.s_addr = inet_addr(group.dataAscii());
if (ethSetsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) != 0) {
piCoutObj << "Can`t join multicast group " << group << ", " << ethErrorString();
return false;
}
if (params[PIEthernet::MulticastLoop]) ethSetsockoptInt(sock, IPPROTO_IP, IP_MULTICAST_LOOP);
applyOptInt(IPPROTO_IP, IP_MULTICAST_TTL, multicastTTL());
if (!mcast_groups.contains(group)) mcast_groups << group;
//#else
// parseAddress(path(), &ip_, &port_);
// struct ip_mreq mreq;
// memset(&mreq, 0, sizeof(mreq));
// mreq.imr_interface.s_addr = inet_addr(ip_.dataAscii());
// mreq.imr_multiaddr.s_addr = inet_addr(group.dataAscii());
// if (ethSetsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) != 0) {
// piCoutObj << "Can`t join multicast group " << group << ", " << ethErrorString();
// return false;
// }
// if (params[PIEthernet::MulticastLoop]) ethSetsockoptInt(sock, IPPROTO_IP, IP_MULTICAST_LOOP);
// applyOptInt(IPPROTO_IP, IP_MULTICAST_TTL, multicastTTL());
// if (!mcast_groups.contains(group)) mcast_groups << group;
//#endif
return true;
}
bool PIEthernet::leaveMulticastGroup(const PIString & group) {
if (sock == -1) init();
if (sock == -1) return false;
if (type() != UDP) {
piCoutObj << "Only UDP sockets can leave multicast groups";
return false;
}
PIFlags<Parameters> params = parameters();
/// TODO windows
parseAddress(path(), &ip_, &port_);
struct ip_mreqn mreq;
memset(&mreq, 0, sizeof(mreq));
if (params[PIEthernet::Broadcast]) mreq.imr_address.s_addr = INADDR_ANY;
else mreq.imr_address.s_addr = inet_addr(ip_.dataAscii());
mreq.imr_multiaddr.s_addr = inet_addr(group.dataAscii());
if (ethSetsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) {
piCoutObj << "Can`t leave multicast group " << group << ", " << ethErrorString();
return false;
}
mcast_groups.removeAll(group);
return true;
}
bool PIEthernet::connect() {
connecting_ = true;
return true;
/*if (sock == -1) return false;
memset(addr_, 0, sizeof(*addr_));
parseAddress(path_, &ip_, &port_);
PRIVATE->addr_.sin_port = htons(port_);
PRIVATE->addr_.sin_addr.s_addr = inet_addr(ip_.data());
PRIVATE->addr_.sin_family = AF_INET;
#ifdef QNX
PRIVATE->addr_.sin_len = sizeof(*addr_);
#endif
//piCoutObj << "connect to " << ip << ":" << port_;
connected_ = (::connect(sock, (sockaddr * )addr_, sizeof(*addr_)) == 0);
if (!connected_)
piCoutObj << "Can`t connect to " << ip_ << ":" << port_ << ", " << ethErrorString();
opened_ = connected_;
if (connected_) connected();
return connected_;*/
}
bool PIEthernet::listen(bool threaded) {
if (sock == -1) init();
if (sock == -1) return false;
if (threaded) {
if (server_thread_.isRunning()) {
if (!server_bounded) return true;
server_thread_.stop();
if (!server_thread_.waitForFinish(100))
server_thread_.terminate();
}
listen_threaded = true;
server_bounded = false;
server_thread_.start(server_func);
return true;
}
listen_threaded = server_bounded = false;
parseAddress(path(), &ip_, &port_);
memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
PRIVATE->addr_.sin_port = htons(port_);
PRIVATE->addr_.sin_addr.s_addr = inet_addr(ip_.dataAscii());
PRIVATE->addr_.sin_family = AF_INET;
#ifdef QNX
PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
opened_ = false;
int tries = 0;
while ((bind(sock, (sockaddr * )&PRIVATE->addr_, sizeof(PRIVATE->addr_)) == -1) && (tries < 2)) {
init();
tries++;
}
if (tries == 2) {
piCoutObj << "Can`t bind to " << ip_ << ":" << port_ << ", " << ethErrorString();
return false;
}
if (::listen(sock, 64) == -1) {
piCoutObj << "Can`t listen on "<< ip_ << ":" << port_ << ", " << ethErrorString();
return false;
}
opened_ = server_bounded = true;
//piCoutObj << "listen on " << ip_ << ":" << port_;
server_thread_.start(server_func);
return true;
}
//#include <QDebug>
int PIEthernet::readDevice(void * read_to, int max_size) {
//piCout << "read" << sock;
if (sock == -1) init();
if (sock == -1 || read_to == 0) return -1;
int rs = 0, s = 0, lerr = 0;
sockaddr_in client_addr;
socklen_t slen = sizeof(client_addr);
// piCoutObj << "read from " << ip_ << ":" << port_;
switch (type()) {
case TCP_SingleTCP:
::listen(sock, 64);
s = accept(sock, (sockaddr * )&client_addr, &slen);
if (s == -1) {
//piCoutObj << "Can`t accept new connection, " << ethErrorString();
msleep(1);
return -1;
}
rs = ethRecv(s, read_to, max_size);
closeSocket(s);
return rs;
case TCP_Client:
if (connecting_) {
#ifdef ANDROID
/*if (sock_s == sock)
sock_s = -1;
closeSocket(sock);
closeSocket(sock_s);
init();
qDebug() << "init() in read thread";*/
#endif
memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
parseAddress(path(), &ip_, &port_);
PRIVATE->addr_.sin_port = htons(port_);
PRIVATE->addr_.sin_addr.s_addr = inet_addr(ip_.dataAscii());
PRIVATE->addr_.sin_family = AF_INET;
#ifdef QNX
PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
//piCoutObj << "connect to " << ip_ << ":" << port_ << "...";
//qDebug() << "connect to " << ip_.data() << ":" << port_ << sock << PRIVATE->addr_.sin_addr.s_addr << "...";
connected_ = (::connect(sock, (sockaddr * )&(PRIVATE->addr_), sizeof(PRIVATE->addr_)) == 0);
//piCoutObj << "connect to " << ip_ << ":" << port_ << connected_;
//qDebug() << "connect to " << ip_.data() << ":" << port_ << connected_;
if (!connected_)
piCoutObj << "Can`t connect to " << ip_ << ":" << port_ << ", " << ethErrorString();
opened_ = connected_;
if (connected_) {
connecting_ = false;
connected();
} else
piMSleep(10);
//piCout << "connected to" << path();
//qDebug() << "connected to" << path().data();
}
if (!connected_) return -1;
errorClear();
rs = ethRecv(sock, read_to, max_size);
//piCoutObj << "readed" << rs;
//qDebug() << "readed" << rs;
if (rs <= 0) {
lerr = ethErrorCore();
//piCoutObj << "readed error" << lerr << errorString().data() << parameters()[DisonnectOnTimeout];
#ifdef WINDOWS
if ((lerr == WSAEWOULDBLOCK || lerr == WSAETIMEDOUT) && !parameters()[DisonnectOnTimeout]) {
#else
if ((lerr == EWOULDBLOCK || lerr == EAGAIN) && !parameters()[DisonnectOnTimeout]) {
#endif
//piCoutObj << errorString();
piMSleep(10);
return -1;
}
if (connected_) {
init();
connected_ = false;
disconnected(rs < 0);
}
if (parameters()[KeepConnection])
connect();
//piCoutObj << "eth" << ip_ << "disconnected";
}
if (rs > 0) received(read_to, rs);
//qDebug() << "return from read" << rs;
return rs;
case UDP:
memset(&PRIVATE->raddr_, 0, sizeof(PRIVATE->raddr_));
rs = ethRecvfrom(sock, read_to, max_size, 0, (sockaddr*)&PRIVATE->raddr_);
if (rs > 0) {
port_r = ntohs(PRIVATE->raddr_.sin_port);
ip_r = PIStringAscii(inet_ntoa(PRIVATE->raddr_.sin_addr));
//piCoutObj << "read from" << ip_r << ":" << port_r << rs << "bytes";
//piCout << "received from" << lastReadAddress();
received(read_to, rs);
}
//else piCoutObj << "read returt" << rs << ", error" << ethErrorString();
return rs;
//return ::read(sock, read_to, max_size);
default: break;
//return ::read(sock, (char * )read_to, max_size);
}
return -1;
}
int PIEthernet::writeDevice(const void * data, int max_size) {
if (sock == -1) init();
if (sock == -1 || !isWriteable()) {
//piCoutObj << "Can`t send to uninitialized socket";
return -1;
}
//piCoutObj << "sending to " << ip_s << ":" << port_s << " " << max_size << " bytes";
int ret = 0;
switch (type()) {
case TCP_SingleTCP:
memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
PRIVATE->addr_.sin_port = htons(port_s);
PRIVATE->addr_.sin_addr.s_addr = inet_addr(ip_s.dataAscii());
PRIVATE->addr_.sin_family = AF_INET;
#ifdef QNX
PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
//piCoutObj << "connect SingleTCP" << ip_s << ":" << port_s << "...";
if (::connect(sock, (sockaddr * )&PRIVATE->addr_, sizeof(PRIVATE->addr_)) != 0) {
//piCoutObj << "Can`t connect to " << ip_s << ":" << port_s << ", " << ethErrorString();
msleep(1);
return -1;
}
//piCoutObj << "ok, write SingleTCP" << int(data) << max_size << "bytes ...";
ret = ::send(sock, (const char *)data, max_size, 0);
//piCoutObj << "ok, ret" << ret;
closeSocket(sock);
init();
return ret;
case UDP:
PRIVATE->saddr_.sin_port = htons(port_s);
/*if (params[PIEthernet::Broadcast]) PRIVATE->saddr_.sin_addr.s_addr = INADDR_BROADCAST;
else*/ PRIVATE->saddr_.sin_addr.s_addr = inet_addr(ip_s.dataAscii());
PRIVATE->saddr_.sin_family = AF_INET;
//piCoutObj << "write to" << ip_s << ":" << port_s << "socket" << sock_s << max_size << "bytes ...";
return ethSendto(sock_s, data, max_size, 0, (sockaddr * )&PRIVATE->saddr_, sizeof(PRIVATE->saddr_));
//piCout << "[PIEth] write to" << ip_s << ":" << port_s << "ok";
case TCP_Client:
if (connecting_) {
memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
parseAddress(path(), &ip_, &port_);
PRIVATE->addr_.sin_port = htons(port_);
PRIVATE->addr_.sin_addr.s_addr = inet_addr(ip_.dataAscii());
PRIVATE->addr_.sin_family = AF_INET;
#ifdef QNX
PRIVATE->addr_.sin_len = sizeof(PRIVATE->addr_);
#endif
//piCoutObj << "connect to " << ip << ":" << port_;
connected_ = (::connect(sock, (sockaddr * )&PRIVATE->addr_, sizeof(PRIVATE->addr_)) == 0);
if (!connected_)
piCoutObj << "Can`t connect to " << ip_ << ":" << port_ << ", " << ethErrorString();
opened_ = connected_;
if (connected_) {
connecting_ = false;
connected();
}
}
if (!connected_) return -1;
ret = ::send(sock, (const char *)data, max_size, 0);
if (ret < 0) {
connected_ = false; {
init();
disconnected(true);
}
}
return ret;
default: break;
//return ::read(sock, read_to, max_size);
}
return -1;
}
void PIEthernet::clientDeleted() {
clients_mutex.lock();
clients_.removeOne((PIEthernet*)emitter());
clients_mutex.unlock();
}
void PIEthernet::server_func(void * eth) {
PIEthernet * ce = (PIEthernet * )eth;
if (ce->listen_threaded) {
if (!ce->server_bounded) {
if (!ce->listen(false)) {
ce->listen_threaded = true;
piMSleep(100);
return;
}
}
}
sockaddr_in client_addr;
socklen_t slen = sizeof(client_addr);
int s = accept(ce->sock, (sockaddr * )&client_addr, &slen);
if (s == -1) {
int lerr = ethErrorCore();
#ifdef WINDOWS
if (lerr == WSAETIMEDOUT) {
#else
if (lerr == EAGAIN) {
#endif
piMSleep(10);
return;
}
if (ce->debug()) piCout << "[PIEthernet] Can`t accept new connection, " << ethErrorString();
piMSleep(10);
return;
}
PIString ip = PIStringAscii(inet_ntoa(client_addr.sin_addr));
ip += ":" + PIString::fromNumber(htons(client_addr.sin_port));
PIEthernet * e = new PIEthernet(s, ip);
ce->clients_mutex.lock();
CONNECTU(e, deleted, ce, clientDeleted)
ce->clients_ << e;
ce->clients_mutex.unlock();
ce->newConnection(e);
//cout << "connected " << ip << endl;
//char d[256];
//cout << " recv " << recv(s, d, 256, 0) << endl;
//cout << recv(ce->clients_.back()->sock, d, 256, 0) << endl;
}
bool PIEthernet::configureDevice(const void * e_main, const void * e_parent) {
PIConfig::Entry * em = (PIConfig::Entry * )e_main;
PIConfig::Entry * ep = (PIConfig::Entry * )e_parent;
setReadIP(readDeviceSetting<PIString>("ip", readIP(), em, ep));
setReadPort(readDeviceSetting<int>("port", readPort(), em, ep));
setParameter(PIEthernet::Broadcast, readDeviceSetting<bool>("broadcast", isParameterSet(PIEthernet::Broadcast), em, ep));
setParameter(PIEthernet::ReuseAddress, readDeviceSetting<bool>("reuseAddress", isParameterSet(PIEthernet::ReuseAddress), em, ep));
return true;
}
void PIEthernet::propertyChanged(const PIString & name) {
if (name.endsWith("Timeout")) applyTimeouts();
if (name == "TTL") applyOptInt(IPPROTO_IP, IP_TTL, TTL());
if (name == "MulticastTTL") applyOptInt(IPPROTO_IP, IP_MULTICAST_TTL, multicastTTL());
}
PIString PIEthernet::constructFullPathDevice() const {
PIString ret;
ret << (type() == PIEthernet::UDP ? "UDP" : "TCP") << ":" << readIP() << ":" << readPort();
if (type() == PIEthernet::UDP) {
ret << ":" << sendIP() << ":" << sendPort();
piForeachC (PIString & m, multicastGroups())
ret << ":mcast:" << m;
}
return ret;
}
void PIEthernet::configureFromFullPathDevice(const PIString & full_path) {
PIStringList pl = full_path.split(":");
bool mcast = false;
for (int i = 0; i < pl.size_s(); ++i) {
PIString p(pl[i]);
switch (i) {
case 0:
p = p.toLowerCase();
if (p == "udp") setType(UDP);
if (p == "tcp") setType(TCP_Client);
break;
case 1: setReadIP(p); setSendIP(p); break;
case 2: setReadPort(p.toInt()); setSendPort(p.toInt()); break;
case 3: setSendIP(p); break;
case 4: setSendPort(p.toInt()); break;
}
if (i <= 4) continue;
if (i % 2 == 1) {if (p.toLowerCase() == "mcast") mcast = true;}
else {if (mcast) {joinMulticastGroup(p); mcast = false;}}
}
}
PIEthernet::InterfaceList PIEthernet::interfaces() {
PIEthernet::InterfaceList il;
Interface ci;
ci.index = -1;
ci.mtu = 1500;
#ifdef WINDOWS
PIP_ADAPTER_INFO pAdapterInfo, pAdapter = 0;
int ret = 0;
ulong ulOutBufLen = sizeof(IP_ADAPTER_INFO);
pAdapterInfo = (IP_ADAPTER_INFO * ) HeapAlloc(GetProcessHeap(), 0, (sizeof (IP_ADAPTER_INFO)));
if (pAdapterInfo == 0) {
piCout << "[PIEthernet] Error allocating memory needed to call GetAdaptersinfo";
return il;
}
if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
HeapFree(GetProcessHeap(), 0, (pAdapterInfo));
pAdapterInfo = (IP_ADAPTER_INFO *) HeapAlloc(GetProcessHeap(), 0, (ulOutBufLen));
if (pAdapterInfo == 0) {
piCout << "[PIEthernet] Error allocating memory needed to call GetAdaptersinfo";
return il;
}
}
if ((ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) {
pAdapter = pAdapterInfo;
while (pAdapter) {
ci.name = PIString(pAdapter->AdapterName);
ci.index = pAdapter->Index;
ci.mac = macFromBytes(PIByteArray(pAdapter->Address, pAdapter->AddressLength));
ci.flags = PIEthernet::ifActive | PIEthernet::ifRunning;
//if (ret->ifa_flags & IFF_BROADCAST) ci.flags |= PIEthernet::ifBroadcast;
//if (ret->ifa_flags & IFF_MULTICAST) ci.flags |= PIEthernet::ifMulticast;
if (pAdapter->Type == MIB_IF_TYPE_PPP) ci.flags |= PIEthernet::ifPTP;
if (pAdapter->Type == MIB_IF_TYPE_LOOPBACK) ci.flags |= PIEthernet::ifLoopback;
ci.broadcast.clear();
ci.ptp.clear();
IP_ADDR_STRING * as = &(pAdapter->IpAddressList);
while (as) {
// piCout << "[pAdapter]" << ci.name << PIString(as->IpAddress.String);
ci.address = PIString(as->IpAddress.String);
ci.netmask = PIString(as->IpMask.String);
if (ci.address == "0.0.0.0") {
as = as->Next;
continue;
}
il << ci;
as = as->Next;
}
/*if (ci.flags[PIEthernet::ifBroadcast])
ci.broadcast = getSockAddr(ret->ifa_broadaddr);
if (ci.flags[PIEthernet::ifPTP])
ci.ptp = getSockAddr(ret->ifa_dstaddr);*/
pAdapter = pAdapter->Next;
}
} else
piCout << "[PIEthernet] GetAdaptersInfo failed with error: " << ret;
if (pAdapterInfo)
HeapFree(GetProcessHeap(), 0, (pAdapterInfo));
#else
/*# ifdef QNX
PIStringList il, sl;
PIProcess proc;
proc.setGrabOutput(true);
proc.exec(ifconfigPath.c_str(), "-l");
if (!proc.waitForFinish(1000)) return sl;
PIString out(proc.readOutput());
il = out.split(" ");
il.removeAll("");
piForeachC (PIString & i, il) {
proc.exec(ifconfigPath.c_str(), i);
if (!proc.waitForFinish(1000)) return il;
sl << i.trimmed();
out = proc.readOutput();
int al = out.length();
al = (al - out.replaceAll("alias", "").length()) / 5;
for (int j = 0; j < al; ++j)
sl << i.trimmed() + ":" + PIString::fromNumber(j);
}
return sl;
# else
PIStringList sl;
PIProcess proc;
proc.setGrabOutput(true);
proc.exec(ifconfigPath.c_str(), "-s");
if (!proc.waitForFinish(1000)) return sl;
PIString out(proc.readOutput());
out.cutLeft(out.find('\n') + 1);
while (!out.isEmpty()) {
sl << out.left(out.find(' '));
out.cutLeft(out.find('\n') + 1);
}
return sl;
# endif*/
# ifdef ANDROID
struct ifconf ifc;
int s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
ifc.ifc_len = 256;
ifc.ifc_buf = new char[ifc.ifc_len];
if (ioctl(s, SIOCGIFCONF, &ifc) < 0) {
piCout << "[PIEthernet] Can`t get interfaces:" << errorString();
delete[] ifc.ifc_buf;
return il;
}
int icnt = ifc.ifc_len / sizeof(ifreq);
PIStringList inl;
struct ifreq ir;
for (int i = 0; i < icnt; ++i) {
PIString in = PIStringAscii(ifc.ifc_req[i].ifr_name);
if (in.isEmpty()) continue;
ci.name = in;
strcpy(ir.ifr_name, in.dataAscii());
if (ioctl(s, SIOCGIFHWADDR, &ir) == 0)
ci.mac = macFromBytes(PIByteArray(ir.ifr_hwaddr.sa_data, 6));
if (ioctl(s, SIOCGIFADDR, &ir) >= 0)
ci.address = getSockAddr(&ir.ifr_addr);
if (ioctl(s, SIOCGIFNETMASK, &ir) >= 0)
ci.netmask = getSockAddr(&ir.ifr_addr);
ioctl(s, SIOCGIFMTU, &ci.mtu);
il << ci;
}
delete ifc.ifc_buf;
# else
struct ifaddrs * ret;
int s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (getifaddrs(&ret) == 0) {
while (ret != 0) {
if (ret->ifa_addr == 0) {
ret = ret->ifa_next;
continue;
}
if (ret->ifa_addr->sa_family != AF_INET) {
ret = ret->ifa_next;
continue;
}
ci.name = PIString(ret->ifa_name);
ci.address = getSockAddr(ret->ifa_addr);
ci.netmask = getSockAddr(ret->ifa_netmask);
ci.mac.clear();
# ifdef QNX
# ifndef BLACKBERRY
int fd = ::open((PIString("/dev/io-net/") + ci.name).dataAscii(), O_RDONLY);
if (fd != 0) {
nic_config_t nic;
devctl(fd, DCMD_IO_NET_GET_CONFIG, &nic, sizeof(nic), 0);
::close(fd);
ci.mac = macFromBytes(PIByteArray(nic.permanent_address, 6));
}
# endif
# else
# ifdef MAC_OS
PIString req = PISystemInfo::instance()->ifconfigPath + " " + ci.name + " | grep ether";
FILE * fp = popen(req.dataAscii(), "r");
if (fp != 0) {
char in[256];
if (fgets(in, 256, fp) != 0) {
req = PIString(in).trim();
ci.mac = req.cutLeft(req.find(" ") + 1).trim().toUpperCase();
}
pclose(fp);
}
# else
if (s != -1) {
struct ifreq ir;
strcpy(ir.ifr_name, ret->ifa_name);
if (ioctl(s, SIOCGIFHWADDR, &ir) == 0) {
ci.mac = macFromBytes(PIByteArray(ir.ifr_hwaddr.sa_data, 6));
ci.mtu = ir.ifr_mtu;
}
}
# endif
# endif
ci.flags = 0;
if (ret->ifa_flags & IFF_UP) ci.flags |= PIEthernet::ifActive;
if (ret->ifa_flags & IFF_RUNNING) ci.flags |= PIEthernet::ifRunning;
if (ret->ifa_flags & IFF_BROADCAST) ci.flags |= PIEthernet::ifBroadcast;
if (ret->ifa_flags & IFF_MULTICAST) ci.flags |= PIEthernet::ifMulticast;
if (ret->ifa_flags & IFF_LOOPBACK) ci.flags |= PIEthernet::ifLoopback;
if (ret->ifa_flags & IFF_POINTOPOINT) ci.flags |= PIEthernet::ifPTP;
ci.broadcast.clear();
ci.ptp.clear();
if (ci.flags[PIEthernet::ifBroadcast])
ci.broadcast = getSockAddr(ret->ifa_broadaddr);
if (ci.flags[PIEthernet::ifPTP])
ci.ptp = getSockAddr(ret->ifa_dstaddr);
ci.index = if_nametoindex(ret->ifa_name);
il << ci;
ret = ret->ifa_next;
}
freeifaddrs(ret);
} else
piCout << "[PIEthernet] Can`t get interfaces:" << errorString();
if (s != -1) ::close(s);
# endif
#endif
return il;
}
PIString PIEthernet::interfaceAddress(const PIString & interface_) {
#ifdef WINDOWS
piCout << "[PIEthernet] Not implemented on Windows, use \"PIEthernet::allAddresses\" or \"PIEthernet::interfaces\" instead";
return PIString();
#else
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, interface_.dataAscii());
int s = socket(AF_INET, SOCK_DGRAM, 0);
ioctl(s, SIOCGIFADDR, &ifr);
::close(s);
struct sockaddr_in * sa = (struct sockaddr_in * )&ifr.ifr_addr;
return PIStringAscii(inet_ntoa(sa->sin_addr));
#endif
}
PIStringList PIEthernet::allAddresses() {
/*#ifdef WINDOWS
PIStringList al;
PIString ca;
PIP_ADAPTER_INFO pAdapterInfo, pAdapter = 0;
int ret = 0;
ulong ulOutBufLen = sizeof(IP_ADAPTER_INFO);
pAdapterInfo = (IP_ADAPTER_INFO * ) HeapAlloc(GetProcessHeap(), 0, (sizeof (IP_ADAPTER_INFO)));
if (pAdapterInfo == 0) {
piCout << "[PIEthernet] Error allocating memory needed to call GetAdaptersinfo";
return PIStringList();
}
if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
HeapFree(GetProcessHeap(), 0, (pAdapterInfo));
pAdapterInfo = (IP_ADAPTER_INFO *) HeapAlloc(GetProcessHeap(), 0, (ulOutBufLen));
if (pAdapterInfo == 0) {
piCout << "[PIEthernet] Error allocating memory needed to call GetAdaptersinfo";
return PIStringList();
}
}
if ((ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) {
pAdapter = pAdapterInfo;
while (pAdapter) {
ca = PIString(pAdapter->IpAddressList.IpAddress.String);
if (ca != "0.0.0.0") al << ca;
pAdapter = pAdapter->Next;
}
} else
piCout << "[PIEthernet] GetAdaptersInfo failed with error: " << ret;
if (pAdapterInfo)
HeapFree(GetProcessHeap(), 0, (pAdapterInfo));
return al;
#else*/
PIEthernet::InterfaceList il = interfaces();
PIStringList al;
piForeachC (PIEthernet::Interface & i, il)
al << i.address;
// piCout << "[PIEthernet::allAddresses]" << al;
if (!al.contains("127.0.0.1")) al << "127.0.0.1";
return al.removeStrings("0.0.0.0");
//#endif
}
PIString PIEthernet::resolve(const PIString & host) {
PIString ip(host), port;
int i = host.find(':');
if (i >= 0) {
ip = host.left(i);
port = host.right(host.length() - i - 1);
}
PIString ret = PIStringAscii("0.0.0.0");
if (!port.isEmpty()) ret += PIStringAscii(":") + port;
//#ifdef WINDOWS
hostent * he = gethostbyname(ip.dataAscii());
if (!he)
return ret;
struct in_addr addr;
if (he->h_addr_list[0]) {
addr.s_addr = *((uint*)(he->h_addr_list[0]));
ret = inet_ntoa(addr);
if (!port.isEmpty()) ret += PIStringAscii(":") + port;
}
//#else
//#endif
return ret;
}
// System wrap
int PIEthernet::ethErrorCore() {
#ifdef WINDOWS
return WSAGetLastError();
#else
return errno;
#endif
}
PIString PIEthernet::ethErrorString() {
#ifdef WINDOWS
char * msg;
int err = WSAGetLastError();
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&msg, 0, NULL);
return "code " + PIString::fromNumber(err) + " - " + PIString(msg);
#else
return errorString();
#endif
}
int PIEthernet::ethRecv(int sock, void * buf, int size, int flags) {
if (sock < 0) return -1;
return recv(sock,
#ifdef WINDOWS
(char*)
#endif
buf, size, flags);
}
int PIEthernet::ethRecvfrom(int sock, void * buf, int size, int flags, sockaddr * addr) {
if (sock < 0) return -1;
#ifdef QNX
return recv(sock, buf, size, flags);
#else
socklen_t len = sizeof(sockaddr);
return recvfrom(sock,
# ifdef WINDOWS
(char*)
# endif
buf, size, flags, addr, &len);
#endif
}
int PIEthernet::ethSendto(int sock, const void * buf, int size, int flags, sockaddr * addr, int addr_len) {
if (sock < 0) return -1;
return sendto(sock,
#ifdef WINDOWS
(const char*)
#endif
buf, size, flags, addr, addr_len);
}
void PIEthernet::ethClosesocket(int sock, bool shutdown) {
//piCout << "close socket" << sock << shutdown;
if (sock < 0) return;
if (shutdown) ::shutdown(sock,
#ifdef WINDOWS
SD_BOTH);
closesocket(sock);
#else
SHUT_RDWR);
::close(sock);
#endif
}
int PIEthernet::ethSetsockopt(int sock, int level, int optname, const void * optval, int optlen) {
if (sock < 0) return -1;
return setsockopt(sock, level, optname,
#ifdef WINDOWS
(char*)
#endif
optval, optlen);
}
int PIEthernet::ethSetsockoptInt(int sock, int level, int optname, int value) {
if (sock < 0) return -1;
#ifdef WINDOWS
DWORD
#else
int
#endif
so = value;
return ethSetsockopt(sock, level, optname, &so, sizeof(so));
}
int PIEthernet::ethSetsockoptBool(int sock, int level, int optname, bool value) {
if (sock < 0) return -1;
#ifdef WINDOWS
BOOL
#else
int
#endif
so = (value ? 1 : 0);
return ethSetsockopt(sock, level, optname, &so, sizeof(so));
}

424
src_main/io/piethernet.h Executable file
View File

@@ -0,0 +1,424 @@
/*! \file piethernet.h
* \brief Ethernet device
*/
/*
PIP - Platform Independent Primitives
Ethernet, UDP/TCP Broadcast/Multicast
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIETHERNET_H
#define PIETHERNET_H
#include "pitimer.h"
#include "piiodevice.h"
class sockaddr;
class PIP_EXPORT PIEthernet: public PIIODevice
{
PIIODEVICE(PIEthernet)
friend class PIPeer;
public:
//! Contructs UDP %PIEthernet with empty read address
explicit PIEthernet();
//! \brief Type of %PIEthernet
enum PIP_EXPORT Type {
UDP /** UDP - User Datagram Protocol */ ,
TCP_Client /** TCP client - allow connection to TCP server */ ,
TCP_Server /** TCP server - receive connections from TCP clients */ ,
TCP_SingleTCP /** TCP client single mode - connect & send & disconnect, on each packet */
};
//! \brief Parameters of %PIEthernet
enum PIP_EXPORT Parameters {
ReuseAddress /** Rebind address if there is already binded. Enabled by default */ = 0x1,
Broadcast /** Broadcast send. Disabled by default */ = 0x2,
SeparateSockets /** If this parameter is set, %PIEthernet will initialize two different sockets,
for receive and send, instead of single one. Disabled by default */ = 0x4,
MulticastLoop /** Enable receiving multicast packets from same host. Enabled by default */ = 0x8,
KeepConnection /** Automatic reconnect TCP connection on disconnect. Enabled by default */ = 0x10,
DisonnectOnTimeout /** Disconnect TCP connection on read timeout expired. Disabled by default */ = 0x20
};
//! Contructs %PIEthernet with type "type", read address "ip_port" and parameters "params"
explicit PIEthernet(Type type, const PIString & ip_port = PIString(), const PIFlags<Parameters> params = PIEthernet::ReuseAddress | PIEthernet::MulticastLoop | PIEthernet::KeepConnection);
virtual ~PIEthernet();
//! Set read address
void setReadAddress(const PIString & ip, int port) {setPath(ip + PIStringAscii(":") + PIString::fromNumber(port));}
//! Set read address in format "i.i.i.i:p"
void setReadAddress(const PIString & ip_port) {setPath(ip_port);}
//! Set read IP
void setReadIP(const PIString & ip) {parseAddress(path(), &ip_, &port_); setPath(ip + PIStringAscii(":") + PIString::fromNumber(port_));}
//! Set read port
void setReadPort(int port) {parseAddress(path(), &ip_, &port_); setPath(ip_ + PIStringAscii(":") + PIString::fromNumber(port));}
//! Set send address
void setSendAddress(const PIString & ip, int port) {ip_s = ip; port_s = port;}
//! Set send address in format "i.i.i.i:p"
void setSendAddress(const PIString & ip_port) {parseAddress(ip_port, &ip_s, &port_s);}
//! Set send IP
void setSendIP(const PIString & ip) {ip_s = ip;}
//! Set send port
void setSendPort(int port) {port_s = port;}
//! Returns read address in format "i.i.i.i:p"
PIString readAddress() const {return path();}
//! Returns read IP
PIString readIP() const {parseAddress(path(), &ip_, &port_); return ip_;}
//! Returns read port
int readPort() const {parseAddress(path(), &ip_, &port_); return port_;}
//! Returns send address in format "i.i.i.i:p"
PIString sendAddress() const {return ip_s + PIStringAscii(":") + PIString::fromNumber(port_s);}
//! Returns send IP
PIString sendIP() const {return ip_s;}
//! Returns send port
int sendPort() const {return port_s;}
//! Returns address of last received UDP packet in format "i.i.i.i:p"
PIString lastReadAddress() const {return ip_r + PIStringAscii(":") + PIString::fromNumber(port_r);}
//! Returns IP of last received UDP packet
PIString lastReadIP() const {return ip_r;}
//! Returns port of last received UDP packet
int lastReadPort() const {return port_r;}
//! Set parameters to "parameters_". You should to reopen %PIEthernet to apply them
void setParameters(PIFlags<PIEthernet::Parameters> parameters_) {setProperty(PIStringAscii("parameters"), (int)parameters_);}
//! Set parameter "parameter" to state "on". You should to reopen %PIEthernet to apply this
void setParameter(PIEthernet::Parameters parameter, bool on = true);
//! Returns if parameter "parameter" is set
bool isParameterSet(PIEthernet::Parameters parameter) const {return ((PIFlags<PIEthernet::Parameters>)(property(PIStringAscii("parameters")).toInt()))[parameter];}
//! Returns parameters
PIFlags<PIEthernet::Parameters> parameters() const {return (PIFlags<PIEthernet::Parameters>)(property(PIStringAscii("parameters")).toInt());}
//PIByteArray macAddress() {if (!init_) init(); struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); memcpy(ifr.ifr_name, "eth0", 5); ioctl(sock, SIOCSIFHWADDR, &ifr); return PIByteArray(&ifr.ifr_hwaddr.sa_data, 6);}
//! Returns %PIEthernet type
Type type() const {return (Type)(property(PIStringAscii("type")).toInt());}
//! Returns read timeout
double readTimeout() const {return property(PIStringAscii("readTimeout")).toDouble();}
//! Returns write timeout
double writeTimeout() const {return property(PIStringAscii("writeTimeout")).toDouble();}
//! Set timeout for read
void setReadTimeout(double ms) {setProperty(PIStringAscii("readTimeout"), ms);}
//! Set timeout for write
void setWriteTimeout(double ms) {setProperty(PIStringAscii("writeTimeout"), ms);}
//! Returns TTL (Time To Live)
int TTL() const {return property(PIStringAscii("TTL")).toInt();}
//! Returns multicast TTL (Time To Live)
int multicastTTL() const {return property(PIStringAscii("MulticastTTL")).toInt();}
//! Set TTL (Time To Live), default is 64
void setTTL(int ttl) {setProperty(PIStringAscii("TTL"), ttl);}
//! Set multicast TTL (Time To Live), default is 1
void setMulticastTTL(int ttl) {setProperty(PIStringAscii("MulticastTTL"), ttl);}
//! Join to multicast group with address "group". Use only for UDP
bool joinMulticastGroup(const PIString & group);
//! Leave multicast group with address "group". Use only for UDP
bool leaveMulticastGroup(const PIString & group);
//! Returns joined multicast groups. Use only for UDP
const PIStringList & multicastGroups() const {return mcast_groups;}
//! Connect to TCP server with address \a readAddress(). Use only for TCP_Client
bool connect();
//! Connect to TCP server with address "ip":"port". Use only for TCP_Client
bool connect(const PIString & ip, int port) {setPath(ip + PIStringAscii(":") + PIString::fromNumber(port)); return connect();}
//! Connect to TCP server with address "ip_port". Use only for TCP_Client
bool connect(const PIString & ip_port) {setPath(ip_port); return connect();}
//! Returns if %PIEthernet connected to TCP server. Use only for TCP_Client
bool isConnected() const {return connected_;}
//! Returns if %PIEthernet is connecting to TCP server. Use only for TCP_Client
bool isConnecting() const {return connecting_;}
//! Start listen for incoming TCP connections on address \a readAddress(). Use only for TCP_Server
bool listen(bool threaded = false);
//! Start listen for incoming TCP connections on address "ip":"port". Use only for TCP_Server
bool listen(const PIString & ip, int port, bool threaded = false) {setReadAddress(ip, port); return listen(threaded);}
//! Start listen for incoming TCP connections on address "ip_port". Use only for TCP_Server
bool listen(const PIString & ip_port, bool threaded = false) {setReadAddress(ip_port); return listen(threaded);}
PIEthernet * client(int index) {return clients_[index];}
int clientsCount() const {return clients_.size_s();}
PIVector<PIEthernet * > clients() const {return clients_;}
//! Send data "data" with size "size" to address \a sendAddress() for UDP or \a readAddress() for TCP_Client
bool send(const void * data, int size, bool threaded = false) {if (threaded) {writeThreaded(data, size); return true;} return (write(data, size) == size);}
//! Send data "data" with size "size" to address "ip":"port"
bool send(const PIString & ip, int port, const void * data, int size, bool threaded = false) {ip_s = ip; port_s = port; if (threaded) {writeThreaded(data, size); return true;} return send(data, size);}
//! Send data "data" with size "size" to address "ip_port"
bool send(const PIString & ip_port, const void * data, int size, bool threaded = false) {parseAddress(ip_port, &ip_s, &port_s); if (threaded) {writeThreaded(data, size); return true;} return send(data, size);}
//! Send data "data" to address \a sendAddress() for UDP or \a readAddress() for TCP_Client
bool send(const PIByteArray & data, bool threaded = false) {if (threaded) {writeThreaded(data); return true;} return (write(data) == data.size_s());}
//! Send data "data" to address "ip":"port" for UDP
bool send(const PIString & ip, int port, const PIByteArray & data, bool threaded = false) {ip_s = ip; port_s = port; if (threaded) {writeThreaded(data); return true;} return send(data);}
//! Send data "data" to address "ip_port" for UDP
bool send(const PIString & ip_port, const PIByteArray & data, bool threaded = false) {parseAddress(ip_port, &ip_s, &port_s); if (threaded) {writeThreaded(data); return true;} return (write(data) == data.size_s());}
virtual bool canWrite() const {return mode() & WriteOnly;}
EVENT1(newConnection, PIEthernet * , client)
EVENT0(connected)
EVENT1(disconnected, bool, withError)
//! Flags of network interface
enum PIP_EXPORT InterfaceFlag {
ifActive /** Is active */ = 0x1,
ifRunning /** Is running */ = 0x2,
ifBroadcast /** Support broadcast */ = 0x4,
ifMulticast /** Support multicast */ = 0x8,
ifLoopback /** Is loopback */ = 0x10,
ifPTP /** Is point-to-point */ = 0x20
};
//! %PIFlags of network interface flags
typedef PIFlags<InterfaceFlag> InterfaceFlags;
//! Network interface descriptor
struct PIP_EXPORT Interface {
//! System index
int index;
//! MTU
int mtu;
//! System name
PIString name;
//! MAC address in format "hh:hh:hh:hh:hh:hh" or empty if there is no MAC address
PIString mac;
//! IP address in format "i.i.i.i" or empty if there is no IP address
PIString address;
//! Netmask of IP address in format "i.i.i.i" or empty if there is no netmask
PIString netmask;
//! Broadcast address in format "i.i.i.i" or empty if there is no broadcast address
PIString broadcast;
//! Point-to-point address or empty if there is no point-to-point address
PIString ptp;
//! Flags of interface
InterfaceFlags flags;
//! Returns if interface is active
bool isActive() const {return flags[PIEthernet::ifActive];}
//! Returns if interface is running
bool isRunning() const {return flags[PIEthernet::ifRunning];}
//! Returns if interface support broadcast
bool isBroadcast() const {return flags[PIEthernet::ifBroadcast];}
//! Returns if interface support multicast
bool isMulticast() const {return flags[PIEthernet::ifMulticast];}
//! Returns if interface is loopback
bool isLoopback() const {return flags[PIEthernet::ifLoopback];}
//! Returns if interface is point-to-point
bool isPTP() const {return flags[PIEthernet::ifPTP];}
};
//! Array of \a Interface with some features
class PIP_EXPORT InterfaceList: public PIVector<PIEthernet::Interface> {
public:
InterfaceList(): PIVector<PIEthernet::Interface>() {}
//! Get interface with system index "index" or 0 if there is no one
const Interface * getByIndex(int index) const {for (int i = 0; i < size_s(); ++i) if ((*this)[i].index == index) return &((*this)[i]); return 0;}
//! Get interface with system name "name" or 0 if there is no one
const Interface * getByName(const PIString & name) const {for (int i = 0; i < size_s(); ++i) if ((*this)[i].name == name) return &((*this)[i]); return 0;}
//! Get interface with IP address "address" or 0 if there is no one
const Interface * getByAddress(const PIString & address) const {for (int i = 0; i < size_s(); ++i) if ((*this)[i].address == address) return &((*this)[i]); return 0;}
//! Get loopback interface or 0 if there is no one
const Interface * getLoopback() const {for (int i = 0; i < size_s(); ++i) if ((*this)[i].isLoopback()) return &((*this)[i]); return 0;}
};
//! Returns all system network interfaces
static InterfaceList interfaces();
static PIString interfaceAddress(const PIString & interface_);
//! Returns all system network IP addresses
static PIStringList allAddresses();
//! Resolve hostname "host" and return it IPv4 address or "0.0.0.0" on error
static PIString resolve(const PIString & host);
static void parseAddress(const PIString & ipp, PIString * ip, int * port);
static PIString macFromBytes(const PIByteArray & mac);
static PIByteArray macToBytes(const PIString & mac);
static PIString applyMask(const PIString & ip, const PIString & mask);
static PIString getBroadcast(const PIString & ip, const PIString & mask);
//! \events
//! \{
//! \fn void newConnection(PIEthernet * client)
//! \brief Raise on new TCP connection received
//! \fn void connected()
//! \brief Raise if succesfull TCP connection
//! \fn void disconnected(bool withError)
//! \brief Raise if TCP connection was closed
//! \}
//! \ioparams
//! \{
#ifdef DOXYGEN
//! \brief read ip, default ""
string ip;
//! \brief read port, default 0
int port;
//! \brief ethernet parameters
int parameters;
//! \brief read timeout, default 1000 ms
double readTimeout;
//! \brief write timeout, default 1000 ms
double writeTimeout;
//! \brief time-to-live, default 64
int TTL;
//! \brief time-to-live for multicast, default 1
int multicastTTL;
#endif
//! \}
protected:
explicit PIEthernet(int sock, PIString ip_port);
void propertyChanged(const PIString & name);
PIString fullPathPrefix() const {return PIStringAscii("eth");}
PIString constructFullPathDevice() const;
void configureFromFullPathDevice(const PIString & full_path);
bool configureDevice(const void * e_main, const void * e_parent = 0);
int readDevice(void * read_to, int max_size);
int writeDevice(const void * data, int max_size);
//! Executes when any read function was successful. Default implementation does nothing
virtual void received(const void * data, int size) {;}
void construct();
bool init();
bool openDevice();
bool closeDevice();
void closeSocket(int & sd);
void applyTimeouts();
void applyTimeout(int fd, int opt, double ms);
void applyOptInt(int level, int opt, int val);
PRIVATE_DECLARATION
int sock, sock_s;
mutable int port_, port_s, port_r;
bool connected_, connecting_, listen_threaded, server_bounded;
mutable PIString ip_, ip_s, ip_r;
PIThread server_thread_;
PIMutex clients_mutex;
PIVector<PIEthernet * > clients_;
PIQueue<PIString> mcast_queue;
PIStringList mcast_groups;
private:
EVENT_HANDLER(void, clientDeleted);
static void server_func(void * eth);
void setType(Type t, bool reopen = true) {setProperty(PIStringAscii("type"), (int)t); if (reopen && isOpened()) {closeDevice(); init(); openDevice();}}
inline static int ethErrorCore();
inline static PIString ethErrorString();
inline static int ethRecv(int sock, void * buf, int size, int flags = 0);
inline static int ethRecvfrom(int sock, void * buf, int size, int flags, sockaddr * addr);
inline static int ethSendto(int sock, const void * buf, int size, int flags, sockaddr * addr, int addr_len);
inline static void ethClosesocket(int sock, bool shutdown);
inline static int ethSetsockopt(int sock, int level, int optname, const void * optval, int optlen);
inline static int ethSetsockoptInt(int sock, int level, int optname, int value = 1);
inline static int ethSetsockoptBool(int sock, int level, int optname, bool value = true);
};
inline bool operator <(const PIEthernet::Interface & v0, const PIEthernet::Interface & v1) {return (v0.name < v1.name);}
inline bool operator ==(const PIEthernet::Interface & v0, const PIEthernet::Interface & v1) {return (v0.name == v1.name && v0.address == v1.address && v0.netmask == v1.netmask);}
inline bool operator !=(const PIEthernet::Interface & v0, const PIEthernet::Interface & v1) {return (v0.name != v1.name || v0.address != v1.address || v0.netmask != v1.netmask);}
#endif // PIETHERNET_H

732
src_main/io/pifile.cpp Executable file
View File

@@ -0,0 +1,732 @@
/*
PIP - Platform Independent Primitives
File
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 "piincludes_p.h"
#include "pifile.h"
#include "pidir.h"
#include "pitime_win.h"
#ifdef WINDOWS
# undef S_IFDIR
# undef S_IFREG
# undef S_IFLNK
# undef S_IFBLK
# undef S_IFCHR
# undef S_IFSOCK
# define S_IFDIR 0x01
# define S_IFREG 0x02
# define S_IFLNK 0x04
# define S_IFBLK 0x08
# define S_IFCHR 0x10
# define S_IFSOCK 0x20
#else
# include <stdio.h>
# include <sys/stat.h>
# include <sys/time.h>
# include <fcntl.h>
# include <utime.h>
#endif
#define S_IFHDN 0x40
#if defined(QNX) || defined(ANDROID)
# define _fopen_call_ fopen
# define _fseek_call_ fseek
# define _ftell_call_ ftell
# define _stat_struct_ struct stat
# define _stat_call_ stat
# define _stat_link_ lstat
#else
# if defined(MAC_OS)
# define _fopen_call_ fopen
# define _fseek_call_ fseek
# define _ftell_call_ ftell
# else
# define _fopen_call_ fopen64
# define _fseek_call_ fseeko64
# define _ftell_call_ ftello64
# endif
# define _stat_struct_ struct stat64
# define _stat_call_ stat64
# define _stat_link_ lstat64
#endif
/*! \class PIFile
* \brief Local file
*
* \section PIFile_sec0 Synopsis
* This class provide access to local file. You can manipulate
* binary content or use this class as text stream. To binary
* access there are function \a read(), \a write(), and many
* \a writeBinary() functions. For write variables to file in
* their text representation threr are many "<<" operators.
*
* \section PIFile_sec1 Position
* Each opened file has a read/write position - logical position
* in the file content you read from or you write to. You can
* find out current position with function \a pos(). Function
* \a seek(llong position) move position to position "position",
* \a seekToBegin() move position to the begin of file,
* \a seekToEnd() move position to the end of file.
*
*/
REGISTER_DEVICE(PIFile)
PIString PIFile::FileInfo::name() const {
if (path.isEmpty()) return PIString();
return path.mid(path.findLast(PIDir::separator) + 1);
}
PIString PIFile::FileInfo::baseName() const {
if (path.isEmpty()) return PIString();
PIString n = name(), e = extension();
if (e.isEmpty()) return n;
return n.cutRight(e.size_s() + 1);
}
PIString PIFile::FileInfo::extension() const {
PIString n = name();
if (n.isEmpty()) return PIString();
while (n.startsWith("."))
n.pop_front();
if (n.isEmpty()) return PIString();
int i = n.find(".");
if (i < 0) return PIString();
return n.mid(i + 1);
}
PIString PIFile::FileInfo::dir() const {
if (path.isEmpty()) return PIString();
PIString ret = path.mid(0, path.findLast(PIDir::separator));
if (ret.isEmpty()) ret = PIDir::separator;
return ret;
}
PIFile::PIFile(): PIIODevice() {
fd = 0;
fdi = -1;
setPrecision(5);
}
PIFile::PIFile(const PIString & path, PIIODevice::DeviceMode mode): PIIODevice(path, mode) {
fd = 0;
fdi = -1;
setPrecision(5);
if (!path.isEmpty())
open();
}
bool PIFile::openTemporary(PIIODevice::DeviceMode mode) {
return open(PIString(tmpnam(0)), mode);
}
//PIFile::PIFile(const PIFile & other) {
// fd = 0;
// fdi = -1;
// setPrecision(other.prec_);
// setPath(other.path());
// mode_ = other.mode_;
//}
bool PIFile::openDevice() {
close();
PIString p = path();
if (p.isEmpty()) return false;
if ((mode_ & PIIODevice::WriteOnly) == PIIODevice::WriteOnly) {
if (!isExists(p)) {
FILE * fd = fopen(p.data(), "w");
if (fd != 0) fclose(fd);
}
}
fd = _fopen_call_(p.data(), strType(mode_).data());
//piCout << "fopen " << path() << ": " << strType(mode_).data() << fd;
bool opened = (fd != 0);
if (opened) {
fdi = fileno(fd);
#ifndef WINDOWS
fcntl(fdi, F_SETFL, O_NONBLOCK);
#endif
_fseek_call_(fd, 0, SEEK_SET);
clearerr(fd);
}
//piCout << "open file" << fd << opened_;
return opened;
}
bool PIFile::closeDevice() {
//piCout << "close file" << fd << opened_;
if (isClosed() || fd == 0) return true;
bool cs = (fclose(fd) == 0);
if (cs) fd = 0;
fdi = -1;
//piCout << "closed file" << fd << opened_;
return cs;
}
PIString PIFile::readLine() {
PIByteArray str;
if (isClosed()) return str;
int cc;
while (!isEnd()) {
cc = fgetc(fd);
if (char(cc) == '\n' || cc == EOF) break;
str.push_back(char(cc));
}
str.push_back('\0');
if (defaultCharset()) {
return PIString::fromCodepage((const char *)str.data(), defaultCharset());
}
//cout << "readline: " << str << endl;
return PIString(str);
}
llong PIFile::readAll(void * data) {
llong cp = pos(), s = size(), i = -1;
seekToBegin();
if (s < 0) {
while (!isEnd())
read(&(((char*)data)[++i]), 1);
} else
read((char * )data, s);
seek(cp);
return s;
}
PIByteArray PIFile::readAll(bool forceRead) {
PIByteArray a;
llong cp = pos();
if (forceRead) {
seekToBegin();
while (!isEnd())
a.push_back(readChar());
seek(cp);
return a;
}
llong s = size();
if (s < 0) return a;
a.resize(s);
s = readAll(a.data());
seek(cp);
if (s >= 0) a.resize(s);
return a;
}
llong PIFile::size() const {
if (isClosed()) return -1;
llong s, cp = pos();
_fseek_call_(fd, 0, SEEK_END); clearerr(fd);
s = pos();
_fseek_call_(fd, cp, SEEK_SET); clearerr(fd);
return s;
}
void PIFile::resize(llong new_size, uchar fill_) {
llong ds = new_size - size();
if (ds == 0) return;
if (ds > 0) {
uchar * buff = new uchar[ds];
memset(buff, fill_, ds);
write(buff, ds);
delete[] buff;
return;
}
if (new_size == 0) {
clear();
return;
}
piCoutObj << "Downsize is not supported yet :-(";
}
bool PIFile::isExists(const PIString & path) {
FILE * f = _fopen_call_(PIString(path).data(), "r");
bool ok = (f != 0);
if (ok) fclose(f);
return ok;
}
bool PIFile::remove(const PIString & path) {
#ifdef WINDOWS
if (PIDir::isExists(path))
return RemoveDirectory(path.data()) > 0;
else
#endif
return ::remove(path.data()) == 0;
}
bool PIFile::rename(const PIString & from, const PIString & to) {
return ::rename(from.data(), to.data()) == 0;
}
PIString PIFile::constructFullPathDevice() const {
return path();
}
void PIFile::configureFromFullPathDevice(const PIString & full_path) {
setPath(full_path);
}
PIString PIFile::strType(const PIIODevice::DeviceMode type) {
switch (type) {
case PIIODevice::ReadOnly: return "rb";
case PIIODevice::ReadWrite:
case PIIODevice::WriteOnly: return "r+b";
}
return "rb";
}
void PIFile::flush() {
if (isOpened()) fflush(fd);
}
void PIFile::seek(llong position) {
if (isClosed()) return;
_fseek_call_(fd, position, SEEK_SET);
clearerr(fd);
}
void PIFile::seekToBegin() {
if (isClosed()) return;
_fseek_call_(fd, 0, SEEK_SET);
clearerr(fd);
}
void PIFile::seekToEnd() {
if (isClosed()) return;
_fseek_call_(fd, 0, SEEK_END);
clearerr(fd);
}
void PIFile::seekToLine(llong line) {
if (isClosed()) return;
seekToBegin();
piForTimes(line) readLine();
clearerr(fd);
}
char PIFile::readChar() {
return (char)fgetc(fd);
}
void PIFile::setPath(const PIString & path) {
PIIODevice::setPath(path);
if (isOpened()) open();
}
llong PIFile::pos() const {
if (isClosed()) return -1;
return _ftell_call_(fd);
}
bool PIFile::isEnd() const {
if (isClosed()) return true;
return (feof(fd) || ferror(fd));
}
void PIFile::setPrecision(int prec) {
prec_ = prec;
if (prec_ >= 0) prec_str = "." + PIString::fromNumber(prec_);
else prec_str = "";
}
PIFile &PIFile::operator <<(double v) {
if (canWrite() && fd != 0) ret = fprintf(fd, ("%" + prec_str + "lf").data(), v);
return *this;
}
PIFile &PIFile::operator >>(double & v) {
if (canRead() && fd != 0) ret = fscanf(fd, "%lf", &v);
return *this;
}
PIFile &PIFile::operator >>(float & v) {
if (canRead() && fd != 0) ret = fscanf(fd, "%f", &v);
return *this;
}
PIFile &PIFile::operator >>(ullong & v) {
if (canRead() && fd != 0) ret = fscanf(fd, "%lln", &v);
return *this;
}
PIFile &PIFile::operator >>(ulong & v) {
if (canRead() && fd != 0) ret = fscanf(fd, "%ln", &v);
return *this;
}
PIFile &PIFile::operator >>(uint & v) {
if (canRead() && fd != 0) ret = fscanf(fd, "%n", &v);
return *this;
}
PIFile &PIFile::operator >>(ushort & v) {
if (canRead() && fd != 0) ret = fscanf(fd, "%hn", &v);
return *this;
}
PIFile &PIFile::operator >>(uchar & v) {
if (canRead() && fd != 0) ret = fscanf(fd, "%hhn", &v);
return *this;
}
PIFile &PIFile::operator >>(llong & v) {
if (canRead() && fd != 0) ret = fscanf(fd, "%lln", &v);
return *this;
}
PIFile &PIFile::operator >>(long & v) {
if (canRead() && fd != 0) ret = fscanf(fd, "%ln", &v);
return *this;
}
PIFile &PIFile::operator >>(int & v) {
if (canRead() && fd != 0) ret = fscanf(fd, "%n", &v);
return *this;
}
PIFile &PIFile::operator >>(short & v) {
if (canRead() && fd != 0) ret = fscanf(fd, "%hn", &v);
return *this;
}
PIFile &PIFile::operator >>(char & v) {
if (canRead() && fd != 0) ret = fscanf(fd, "%hhn", &v);
return *this;
}
PIFile &PIFile::operator <<(float v) {
if (canWrite() && fd != 0) ret = fprintf(fd, ("%" + prec_str + "f").data(), v);
return *this;
}
PIFile &PIFile::operator <<(ullong v) {
if (canWrite() && fd != 0) ret = fprintf(fd, "%llu", v);
return *this;
}
PIFile &PIFile::operator <<(ulong v) {
if (canWrite() && fd != 0) ret = fprintf(fd, "%lu", v);
return *this;
}
PIFile &PIFile::operator <<(uint v) {
if (canWrite() && fd != 0) ret = fprintf(fd, "%u", v);
return *this;
}
PIFile &PIFile::operator <<(ushort v) {
if (canWrite() && fd != 0) ret = fprintf(fd, "%hu", v);
return *this;
}
PIFile &PIFile::operator <<(uchar v) {
if (canWrite() && fd != 0) ret = fprintf(fd, "%u", int(v));
return *this;
}
PIFile &PIFile::operator <<(llong v) {
if (canWrite() && fd != 0) ret = fprintf(fd, "%lld", v);
return *this;
}
PIFile &PIFile::operator <<(long v) {
if (canWrite() && fd != 0) ret = fprintf(fd, "%ld", v);
return *this;
}
PIFile &PIFile::operator <<(int v) {
if (canWrite() && fd != 0) ret = fprintf(fd, "%d", v);
return *this;
}
PIFile &PIFile::operator <<(short v) {
if (canWrite() && fd != 0) ret = fprintf(fd, "%hd", v);
return *this;
}
PIFile &PIFile::operator <<(const PIByteArray & v) {
if (canWrite() && fd != 0) write(v.data(), v.size());
return *this;
}
PIFile &PIFile::operator <<(const char v) {
if (canWrite() && fd != 0) write(&v, 1);
return *this;
}
int PIFile::readDevice(void * read_to, int max_size) {
if (!canRead() || fd == 0) return -1;
return fread(read_to, 1, max_size, fd);
}
int PIFile::writeDevice(const void * data, int max_size) {
if (!canWrite() || fd == 0) return -1;
return fwrite(data, 1, max_size, fd);
}
PIFile &PIFile::operator <<(const PIString & v) {
if (canWrite() && fd != 0)
*this << v.toCharset(defaultCharset());
return *this;
}
void PIFile::clear() {
close();
fd = fopen(path().data(), "w");
if (fd != 0) fclose(fd);
fd = 0;
opened_ = false;
open();
}
void PIFile::remove() {
close();
::remove(path().data());
}
const char * PIFile::defaultCharset() {
return PIInit::instance()->file_charset;
}
void PIFile::setDefaultCharset(const char * c) {
PIInit::instance()->setFileCharset(c);
}
PIFile::FileInfo PIFile::fileInfo(const PIString & path) {
FileInfo ret;
if (path.isEmpty()) return ret;
ret.path = path.replaceAll("\\", PIDir::separator);
PIString n = ret.name();
//piCout << "open" << path;
#ifdef WINDOWS
DWORD attr = GetFileAttributes((LPCTSTR)(path.data()));
if (attr == 0xFFFFFFFF) return ret;
HANDLE hFile = 0;
if ((attr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) {
hFile = CreateFile(path.data(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
} else {
hFile = CreateFile(path.data(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
}
if (!hFile) return ret;
BY_HANDLE_FILE_INFORMATION fi;
memset(&fi, 0, sizeof(fi));
if (GetFileInformationByHandle(hFile, &fi) != 0) {
LARGE_INTEGER filesize;
filesize.LowPart = filesize.HighPart = 0;
if (fi.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ret.flags |= FileInfo::Hidden;
if (fi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ret.flags |= FileInfo::Dir;
else {
ret.flags |= FileInfo::File;
filesize.LowPart = fi.nFileSizeLow;
filesize.HighPart = fi.nFileSizeHigh;
}
PIString ext = ret.extension();
ret.perm_user = FileInfo::Permissions(true, (attr & FILE_ATTRIBUTE_READONLY) != FILE_ATTRIBUTE_READONLY, ext == "bat" || ext == "exe");
ret.perm_group = ret.perm_other = ret.perm_user;
ret.size = filesize.QuadPart;
ret.time_access = FILETIME2PIDateTime(fi.ftLastAccessTime);
ret.time_modification = FILETIME2PIDateTime(fi.ftLastWriteTime);
/*PIByteArray sec;
DWORD sec_n(0);
//SECURITY_DESCRIPTOR sec;
GetFileSecurity(path.data(), DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, (SECURITY_DESCRIPTOR*)sec.data(), 0, &sec_n);
sec.resize(sec_n);
GetFileSecurity(path.data(), DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, (SECURITY_DESCRIPTOR*)sec.data(), sec.size(), &sec_n);
errorClear();
SID sid; BOOL def;
GetSecurityDescriptorGroup((PSECURITY_DESCRIPTOR)sec.data(), &sid, &def);
char * s(0);
ConvertSidToStringSid((PSID)&sid, s);
piCout << s;
LocalFree(s);
//ret.id_user = ;*/
}
CloseHandle(hFile);
#else
_stat_struct_ fs;
memset(&fs, 0, sizeof(fs));
_stat_call_(path.data(), &fs);
int mode = fs.st_mode;
ret.size = fs.st_size;
ret.id_user = fs.st_uid;
ret.id_group = fs.st_gid;
#ifdef ANDROID
ret.time_access = PIDateTime::fromSystemTime(PISystemTime(fs.st_atime, fs.st_atime_nsec));
ret.time_modification = PIDateTime::fromSystemTime(PISystemTime(fs.st_mtime, fs.st_mtime_nsec));
#else
# ifdef QNX
ret.time_access = PIDateTime::fromSecondSinceEpoch(fs.st_atime);
ret.time_modification = PIDateTime::fromSecondSinceEpoch(fs.st_mtime);
# else
# ifdef MAC_OS
# define ATIME st_atimespec
# define MTIME st_ctimespec
# else
# define ATIME st_atim
# define MTIME st_mtim
# endif
ret.time_access = PIDateTime::fromSystemTime(PISystemTime(fs.ATIME.tv_sec, fs.ATIME.tv_nsec));
ret.time_modification = PIDateTime::fromSystemTime(PISystemTime(fs.MTIME.tv_sec, fs.MTIME.tv_nsec));
# endif
#endif
ret.perm_user = FileInfo::Permissions((mode & S_IRUSR) == S_IRUSR, (mode & S_IWUSR) == S_IWUSR, (mode & S_IXUSR) == S_IXUSR);
ret.perm_group = FileInfo::Permissions((mode & S_IRGRP) == S_IRGRP, (mode & S_IWGRP) == S_IWGRP, (mode & S_IXGRP) == S_IXGRP);
ret.perm_other = FileInfo::Permissions((mode & S_IROTH) == S_IROTH, (mode & S_IWOTH) == S_IWOTH, (mode & S_IXOTH) == S_IXOTH);
memset(&fs, 0, sizeof(fs));
_stat_link_(path.data(), &fs);
mode &= ~S_IFLNK;
mode |= S_IFLNK & fs.st_mode;
if (n.startsWith(".")) mode |= S_IFHDN;
if ((mode & S_IFDIR) == S_IFDIR) ret.flags |= FileInfo::Dir;
if ((mode & S_IFREG) == S_IFREG) ret.flags |= FileInfo::File;
if ((mode & S_IFLNK) == S_IFLNK) ret.flags |= FileInfo::SymbolicLink;
if ((mode & S_IFHDN) == S_IFHDN) ret.flags |= FileInfo::Hidden;
#endif
if (n == ".") ret.flags = FileInfo::Dir | FileInfo::Dot;
if (n == "..") ret.flags = FileInfo::Dir | FileInfo::DotDot;
return ret;
}
bool PIFile::applyFileInfo(const PIString & path, const PIFile::FileInfo & info) {
if (path.isEmpty()) return false;
PIString fp(path);
if (fp.endsWith(PIDir::separator)) fp.pop_back();
#ifdef WINDOWS
DWORD attr = GetFileAttributes((LPCTSTR)(path.data()));
if (attr == 0xFFFFFFFF) return false;
attr &= ~(FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY);
if (info.isHidden()) attr |= FILE_ATTRIBUTE_HIDDEN;
if (!info.perm_user.write) attr |= FILE_ATTRIBUTE_READONLY;
if (SetFileAttributes((LPCTSTR)(fp.data()), attr) == 0) {
piCout << "[PIFile] applyFileInfo: \"SetFileAttributes\" error:" << errorString();
//return false;
}
HANDLE hFile = 0;
if ((attr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) {
hFile = CreateFile(path.data(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
} else {
hFile = CreateFile(path.data(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
}
if (!hFile) return false;
FILETIME atime = PIDateTime2FILETIME(info.time_access), mtime = PIDateTime2FILETIME(info.time_modification);
if (SetFileTime(hFile, 0, &atime, &mtime) == 0) {
piCout << "[PIFile] applyFileInfo: \"SetFileTime\" error:" << errorString();
return false;
}
CloseHandle(hFile);
#else
int mode(0);
if (info.perm_user.read) mode |= S_IRUSR;
if (info.perm_user.write) mode |= S_IWUSR;
if (info.perm_user.exec) mode |= S_IXUSR;
if (info.perm_group.read) mode |= S_IRGRP;
if (info.perm_group.write) mode |= S_IWGRP;
if (info.perm_group.exec) mode |= S_IXGRP;
if (info.perm_other.read) mode |= S_IROTH;
if (info.perm_other.write) mode |= S_IWOTH;
if (info.perm_other.exec) mode |= S_IXOTH;
if (chmod(fp.data(), mode) != 0) {
piCout << "[PIFile] applyFileInfo: \"chmod\" error:" << errorString();
//return false;
}
if (chown(fp.data(), info.id_user, info.id_group) != 0) {
piCout << "[PIFile] applyFileInfo: \"chown\" error:" << errorString();
//return false;
}
struct timeval tm[2];
PISystemTime st = info.time_access.toSystemTime();
tm[0].tv_sec = st.seconds;
tm[0].tv_usec = st.nanoseconds / 1000;
st = info.time_modification.toSystemTime();
tm[1].tv_sec = st.seconds;
tm[1].tv_usec = st.nanoseconds / 1000;
if (utimes(fp.data(), tm) != 0) {
piCout << "[PIFile] applyFileInfo: \"utimes\" error:" << errorString();
//return false;
}
#endif
return true;
}

322
src_main/io/pifile.h Executable file
View File

@@ -0,0 +1,322 @@
/*! \file pifile.h
* \brief Local file
*/
/*
PIP - Platform Independent Primitives
File
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIFILE_H
#define PIFILE_H
#include "piiodevice.h"
class PIP_EXPORT PIFile: public PIIODevice
{
PIIODEVICE(PIFile)
public:
//! Constructs an empty file
explicit PIFile();
struct FileInfo {
FileInfo() {size = 0; id_group = id_user = 0;}
enum Flag {
File = 0x01,
Dir = 0x02,
Dot = 0x04,
DotDot = 0x08,
SymbolicLink = 0x10,
Hidden = 0x20
};
typedef PIFlags<FileInfo::Flag> Flags;
struct Permissions {
Permissions(uchar r = 0): raw(r) {}
Permissions(bool r, bool w, bool e): raw(0) {read = r; write = w; exec = e;}
PIString toString() const {return PIString(read ? "r" : "-") + PIString(write ? "w" : "-") + PIString(exec ? "x" : "-");}
operator int() const {return raw;}
Permissions & operator =(int v) {raw = v; return *this;}
union {
uchar raw;
struct {
uchar read : 1;
uchar write: 1;
uchar exec : 1;
};
};
};
PIString path;
llong size;
PIDateTime time_access;
PIDateTime time_modification;
Flags flags;
uint id_user;
uint id_group;
Permissions perm_user;
Permissions perm_group;
Permissions perm_other;
PIString name() const;
PIString baseName() const;
PIString extension() const;
PIString dir() const;
bool isDir() const {return flags[Dir];}
bool isFile() const {return flags[File];}
bool isSymbolicLink() const {return flags[SymbolicLink];}
bool isHidden() const {return flags[Hidden];}
};
//! Constructs a file with path "path" and open mode "mode"
explicit PIFile(const PIString & path, DeviceMode mode = ReadWrite);
//! Open temporary file with open mode "mode"
bool openTemporary(PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);
// PIFile(const PIFile & other);
~PIFile() {closeDevice();}
//PIFile & operator =(const PIFile & f) {path_ = f.path_; type_ = f.type_; return *this;}
//! Immediate write all buffered data to disk
void flush();
//! Move read/write position to "position"
void seek(llong position);
//! Move read/write position to the begin of the file
void seekToBegin();
//! Move read/write position to the end of the file
void seekToEnd();
//! Move read/write position to text line number "line"
void seekToLine(llong line);
//void fill(char c) {stream.fill(c);}
//! Read one char and return it
char readChar();
//! Read one text line and return it
PIString readLine();
//! Read all file content to "data" and return readed bytes count. Position leaved unchanged
llong readAll(void * data);
//! Read all file content to byte array and return it. Position leaved unchanged
PIByteArray readAll(bool forceRead = false);
//! Set file path to "path" and reopen file if need
void setPath(const PIString & path);
//! Returns file size
llong size() const;
//! Returns read/write position
llong pos() const;
//! Returns if position is at the end of file
bool isEnd() const;
//! Returns if file is empty
bool isEmpty() const {return (size() <= 0);}
//! Returns FileInfo of current file
FileInfo fileInfo() const {return fileInfo(path());}
//! Returns float numbers write precision
int precision() const {return prec_;}
//! Set float numbers write precision to "prec_" digits
void setPrecision(int prec);
//! Write to file binary content of "v"
PIFile & writeBinary(const char v) {write(&v, sizeof(v)); return *this;}
//! Write to file binary content of "v"
PIFile & writeBinary(const short v) {write(&v, sizeof(v)); return *this;}
//! Write to file binary content of "v"
PIFile & writeBinary(const int v) {write(&v, sizeof(v)); return *this;}
//! Write to file binary content of "v"
PIFile & writeBinary(const long v) {write(&v, sizeof(v)); return *this;}
//! Write to file binary content of "v"
PIFile & writeBinary(const llong v) {write(&v, sizeof(v)); return *this;}
//! Write to file binary content of "v"
PIFile & writeBinary(const uchar v) {write(&v, sizeof(v)); return *this;}
//! Write to file binary content of "v"
PIFile & writeBinary(const ushort v) {write(&v, sizeof(v)); return *this;}
//! Write to file binary content of "v"
PIFile & writeBinary(const uint v) {write(&v, sizeof(v)); return *this;}
//! Write to file binary content of "v"
PIFile & writeBinary(const ulong v) {write(&v, sizeof(v)); return *this;}
//! Write to file binary content of "v"
PIFile & writeBinary(const ullong v) {write(&v, sizeof(v)); return *this;}
//! Write to file binary content of "v"
PIFile & writeBinary(const float v) {write(&v, sizeof(v)); return *this;}
//! Write to file binary content of "v"
PIFile & writeBinary(const double v) {write(&v, sizeof(v)); return *this;}
//PIFile & operator =(const PIFile & f) {PIIODevice::setPath(f.path()); mode_ = f.mode_; return *this;}
//! Write to file text representation of "v"
PIFile & operator <<(const char v);
//! Write to file string "v"
PIFile & operator <<(const PIString & v);
//! Write to file text representation of "v"
PIFile & operator <<(const PIByteArray & v);
//! Write to file text representation of "v"
PIFile & operator <<(short v);
//! Write to file text representation of "v"
PIFile & operator <<(int v);
//! Write to file text representation of "v"
PIFile & operator <<(long v);
//! Write to file text representation of "v"
PIFile & operator <<(llong v);
//! Write to file text representation of "v"
PIFile & operator <<(uchar v);
//! Write to file text representation of "v"
PIFile & operator <<(ushort v);
//! Write to file text representation of "v"
PIFile & operator <<(uint v);
//! Write to file text representation of "v"
PIFile & operator <<(ulong v);
//! Write to file text representation of "v"
PIFile & operator <<(ullong v);
//! Write to file text representation of "v" with precision \a precision()
PIFile & operator <<(float v);
//! Write to file text representation of "v" with precision \a precision()
PIFile & operator <<(double v);
//! Read from file text representation of "v"
PIFile & operator >>(char & v);
//! Read from file text representation of "v"
PIFile & operator >>(short & v);
//! Read from file text representation of "v"
PIFile & operator >>(int & v);
//! Read from file text representation of "v"
PIFile & operator >>(long & v);
//! Read from file text representation of "v"
PIFile & operator >>(llong & v);
//! Read from file text representation of "v"
PIFile & operator >>(uchar & v);
//! Read from file text representation of "v"
PIFile & operator >>(ushort & v);
//! Read from file text representation of "v"
PIFile & operator >>(uint & v);
//! Read from file text representation of "v"
PIFile & operator >>(ulong & v);
//! Read from file text representation of "v"
PIFile & operator >>(ullong & v);
//! Read from file text representation of "v"
PIFile & operator >>(float & v);
//! Read from file text representation of "v"
PIFile & operator >>(double & v);
EVENT_HANDLER(void, clear);
EVENT_HANDLER(void, remove);
EVENT_HANDLER1(void, resize, llong, new_size) {resize(new_size, 0);}
EVENT_HANDLER2(void, resize, llong, new_size, uchar, fill);
//!
static const char * defaultCharset();
//!
static void setDefaultCharset(const char * c);
//! Returns if file with path "path" does exists
static bool isExists(const PIString & path);
//! Remove file with path "path" and returns if remove was successful
static bool remove(const PIString & path);
//! Rename file with path "from" to path "to" and returns if rename was successful
static bool rename(const PIString & from, const PIString & to);
//! Returns FileInfo of file or dir with path "path"
static FileInfo fileInfo(const PIString & path);
//! Apply "info" parameters to file or dir with path "path"
static bool applyFileInfo(const PIString & path, const FileInfo & info);
//! Apply "info" parameters to file or dir with path "info".path
static bool applyFileInfo(const FileInfo & info) {return applyFileInfo(info.path, info);}
//! \handlers
//! \{
//! \fn void clear()
//! \brief Clear content of file
//! \fn void resize(llong new_size)
//! \brief Resize file to "new_size" with "fill" filling
//! \fn void resize(llong new_size, uchar fill)
//! \brief Resize file to "new_size" with "fill" filling
//! \fn void remove()
//! \brief Remove file
//! \}
//! \ioparams
//! \{
#ifdef DOXYGEN
#endif
//! \}
protected:
PIString fullPathPrefix() const {return "file";}
PIString constructFullPathDevice() const;
void configureFromFullPathDevice(const PIString & full_path);
int readDevice(void * read_to, int max_size);
int writeDevice(const void * data, int max_size);
bool openDevice();
bool closeDevice();
private:
PIString strType(const PIIODevice::DeviceMode type);
FILE * fd;
int ret, prec_, fdi;
PIString prec_str;
};
inline PICout operator <<(PICout s, const PIFile::FileInfo & v) {
s.setControl(0, true);
s << "FileInfo(\"" << v.path << "\", " << PIString::readableSize(v.size) << ", "
<< v.perm_user.toString() << " " << v.perm_group.toString() << " " << v.perm_other.toString() << ", "
<< v.time_access.toString() << ", " << v.time_modification.toString()
<< ", 0x" << PICoutManipulators::Hex << v.flags << ")";
s.restoreControl();
return s;
}
inline PIByteArray & operator <<(PIByteArray & s, const PIFile::FileInfo & v) {s << v.path << v.size << v.time_access << v.time_modification <<
(int)v.flags << v.id_user << v.id_group << v.perm_user.raw << v.perm_group.raw << v.perm_other.raw; return s;}
inline PIByteArray & operator >>(PIByteArray & s, PIFile::FileInfo & v) {s >> v.path >> v.size >> v.time_access >> v.time_modification >>
*(int*)(&(v.flags)) >> v.id_user >> v.id_group >> v.perm_user.raw >> v.perm_group.raw >> v.perm_other.raw; return s;}
#endif // PIFILE_H

View File

@@ -0,0 +1,308 @@
#include "pifiletransfer.h"
const char PIFileTransfer::sign[] = {'P', 'F', 'T'};
PIFileTransfer::PIFileTransfer() {
for (uint i = 0; i < sizeof(sign); i++)
pftheader.sig[i] = sign[i];
pftheader.version = __PIFILETRANSFER_VERSION;
pftheader.session_id = 0;
pftheader.step = pft_None;
dir = PIDir::current();
started_ = scanning = false;
bytes_file_all = bytes_file_cur = 0;
// CONNECT(void, this, sendStarted, this, send_started);
CONNECT(void, this, receiveStarted, this, receive_started);
CONNECT1(void, bool, this, sendFinished, this, send_finished);
CONNECT1(void, bool, this, receiveFinished, this, receive_finished);
}
PIFileTransfer::~PIFileTransfer() {
stop();
work_file.close();
}
bool PIFileTransfer::send(const PIFile& file) {
return send(file.path());
}
bool PIFileTransfer::send(const PIString & file) {
return send(PIVector<PIFile::FileInfo>() << PIFile::fileInfo(file));
}
bool PIFileTransfer::send(const PIStringList& files) {
PIVector<PIFile::FileInfo> fil;
piForeachC(PIString &file, files) fil << PIFile::fileInfo(file);
return send(fil);
}
bool PIFileTransfer::send(PIVector<PIFile::FileInfo> entries) {
started_ = true;
PIVector<PFTFileInfo> allEntries;
for (int i = 0; i < entries.size_s(); i++) {
allEntries << PFTFileInfo(entries[i]);
allEntries.back().dest_path = entries[i].name();
if (entries[i].isDir()) {
cur_file_string = "scanning ... ";
scan_dir.setDir(entries[i].path);
scanning = true;
PIVector<PIFile::FileInfo> fls = scan_dir.allEntries();
scanning = false;
scan_dir.up();
//piCout << "files rel to" << d.absolutePath();
for (uint j = 0; j < fls.size(); j++) {
allEntries << PFTFileInfo(fls[j]);
allEntries.back().dest_path = scan_dir.relative(fls[j].path);
//piCout << " abs path" << fls[j].path;
//piCout << " rel path" << allEntries.back().dest_path;
}
}
}
return sendFiles(allEntries);
}
bool PIFileTransfer::sendFiles(const PIVector<PFTFileInfo> &files) {
files_ = files;
PIStringList names;
// piCoutObj << "prepare to send" << files_.size() << "files";
for(int i=0; i<files_.size_s(); i++) {
// files_[i].path = dir.relative(files_[i].path);
if (names.contains(files_[i].path)) {files_.remove(i); i--;}
else names << files_[i].path;
if (files_[i].isDir()) files_[i].size = 0;
// piCout << "prepare" << i << files_[i].path << files_[i].dest_path << files_[i].name();
}
randomize();
pftheader.session_id = randomi();
sendFilesStarted();
cur_file_string = "build session";
desc.clear();
desc << files_;
pftheader.step = pft_Description;
buildSession(PIVector<Part>() << Part(0, desc.size()));
cur_file_string = "";
if (!send_process()) {
sendFilesFinished(false);
return false;
}
pftheader.step = pft_Data;
PIVector<Part> pts;
for (int i=0; i<files_.size_s(); i++) {
pts << Part(i+1, files_[i].size);
}
buildSession(pts);
bool ok = send_process();
sendFilesFinished(ok);
return ok;
}
void PIFileTransfer::processFile(int id, ullong start, PIByteArray & data) {
//piCout << "processFile" << id << files_.size();
PFTFileInfo fi = files_[id-1];
bytes_file_all = fi.size;
bytes_file_cur = start;
cur_file_string = fi.dest_path;
PIString path = dir.absolutePath() + dir.separator + fi.dest_path;
//piCout << "receive" << path << fi.size << data.size();
if (fi.isDir()) {
//piCoutObj << "make dir" << fi.entry.path;
if (!PIDir::make(path)) {
cur_file_string = "ERROR! while create directory " + path;
piCoutObj << cur_file_string;
stopReceive();
return;
}
}
if (fi.isFile()) {
if (work_file.path() != path || !work_file.isOpened()) {
//piCout << "file close";
work_file.close();
//piCout << "new file" << path << work_file.path() << work_file.isOpened();
if (!work_file.open(path, PIIODevice::ReadWrite)) {
cur_file_string = "ERROR! while open file " + path;
piCoutObj << cur_file_string;
stopReceive();
return;
}
PIFile::applyFileInfo(path, fi);
if (work_file.size() > fi.size) {
//piCoutObj << "error size" << work_file.size() << fi.size;
work_file.clear();
work_file.resize(fi.size);
//piCoutObj << "correct size" << work_file.size() << fi.size;
}
}
//piCoutObj << "write file" << path << work_file.path() << work_file.size() << fi.entry.size << work_file.pos() << fi.fstart << fi.fsize;
if (work_file.size() < (llong)start) {
//piCoutObj << "error pos size" << work_file.pos() << fi.fstart;
work_file.resize(start);
//piCoutObj << "correct pos size" << work_file.pos() << fi.fstart;
}
if (work_file.size() > fi.size) {
piCoutObj << "****** error size" << work_file.size() << fi.size;
work_file.clear();
work_file.resize(fi.size);
piCoutObj << "****** correct size" << work_file.size() << fi.size;
}
//if (fi.fstart != work_file.pos()) piCoutObj << "error pos" << work_file.pos() << fi.fstart;
work_file.seek(start);
int rs = work_file.write(data.data(), data.size());
if (rs != data.size_s()) {
cur_file_string = "ERROR! while write file " + fi.path + " (must " + PIString::fromNumber(data.size()) + ", but write " + PIString::fromNumber(rs) + ")";
piCoutObj << cur_file_string;
stopReceive();
return;
}
}
}
PIByteArray PIFileTransfer::buildPacket(Part p) {
//piCoutObj << "Step" << pftheader.step;
//piCoutObj << "session id" << pftheader.session_id;
PIByteArray ba;
switch(pftheader.step) {
case pft_None:
stopSend();
return PIByteArray();
case pft_Description:
ba.resize(p.size);
memcpy(ba.data(), desc.data(p.start), p.size);
return ba;
case pft_Data:
// piCout << "send data" << p.id << files_.size();
PIFile::FileInfo fi = files_[p.id-1];
if (fi.isFile()) {
// piCout << "send file" << fi.name() << fi.size;
PIString path = fi.path;
if (work_file.path() != path || !work_file.isOpened()) {
if (!work_file.open(path, PIIODevice::ReadOnly)) {
break_ = true;
cur_file_string = "ERROR! while open file " + fi.path;
piCoutObj << cur_file_string;
stopSend();
return PIByteArray();
}
}
work_file.seek(p.start);
ba.resize(p.size);
int rs = work_file.read(ba.data(), ba.size());
if ((llong)rs != (llong)p.size) {
break_ = true;
cur_file_string = "ERROR! while read file " + fi.path + " (must " + PIString::fromNumber(p.size) + ", but read " + PIString::fromNumber(rs) + ")";
piCoutObj << cur_file_string;
stopSend();
return PIByteArray();
}
}
// if (fi.isDir()) {
// piCout << "create dir" << fi.path;
// dir.make(fi.path);
// }
cur_file_string = fi.path;
bytes_file_all = fi.size;
bytes_file_cur = p.start;
return ba;
}
return ba;
}
void PIFileTransfer::receivePart(PIBaseTransfer::Part fi, PIByteArray ba, PIByteArray pheader) {
PFTHeader h;
// piCout << pheader.size() << sizeof(PFTHeader);
pheader >> h;
// piCout << h.session_id;
StepType st = (StepType)h.step;
pftheader.step = st;
if (!h.check_sig()) {
cur_file_string = "ERROR check signature: File Transfer incompatable or invalid version!";
piCoutObj << cur_file_string;
stopReceive();
return;
}
switch(st) {
case pft_None:
break;
case pft_Description:
pftheader.session_id = h.session_id;
if (desc.size() < fi.start + fi.size) desc.resize(fi.start + fi.size);
memcpy(desc.data(fi.start), ba.data(), ba.size_s());
break;
case pft_Data:
if (h.session_id == pftheader.session_id)
processFile(fi.id, fi.start, ba);
else stopReceive();
break;
default:
break;
}
}
PIString PIFileTransfer::curFile() const {
if (scanning)
return cur_file_string + scan_dir.scanDir();
return cur_file_string;
}
PIByteArray PIFileTransfer::customHeader() {
PIByteArray ba;
ba << pftheader;
return ba;
}
void PIFileTransfer::receive_started() {
if (pftheader.step == pft_None) {
files_.clear();
// piCoutObj << "start receive"
started_ = true;
receiveFilesStarted();
}
}
void PIFileTransfer::receive_finished(bool ok) {
if (pftheader.step == pft_Description) {
bool user_ok = true;
if (ok) {
// piCoutObj << desc.size() << PICoutManipulators::Hex << desc;
desc >> files_;
// piCoutObj << files_;
PIStringList files;
piForeachC(PFTFileInfo &fi, files_) files << fi.dest_path;
pause();
receiveFilesRequest(files, bytesAll(), &user_ok);
}
if (!ok || !user_ok) {
pftheader.session_id--;
started_ = false;
piCoutObj << "file cancel";
receiveFilesFinished(false);
} else resume();
}
if (pftheader.step == pft_Data) {
//piCoutObj << "file close";
work_file.close();
started_ = false;
receiveFilesFinished(ok);
}
}
void PIFileTransfer::send_finished(bool ok) {
// piCoutObj << "file close";
if (pftheader.step == pft_Data) {
work_file.close();
started_ = false;
}
}

View File

@@ -0,0 +1,127 @@
/*! \file pifiletransfer.h
* \brief Class for send and receive files and directories via \a PIBaseTransfer
*/
/*
PIP - Platform Independent Primitives
Class for send and receive files and directories via PIBaseTransfer
Copyright (C) 2016 Andrey Bychkov work.a.b@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIFILETRANSFER_H
#define PIFILETRANSFER_H
#include "pidir.h"
#include "pibasetransfer.h"
#define __PIFILETRANSFER_VERSION 2
class PIFileTransfer : public PIBaseTransfer
{
PIOBJECT_SUBCLASS(PIFileTransfer, PIBaseTransfer)
public:
PIFileTransfer();
~PIFileTransfer();
enum StepType {pft_None, pft_Description, pft_Data};
struct PFTFileInfo : public PIFile::FileInfo {
PFTFileInfo(const PIFile::FileInfo &fi = PIFile::FileInfo()) : PIFile::FileInfo(fi) {}
PIString dest_path;
};
#pragma pack(push,1)
struct PFTHeader {
union {
struct {
char sig[3]; // PFT
uchar version;
};
uint raw_sig;
};
int step; // PacketType
int session_id;
bool check_sig() {
if (sig[0] != sign[0] || sig[1] != sign[1] || sig[2] != sign[2] || version != __PIFILETRANSFER_VERSION) return false;
return true;
}
};
#pragma pack(pop)
bool send(const PIFile & file);
bool send(const PIString & file);
bool send(const PIStringList &files);
bool send(PIFile::FileInfo entry) {return send(PIVector<PIFile::FileInfo>() << entry);}
bool send(PIVector<PIFile::FileInfo> entries);
void setDirectory(const PIDir &d) {dir = d;}
void setDirectory(const PIString &path) {dir.setDir(path);}
PIDir directory() const {return dir;}
bool isStarted() const {return started_;}
PIString curFile() const;
llong bytesFileAll() const {return bytes_file_all;}
llong bytesFileCur() const {return bytes_file_cur;}
const PIString * curFile_ptr() const {return &cur_file_string;}
const llong * bytesFileAll_ptr() const {return &bytes_file_all;}
const llong * bytesFileCur_ptr() const {return &bytes_file_cur;}
EVENT(receiveFilesStarted)
EVENT1(receiveFilesFinished, bool, ok)
EVENT(sendFilesStarted)
EVENT1(sendFilesFinished, bool, ok)
EVENT3(receiveFilesRequest, PIStringList, files, llong, total_bytes, bool *, ok)
private:
static const char sign[];
PIVector<PFTFileInfo> files_;
PIString cur_file_string;
llong bytes_file_all, bytes_file_cur;
PFTHeader pftheader;
PIDir dir;
PIFile work_file;
PIByteArray desc;
PIDir scan_dir;
bool started_, scanning;
bool sendFiles(const PIVector<PFTFileInfo> &files);
void processFile(int id, ullong start, PIByteArray &data);
virtual void receivePart(Part fi, PIByteArray ba, PIByteArray pheader);
virtual PIByteArray buildPacket(Part fi);
virtual PIByteArray customHeader();
// EVENT_HANDLER(void, send_started);
EVENT_HANDLER(void, receive_started);
EVENT_HANDLER1(void, send_finished, bool, ok);
EVENT_HANDLER1(void, receive_finished, bool, ok);
};
inline PIByteArray & operator <<(PIByteArray & s, const PIFileTransfer::PFTHeader & v) {s << v.raw_sig << v.step << v.session_id; return s;}
inline PIByteArray & operator >>(PIByteArray & s, PIFileTransfer::PFTHeader & v) {s >> v.raw_sig >> v.step >> v.session_id; return s;}
inline PIByteArray & operator <<(PIByteArray & s, const PIFileTransfer::PFTFileInfo & v) {s << v.dest_path << v.size << v.time_access << v.time_modification <<
(int)v.flags << v.id_user << v.id_group << v.perm_user.raw << v.perm_group.raw << v.perm_other.raw; return s;}
inline PIByteArray & operator >>(PIByteArray & s, PIFileTransfer::PFTFileInfo & v) {s >> v.dest_path >> v.size >> v.time_access >> v.time_modification >>
*(int*)(&(v.flags)) >> v.id_user >> v.id_group >> v.perm_user.raw >> v.perm_group.raw >> v.perm_other.raw; return s;}
inline PICout operator <<(PICout s, const PIFileTransfer::PFTFileInfo & v) {
s.setControl(0, true);
s << "FileInfo(\"" << v.dest_path << "\", " << PIString::readableSize(v.size) << ", "
<< v.perm_user.toString() << " " << v.perm_group.toString() << " " << v.perm_other.toString() << ", "
<< v.time_access.toString() << ", " << v.time_modification.toString()
<< ", 0x" << PICoutManipulators::Hex << v.flags << ")";
s.restoreControl();
return s;
}
#endif // PIFILETRANSFER_H

View File

@@ -0,0 +1,95 @@
/*
PIP - Platform Independent Primitives
PIIODevice wrapper around PIByteArray
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 "piiobytearray.h"
/*! \class PIIOByteArray
* \brief PIIODevice wrapper around PIByteArray
*
* \section PIIOByteArray_sec0 Synopsis
* This class sllow you to use PIByteArray as PIIODevice and pass it to, e.g. PIConfig
*/
//REGISTER_DEVICE(PIIOByteArray);
PIIOByteArray::PIIOByteArray(PIByteArray *buffer, PIIODevice::DeviceMode mode) {
open(buffer, mode);
}
PIIOByteArray::PIIOByteArray(const PIByteArray &buffer) {
open(buffer);
}
bool PIIOByteArray::open(PIByteArray *buffer, PIIODevice::DeviceMode mode) {
data_ = buffer;
mode_ = mode;
return PIIODevice::open(mode);
}
bool PIIOByteArray::open(const PIByteArray &buffer) {
data_ = const_cast<PIByteArray*>(&buffer);
mode_ = PIIODevice::ReadOnly;
return PIIODevice::open(PIIODevice::ReadOnly);
}
int PIIOByteArray::readDevice(void * read_to, int size) {
// piCout << "PIIOByteArray::read" << data_ << size << canRead();
if (!canRead() || !data_) return -1;
int ret = piMini(size, data_->size_s() - pos);
if (ret <= 0) return -1;
memcpy(read_to, data_->data(pos), ret);
// piCout << "readed" << ret;
pos += size;
if (pos > data_->size_s()) pos = data_->size_s();
return ret;
}
int PIIOByteArray::writeDevice(const void * data, int size) {
// piCout << "PIIOByteArray::write" << data << size << canWrite();
if (!canWrite() || !data) return -1;
//piCout << "write" << data;
if (pos > data_->size_s()) pos = data_->size_s();
PIByteArray rs = PIByteArray(data, size);
// piCoutObj << rs;
data_->insert(pos, rs);
pos += rs.size_s();
return rs.size_s();
}
int PIIOByteArray::writeByteArray(const PIByteArray &ba) {
if (!canWrite() || !data_) return -1;
if (pos > data_->size_s()) pos = data_->size_s();
data_->insert(pos, ba);
pos += ba.size_s();
return ba.size_s();
}
bool PIIOByteArray::openDevice() {
pos = 0;
return (data_ != 0);
}

View File

@@ -0,0 +1,81 @@
/*! \file piiobytearray.h
* \brief PIIODevice wrapper around PIByteArray
*/
/*
PIP - Platform Independent Primitives
PIIODevice wrapper around PIByteArray
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIIOBYTEARRAY_H
#define PIIOBYTEARRAY_H
#include "piiodevice.h"
class PIP_EXPORT PIIOByteArray: public PIIODevice
{
PIIODEVICE(PIIOByteArray)
public:
//! Contructs %PIIOByteArray with \"buffer\" content and \"mode\" open mode
explicit PIIOByteArray(PIByteArray * buffer = 0, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);
//! Contructs %PIIOByteArray with \"buffer\" content only for read
explicit PIIOByteArray(const PIByteArray & buffer);
~PIIOByteArray() {closeDevice();}
//! Returns content
PIByteArray * byteArray() const {return data_;}
//! Clear content buffer
void clear() {if (data_) data_->clear(); pos = 0;}
//! Open \"buffer\" content with \"mode\" open mode
bool open(PIByteArray * buffer, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);
//! Open \"buffer\" content only for read
bool open(const PIByteArray & buffer);
//! Returns if position is at the end of content
bool isEnd() const {if (!data_) return true; return pos >= data_->size_s();}
//! Move read/write position to \"position\"
void seek(llong position) {pos = position;}
//! Move read/write position to the begin of the string
void seekToBegin() {if (data_) pos = 0;}
//! Move read/write position to the end of the string
void seekToEnd() {if (data_) pos = data_->size_s();}
//! Insert data \"ba\" into content at current position
int writeByteArray(const PIByteArray & ba);
protected:
bool openDevice();
int readDevice(void * read_to, int size);
int writeDevice(const void * data_, int size);
ssize_t pos;
PIByteArray * data_;
};
#endif // PIIOBYTEARRAY_H

405
src_main/io/piiodevice.cpp Executable file
View File

@@ -0,0 +1,405 @@
/*
PIP - Platform Independent Primitives
Abstract input/output device
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 "piiodevice.h"
#include "piconfig.h"
#include "piconnection.h"
/*! \class PIIODevice
* \brief Base class for input/output classes
*
* \section PIIODevice_sec0 Synopsis
* This class provide open/close logic, threaded read/write and virtual input/output
* functions \a read() and \a write(). You should implement pure virtual
* function \a openDevice() in your subclass.
*
* \section PIIODevice_sec1 Open and close
* PIIODevice have boolean variable indicated open status. Returns of functions
* \a openDevice() and \a closeDevice() change this variable.
*
* \section PIIODevice_sec2 Threaded read
* PIIODevice based on PIThread, so it`s overload \a run() to exec \a read()
* in background thread. If read is successful virtual function \a threadedRead()
* is executed. Default implementation of this function execute external static
* function set by \a setThreadedReadSlot() with data set by \a setThreadedReadData().
* Extrenal static function should have format \n
* bool func_name(void * Threaded_read_data, uchar * readed_data, int readed_size)\n
* Threaded read starts with function \a startThreadedRead().
*
* \section PIIODevice_sec3 Threaded write
* PIIODevice aggregate another PIThread to perform a threaded write by function
* \a writeThreaded(). This function add task to internal queue and return
* queue entry ID. You should start write thread by function \a startThreadedWrite.
* On successful write event \a threadedWriteEvent is raised with two arguments -
* task ID and written bytes count.
*
* \section PIIODevice_sec4 Internal buffer
* PIIODevice have internal buffer for threaded read, and \a threadedRead() function
* receive pointer to this buffer in first argument. You can adjust size of this buffer
* by function \a setThreadedReadBufferSize() \n
* Default size of this buffer is 4096 bytes.
*
* \section PIIODevice_sec5 Reopen
* When threaded read is begin its call \a open() if device is closed. While threaded
* read running PIIODevice check if device opened every read and if not call \a open()
* every reopen timeout if reopen enabled. Reopen timeout is set by \a setReopenTimeout(),
* reopen enable is set by \a setReopenEnabled().
*
* \section PIIODevice_sec6 Configuration
* This is virtual function \a configureDevice() which executes when \a configure()
* executes. This function takes two arguments: "e_main" and "e_parent" as void*. There
* are pointers to PIConfig::Entry entries of section "section" and their parent. If
* there is no parent "e_parent" = 0. Function \a configure() set three parameters of
* device: "reopenEnabled", "reopenTimeout" and "threadedReadBufferSize", then execute
* function \a configureDevice().
* \n Each ancestor of %PIIODevice reimlements \a configureDevice() function to be able
* to be confured from configuration file. This parameters described at section
* "Configurable parameters" in the class reference. \n Usage example:
* \snippet piiodevice.cpp configure
* Implementation example:
* \snippet piiodevice.cpp configureDevice
*
* \section PIIODevice_sec7 Creating devices by unambiguous string
* There are some virtual functions to describe child class without its declaration.
* \n \a fullPathPrefix() should returns unique prefix of device
* \n \a constructFullPath() should returns full unambiguous string, contains prefix and all device parameters
* \n \a configureFromFullPath() provide configuring device from full unambiguous string without prefix and "://"
* \n Macro PIIODEVICE should be used instead of PIOBJECT
* \n Macro REGISTER_DEVICE should be used after definition of class, i.e. at the last line of *.cpp file
* \n \n If custom I/O device corresponds there rules, it can be returned by function \a createFromFullPath().
* \n Each PIP I/O device has custom unambiguous string description:
* * PIFile: "file://<path>"
* * PIBinaryLog: "binlog://<logDir>[:<filePrefix>][:<defaultID>]"
* * PISerial: "ser://<device>:<speed(50|...|115200)>[:<dataBitsCount(6|7|8)>][:<parity(N|E|O)>][:<stopBits(1|2)>]"
* * PIEthernet: UDP "eth://UDP:<readIP>:<readPort>:<sendIP>:<sendPort>[:<multicast(mcast:<ip>)>]"
* * PIEthernet: TCP "eth://TCP:<IP>:<Port>"
* * PIUSB: "usb://<vid>:<pid>[:<deviceNumber>][:<readEndpointNumber>][:<writeEndpointNumber>]"
* \n \n Examples:
* * PIFile: "file://../text.txt"
* * PIBinaryLog: "binlog://../logs/:mylog_:1"
* * PISerial: "ser:///dev/ttyUSB0:9600:8:N:1", equivalent "ser:///dev/ttyUSB0:9600"
* * PIEthernet: "eth://TCP:127.0.0.1:16666", "eth://UDP:192.168.0.5:16666:192.168.0.6:16667:mcast:234.0.2.1:mcast:234.0.2.2"
* * PIUSB: "usb://0bb4:0c86:1:1:2"
* \n \n
* So, custom I/O device can be created with next call:
* \code{cpp}
* // creatring devices
* PISerial * ser = (PISerial * )PIIODevice::createFromFullPath("ser://COM1:115200");
* PIEthernet * eth = (PIEthernet * )PIIODevice::createFromFullPath("eth://UDP:127.0.0.1:4001:127.0.0.1:4002");
* // examine devices
* piCout << ser << ser->properties();
* piCout << eth << eth->properties();
* \endcode
*
* \section PIIODevice_ex0 Example
* \snippet piiodevice.cpp 0
*/
PIMutex PIIODevice::nfp_mutex;
PIMap<PIString, PIString> PIIODevice::nfp_cache;
PIIODevice::PIIODevice(): PIThread() {
mode_ = ReadOnly;
_init();
setPath(PIString());
}
/*! \brief Constructs a PIIODevice with path and mode
* \param path path to device
* \param type mode for open */
PIIODevice::PIIODevice(const PIString & path, PIIODevice::DeviceMode mode): PIThread() {
mode_ = mode;
_init();
setPath(path);
}
PIIODevice::~PIIODevice() {
stop();
if (opened_) {
closeDevice();
if (!opened_)
closed();
}
}
void PIIODevice::setOptions(PIIODevice::DeviceOptions o) {
options_ = o;
optionsChanged();
}
bool PIIODevice::setOption(PIIODevice::DeviceOption o, bool yes) {
bool ret = isOptionSet(o);
options_.setFlag(o, yes);
optionsChanged();
return ret;
}
void PIIODevice::_init() {
opened_ = init_ = thread_started_ = false;
raise_threaded_read_ = true;
ret_func_ = 0;
ret_data_ = 0;
tri = 0;
setOptions(0);
setReopenEnabled(true);
setReopenTimeout(1000);
setThreadedReadBufferSize(4096);
timer.setName("__S__reopen_timer");
write_thread.setName("__S__write_thread");
CONNECT2(void, void * , int, &timer, tickEvent, this, check_start);
CONNECT(void, &write_thread, started, this, write_func);
}
void PIIODevice::check_start(void * data, int delim) {
//cout << "check " << tread_started_ << endl;
if (open()) {
thread_started_ = true;
timer.stop();
}
}
void PIIODevice::write_func() {
while (!write_thread.isStopping()) {
while (!write_queue.isEmpty()) {
if (write_thread.isStopping()) return;
write_thread.lock();
PIPair<PIByteArray, ullong> item(write_queue.dequeue());
write_thread.unlock();
int ret = write(item.first);
threadedWriteEvent(item.second, ret);
}
msleep(1);
}
}
void PIIODevice::terminate() {
timer.stop();
thread_started_ = false;
if (!init_) return;
if (isRunning()) {
stop();
PIThread::terminate();
}
}
void PIIODevice::begin() {
//cout << " begin\n";
thread_started_ = false;
if (!opened_) {
if (open()) {
thread_started_ = true;
//cout << " open && ok\n";
return;
}
} else {
thread_started_ = true;
//cout << " ok\n";
return;
}
//init();
if (!timer.isRunning() && isReopenEnabled()) timer.start(reopenTimeout());
}
void PIIODevice::run() {
if (!isReadable()) {
//cout << "not readable\n";
PIThread::stop();
return;
}
if (!thread_started_) {
piMSleep(5);
//cout << "not started\n";
return;
}
readed_ = read(buffer_tr.data(), buffer_tr.size_s());
if (readed_ <= 0) {
piMSleep(10);
//cout << readed_ << ", " << errno << ", " << errorString() << endl;
return;
}
//piCoutObj << "readed" << readed_;// << ", " << errno << ", " << errorString();
threadedRead(buffer_tr.data(), readed_);
if (raise_threaded_read_) threadedReadEvent(buffer_tr.data(), readed_);
}
PIByteArray PIIODevice::readForTime(double timeout_ms) {
PIByteArray str;
if (timeout_ms <= 0.) return str;
int ret;
uchar * td = new uchar[threadedReadBufferSize()];
tm.reset();
while (tm.elapsed_m() < timeout_ms) {
ret = read(td, threadedReadBufferSize());
if (ret <= 0) msleep(1);
else str.append(td, ret);
}
delete[] td;
return str;
}
ullong PIIODevice::writeThreaded(const PIByteArray & data) {
write_thread.lock();
write_queue.enqueue(PIPair<PIByteArray, ullong>(data, tri));
++tri;
write_thread.unlock();
return tri - 1;
}
bool PIIODevice::configure(const PIString & config_file, const PIString & section, bool parent_section) {
PIConfig conf(config_file, PIIODevice::ReadOnly);
if (!conf.isOpened()) return false;
bool ex = true;
PIConfig::Entry em;
if (section.isEmpty()) em = conf.rootEntry();
else em = conf.getValue(section, PIString(), &ex);
if (!ex) return false;
PIConfig::Entry * ep = 0;
if (parent_section) ep = em.parent();
if (ep != 0) {
setReopenEnabled(ep->getValue("reopenEnabled", isReopenEnabled(), &ex));
if (!ex) setReopenEnabled(em.getValue("reopenEnabled", isReopenEnabled()));
setReopenTimeout(ep->getValue("reopenTimeout", reopenTimeout(), &ex));
if (!ex) setReopenTimeout(em.getValue("reopenTimeout", reopenTimeout()));
setThreadedReadBufferSize(ep->getValue("threadedReadBufferSize", int(buffer_tr.size_s()), &ex));
if (!ex) setThreadedReadBufferSize(em.getValue("threadedReadBufferSize", int(buffer_tr.size_s())));
} else {
setReopenEnabled(em.getValue("reopenEnabled", isReopenEnabled()));
setReopenTimeout(em.getValue("reopenTimeout", reopenTimeout()));
setThreadedReadBufferSize(em.getValue("threadedReadBufferSize", int(buffer_tr.size_s())));
}
return configureDevice(&em, ep);
}
PIString PIIODevice::constructFullPath() const {
return fullPathPrefix() + "://" + constructFullPathDevice() + fullPathOptions();
}
void PIIODevice::configureFromFullPath(const PIString & full_path) {
PIString fp;
DeviceMode dm = ReadWrite;
DeviceOptions op = 0;
splitFullPath(full_path, &fp, &dm, &op);
setMode(dm);
setOptions(op);
configureFromFullPathDevice(fp);
}
void PIIODevice::splitFullPath(PIString fpwm, PIString * full_path, DeviceMode * mode, DeviceOptions * opts) {
int dm = 0;
DeviceOptions op = 0;
if (fpwm.find("(") > 0 && fpwm.find(")") > 0) {
PIString dms(fpwm.right(fpwm.length() - fpwm.findLast("(")).takeRange("(", ")").trim().toLowerCase().removeAll(" "));
PIStringList opts(dms.split(","));
piForeachC (PIString & o, opts) {
//piCout << dms;
if (o == "r" || o == "ro" || o == "read" || o == "readonly")
dm |= ReadOnly;
if (o == "w" || o == "wo" || o == "write" || o == "writeonly")
dm |= WriteOnly;
if (o == "br" || o == "blockr" || o == "blockread" || o == "blockingread")
op |= BlockingRead;
if (o == "bw" || o == "blockw" || o == "blockwrite" || o == "blockingwrite")
op |= BlockingWrite;
if (o == "brw" || o == "bwr" || o == "blockrw" || o == "blockwr" || o == "blockreadrite" || o == "blockingreadwrite")
op |= BlockingRead | BlockingWrite;
}
fpwm.cutRight(fpwm.length() - fpwm.findLast("(")).trim();
}
if (dm == 0) dm = ReadWrite;
if (full_path) *full_path = fpwm;
if (mode) *mode = (DeviceMode)dm;
if (opts) *opts = op;
}
PIString PIIODevice::fullPathOptions() const {
if (mode_ == ReadWrite && options_ == 0) return PIString();
PIString ret(" (");
bool f = true;
if (mode_ == ReadOnly) {if (!f) ret += ","; f = false; ret += "ro";}
if (mode_ == WriteOnly) {if (!f) ret += ","; f = false; ret += "wo";}
if (options_[BlockingRead]) {if (!f) ret += ","; f = false; ret += "br";}
if (options_[BlockingWrite]) {if (!f) ret += ","; f = false; ret += "bw";}
return ret + ")";
}
PIIODevice * PIIODevice::createFromFullPath(const PIString & full_path) {
PIString prefix = full_path.left(full_path.find(":"));
if (prefix.isEmpty()) return 0;
PIVector<const PIObject * > rd(PICollection::groupElements("__PIIODevices__"));
piForeachC (PIObject * d, rd) {
if (prefix == ((const PIIODevice * )d)->fullPathPrefix()) {
PIIODevice * nd = ((const PIIODevice * )d)->copy();
if (nd) nd->configureFromFullPath(full_path.mid(prefix.length() + 3));
cacheFullPath(full_path, nd);
return nd;
}
}
return 0;
}
PIString PIIODevice::normalizeFullPath(const PIString & full_path) {
nfp_mutex.lock();
PIString ret = nfp_cache.value(full_path);
if (!ret.isEmpty()) {
nfp_mutex.unlock();
return ret;
}
nfp_mutex.unlock();
PIIODevice * d = createFromFullPath(full_path);
//piCout << "normalizeFullPath" << d;
if (d == 0) return PIString();
ret = d->constructFullPath();
delete d;
return ret;
}
void PIIODevice::cacheFullPath(const PIString & full_path, const PIIODevice * d) {
PIMutexLocker nfp_ml(nfp_mutex);
nfp_cache[full_path] = d->constructFullPath();
}
bool PIIODevice::threadedRead(uchar *readed, int size) {
// piCout << "iodevice threaded read";
if (ret_func_ != 0) return ret_func_(ret_data_, readed, size);
return true;
}

377
src_main/io/piiodevice.h Executable file
View File

@@ -0,0 +1,377 @@
/*! \file piiodevice.h
* \brief Abstract input/output device
*/
/*
PIP - Platform Independent Primitives
Abstract input/output device
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIIODEVICE_H
#define PIIODEVICE_H
#include "piinit.h"
#include "picollection.h"
#include "pitimer.h"
#include "piqueue.h"
// function executed from threaded read, pass ThreadedReadData, readedData, sizeOfData
typedef bool (*ReadRetFunc)(void * , uchar * , int );
#ifdef DOXYGEN
//! \relatesalso PIIODevice \brief Use this macro to enable automatic creation instances of your class with \a createFromFullPath() function
# define REGISTER_DEVICE(class)
//! \relatesalso PIIODevice \brief Use this macro instead of PIOBJECT when describe your own PIIODevice
# define PIIODEVICE(class)
#else
# define REGISTER_DEVICE(class) ADD_NEW_TO_COLLECTION_WITH_NAME(__PIIODevices__, class, __S__collection_##class##__)
# define PIIODEVICE(class) PIOBJECT_SUBCLASS(class, PIIODevice) PIIODevice * copy() const {return new class();}
#endif
class PIP_EXPORT PIIODevice: public PIThread
{
PIOBJECT_SUBCLASS(PIIODevice, PIThread)
friend void __DevicePool_threadReadDP(void * ddp);
public:
//! Constructs a empty PIIODevice
explicit PIIODevice();
//! \brief Open modes for PIIODevice
enum DeviceMode {
ReadOnly /*! Device can only read */ = 0x01,
WriteOnly /*! Device can only write */ = 0x02,
ReadWrite /*! Device can both read and write */ = 0x03
};
//! \brief Options for PIIODevice, works with some devices
enum DeviceOption {
BlockingRead /*! \a read block until data is received, default off */ = 0x01,
BlockingWrite /*! \a write block until data is sent, default off */ = 0x02
};
typedef PIFlags<DeviceOption> DeviceOptions;
explicit PIIODevice(const PIString & path, DeviceMode mode = ReadWrite);
virtual ~PIIODevice();
//! Current open mode of device
DeviceMode mode() const {return mode_;}
//! Set open mode of device
void setMode(DeviceMode m) {mode_ = m;}
//! Current device options
DeviceOptions options() const {return options_;}
//! Current device option "o" state
bool isOptionSet(DeviceOption o) const {return options_[o];}
//! Set device options
void setOptions(DeviceOptions o);
//! Set device option "o" to "yes" and return previous state
bool setOption(DeviceOption o, bool yes = true);
//! Current path of device
PIString path() const {return property(PIStringAscii("path")).toString();}
//! Set path of device
void setPath(const PIString & path) {setProperty(PIStringAscii("path"), path);}
//! Return \b true if mode is ReadOnly or ReadWrite
bool isReadable() const {return (mode_ & ReadOnly);}
//! Return \b true if mode is WriteOnly or ReadWrite
bool isWriteable() const {return (mode_ & WriteOnly);}
//! Return \b true if device is successfully opened
bool isOpened() const {return opened_;}
//! Return \b true if device is closed
bool isClosed() const {return !opened_;}
//! Return \b true if device can read \b now
virtual bool canRead() const {return opened_ && (mode_ & ReadOnly);}
//! Return \b true if device can write \b now
virtual bool canWrite() const {return opened_ && (mode_ & WriteOnly);}
//! Set execution of \a open enabled while threaded read on closed device
void setReopenEnabled(bool yes = true) {setProperty(PIStringAscii("reopenEnabled"), yes);}
//! Set timeout in milliseconds between \a open tryings if reopen is enabled
void setReopenTimeout(int msecs) {setProperty(PIStringAscii("reopenTimeout"), msecs);}
//! Return reopen enable
bool isReopenEnabled() const {return property(PIStringAscii("reopenEnabled")).toBool();}
//! Return reopen timeout
int reopenTimeout() {return property(PIStringAscii("reopenTimeout")).toInt();}
/** \brief Set "threaded read slot"
* \details Set external static function of threaded read that will be executed
* at every successful threaded read. Function should have format
* "bool func(void * data, uchar * readed, int size)" */
void setThreadedReadSlot(ReadRetFunc func) {ret_func_ = func;}
//! Set custom data that will be passed to "threaded read slot"
void setThreadedReadData(void * d) {ret_data_ = d;}
/** \brief Set size of threaded read buffer
* \details Default size is 4096 bytes. If your device can read at single read
* more than 4096 bytes you should use this function to adjust buffer size */
void setThreadedReadBufferSize(int new_size) {buffer_tr.resize(new_size);}
//! Return size of threaded read buffer
int threadedReadBufferSize() const {return buffer_tr.size_s();}
//! Return content of threaded read buffer
const uchar * threadedReadBuffer() const {return buffer_tr.data();}
//! Return custom data that will be passed to "threaded read slot"
void * threadedReadData() const {return ret_data_;}
//! Return \b true if threaded read is started
bool isThreadedRead() const {return isRunning();}
//! Start threaded read
void startThreadedRead() {if (!isRunning()) PIThread::start();}
//! Start threaded read and assign "threaded read slot" to "func"
void startThreadedRead(ReadRetFunc func) {ret_func_ = func; if (!isRunning()) PIThread::start();}
//! Stop threaded read
void stopThreadedRead() {PIThread::terminate();}
//! Return \b true if threaded write is started
bool isThreadedWrite() const {return write_thread.isRunning();}
//! Start threaded write
void startThreadedWrite() {if (!write_thread.isRunning()) write_thread.startOnce();}
//! Stop threaded write
void stopThreadedWrite() {write_thread.terminate();}
//! Clear threaded write task queue
void clearThreadedWriteQueue() {write_thread.lock(); write_queue.clear(); write_thread.unlock();}
//! Start both threaded read and threaded write
void start() {startThreadedRead(); startThreadedWrite();}
//! Stop both threaded read and threaded write and if "wait" block until both threads are stop
void stop(bool wait = false) {stopThreadedRead(); stopThreadedWrite(); if (wait) while (write_thread.isRunning() || isRunning()) msleep(1);}
//! Read from device maximum "max_size" bytes to "read_to"
int read(void * read_to, int max_size) {return readDevice(read_to, max_size);}
//! Read from device maximum "max_size" bytes and return them as PIByteArray
PIByteArray read(int max_size) {buffer_in.resize(max_size); int ret = readDevice(buffer_in.data(), max_size); if (ret < 0) return PIByteArray(); return buffer_in.resized(ret);}
//! Write maximum "max_size" bytes of "data" to device
int write(const void * data, int max_size) {return writeDevice(data, max_size);}
//! Write "data" to device
int write(const PIByteArray & data) {return writeDevice(data.data(), data.size_s());}
//! Read from device for "timeout_ms" milliseconds and return readed data as PIByteArray. Timeout should to be greater than 0
PIByteArray readForTime(double timeout_ms);
//! Add task to threaded write queue and return task ID
ullong writeThreaded(const void * data, int max_size) {return writeThreaded(PIByteArray(data, uint(max_size)));}
//! Add task to threaded write queue and return task ID
ullong writeThreaded(const PIByteArray & data);
//! Configure device from section "section" of file "config_file", if "parent_section" parent section also will be read
bool configure(const PIString & config_file, const PIString & section, bool parent_section = false);
//! Reimplement to construct full unambiguous string prefix. \ref PIIODevice_sec7
virtual PIString fullPathPrefix() const {return PIString();}
//! Reimplement to construct full unambiguous string, describes this device, default returns \a fullPathPrefix() + "://" + \a path()
PIString constructFullPath() const;
//! Reimplement to configure your device with parameters of full unambiguous string. Default implementation does nothing
void configureFromFullPath(const PIString & full_path);
//! \brief Try to determine suitable device, create new one, configure it with \a configureFromFullPath() and returns it.
//! \details To function \a configureFromFullPath() "full_path" passed without \a fullPathPrefix() + "://".
//! See \ref PIIODevice_sec7
static PIIODevice * createFromFullPath(const PIString & full_path);
static PIString normalizeFullPath(const PIString & full_path);
static void splitFullPath(PIString fpwm, PIString * full_path, DeviceMode * mode = 0, DeviceOptions * opts = 0);
EVENT_HANDLER(bool, open) {if (!init_) init(); opened_ = openDevice(); if (opened_) opened(); return opened_;}
EVENT_HANDLER1(bool, open, const PIString &, _path) {setPath(_path); if (!init_) init(); opened_ = openDevice(); if (opened_) opened(); return opened_;}
EVENT_HANDLER1(bool, open, const DeviceMode &, _mode) {mode_ = _mode; if (!init_) init(); opened_ = openDevice(); if (opened_) opened(); return opened_;}
EVENT_HANDLER2(bool, open, const PIString &, _path, const DeviceMode &, _mode) {setPath(_path); mode_ = _mode; if (!init_) init(); opened_ = openDevice(); if (opened_) opened(); return opened_;}
EVENT_HANDLER(bool, close) {opened_ = !closeDevice(); if (!opened_) closed(); return !opened_;}
EVENT_VHANDLER(void, flush) {;}
EVENT(opened)
EVENT(closed)
EVENT2(threadedReadEvent, uchar * , readed, int, size)
EVENT2(threadedWriteEvent, ullong, id, int, written_size)
//! \handlers
//! \{
//! \fn bool open()
//! \brief Open device
//! \fn bool open(const PIString & path)
//! \brief Open device with path "path"
//! \fn bool open(const DeviceMode & mode)
//! \brief Open device with mode "mode"
//! \fn bool open(const PIString & path, const DeviceMode & mode)
//! \brief Open device with path "path" and mode "mode"
//! \fn bool close()
//! \brief Close device
//! \}
//! \vhandlers
//! \{
//! \fn void flush()
//! \brief Immediate write all buffers
//! \}
//! \events
//! \{
//! \fn void opened()
//! \brief Raise if succesfull open
//! \fn void closed()
//! \brief Raise if succesfull close
//! \fn void threadedReadEvent(uchar * readed, int size)
//! \brief Raise if read thread succesfull read some data
//! \fn void threadedWriteEvent(ullong id, int written_size)
//! \brief Raise if write thread successfull write some data of task with ID "id"
//! \}
//! \ioparams
//! \{
#ifdef DOXYGEN
//! \brief setReopenEnabled, default "true"
bool reopenEnabled;
//! \brief setReopenTimeout in ms, default 1000
int reopenTimeout;
//! \brief setThreadedReadBufferSize in bytes, default 4096
int threadedReadBufferSize;
#endif
//! \}
protected:
//! Function executed before first \a openDevice() or from constructor
virtual bool init() {return true;}
//! Reimplement to configure device from entries "e_main" and "e_parent", cast arguments to \a PIConfig::Entry*
virtual bool configureDevice(const void * e_main, const void * e_parent = 0) {return true;}
//! Reimplement to open device, return value will be set to "opened_" variable; don't call this function in subclass, use open()
virtual bool openDevice() = 0; // use path_, type_, opened_, init_ variables
//! Reimplement to close device, inverse return value will be set to "opened_" variable
virtual bool closeDevice() {return true;} // use path_, type_, opened_, init_ variables
//! Reimplement this function to read from your device
virtual int readDevice(void * read_to, int max_size) {piCoutObj << "\"read\" is not implemented!"; return -2;}
//! Reimplement this function to write to your device
virtual int writeDevice(const void * data, int max_size) {piCoutObj << "\"write\" is not implemented!"; return -2;}
//! Function executed when thread read some data, default implementation execute external slot "ret_func_"
virtual bool threadedRead(uchar * readed, int size);
//! Reimplement to construct full unambiguous string, describes this device. Default implementation returns \a path()
virtual PIString constructFullPathDevice() const {return path();}
//! Reimplement to configure your device with parameters of full unambiguous string. Default implementation does nothing
virtual void configureFromFullPathDevice(const PIString & full_path) {;}
//! Reimplement to apply new device options
virtual void optionsChanged() {;}
void terminate();
DeviceMode mode_;
DeviceOptions options_;
ReadRetFunc ret_func_;
bool opened_;
void * ret_data_;
private:
EVENT_HANDLER2(void, check_start, void * , data, int, delim);
EVENT_HANDLER(void, write_func);
virtual PIIODevice * copy() const {return 0;}
PIString fullPathOptions() const;
void _init();
void begin();
void run();
void end() {terminate();}
static void cacheFullPath(const PIString & full_path, const PIIODevice * d);
explicit PIIODevice(const PIIODevice & );
void operator =(const PIIODevice & );
PITimer timer;
PITimeMeasurer tm;
PIThread write_thread;
PIByteArray buffer_in, buffer_tr;
PIQueue<PIPair<PIByteArray, ullong> > write_queue;
ullong tri;
int readed_;
bool init_, thread_started_, raise_threaded_read_;
static PIMutex nfp_mutex;
static PIMap<PIString, PIString> nfp_cache;
};
#endif // PIIODEVICE_H

41
src_main/io/piiomodule.h Normal file
View File

@@ -0,0 +1,41 @@
/*
PIP - Platform Independent Primitives
Module includes
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIIOMODULE_H
#define PIIOMODULE_H
#include "pifile.h"
#include "piconfig.h"
#include "piserial.h"
#include "piethernet.h"
#include "piusb.h"
#include "pidiagnostics.h"
#include "pidir.h"
#include "pibinarylog.h"
#include "pifiletransfer.h"
#include "piiostring.h"
#include "piiobytearray.h"
#include "pitransparentdevice.h"
#include "pipeer.h"
#include "pipacketextractor.h"
#include "piprotocol.h"
#include "piconnection.h"
#include "pisharedmemory.h"
#endif // PIIOMODULE_H

102
src_main/io/piiostring.cpp Normal file
View File

@@ -0,0 +1,102 @@
/*
PIP - Platform Independent Primitives
PIIODevice wrapper around PIString
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 "piiostring.h"
/*! \class PIIOString
* \brief PIIODevice wrapper around PIString
*
* \section PIIOString_sec0 Synopsis
* This class allow you to use PIString as PIIODevice and pass it to, e.g. PIConfig
*/
//REGISTER_DEVICE(PIIOString);
PIIOString::PIIOString(PIString * string, PIIODevice::DeviceMode mode) {
open(string, mode);
}
PIIOString::PIIOString(const PIString & string) {
open(string);
}
bool PIIOString::open(PIString * string, PIIODevice::DeviceMode mode) {
str = string;
return PIIODevice::open(mode);
}
bool PIIOString::open(const PIString & string) {
str = const_cast<PIString*>(&string);
return PIIODevice::open(PIIODevice::ReadOnly);
}
PIString PIIOString::readLine() {
if (!canRead() || !str) return PIString();
int np = pos;
while (++np < str->size_s())
if ((*str)[np] == '\n')
break;
PIString ret = str->mid(pos, np - pos);
pos = piMini(np + 1, str->size_s());
return ret;
}
int PIIOString::readDevice(void * read_to, int max_size) {
if (!canRead() || !str) return -1;
PIString rs = str->mid(pos, max_size);
pos += max_size;
if (pos > str->size_s()) pos = str->size_s();
int ret = rs.lengthAscii();
memcpy(read_to, rs.data(), rs.lengthAscii());
return ret;
}
int PIIOString::writeDevice(const void * data, int max_size) {
if (!canWrite() || !str) return -1;
//piCout << "write" << data;
if (pos > str->size_s()) pos = str->size_s();
PIString rs = PIString::fromUTF8((const char *)data);
if (rs.size_s() > max_size) rs.resize(max_size);
str->insert(pos, rs);
pos += rs.size_s();
return rs.lengthAscii();
}
int PIIOString::writeString(const PIString & string) {
if (!canWrite() || !str) return -1;
if (pos > str->size_s()) pos = str->size_s();
str->insert(pos, string);
pos += string.size_s();
return string.lengthAscii();
}
bool PIIOString::openDevice() {
pos = 0;
return (str != 0);
}

84
src_main/io/piiostring.h Normal file
View File

@@ -0,0 +1,84 @@
/*! \file piiostring.h
* \brief PIIODevice wrapper around PIString
*/
/*
PIP - Platform Independent Primitives
PIIODevice wrapper around PIString
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIIOSTRING_H
#define PIIOSTRING_H
#include "piiodevice.h"
class PIP_EXPORT PIIOString: public PIIODevice
{
PIIODEVICE(PIIOString)
public:
//! Contructs %PIIOString with \"string\" content and \"mode\" open mode
explicit PIIOString(PIString * string = 0, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);
//! Contructs %PIIOString with \"string\" content only for read
explicit PIIOString(const PIString & string);
~PIIOString() {closeDevice();}
//! Returns content
PIString * string() const {return str;}
//! Clear content string
void clear() {if (str) str->clear(); pos = 0;}
//! Open \"string\" content with \"mode\" open mode
bool open(PIString * string, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);
//! Open \"string\" content only for read
bool open(const PIString & string);
//! Returns if position is at the end of content
bool isEnd() const {if (!str) return true; return pos >= str->size_s();}
//! Move read/write position to \"position\"
void seek(llong position) {pos = position;}
//! Move read/write position to the begin of the string
void seekToBegin() {if (str) pos = 0;}
//! Move read/write position to the end of the string
void seekToEnd() {if (str) pos = str->size_s();}
//! Read one text line and return it
PIString readLine();
//! Insert string \"string\" into content at current position
int writeString(const PIString & string);
protected:
bool openDevice();
int readDevice(void * read_to, int max_size);
int writeDevice(const void * data, int max_size);
ssize_t pos;
PIString * str;
};
#endif // PIIOSTRING_H

21
src_main/io/pimultiprotocol.cpp Executable file
View File

@@ -0,0 +1,21 @@
/*
PIP - Platform Independent Primitives
Multiprotocol
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 "pimultiprotocol.h"
/// DEPRECATED

93
src_main/io/pimultiprotocol.h Executable file
View File

@@ -0,0 +1,93 @@
/*
PIP - Platform Independent Primitives
Multiprotocol
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIMULTIPROTOCOL_H
#define PIMULTIPROTOCOL_H
#include "piprotocol.h"
class PIMultiProtocol: public PIMultiProtocolBase /// DEPRECATED
{
public:
PIMultiProtocol() {;} /// DEPRECATED
virtual ~PIMultiProtocol() {clear();}
void addProtocol(PIProtocol & prot) {prots.push_back(&prot); prot.setMultiProtocolOwner(this); prot.new_mp_prot = false;}
void addProtocol(PIProtocol * prot) {prots.push_back(prot); prot->setMultiProtocolOwner(this); prot->new_mp_prot = false;}
void addProtocol(const PIString & config, const PIString & name, void * recHeaderPtr = 0, int recHeaderSize = 0,
void * recDataPtr = 0, int recDataSize = 0, void * sendDataPtr = 0, int sendDataSize = 0) {;
prots.push_back(new PIProtocol(config, name, recHeaderPtr, recHeaderSize, recDataPtr, recDataSize, sendDataPtr, sendDataSize));
prots.back()->setMultiProtocolOwner(this);
prots.back()->new_mp_prot = true;
}
PIProtocol * protocol(const PIString & name) {piForeach (PIProtocol * i, prots) if (i->name() == name) return i; return 0;}
PIProtocol * protocol(const int index) {return prots[index];}
PIProtocol * operator [](const int index) {return prots[index];}
void startSend() {piForeach (PIProtocol * i, prots) i->startSend();}
void startReceive() {piForeach (PIProtocol * i, prots) i->startReceive();}
void start() {piForeach (PIProtocol * i, prots) i->start();}
void stopSend() {piForeach (PIProtocol * i, prots) i->stopSend();}
void stopReceive() {piForeach (PIProtocol * i, prots) i->stopReceive();}
void stop() {piForeach (PIProtocol * i, prots) i->stop();}
PIProtocol::Quality worseQuality() const {PIProtocol::Quality cq = PIProtocol::Good; piForeachC (PIProtocol * i, prots) if (cq > i->quality()) cq = i->quality(); return cq;}
PIProtocol::Quality bestQuality() const {PIProtocol::Quality cq = PIProtocol::Unknown; piForeachC (PIProtocol * i, prots) if (cq < i->quality()) cq = i->quality(); return cq;}
int count() const {return prots.size_s();}
void clear() {stop(); piForeach (PIProtocol * i, prots) if (i->new_mp_prot) delete i; prots.clear();}
private:
PIVector<PIProtocol * > prots;
};
class PIRepeater: public PIMultiProtocol { /// DEPRECATED
public:
PIRepeater(const PIString & config, const PIString & name_) { /// DEPRECATED
PIConfig conf(config, PIIODevice::ReadOnly);
if (!conf.isOpened()) {
piCoutObj << "[PIRepeater \"" << name_ << "\"] Can`t open \"" << config << "\"!";
return;
}
PIConfig::Entry & b(conf.getValue(name_));
if (b.childCount() != 2) {
piCoutObj << "[PIRepeater \"" << name_ << "\"] \"" << config << "\" should consist 2 nodes!";
return;
}
addProtocol(config, b.child(0)->fullName());
addProtocol(config, b.child(1)->fullName());
start();
}
PIString firstChannelName() {if (count() == 2) return protocol(0)->receiverDeviceName() + " -> " + protocol(1)->senderDeviceName(); return "Config error";}
PIString secondChannelName() {if (count() == 2) return protocol(1)->receiverDeviceName() + " -> " + protocol(0)->senderDeviceName(); return "Config error";}
ullong receiveCount() {if (count() == 2) return protocol(0)->receiveCount(); return 0;}
const ullong * receiveCount_ptr() {if (count() == 2) return protocol(0)->receiveCount_ptr(); return 0;}
ullong sendCount() {if (count() == 2) return protocol(0)->sendCount(); return 0;}
const ullong * sendCount_ptr() {if (count() == 2) return protocol(0)->sendCount_ptr(); return 0;}
private:
void received(PIProtocol * prot, bool , uchar * data, int size) {if (prot == protocol(0)) protocol(1)->send(data, size); else protocol(0)->send(data, size);}
};
#endif // PIMULTIPROTOCOL_H

301
src_main/io/pipacketextractor.cpp Executable file
View File

@@ -0,0 +1,301 @@
/*
PIP - Platform Independent Primitives
Packets extractor
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 "pipacketextractor.h"
/** \class PIPacketExtractor
* \brief Packets extractor
* \details
* \section PIPacketExtractor_main Synopsis
* This class implements packet recognition by various algorithms and custom
* validating from data stream. Stream is formed from child %PIIODevice
* passed from contructor or with function \a setDevice().
*
* \section PIPacketExtractor_work Principle of work
* %PIPacketExtractor works with child %PIIODevice. \a read and \a write
* functions directly call child device functions. You should start threaded
* read of \b extractor (not child device) to proper work. Extractor read data
* from child device, try to detect packet from readed data and raise
* \a packetReceived() event on success.
*
* \section PIPacketExtractor_algorithms Algorithms
* There are 6 algorithms: \n
* * PIPacketExtractor::None \n
* Packet is successfully received on every read without any validation. \n \n
* * PIPacketExtractor::Header \n
* Wait for at least \a header() bytes + \a payloadSize(), then validate
* header with virtual function \a validateHeader() and if it fail, shifts
* for next 1 byte. If header is successfully validated check payload with
* function \a validatePayload() and if it fail, shifts for next 1 byte. If
* all validations were successful raise \a packetReceived() event. \n \n
* * PIPacketExtractor::Footer \n
* This algorithm similar to previous, but instead of \a header() first validate
* \a footer() at after \a payloadSize() bytes with function \a validateFooter(). \n \n
* * PIPacketExtractor::HeaderAndFooter \n
* Wait for at least \a header() bytes + \a footer() bytes, then validate
* header with virtual function \a validateHeader() and if it fail, shifts
* for next 1 byte. If header is successfully validated check footer with
* function \a validateFooter() and if it fail, shifts footer position for
* next 1 byte. Then validate payload and if it fail, search header again,
* starts from next byte of previous header. If all validations were successful
* raise \a packetReceived() event. \n \n
* * PIPacketExtractor::Size \n
* Wait for at least \a packetSize() bytes, then validate packet with function
* \a validatePayload() and if it fail, shifts for next 1 byte. If validating
* was successfull raise \a packetReceived() event. \n \n
* * PIPacketExtractor::Timeout \n
* Wait for first read, then read for \a timeout() milliseconds and raise
* \a packetReceived() event. \n
*
* \section PIPacketExtractor_control Control validating
* There are three parameters:
* * header content
* * header size
* * payload size
*
* Extractor can detect packet with compare your header with readed data.
* It is default implementation of function \a packetHeaderValidate().
* If header validating passed, function \a packetValidate() will be called.
* If either of this function return \b false extractor shifts by one byte
* and takes next header. If both functions returns \b true extractor shifts
* by whole packet size.
* \image html packet_detection.png
*
* */
REGISTER_DEVICE(PIPacketExtractor)
PIPacketExtractor::PIPacketExtractor(PIIODevice * device_, PIPacketExtractor::SplitMode mode) {
construct();
setDevice(device_);
setSplitMode(mode);
}
void PIPacketExtractor::construct() {
ret_func_header = ret_func_footer = 0;
setPayloadSize(0);
setTimeout(100);
setThreadedReadBufferSize(65536);
setBufferSize(65536);
setDevice(0);
setPacketSize(0);
setSplitMode(None);
missed = missed_packets = footerInd = 0;
header_found = false;
}
void PIPacketExtractor::propertyChanged(const PIString &) {
packetSize_ = property("packetSize").toInt();
mode_ = (SplitMode)(property("splitMode").toInt());
dataSize = property("payloadSize").toInt();
src_header = property("header").toByteArray();
src_footer = property("footer").toByteArray();
packetSize_hf = src_header.size_s() + src_footer.size_s() + payloadSize();
}
void PIPacketExtractor::setDevice(PIIODevice * device_) {
dev = device_;
if (dev == 0) return;
}
void PIPacketExtractor::setPayloadSize(int size) {
setProperty("payloadSize", size);
dataSize = size;
packetSize_hf = src_header.size_s() + src_footer.size_s() + payloadSize();
}
void PIPacketExtractor::setHeader(const PIByteArray & data) {
setProperty("header", data);
src_header = data;
packetSize_hf = src_header.size_s() + src_footer.size_s() + payloadSize();
}
void PIPacketExtractor::setFooter(const PIByteArray & data) {
setProperty("footer", data);
src_footer = data;
packetSize_hf = src_header.size_s() + src_footer.size_s() + payloadSize();
}
bool PIPacketExtractor::threadedRead(uchar * readed, int size_) {
//piCoutObj << "readed" << size_;
int ss;
switch (mode_) {
case PIPacketExtractor::None:
if (validatePayload(readed, size_))
packetReceived(readed, size_);
break;
case PIPacketExtractor::Header:
tmpbuf.append(readed, size_);
ss = src_header.size_s() + dataSize;
while (tmpbuf.size_s() >= ss) {
while (!validateHeader(src_header.data(), tmpbuf.data(), src_header.size_s())) {
tmpbuf.pop_front();
++missed;
if (tmpbuf.size_s() < ss) return true;
}
while (!validatePayload(tmpbuf.data(src_header.size_s()), dataSize)) {
tmpbuf.pop_front();
++missed;
if (tmpbuf.size_s() < ss) return true;
}
packetReceived(tmpbuf.data(), ss);
tmpbuf.remove(0, ss);
}
break;
case PIPacketExtractor::Footer:
/*memcpy(buffer.data(allReaded), readed, size_);
allReaded += size_;
footer_ = (mode_ == PIPacketExtractor::Footer);
while (allReaded >= packetSize_hf + addSize && allReaded > 0) {
if (!src_header.isEmpty()) {
if (allReaded + curInd >= buffer_size) {
memcpy(sbuffer.data(), buffer.data(), buffer_size);
memcpy(buffer.data(), sbuffer.data(buffer_size - packetSize_hf), allReaded);
allReaded = packetSize_hf;
addSize = curInd = 0;
}
bool brk = false;
while (!validateHeader((uchar * )(footer_ ? src_footer.data() : src_header.data()), buffer.data(curInd + (footer_ ? dataSize : 0)), footer_ ? src_footer.size_s() : src_header.size_s())) {
++curInd; ++missed;
if (packetSize_hf > 0) missed_packets = missed / packetSize_hf;
if (curInd > addSize) {
addSize += packetSize_hf;
brk = true;
break;
}
}
if (brk) continue;
//memcpy(mheader.data(), buffer.data(curInd + (footer_ ? dataSize : 0)), src_header.size_s());
if (!src_header.isEmpty()) memcpy(src_header.data(), buffer.data(curInd), src_header.size_s());
if (!validatePayload(buffer.data(curInd + src_header.size_s()), dataSize)) {
++curInd; ++missed;
if (packetSize_hf > 0) missed_packets = missed / packetSize_hf;
continue;
}
packetReceived(buffer.data(curInd), packetSize_hf);
memcpy(sbuffer.data(), buffer.data(), allReaded);
memcpy(buffer.data(), sbuffer.data(packetSize_hf + curInd), allReaded);
allReaded -= packetSize_hf + curInd;
curInd = addSize = 0;
} else {
if (dataSize == 0) {
if (validatePayload(buffer.data(), size_))
packetReceived(buffer.data(), size_);
memcpy(sbuffer.data(), buffer.data(), allReaded);
memcpy(buffer.data(), sbuffer.data(size_), allReaded);
allReaded -= size_;
} else {
if (validatePayload(buffer.data(), dataSize))
packetReceived(buffer.data(), dataSize);
memcpy(sbuffer.data(), buffer.data(), allReaded);
memcpy(buffer.data(), sbuffer.data(packetSize_hf), allReaded);
allReaded -= packetSize_hf;
}
}
}*/
tmpbuf.append(readed, size_);
ss = src_footer.size_s() + dataSize;
while (tmpbuf.size_s() >= ss) {
while (!validateFooter(src_footer.data(), tmpbuf.data(dataSize), src_footer.size_s())) {
tmpbuf.pop_front();
++missed;
if (tmpbuf.size_s() < ss) return true;
}
while (!validatePayload(tmpbuf.data(), dataSize)) {
tmpbuf.pop_front();
++missed;
if (tmpbuf.size_s() < ss) return true;
}
packetReceived(tmpbuf.data(), ss);
tmpbuf.remove(0, ss);
}
break;
case PIPacketExtractor::HeaderAndFooter:
tmpbuf.append(readed, size_);
ss = src_header.size_s() + src_footer.size_s();
while (tmpbuf.size_s() >= ss) {
if (!header_found) {
if (tmpbuf.size_s() < ss) return true;
while (!validateHeader(src_header.data(), tmpbuf.data(), src_header.size_s())) {
tmpbuf.pop_front();
++missed;
if (tmpbuf.size_s() < ss) return true;
}
header_found = true;
footerInd = src_header.size_s();
} else {
if (tmpbuf.size_s() < footerInd + src_footer.size_s()) return true;
while (!validateFooter(src_footer.data(), tmpbuf.data(footerInd), src_footer.size_s())) {
++footerInd;
if (tmpbuf.size_s() < footerInd + src_footer.size_s()) return true;
}
//piCout << "footer found at" << footerInd;
header_found = false;
if (!validatePayload(tmpbuf.data(src_header.size_s()), footerInd - src_header.size_s())) {
tmpbuf.pop_front();
++missed;
continue;
}
packetReceived(tmpbuf.data(), footerInd + src_footer.size_s());
tmpbuf.remove(0, footerInd + src_footer.size_s());
footerInd = src_header.size_s();
}
}
break;
case PIPacketExtractor::Size:
tmpbuf.append(readed, size_);
if (packetSize_ <= 0) {
tmpbuf.clear();
return true;
}
while (tmpbuf.size_s() >= packetSize_) {
if (!validatePayload(tmpbuf.data(), packetSize_)) {
tmpbuf.pop_front();
++missed;
missed_packets = missed / packetSize_;
continue;
}
packetReceived(tmpbuf.data(), packetSize_);
tmpbuf.remove(0, packetSize_);
}
break;
case PIPacketExtractor::Timeout:
memcpy(buffer.data(), readed, size_);
trbuf = dev->readForTime(time_);
memcpy(buffer.data(size_), trbuf.data(), trbuf.size());
if (size_ + trbuf.size() > 0)
packetReceived(buffer.data(), size_ + trbuf.size());
break;
};
return true;
}
PIString PIPacketExtractor::constructFullPathDevice() const {
return "";
}

176
src_main/io/pipacketextractor.h Executable file
View File

@@ -0,0 +1,176 @@
/*! \file pipacketextractor.h
* \brief Packets extractor
*/
/*
PIP - Platform Independent Primitives
Packets extractor
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIPACKETEXTRACTOR_H
#define PIPACKETEXTRACTOR_H
#include "piiodevice.h"
// Pass data, recHeaderPtr, received_data, recHeaderSize. Return true if packet is correct nor return false.
typedef bool (*PacketExtractorCheckFunc)(void * , uchar * , uchar * , int );
class PIP_EXPORT PIPacketExtractor: public PIIODevice
{
PIIODEVICE(PIPacketExtractor)
friend class PIConnection;
public:
//! Extract algorithms
enum SplitMode {
None /** No data processing */ ,
Header /** Detect packets with \a header() and following \a payloadSize() */ ,
Footer /** Detect packets with \a footer() and leading \a payloadSize() */ ,
HeaderAndFooter /** Detect packets with \a header() and \a footer() without \a payloadSize() */ ,
Size /** Detect packets with \a packetSize() */ ,
Timeout /** Wait for first read, then read for \a timeout() milliseconds */
};
//! Contructs extractor with child device "device_" and extract algorithm "mode"
explicit PIPacketExtractor(PIIODevice * device_ = 0, SplitMode mode = None);
virtual ~PIPacketExtractor() {stop();}
//! Returns child %device
PIIODevice * device() {return dev;}
//! Set child %device to "device_"
void setDevice(PIIODevice * device_);
//! Returns buffer size
int bufferSize() const {return buffer_size;}
//! Set buffer size to "new_size" bytes, should be at least greater than whole packet size
void setBufferSize(int new_size) {buffer_size = new_size; buffer.resize(buffer_size); memset(buffer.data(), 0, buffer.size());}
void setHeaderCheckSlot(PacketExtractorCheckFunc f) {ret_func_header = f;}
void setFooterCheckSlot(PacketExtractorCheckFunc f) {ret_func_footer = f;}
void setPayloadCheckSlot(ReadRetFunc f) {ret_func_ = f;}
//! Set extract algorithm
void setSplitMode(SplitMode mode) {setProperty("splitMode", int(mode)); mode_ = mode;}
//! Set payload size, used for PIPacketExtractor::Header and PIPacketExtractor::Footer algorithms
void setPayloadSize(int size);
//! Set header data, used for PIPacketExtractor::Header and PIPacketExtractor::HeaderAndFooter algorithms
void setHeader(const PIByteArray & data);
//! Set footer data, used for PIPacketExtractor::Footer and PIPacketExtractor::HeaderAndFooter algorithms
void setFooter(const PIByteArray & data);
//! Set packet size, used for PIPacketExtractor::Size algorithm
void setPacketSize(int size) {setProperty("packetSize", size); packetSize_ = size;}
//! Set timeout in milliseconds, used for PIPacketExtractor::Timeout algorithm
void setTimeout(double msecs) {setProperty("timeout", msecs); time_ = msecs;}
//! Returns current extract algorithm
SplitMode splitMode() const {return (SplitMode)(property("splitMode").toInt());}
//! Returns current payload size, used for PIPacketExtractor::Header and PIPacketExtractor::Footer algorithms
int payloadSize() const {return property("payloadSize").toInt();}
//! Returns current header data, used for PIPacketExtractor::Header and PIPacketExtractor::HeaderAndFooter algorithms
PIByteArray header() const {return src_header;}
//! Returns current footer data, used for PIPacketExtractor::Footer and PIPacketExtractor::HeaderAndFooter algorithms
PIByteArray footer() const {return src_footer;}
//! Returns current packet size, used for PIPacketExtractor::Size algorithm
int packetSize() const {return property("packetSize").toInt();}
//! Returns current timeout in milliseconds, used for PIPacketExtractor::Timeout algorithm
double timeout() const {return property("timeout").toDouble();}
//! Returns missed by validating functions bytes count
ullong missedBytes() const {return missed;}
// //! Returns missed by validating functions packets count, = missedBytes() / packetSize
ullong missedPackets() const {/*if (packetSize_hf == 0) return missed; return missed / packetSize_hf*/; return missed_packets;}
//! Returns pointer to \a missedBytes() count. Useful for output to PIConsole
const ullong * missedBytes_ptr() const {return &missed;}
// //! Returns pointer to \a missedPackets() count. Useful for output to PIConsole
const ullong * missedPackets_ptr() const {return &missed_packets;}
EVENT2(packetReceived, uchar * , data, int, size)
//! \events
//! \{
//! \fn void packetReceived(uchar * data, int size)
//! \brief Raise on successfull \a packetValidate() function
//! \}
protected:
/** \brief Function to validate header
* \param src Your header content
* \param rec Received header
* \param size Header size
* \details Default implementation returns by-byte "src" with "rec" compare result */
virtual bool validateHeader(uchar * src, uchar * rec, int size) {if (ret_func_header != 0) return ret_func_header(ret_data_, src, rec, size); for (int i = 0; i < size; ++i) if (src[i] != rec[i]) return false; return true;}
/** \brief Function to validate footer
* \param src Your footer content
* \param rec Received footer
* \param size Footer size
* \details Default implementation returns by-byte "src" with "rec" compare result */
virtual bool validateFooter(uchar * src, uchar * rec, int size) {if (ret_func_footer != 0) return ret_func_footer(ret_data_, src, rec, size); for (int i = 0; i < size; ++i) if (src[i] != rec[i]) return false; return true;}
/** \brief Function to validate payload
* \param rec Received payload
* \param size payload size
* \details Default implementation returns \b true */
virtual bool validatePayload(uchar * rec, int size) {if (ret_func_ != 0) return ret_func_(ret_data_, rec, size); return true;}
private:
void construct();
void propertyChanged(const PIString & );
int readDevice(void * read_to, int max_size) {if (dev == 0) return -1; return dev->read(read_to, max_size);}
int writeDevice(const void * data, int max_size) {if (dev == 0) return -1; return dev->write(data, max_size);}
bool threadedRead(uchar * readed, int size);
PIString fullPathPrefix() const {return "pckext";}
PIString constructFullPathDevice() const;
bool openDevice() {if (dev == 0) return false; return dev->open();}
PIIODevice * dev;
PIByteArray buffer, tmpbuf, src_header, src_footer, trbuf;
PacketExtractorCheckFunc ret_func_header, ret_func_footer;
SplitMode mode_;
int buffer_size, dataSize, packetSize_hf, footerInd, packetSize_;
double time_;
bool header_found;
ullong missed, missed_packets;
};
#endif // PIPACKETEXTRACTOR_H

1055
src_main/io/pipeer.cpp Executable file
View File

@@ -0,0 +1,1055 @@
/*
PIP - Platform Independent Primitives
Peer - named I/O ethernet node
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 "pipeer.h"
#include "piconfig.h"
#define _PIPEER_MSG_SIZE 4000
#define _PIPEER_MSG_TTL 100
#define _PIPEER_MULTICAST_TTL 4
#define _PIPEER_MULTICAST_IP "232.13.3.12"
#define _PIPEER_LOOPBACK_PORT_S 13313
#define _PIPEER_LOOPBACK_PORT_E (13313+32)
#define _PIPEER_MULTICAST_PORT 13360
#define _PIPEER_TCP_PORT _PIPEER_MULTICAST_PORT
#define _PIPEER_BROADCAST_PORT 13361
#define _PIPEER_TRAFFIC_PORT_S 13400
#define _PIPEER_TRAFFIC_PORT_E 14000
#define _PIPEER_PING_TIMEOUT 5.0
PIPeer::PeerData::PeerData(const PIString & n): PIObject(n) {
dt_in.setPacketSize(_PIPEER_MSG_SIZE);
dt_out.setPacketSize(_PIPEER_MSG_SIZE);
CONNECTU(&dt_in, sendRequest, this, dtSendRequestIn)
CONNECTU(&dt_out, sendRequest, this, dtSendRequestOut)
CONNECTU(&dt_in, receiveFinished, this, dtReceiveFinishedIn)
CONNECTU(&dt_out, receiveFinished, this, dtReceiveFinishedOut)
CONNECTU(&t, started, this, dtThread)
}
PIPeer::PeerData::~PeerData() {
dt_in.stop();
dt_out.stop();
t.stop();
if (!t.waitForFinish(1000))
t.terminate();
}
void PIPeer::PeerData::dtThread() {
// << "send DT ...";
dt_out.send(data);
//piCoutObj << "send DT done";
}
bool PIPeer::PeerData::send(const PIByteArray & d) {
//piCout << "send ..." << t.isRunning();
if (t.isRunning()) return false;
data = d;
t.startOnce();
return true;
}
void PIPeer::PeerData::receivedPacket(uchar type, const PIByteArray & d) {
PIDataTransfer * dt = 0;
if (type == 3)
dt = &dt_in;
if (type == 2)
dt = &dt_out;
//piCoutObj << "DT received" << int(type) << d.size_s() << "...";
if (dt) dt->received(d);
//piCoutObj << "DT received" << int(type) << d.size_s() << "done";
}
void PIPeer::PeerData::setDist(int dist) {
dt_in.setTimeout(10 * dist);
}
PIPeer::PeerInfo::Address::Address(const PIString & a, const PIString & m): address(a), netmask(m) {
ping = -1.;
wait_ping = false;
last_ping = PISystemTime::current(true);
}
int PIPeer::PeerInfo::ping() const {
int ret = -1;
piForeachC (Address & a, addresses)
if (a.ping > 0.) {
if (ret < 0) ret = piRoundd(a.ping);
else ret = piMini(ret, piRoundd(a.ping));
}
return ret;
}
void PIPeer::PeerInfo::init() {
if (_data == 0) _data = new PeerData(name);
}
void PIPeer::PeerInfo::destroy() {
if (_data) delete _data;
_data = 0;
}
PIString PIPeer::PeerInfo::fastestAddress() const {
double mp = -1.;
PIString ret;
piForeachC (Address & a, addresses) {
if (a.ping <= 0.) continue;
if ((mp < 0) || (mp > a.ping)) {
mp = a.ping;
ret = a.address;
}
}
return ret;
}
REGISTER_DEVICE(PIPeer)
PIPeer::PIPeer(const PIString & n): PIIODevice(), inited__(false), eth_tcp_srv(PIEthernet::TCP_Server), eth_tcp_cli(PIEthernet::TCP_Client), diag_s(false), diag_d(false) {
//piCout << " PIPeer" << uint(this);
destroyed = false;
setDebug(false);
PIMutexLocker mbl(mc_mutex);
PIMutexLocker ethl(eth_mutex);
PIMutexLocker pl(peers_mutex);
PIMutexLocker sl(send_mutex);
changeName(n);
setReopenTimeout(100);
read_buffer_size = 128;
self_info.dist = 0;
self_info.time = PISystemTime::current();
//joinMulticastGroup("239.240.241.242");
randomize();
//id_ = self_info.name + "_" + PIString::fromNumber(randomi());
CONNECTU(&sync_timer, tickEvent, this, timerEvent);
prev_ifaces = PIEthernet::interfaces();
no_timer = false;
// initNetwork();
sync_timer.addDelimiter(5);
}
PIPeer::~PIPeer() {
//piCout << "~PIPeer" << uint(this);
if (destroyed) return;
destroyed = true;
PIMutexLocker ml(peers_mutex);
piForeach (PeerInfo & p, peers)
if (p._data) {
p._data->dt_in.stop();
p._data->dt_out.stop();
p._data->t.stop(true);
}
sync_timer.stop();
diag_s.stop();
diag_d.stop();
destroyEths();
piForeach (PIEthernet * i, eths_mcast) {
if (!i) continue;
i->stopThreadedRead();
}
piForeach (PIEthernet * i, eths_bcast) {
if (!i) continue;
i->stopThreadedRead();
}
eth_lo.stopThreadedRead();
eth_tcp_srv.stopThreadedRead();
eth_tcp_cli.stopThreadedRead();
sendSelfRemove();
destroyMBcasts();
eth_send.close();
piForeach (PeerInfo & p, peers)
p.destroy();
}
void PIPeer::timerEvent(void * data, int delim) {
// piCoutObj << "timerEvent" << delim;
if (no_timer) return;
switch (delim) {
case 1: // every 1 s
syncPeers();
piMSleep(100);
pingNeighbours();
//piCoutObj << "isOpened" << isOpened();
break;
case 5: // every 5 s
checkNetwork();
break;
}
//send("broadcast", 9);
}
void PIPeer::initEths(PIStringList al) {
// piCoutObj << "initEths start";
PIEthernet * ce;
const PIEthernet::Interface * cint = 0;
piForeachC (PIString & a, al) {
ce = new PIEthernet();
ce->setDebug(false);
ce->setName("__S__PIPeer_traffic_eth_rec_" + a);
ce->setParameters(0);
bool ok = false;
for (int p = _PIPEER_TRAFFIC_PORT_S; p < _PIPEER_TRAFFIC_PORT_E; ++p) {
ce->setReadAddress(a, p);
if (ce->open()) {
eths_traffic << ce;
cint = prev_ifaces.getByAddress(a);
self_info.addresses << PeerInfo::Address(ce->path(), cint == 0 ? "255.255.255.0" : cint->netmask);
CONNECTU(ce, threadedReadEvent, this, dataRead);
ce->startThreadedRead();
// piCoutObj << "dc binded to" << ce->path();
// piCoutObj << "add eth" << a;
ok = true;
break;
}
}
if (!ok) delete ce;
}
eth_send.setDebug(false);
eth_send.setName("__S__PIPeer_traffic_eth_send");
eth_send.setParameters(0);
// piCoutObj << "initEths ok";
}
void PIPeer::initMBcasts(PIStringList al) {
// destroyMBcasts();
PIEthernet * ce;
const PIEthernet::Interface * cint;
PIString nm;
al << _PIPEER_MULTICAST_IP;
// piCoutObj << "initMBcasts start" << al;
piForeachC (PIString & a, al) {
//piCout << "mcast try" << a;
ce = new PIEthernet();
ce->setDebug(false);
ce->setName("__S__PIPeer_mcast_eth_" + a);
ce->setParameters(0);
ce->setSendAddress(_PIPEER_MULTICAST_IP, _PIPEER_MULTICAST_PORT);
ce->setReadAddress(a, _PIPEER_MULTICAST_PORT);
ce->setMulticastTTL(_PIPEER_MULTICAST_TTL);
ce->joinMulticastGroup(_PIPEER_MULTICAST_IP);
if (ce->open()) {
eths_mcast << ce;
CONNECTU(ce, threadedReadEvent, this, mbcastRead);
ce->startThreadedRead();
// piCout << "mcast bind to" << a << ce->sendIP();
} else {
delete ce;
//piCoutObj << "invalid address for mcast" << a;
}
}
al.removeAll(_PIPEER_MULTICAST_IP);
piForeachC (PIString & a, al) {
ce = new PIEthernet();
ce->setDebug(false);
ce->setName("__S__PIPeer_bcast_eth_" + a);
ce->setParameters(PIEthernet::Broadcast);
cint = prev_ifaces.getByAddress(a);
nm = (cint == 0) ? "255.255.255.0" : cint->netmask;
ce->setSendAddress(PIEthernet::getBroadcast(a, nm), _PIPEER_BROADCAST_PORT);
ce->setReadAddress(a, _PIPEER_BROADCAST_PORT);
if (ce->open()) {
eths_bcast << ce;
CONNECTU(ce, threadedReadEvent, this, mbcastRead);
ce->startThreadedRead();
// piCout << "mc BC try" << a << nm << ce->sendIP();
// piCout << "bcast bind to" << a << nm;
} else {
delete ce;
//piCoutObj << "invalid address for bcast" << a;
}
}
// eth_lo.setDebug(false);
eth_lo.setName("__S__PIPeer_eth_loopback");
eth_lo.setParameters(PIEthernet::SeparateSockets);
eth_lo.init();
cint = prev_ifaces.getByAddress("127.0.0.1");
for (int p = _PIPEER_LOOPBACK_PORT_S; p <= _PIPEER_LOOPBACK_PORT_E; ++p) {
eth_lo.setReadAddress("127.0.0.1", p);
if (eth_lo.open()) {
eth_lo.setSendIP("127.0.0.1");
CONNECTU(&eth_lo, threadedReadEvent, this, mbcastRead);
eth_lo.startThreadedRead();
// piCout << "lo binded to" << eth_lo.readAddress() << eth_lo.sendAddress();
//piCout << "add eth" << ta;
break;
}
}
eth_tcp_srv.setName("__S__PIPeer_eth_TCP_Server");
eth_tcp_srv.init();
eth_tcp_srv.listen("0.0.0.0", _PIPEER_TCP_PORT, true);
eth_tcp_srv.setDebug(false);
CONNECTU(&eth_tcp_srv, newConnection, this, newTcpClient);
eth_tcp_srv.startThreadedRead();
eth_tcp_cli.setName("__S__PIPeer_eth_TCP_Client");
eth_tcp_cli.init();
eth_tcp_cli.setDebug(false);
tcpClientReconnect();
CONNECTU(&eth_tcp_cli, threadedReadEvent, this, mbcastRead);
CONNECTU(&eth_tcp_cli, disconnected, this, tcpClientReconnect);
eth_tcp_cli.startThreadedRead();
if (eths_mcast.isEmpty() && eths_bcast.isEmpty() && !eth_lo.isOpened()) piCoutObj << "Warning! Can`t find suitable network interface for multicast receive, check for exists at least one interface with multicasting enabled!";
// piCoutObj << "initMBcasts ok";
}
void PIPeer::destroyEths() {
piForeach (PIEthernet * i, eths_traffic) {
if (!i) continue;
((PIThread*)i)->stop();
((PIThread*)i)->waitForFinish(100);
i->stopThreadedRead();
i->close();
delete i;
i = 0;
}
eths_traffic.clear();
}
void PIPeer::destroyMBcasts() {
piForeach (PIEthernet * i, eths_mcast) {
if (!i) continue;
((PIThread*)i)->stop();
((PIThread*)i)->waitForFinish(100);
i->stopThreadedRead();
i->leaveMulticastGroup(_PIPEER_MULTICAST_IP);
i->close();
delete i;
i = 0;
}
eths_mcast.clear();
piForeach (PIEthernet * i, eths_bcast) {
if (!i) continue;
((PIThread*)i)->stop();
((PIThread*)i)->waitForFinish(100);
i->stopThreadedRead();
i->close();
delete i;
i = 0;
}
eths_bcast.clear();
((PIThread*)&eth_lo)->stop();
((PIThread*)&eth_lo)->waitForFinish(100);
eth_lo.stopThreadedRead();
eth_lo.close();
eth_tcp_srv.stop();
}
PIPeer::PeerInfo * PIPeer::quickestPeer(const PIString & to) {
if (!peers_map.contains(to)) return 0;
//piCout << "*** search quickest peer" << to;
PIVector<PeerInfo * > tp = addresses_map.value(to);
/*PeerInfo * dp = 0;
int mping = 0x7FFFFFFF;
for (int i = 0; i < tp.size_s(); ++i) {
int p = tp[i]->ping();
if (mping > p && p > 0) {
mping = p;
dp = tp[i];
}
}
//piCout << "*** search quickest peer: found" << (dp ? dp->name : "0");
return dp;*/
if (tp.isEmpty()) return 0;
return tp.back();
}
bool PIPeer::send(const PIString & to, const void * data, int size) {
PIByteArray ba(data, size);
// piCoutObj << "send" << ba.size_s() << "bytes" << _PIPEER_MSG_SIZE;
if (ba.size_s() <= _PIPEER_MSG_SIZE) {
ba.insert(0, uchar(1));
return sendInternal(to, ba);
} else {
//ba.insert(0, uchar(2));
PIMutexLocker mlocker(peers_mutex);
PeerInfo * dp = const_cast<PeerInfo *>(getPeerByName(to));
if (!dp) return false;
return dp->_data->send(ba);
}
return true;
}
bool PIPeer::sendInternal(const PIString & to, const PIByteArray & data) {
PIMutexLocker mlocker(peers_mutex);
PeerInfo * dp = quickestPeer(to);
if (dp == 0) {
//piCoutObj << "Can`t find peer \"" << to << "\"!";
return false;
}
PIByteArray ba;
ba << int(4) << self_info.name << to << int(0) << data;
// piCoutObj << "sendInternal to" << to << data.size_s() << int(data.front());
if (!sendToNeighbour(dp, ba)) {
//piCoutObj << "send error";
return false;
}
return true;
}
void PIPeer::dtReceived(const PIString & from, const PIByteArray & data) {
dataReceived(from, data);
dataReceivedEvent(from, data);
if (trust_peer.isEmpty() || trust_peer == from) {
read_buffer_mutex.lock();
if (read_buffer.size_s() < read_buffer_size) read_buffer.enqueue(data);
read_buffer_mutex.unlock();
}
}
bool PIPeer::dataRead(uchar * readed, int size) {
if (destroyed) {
piCout << "[PIPeer] SegFault";
return true;
}
if (size < 16) return true;
PIByteArray ba(readed, size), sba, pba;
int type, cnt;
PIString from, to;
ba >> type;
PIMutexLocker locker(eth_mutex);
if (type == 5) { // ping request
PIString addr;
PISystemTime time;
ba >> to >> from >> addr >> time;
// piCout << "ping request" << to << from << addr;
PIMutexLocker plocker(peers_mutex);
if (from == self_info.name) { // send ping back
const PeerInfo * pi = getPeerByName(to);
if (pi) {
if (pi->isNeighbour()) {
sba << int(6) << to << from << addr << time;
// piCout << " ping from" << from << addr << ", send back to" << pi->name;
send_mutex.lock();
piForeachC (PeerInfo::Address & a, pi->addresses) {
if (eth_send.send(a.address, sba))
diag_s.received(sba.size_s());
}
send_mutex.unlock();
}
}
}
return true;
}
if (type == 6) { // ping request
PIString addr;
PISystemTime time, ptime, ctime = PISystemTime::current(true);
ba >> to >> from >> addr >> time;
// piCout << "ping reply" << to << from << addr;
PIMutexLocker plocker(peers_mutex);
if (to == self_info.name) { // ping echo
piForeach (PeerInfo & p, peers) {
if (!p.isNeighbour()) continue;
if (p.name != from) continue;
piForeach (PeerInfo::Address & a, p.addresses) {
if (a.address != addr) continue;
if (a.last_ping >= time) piBreak;
ptime = ctime - time;
a.last_ping = time;
a.wait_ping = false;
if (a.ping < 0) a.ping = ptime.toMilliseconds();
else a.ping = 0.6 * a.ping + 0.4 * ptime.toMilliseconds();
// piCout << " ping echo" << p.name << a.address << a.ping;
return true;
}
}
}
return true;
}
// piCoutObj << "received data from" << from << "packet" << type;
if (type != 4) return true;
diag_d.received(size);
ba >> from >> to >> cnt >> pba;
//piCoutObj << "Received packet" << type << from << to << pba.size_s();
if (type == 4) { // data packet
if (to == self_info.name) { // my packet
uchar pt = pba.take_front();
//piCoutObj << "Received packet" << type << from << to << int(pt) << pba.size_s();
peers_mutex.lock();
PeerInfo * fp = const_cast<PeerInfo * >(getPeerByName(from));
if (fp == 0) {
peers_mutex.unlock();
return true;
}
if (pt == 1) {
peers_mutex.unlock();
dtReceived(from, pba);
return true;
}
if (pt == 2 || pt == 3) {
peers_mutex.unlock();
if (fp->_data)
fp->_data->receivedPacket(pt, pba);
return true;
}
peers_mutex.unlock();
return true;
}
PIMutexLocker plocker(peers_mutex);
PeerInfo * dp = quickestPeer(to);
if (dp == 0) {
//piCoutObj << "Can`t find peer \"" << to << "\"!";
return true;
}
cnt++;
if (cnt > _PIPEER_MSG_TTL || from == dp->name) return true;
sba << type << from << to << cnt << pba;
//piCout << "translate packet" << from << "->" << to << ", ttl =" << cnt;
sendToNeighbour(dp, sba);
}
return true;
}
bool PIPeer::mbcastRead(uchar * data, int size) {
if (destroyed) {
//piCout << "[PIPeer] SegFault";
return true;
}
if (size < 8) return true;
int type, dist;
PIByteArray ba(data, size);
ba >> type;
if (type <= 0 || type >= 4) return true;
PeerInfo pi;
ba >> pi.name;
// piCoutObj << "received mb from" << pi.name << "packet" << type;
if (pi.name == self_info.name) return true;
PIMutexLocker locker(mc_mutex);
diag_s.received(size);
const PeerInfo * rpi = 0;
bool ch = false;
PIVector<PeerInfo> rpeers;
//piCout << "analyz ...";
switch (type) {
case 1: // new peer
//piCout << "new peer packet ...";
peers_mutex.lock();
if (!hasPeer(pi.name)) {
ba >> pi;
pi.sync = 0;
if (pi.dist == 0) {
pi.addNeighbour(self_info.name);
self_info.addNeighbour(pi.name);
}
pi.resetPing();
addPeer(pi);
buildMap();
// piCoutObj << "new peer \"" << pi.name << "\"" << " dist " << pi.dist;
// piCoutObj << mode() << opened_;
pi.dist++;
sendSelfInfo();
sendPeerInfo(pi);
ch = true;
//piCout << "new peer packet ok";
}
peers_mutex.unlock();
if (ch) {
peerConnected(pi.name);
peerConnectedEvent(pi.name);
}
break;
case 2: // remove peer
//piCout << "remove peer packet ..." << pi.name;
peers_mutex.lock();
removeNeighbour(pi.name);
rpi = getPeerByName(pi.name);
if (rpi) {
dist = rpi->dist;
addToRemoved(*rpi);
removePeer(pi.name);
//piCoutObj << "remove peer \"" << pi.name << "\"";
if (dist == 0)
self_info.removeNeighbour(pi.name);
sendPeerRemove(pi.name);
buildMap();
ch = true;
//piCout << "remove peer packet ok";
}
peers_mutex.unlock();
if (ch) {
peerDisconnected(pi.name);
peerDisconnectedEvent(pi.name);
}
break;
case 3: // sync peers
//piCout << "sync packet ...";
ba >> pi >> rpeers;
rpeers << pi;
//piCoutObj << "rec sync " << rpeers.size_s() << " peers";
peers_mutex.lock();
if (!self_info.neighbours.contains(pi.name)) {
//piCoutObj << "add new nei to me" << pi.name;
self_info.addNeighbour(pi.name);
PeerInfo * np = peers_map.value(pi.name);
if (np) {
np->addNeighbour(self_info.name);
np->dist = 0;
}
ch = true;
}
piForeach (PeerInfo & rpeer, rpeers) {
//piCout << " to sync " << rpeer.name;
if (rpeer.name == self_info.name) continue;
bool exist = false;
piForeach (PeerInfo & peer, peers) {
if (peer.name == rpeer.name) {
exist = true;
if (isPeerRecent(peer, rpeer)) {
//piCout << "synced " << peer.name;
for (int z = 0; z < rpeer.addresses.size_s(); ++z) {
PeerInfo::Address & ra(rpeer.addresses[z]);
for (int k = 0; k < peer.addresses.size_s(); ++k) {
PeerInfo::Address & a(peer.addresses[k]);
if (ra.address == a.address) {
ra.ping = a.ping;
ra.wait_ping = a.wait_ping;
ra.last_ping = a.last_ping;
break;
}
}
}
peer.was_update = true;
peer.addresses = rpeer.addresses;
peer.cnt = rpeer.cnt;
peer.time = rpeer.time;
peer.addNeighbours(rpeer.neighbours);
rpeer.neighbours = peer.neighbours;
if (peer.name == pi.name) peer.sync = 0;
ch = true;
}
piBreak;
}
}
if (exist || isRemoved(rpeer)) continue;
rpeer.dist++;
if (rpeer.name == pi.name) rpeer.dist = 0;
rpeer.resetPing();
addPeer(rpeer);
ch = true;
peerConnected(rpeer.name);
peerConnectedEvent(rpeer.name);
}
//piCout << "***";
//piCout << self_info.name << self_info.neighbours;
piForeach (PeerInfo & i, peers) {
if (i.dist == 0) {
self_info.addNeighbour(i.name);
i.addNeighbour(self_info.name);
}
//piCout << i.name << i.neighbours;
}
if (ch)
buildMap();
peers_mutex.unlock();
//piCoutObj << "after sync " << peers.size_s() << " peers";
break;
}
return true;
}
bool PIPeer::sendToNeighbour(PIPeer::PeerInfo * peer, const PIByteArray & ba) {
//if (peer->_neth == 0) return false;
PIString addr = peer->fastestAddress();
//piCout << "[PIPeer] sendToNeighbour" << peer->name << addr << ba.size_s() << "bytes ...";
//bool ok = peer->_neth->send(peer->_naddress, ba.data(), ba.size_s());
send_mutex.lock();
bool ok = eth_send.send(addr, ba);
//piCout << "[PIPeer] sendToNeighbour" << (ok ? "ok" : "fail");
if (ok) diag_d.sended(ba.size_s());
send_mutex.unlock();
return ok;
}
void PIPeer::sendMBcast(const PIByteArray & ba) {
send_mc_mutex.lock();
// piCout << "sendMBcast" << ba.size() << "bytes ...";
piForeach (PIEthernet * e, eths_mcast) {
//errorClear();
//piCout << "send to" << e->path() << e->sendAddress();// << e->send(ba);
//piCout << PIEthernet::ethErrorString();
if (e->isOpened())
if (e->send(ba))
diag_s.sended(ba.size_s());
}
piForeach (PIEthernet * e, eths_bcast) {
//errorClear();
//piCout << "send to" << e->path() << e->sendAddress();// << e->send(ba);
//piCout << PIEthernet::ethErrorString();
if (e->isOpened())
if (e->send(ba))
diag_s.sended(ba.size_s());
}
for (int p = _PIPEER_LOOPBACK_PORT_S; p <= _PIPEER_LOOPBACK_PORT_E; ++p) {
eth_lo.setSendPort(p);
if (eth_lo.send(ba))
diag_s.sended(ba.size_s());
}
PIVector<PIEthernet * > cl = eth_tcp_srv.clients();
piForeach (PIEthernet * e, cl) {
if (e->isOpened() && e->isConnected())
if (e->send(ba))
diag_s.sended(ba.size_s());
}
if (eth_tcp_cli.isOpened() && eth_tcp_cli.isConnected()) {
if (eth_tcp_cli.send(ba))
diag_s.sended(ba.size_s());
}
// piCout << "send muticast ok";
send_mc_mutex.unlock();
}
void PIPeer::removeNeighbour(const PIString & name) {
piForeach (PeerInfo & p, peers)
p.neighbours.removeOne(name);
self_info.removeNeighbour(name);
}
void PIPeer::addPeer(const PIPeer::PeerInfo & pd) {
peers << pd;
PeerInfo & p(peers.back());
p.init();
CONNECTU(p._data, sendRequest, this, sendInternal)
CONNECTU(p._data, received, this, dtReceived)
}
bool PIPeer::removePeer(const PIString & name) {
for (int i = 0; i < peers.size_s(); ++i)
if (peers[i].name == name) {
peers[i].destroy();
peers.remove(i);
return true;
}
return false;
}
void PIPeer::sendPeerInfo(const PeerInfo & info) {
PIByteArray ba;
ba << int(1) << info.name << info;
sendMBcast(ba);
}
void PIPeer::sendPeerRemove(const PIString & peer) {
PIByteArray ba;
ba << int(2) << peer;
sendMBcast(ba);
}
void PIPeer::pingNeighbours() {
PIMutexLocker ml(peers_mutex);
PIByteArray ba, sba;
ba << int(5) << self_info.name;
// piCoutObj << "*** pingNeighbours" << peers.size() << "...";
piForeach (PeerInfo & p, peers) {
if (!p.isNeighbour()) continue;
//piCout << " ping neighbour" << p.name << p.ping();
send_mutex.lock();
piForeach (PeerInfo::Address & a, p.addresses) {
// piCout << " address" << a.address << a.wait_ping;
if (a.wait_ping) {
if ((PISystemTime::current(true) - a.last_ping).abs().toSeconds() <= _PIPEER_PING_TIMEOUT)
continue;
a.ping = -1.;
}
a.wait_ping = true;
sba = ba;
sba << p.name << a.address << PISystemTime::current(true);
// piCout << "ping" << p.name << a.address << a.last_ping;
if (eth_send.send(a.address, sba))
diag_s.sended(sba.size_s());
}
send_mutex.unlock();
}
//piCout << "*** pingNeighbours" << peers.size() << "done";
}
bool PIPeer::openDevice() {
PIConfig conf(
#ifndef WINDOWS
"/etc/pip.conf"
#else
"pip.conf"
#endif
, PIIODevice::ReadOnly);
server_ip = conf.getValue("peer_server_ip", "");
reinit();
diag_d.reset();
diag_s.reset();
//piCoutObj << "open...";
PIMutexLocker ml(peers_mutex);
if (trust_peer.isEmpty())
return !peers.isEmpty();
return hasPeer(trust_peer);
}
bool PIPeer::closeDevice() {
return false;
}
void PIPeer::syncPeers() {
//piCout << "[PIPeer \"" + self_info.name + "\"] sync " << peers.size_s() << " peers";
PIMutexLocker locker(eth_mutex);
PIString pn;
bool change = false;
PIStringList dpeers;
peers_mutex.lock();
for (int i = 0; i < peers.size_s(); ++i) {
PeerInfo & cp(peers[i]);
if (cp.sync > 3) {
pn = cp.name;
//piCoutObj << "sync: remove " << pn;
cp.destroy();
addToRemoved(cp);
peers.remove(i);
sendPeerRemove(pn);
--i;
removeNeighbour(pn);
dpeers << pn;
change = true;
continue;
}
if (cp.was_update)
cp.sync = 0;
else
cp.sync++;
if (cp._data)
cp._data->setDist(cp.dist + 1);
cp.was_update = false;
}
if (change) buildMap();
self_info.cnt++;
self_info.time = PISystemTime::current();
PIByteArray ba;
ba << int(3) << self_info.name << self_info << peers;
peers_mutex.unlock();
sendMBcast(ba);
piForeachC (PIString & p, dpeers) {
peerDisconnected(p);
peerDisconnectedEvent(p);
}
}
void PIPeer::checkNetwork() {
PIEthernet::InterfaceList ifaces = PIEthernet::interfaces();
if (prev_ifaces == ifaces) return;
prev_ifaces = ifaces;
reinit();
}
void PIPeer::reinit() {
no_timer = true;
// timer.stop();
// timer.clearDelimiters();
PIMutexLocker mbl(mc_mutex);
PIMutexLocker ethl(eth_mutex);
PIMutexLocker pl(peers_mutex);
PIMutexLocker sl(send_mutex);
initNetwork();
sendSelfInfo();
// eth_send.close();
// eth_lo.stopThreadedRead();
// eth_lo.close();
// eth_send.init();
// eth_send.open();
// eth_lo.startThreadedRead();
// timer.addDelimiter(5);
// timer.start(1000);
no_timer = false;
if (!sync_timer.isRunning()) sync_timer.start(1000);
}
void PIPeer::changeName(const PIString &new_name) {
PIString name_ = new_name;
if (name_.isEmpty()) name_ = "rnd_" + PIString::fromNumber(randomi() % 1000);
setName(name_);
self_info.name = name_;
diag_d.setName(name_+"_data");
diag_s.setName(name_+"_service");
}
PIString PIPeer::constructFullPathDevice() const {
PIString ret;
ret << self_info.name << ":" << trustPeerName();
return ret;
}
int PIPeer::readDevice(void *read_to, int max_size) {
read_buffer_mutex.lock();
bool empty = read_buffer.isEmpty();
read_buffer_mutex.unlock();
while (empty) {
read_buffer_mutex.lock();
empty = read_buffer.isEmpty();
read_buffer_mutex.unlock();
piMSleep(10);
}
read_buffer_mutex.lock();
if (!read_buffer.isEmpty()) {
PIByteArray ba = read_buffer.dequeue();
read_buffer_mutex.unlock();
int sz = piMini(ba.size_s(), max_size);
memcpy(read_to, ba.data(), sz);
return sz;
}
read_buffer_mutex.unlock();
return 0;
}
int PIPeer::writeDevice(const void *data, int size) {
if (trust_peer.isEmpty()) {
sendToAll(data, size);
return size;
}
if (send(trust_peer, data, size))
return size;
else return -1;
}
void PIPeer::newTcpClient(PIEthernet *client) {
client->setName("__S__PIPeer_eth_TCP_ServerClient" + client->path());
piCoutObj << "client" << client->path();
CONNECTU(client, threadedReadEvent, this, mbcastRead);
client->startThreadedRead();
}
void PIPeer::configureFromFullPathDevice(const PIString & full_path) {
PIStringList pl = full_path.split(":");
for (int i = 0; i < pl.size_s(); ++i) {
PIString p(pl[i]);
switch (i) {
case 0: changeName(p); break;
case 1: setTrustPeerName(p); break;
}
}
}
void PIPeer::initNetwork() {
// piCoutObj << "initNetwork ...";
eth_send.init();
destroyEths();
destroyMBcasts();
piMSleep(100);
// piCoutObj << self_info.addresses.size();
self_info.addresses.clear();
PIStringList sl = PIEthernet::allAddresses();
initEths(sl);
// piCoutObj << sl << self_info.addresses.size();
sl.removeAll("127.0.0.1");
initMBcasts(sl);
diag_d.start();
diag_s.start();
// piCoutObj << "initNetwork done";
}
void PIPeer::buildMap() {
//piCout << "[PIPeer \"" + name_ + "\"] buildMap";
peers_map.clear();
addresses_map.clear();
piForeach (PeerInfo & i, peers) {
i.trace = -1;
peers_map[i.name] = &i;
}
PIVector<PeerInfo * > cwave, nwave;
int cwi = 0;
self_info.trace = 0;
cwave << &self_info;
while (!cwave.isEmpty()) {
nwave.clear();
++cwi;
piForeachC (PeerInfo * p, cwave) {
piForeachC (PIString & nn, p->neighbours) {
PeerInfo * np = peers_map.value(nn);
if (!np) continue;
if (np->trace >= 0) continue;
np->trace = cwi;
nwave << np;
}
}
cwave = nwave;
}
PIVector<PeerInfo * > cpath;
piForeach (PeerInfo & c, peers) {
cpath.clear();
cpath << &c;
cwave << &c;
for (int w = c.trace - 1; w >= 0; --w) {
nwave.clear();
piForeachC (PeerInfo * p, cwave) {
piForeachC (PIString & nn, p->neighbours) {
PeerInfo * np = peers_map.value(nn);
if (!np) continue;
if (np->trace != w) continue;
cpath << np;
nwave << np;
}
}
cwave = nwave;
}
addresses_map[c.name] = cpath;
//piCout << "map" << c.name << "=" << cpath;
}
}
void PIPeer::tcpClientReconnect() {
eth_tcp_cli.connect(server_ip, _PIPEER_TCP_PORT);
}

229
src_main/io/pipeer.h Executable file
View File

@@ -0,0 +1,229 @@
/*! \file pipeer.h
* \brief Peering net node
*/
/*
PIP - Platform Independent Primitives
Peer - named I/O ethernet node, forming self-organized peering network
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIPEER_H
#define PIPEER_H
#include "piethernet.h"
#include "pidiagnostics.h"
#include "pidatatransfer.h"
class PIP_EXPORT PIPeer: public PIIODevice
{
PIIODEVICE(PIPeer)
private:
class PeerData: public PIObject {
PIOBJECT_SUBCLASS(PeerData, PIObject)
public:
PeerData(const PIString & n);
~PeerData();
EVENT_HANDLER1(void, dtSendRequestIn, PIByteArray &, data) {data.push_front(uchar(2)); sendRequest(name(), data);}
EVENT_HANDLER1(void, dtSendRequestOut, PIByteArray &, data) {data.push_front(uchar(3)); sendRequest(name(), data);}
EVENT_HANDLER1(void, dtReceiveFinishedIn, bool, ok) {if (ok) received(name(), dt_in.data());}
EVENT_HANDLER1(void, dtReceiveFinishedOut, bool, ok) {if (ok) received(name(), dt_out.data());}
EVENT_HANDLER(void, dtThread);
EVENT2(received, const PIString &, from, const PIByteArray &, data)
EVENT2(sendRequest, const PIString &, to, const PIByteArray &, data)
bool send(const PIByteArray & d);
void receivedPacket(uchar type, const PIByteArray & d);
void setDist(int dist);
PIByteArray data;
PIThread t;
PIDataTransfer dt_in, dt_out;
};
public:
explicit PIPeer(const PIString & name = PIString());
virtual ~PIPeer();
class PeerInfo {
friend class PIPeer;
friend PIByteArray & operator <<(PIByteArray & s, const PIPeer::PeerInfo & v);
friend PIByteArray & operator >>(PIByteArray & s, PIPeer::PeerInfo & v);
public:
PeerInfo() {dist = sync = cnt = 0; trace = -1; was_update = false; _data = 0;}
~PeerInfo() {}
struct Address {
Address(const PIString & a = PIString(), const PIString & m = "255.255.255.0");
bool isAvailable() const {return ping > 0;}
//inline const Address & operator =(const Address & v) {address = v.address; netmask = v.netmask; piCout << "!!!!!!!!!" << last_ping; return *this;}
PIString address;
PIString netmask;
double ping; // ms
bool wait_ping;
PISystemTime last_ping;
};
PIString name;
PIVector<Address> addresses;
int dist;
PIStringList neighbours;
bool isNeighbour() const {return dist == 0;}
int ping() const;
PIString fastestAddress() const;
protected:
void addNeighbour(const PIString & n) {if (!neighbours.contains(n)) neighbours << n;}
void addNeighbours(const PIStringList & l) {piForeachC (PIString & n, l) if (!neighbours.contains(n)) neighbours << n;}
void removeNeighbour(const PIString & n) {neighbours.removeAll(n);}
void resetPing() {for (int i = 0; i < addresses.size_s(); ++i) addresses[i].ping = -1;}
void init();
void destroy();
int sync, cnt, trace;
bool was_update;
PISystemTime time;
PeerData * _data;
};
friend PIByteArray & operator <<(PIByteArray & s, const PIPeer::PeerInfo & v);
friend PIByteArray & operator >>(PIByteArray & s, PIPeer::PeerInfo & v);
bool send(const PIString & to, const PIByteArray & data) {return send(to, data.data(), data.size_s());}
bool send(const PIString & to, const PIString & data) {return send(to, data.data(), data.size_s());}
bool send(const PIString & to, const void * data, int size);
bool send(const PeerInfo & to, const PIByteArray & data) {return send(to.name, data.data(), data.size_s());}
bool send(const PeerInfo & to, const PIString & data) {return send(to.name, data.data(), data.size_s());}
bool send(const PeerInfo & to, const void * data, int size) {return send(to.name, data, size);}
bool send(const PeerInfo * to, const PIByteArray & data) {if (to == 0) return false; return send(to->name, data.data(), data.size_s());}
bool send(const PeerInfo * to, const PIString & data) {if (to == 0) return false; return send(to->name, data.data(), data.size_s());}
bool send(const PeerInfo * to, const void * data, int size) {if (to == 0) return false; return send(to->name, data, size);}
void sendToAll(const PIByteArray & data) {piForeachC (PeerInfo & i, peers) send(i.name, data.data(), data.size_s());}
void sendToAll(const PIString & data) {piForeachC (PeerInfo & i, peers) send(i.name, data.data(), data.size_s());}
void sendToAll(const void * data, int size) {piForeachC (PeerInfo & i, peers) send(i.name, data, size);}
bool isMulticastReceive() const {return !eths_mcast.isEmpty();}
bool isBroadcastReceive() const {return !eths_bcast.isEmpty();}
PIDiagnostics & diagnosticService() {return diag_s;}
PIDiagnostics & diagnosticData() {return diag_d;}
const PIVector<PIPeer::PeerInfo> & allPeers() const {return peers;}
bool isPeerExists(const PIString & name) const {return getPeerByName(name) != 0;}
const PeerInfo * getPeerByName(const PIString & name) const {return peers_map.value(name, 0);}
const PeerInfo & selfInfo() const {return self_info;}
const PIMap<PIString, PIVector<PeerInfo * > > & _peerMap() const {return addresses_map;}
void reinit();
void lock() {peers_mutex.lock();}
void unlock() {peers_mutex.unlock();}
void changeName(const PIString & new_name);
const PIString & trustPeerName() const {return trust_peer;}
void setTrustPeerName(const PIString & peer_name) {trust_peer = peer_name;}
void setTcpServerIP(const PIString & ip) {server_ip = ip; tcpClientReconnect();}
EVENT2(dataReceivedEvent, const PIString &, from, const PIByteArray &, data)
EVENT1(peerConnectedEvent, const PIString &, name)
EVENT1(peerDisconnectedEvent, const PIString &, name)
protected:
virtual void dataReceived(const PIString & from, const PIByteArray & data) {;}
virtual void peerConnected(const PIString & name) {;}
virtual void peerDisconnected(const PIString & name) {;}
EVENT_HANDLER2(bool, dataRead, uchar *, readed, int, size);
EVENT_HANDLER2(bool, mbcastRead, uchar *, readed, int, size);
private:
EVENT_HANDLER2(void, timerEvent, void * , data, int, delim);
EVENT_HANDLER2(bool, sendInternal, const PIString &, to, const PIByteArray &, data);
EVENT_HANDLER2(void, dtReceived, const PIString &, from, const PIByteArray &, data);
EVENT_HANDLER1(void, newTcpClient, PIEthernet * , client);
EVENT_HANDLER(void, tcpClientReconnect);
bool hasPeer(const PIString & name) {piForeachC (PeerInfo & i, peers) if (i.name == name) return true; return false;}
bool removePeer(const PIString & name);
void removeNeighbour(const PIString & name);
void addPeer(const PeerInfo & pd);
void sendPeerInfo(const PeerInfo & info);
void sendPeerRemove(const PIString & peer);
void sendSelfInfo() {sendPeerInfo(self_info);}
void sendSelfRemove() {sendPeerRemove(self_info.name);}
void syncPeers();
void checkNetwork();
void initNetwork();
void buildMap();
void initEths(PIStringList al);
void initMBcasts(PIStringList al);
void destroyEths();
void destroyMBcasts();
void sendMBcast(const PIByteArray & ba);
void pingNeighbours();
void addToRemoved(const PeerInfo & pi) {removed[pi.name] = PIPair<int, PISystemTime>(pi.cnt, pi.time);}
bool isRemoved(const PeerInfo & pi) const {return (removed.value(pi.name) == PIPair<int, PISystemTime>(pi.cnt, pi.time));}
bool openDevice();
bool closeDevice();
PIString fullPathPrefix() const {return "peer";}
PIString constructFullPathDevice() const;
void configureFromFullPathDevice(const PIString &full_path);
int readDevice(void * read_to, int max_size);
int writeDevice(const void * data, int size);
PeerInfo * quickestPeer(const PIString & to);
bool sendToNeighbour(PeerInfo * peer, const PIByteArray & ba);
inline static bool isPeerRecent(const PeerInfo & my, const PeerInfo & income) {return (my.cnt < income.cnt) || (my.time < income.time);}
// 1 - new peer, 2 - remove peer, 3 - sync peers, 4 - data, 5 - ping request, 6 - ping reply
// Data packet: 4, from, to, ticks, data_size, data
protected:
bool inited__; //for internal use
PIMutex mc_mutex, eth_mutex, peers_mutex, send_mutex, send_mc_mutex;
private:
PIVector<PIEthernet * > eths_traffic, eths_mcast, eths_bcast;
PIEthernet::InterfaceList prev_ifaces;
PIEthernet eth_send, eth_lo, eth_tcp_srv, eth_tcp_cli;
PITimer sync_timer;
PeerInfo self_info;
PIVector<PeerInfo> peers;
PIMap<PIString, PeerInfo * > peers_map;
PIMap<PIString, PIVector<PeerInfo * > > addresses_map; // map {"to" = list of nearest peers}
PIMap<PIString, PIPair<int, PISystemTime> > removed;
PIDiagnostics diag_s, diag_d;
bool destroyed, no_timer;
PIString trust_peer;
PIString server_ip;
PIMutex read_buffer_mutex;
PIQueue<PIByteArray> read_buffer;
int read_buffer_size;
};
inline PICout operator <<(PICout c, const PIPeer::PeerInfo::Address & v) {c.space(); c << "PeerAddress(" << v.address << ", " << v.netmask << ", " << v.ping << ")"; return c;}
inline PICout operator <<(PICout c, const PIPeer::PeerInfo & v) {c.space(); c << "PeerInfo(" << v.name << ", " << v.dist << ", " << v.addresses << ")"; return c;}
inline PIByteArray & operator <<(PIByteArray & s, const PIPeer::PeerInfo::Address & v) {s << v.address << v.netmask << v.ping; return s;}
inline PIByteArray & operator >>(PIByteArray & s, PIPeer::PeerInfo::Address & v) {s >> v.address >> v.netmask >> v.ping; return s;}
inline PIByteArray & operator <<(PIByteArray & s, const PIPeer::PeerInfo & v) {s << v.name << v.addresses << v.dist << v.neighbours << v.cnt << v.time; return s;}
inline PIByteArray & operator >>(PIByteArray & s, PIPeer::PeerInfo & v) {s >> v.name >> v.addresses >> v.dist >> v.neighbours >> v.cnt >> v.time; return s;}
#endif // PIPEER_H

718
src_main/io/piprotocol.cpp Executable file
View File

@@ -0,0 +1,718 @@
/*
PIP - Platform Independent Primitives
Protocol, input/output channel (COM, UDP)
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru, Bychkov Andrey wapmobil@gmail.com
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, either version 3 of the License, or
(at your option) any later version.
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 "piprotocol.h"
/** \class PIProtocol
* \brief
* \details
* \section PIProtocol_sec0 Synopsis
*
*
*
* */
/// DEPRECATED
PIProtocol::PIProtocol(const PIString & config, const PIString & name_, void * recHeaderPtr, int recHeaderSize, void * recDataPtr, int recDataSize, void * sendDataPtr_, int sendDataSize_): PIObject() {
init();
protName = name_;
PIObject::setName(name_);
PIConfig conf(config, PIIODevice::ReadOnly);
if (!conf.isOpened()) {
piCoutObj << "Can`t open \"" << config << "\"!";
devReceiverState = devSenderState = "Config error";
return;
}
PIConfig::Entry & b(conf.getValue(name_)),
& rb(b.getValue("receiver")),
& sb(b.getValue("sender"));
init_receiver(b, rb, config);
init_sender(b, sb, config);
headerPtr = (uchar * )recHeaderPtr;
headerSize = recHeaderSize;
dataPtr = (uchar * )recDataPtr;
dataSize = recDataSize;
sendDataPtr = (uchar * )sendDataPtr_;
sendDataSize = sendDataSize_;
packet_ext->setHeader(PIByteArray(recHeaderPtr, recHeaderSize));
packet_ext->setPayloadSize(recDataSize);
packet_ext->setPacketSize(recDataSize);
packet_ext->setSplitMode(PIPacketExtractor::Header);
bool null_h = (recHeaderPtr == 0 || recHeaderSize == 0), null_d = (recDataPtr == 0 || recDataSize == 0);
if (null_h && null_d) packet_ext->setSplitMode(PIPacketExtractor::None);
else {
if (null_h) packet_ext->setSplitMode(PIPacketExtractor::Size);
}
}
PIProtocol::~PIProtocol() {
delete diagTimer;
delete sendTimer;
delete secTimer;
delete packet_ext;
if (eth != 0) delete eth;
if (ser != 0) delete ser;
}
void PIProtocol::init() {
packet_ext = new PIPacketExtractor(0, PIPacketExtractor::None);
packet_ext->setThreadedReadData(this);
packet_ext->setThreadedReadSlot(receiveEvent);
packet_ext->setHeaderCheckSlot(headerValidateEvent);
packet_ext->setName("__S__PIProtocol::packet_ext");
work = new_mp_prot = false;
eth = 0;
ser = 0;
ret_func = 0;
mp_owner = 0;
net_diag = PIProtocol::Unknown;
cur_pckt = 0;
packets[0] = packets[1] = pckt_cnt = pckt_cnt_max = 0;
diagTimer = 0;
timeout_ = 3.f;
sendTimer = new PITimer(sendEvent, this);
diagTimer = new PITimer(diagEvent, this);
secTimer = new PITimer(secEvent, this);
sendTimer->setName("__S__PIProtocol::sendTimer");
diagTimer->setName("__S__PIProtocol::diagTimer");
secTimer->setName("__S__PIProtocol::secTimer");
wrong_count = receive_count = send_count = missed_count = 0;
packets_in_sec = packets_out_sec = bytes_in_sec = bytes_out_sec = 0;
immediate_freq = integral_freq = ifreq = 0.f;
headerPtr = dataPtr = sendDataPtr = 0;
headerSize = dataSize = sendDataSize = 0;
type_rec = type_send = PIProtocol::None;
devSenderState = devReceiverState = "Unknown";
devSenderName = devReceiverName = "no device";
secTimer->start(1000.);
/*addEvent("receiver started");
addEvent("receiver stopped");
addEvent("sender started");
addEvent("sender stopped");
addEvent<bool>("received");
addEvent<PIProtocol::Quality>("quality changed");
addEventHandler<float>(HANDLER(PIProtocol, startReceive));
addEventHandler<float>(HANDLER(PIProtocol, startSend));
addEventHandler(HANDLER(PIProtocol, start));
addEventHandler(HANDLER(PIProtocol, stopReceive));
addEventHandler(HANDLER(PIProtocol, stopSend));
addEventHandler(HANDLER(PIProtocol, stop));*/
}
void PIProtocol::init_sender(PIConfig::Entry & b, PIConfig::Entry & sb, const PIString & config) {
int ps, gps;
bool ok, gok, flag, gflag, has_dev = false;
float freq, gfreq;
PIFlags<PISerial::Parameters> pp(0);
PIString dev, gdev;
if (sb.isEntryExists("ip") && sb.isEntryExists("device")) {
piCoutObj << "Ambiguous sender type in \"" << config << "\"!";
devSenderState = "Config error";
return;
}
dev = sb.getValue("ip", "", &ok);
gdev = b.getValue("ip", "", &gok);
has_dev = false;
if (ok || gok) {
if (gok && !ok) dev = gdev;
if (gok && ok && (dev != gdev)) {
piCoutObj << "Ambiguous sender type in \"" << config << "\"!";
devSenderState = "Config error";
return;
}
ps = sb.getValue("port", 0, &ok);
gps = b.getValue("port", 0, &gok);
if (ok || gok) {
if (gok && !ok) ps = gps;
if (gok && ok && (ps != gps)) {
piCoutObj << "Ambiguous send port in \"" << config << "\"!";
devSenderState = "Config error";
return;
}
type_send = PIProtocol::Ethernet;
if (eth == 0) eth = new PIEthernet();
eth->setName("__S__PIProtocol::eth");
setSenderAddress(dev, ps);
//setReceiverAddress(dev, ps);
has_dev = true;
flag = sb.getValue("reconnectEnabled", true, &ok);
gflag = b.getValue("reconnectEnabled", true, &gok);
if (ok || gok) {
if (gok && !ok) flag = gflag;
if (gok && ok && (flag != gflag)) {
piCoutObj << "Ambiguous \"reconnectEnabled\" flag in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
eth->setReopenEnabled(flag);
}
freq = sb.getValue("reconnectTimeout", 1., &ok);
gfreq = b.getValue("reconnectTimeout", 1., &gok);
if (ok || gok) {
if (gok && !ok) freq = gfreq;
if (gok && ok && (freq != gfreq)) {
piCoutObj << "Ambiguous \"reconnectTimeout\" value in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
eth->setReopenTimeout(freq * 1000);
}
/*if (sendDataPtr_ == 0)
piCoutObj << "Warning: null send data pointer!";
if (sendDataSize_ == 0)
piCoutObj << "Warning: null send data size!";*/
} else {
piCoutObj << "Can`t find \"" << name() << ".sender.port\" or \"" << name() << ".port\" in \"" << config << "\"!";
devSenderState = "Config error";
return;
}
}
dev = sb.getValue("device", "", &ok);
gdev = b.getValue("device", "", &gok);
if (ok || gok) {
if (gok && !ok) dev = gdev;
if (gok && ok && (dev != gdev)) {
piCoutObj << "Ambiguous sender type in \"" << config << "\"!";
devSenderState = "Config error";
return;
}
ps = sb.getValue("speed", 0, &ok);
gps = b.getValue("speed", 0, &gok);
if (ok || gok) {
if (gok && !ok) ps = gps;
if (gok && ok && (ps != gps)) {
piCoutObj << "Ambiguous send \"speed\" in \"" << config << "\"!";
devSenderState = "Config error";
return;
}
flag = sb.getValue("parity", false, &ok);
gflag = b.getValue("parity", false, &gok);
if (ok || gok) {
if (gok && !ok) flag = gflag;
if (gok && ok && (flag != gflag)) {
piCoutObj << "Ambiguous send \"parity\" in \"" << config << "\"!";
devSenderState = "Config error";
return;
}
pp.setFlag(PISerial::ParityControl, flag);
}
flag = sb.getValue("twoStopBits", false, &ok);
gflag = b.getValue("twoStopBits", false, &gok);
if (ok || gok) {
if (gok && !ok) flag = gflag;
if (gok && ok && (flag != gflag)) {
piCoutObj << "Ambiguous send \"twoStopBits\" parity in \"" << config << "\"!";
devSenderState = "Config error";
return;
}
pp.setFlag(PISerial::TwoStopBits, flag);
}
} else {
piCoutObj << "Can`t find \"" << name() << ".sender.speed\" or \"" << name() << ".speed\" in \"" << config << "\"!";
devSenderState = "Config error";
return;
}
type_send = PIProtocol::Serial;
if (ser == 0) ser = new PISerial(dev);
ser->setName("__S__PIProtocol::ser");
setSenderDevice(dev, (PISerial::Speed)ps);
ser->setOutSpeed((PISerial::Speed)ps);
ser->setParameters(pp);
has_dev = true;
/*if (sendDataPtr_ == 0)
piCoutObj << "Warning: null send data pointer!";
if (sendDataSize_ == 0)
piCoutObj << "Warning: null send data size!";*/
}
freq = sb.getValue("frequency", -1.f, &ok);
gfreq = b.getValue("frequency", -1.f, &gok);
if (gok && !ok) freq = gfreq;
if (gok && ok && (freq != gfreq)) {
piCoutObj << "Ambiguous sender frequency in \"" << config << "\"!";
devSenderState = "Config error";
return;
}
if (freq > 0.f && !has_dev)
piCoutObj << "Warning: no sender device and not null send frequency!";
setSenderFrequency(freq);
}
void PIProtocol::init_receiver(PIConfig::Entry & b, PIConfig::Entry & rb, const PIString & config) {
int ps, gps;
bool ok, gok, flag, gflag, has_dev = false;
float freq, gfreq;
PIFlags<PISerial::Parameters> pp(0);
PIString dev, gdev;
if (rb.isEntryExists("ip") && rb.isEntryExists("device")) {
piCoutObj << "Ambiguous receiver type in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
dev = rb.getValue("ip", "", &ok);
gdev = b.getValue("ip", "", &gok);
if (ok || gok) {
if (gok && !ok) dev = gdev;
if (gok && ok && (dev != gdev)) {
piCoutObj << "Ambiguous receiver type in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
ps = rb.getValue("port", 0, &ok);
gps = b.getValue("port", 0, &gok);
if (ok || gok) {
if (gok && !ok) ps = gps;
if (gok && ok && (ps != gps)) {
piCoutObj << "Ambiguous receive port in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
type_rec = PIProtocol::Ethernet;
eth = new PIEthernet();
eth->setName("__S__PIProtocol::eth");
packet_ext->setDevice(eth);
//setSenderAddress(dev, ps);
setReceiverAddress(dev, ps);
has_dev = true;
flag = rb.getValue("reconnectEnabled", true, &ok);
gflag = b.getValue("reconnectEnabled", true, &gok);
if (ok || gok) {
if (gok && !ok) flag = gflag;
if (gok && ok && (flag != gflag)) {
piCoutObj << "Ambiguous \"reconnectEnabled\" flag in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
eth->setReopenEnabled(flag);
}
freq = rb.getValue("reconnectTimeout", 1., &ok);
gfreq = b.getValue("reconnectTimeout", 1., &gok);
if (ok || gok) {
if (gok && !ok) freq = gfreq;
if (gok && ok && (freq != gfreq)) {
piCoutObj << "Ambiguous \"reconnectTimeout\" value in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
eth->setReopenTimeout(freq * 1000);
}
/*if (recDataPtr == 0)
piCoutObj << "Warning: null receive data pointer!";
if (recDataSize == 0)
piCoutObj << "Warning: null receive data size!";*/
} else {
piCoutObj << "Can`t find \"" << name() << ".receiver.port\" or \"" << name() << ".port\" in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
}
dev = rb.getValue("device", "", &ok);
gdev = b.getValue("device", "", &gok);
if (ok || gok) {
if (gok && !ok) dev = gdev;
if (gok && ok && (dev != gdev)) {
piCoutObj << "Ambiguous receiver type in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
ps = rb.getValue("speed", 0, &ok);
gps = b.getValue("speed", 0, &gok);
if (ok || gok) {
if (gok && !ok) ps = gps;
if (gok && ok && (ps != gps)) {
piCoutObj << "Ambiguous receive \"speed\" in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
flag = rb.getValue("parity", false, &ok);
gflag = b.getValue("parity", false, &gok);
if (ok || gok) {
if (gok && !ok) flag = gflag;
if (gok && ok && (flag != gflag)) {
piCoutObj << "Ambiguous receive \"parity\" in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
pp.setFlag(PISerial::ParityControl, flag);
}
flag = rb.getValue("twoStopBits", false, &ok);
gflag = b.getValue("twoStopBits", false, &gok);
if (ok || gok) {
if (gok && !ok) flag = gflag;
if (gok && ok && (flag != gflag)) {
piCoutObj << "Ambiguous receive \"twoStopBits\" parity in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
pp.setFlag(PISerial::TwoStopBits, flag);
}
type_rec = PIProtocol::Serial;
type_send = PIProtocol::Serial;
ser = new PISerial(dev);
ser->setName("__S__PIProtocol::ser");
packet_ext->setDevice(ser);
//setSenderDevice(dev, (PISerial::Speed)ps);
setReceiverDevice(dev, (PISerial::Speed)ps);
ser->setInSpeed((PISerial::Speed)ps);
ser->setParameters(pp);
ps = rb.getValue("vtime", 1, &ok);
gps = b.getValue("vtime", 1, &gok);
if (ok || gok) {
if (gok && !ok) ps = gps;
if (gok && ok && (ps != gps)) {
piCoutObj << "Ambiguous receive \"vtime\" in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
ser->setVTime(ps);
}
has_dev = true;
/*if (recDataPtr == 0)
piCoutObj << "Warning: null receive data pointer!";
if (recDataSize == 0)
piCoutObj << "Warning: null receive data size!";*/
} else {
piCoutObj << "Can`t find \"" << name() << ".receiver.speed\" or \"" << name() << ".speed\" in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
}
freq = rb.getValue("frequency", -1.f, &ok);
gfreq = b.getValue("frequency", -1.f, &gok);
if (gok && !ok) freq = gfreq;
if (gok && ok && (freq != gfreq)) {
piCoutObj << "Ambiguous expected frequency in \"" << config << "\"!";
devReceiverState = "Config error";
return;
}
if (freq > 0.f && !has_dev)
piCoutObj << "Warning: no receiver device and not null expected frequency!";
float tm = b.getValue("disconnectTimeout", 3.f);
if (tm <= 0.f)
piCoutObj << "Warning: diconnect timeout <= 0 s!";
timeout_ = (tm < 0.f) ? 0.f : tm;
setExpectedFrequency(freq);
}
void PIProtocol::setReceiverDevice(const PIString & device, PISerial::Speed speed, bool force) {
if (force) {
type_send = type_rec = PIProtocol::Serial;
if (ser == 0) {
ser = new PISerial();
ser->setName("__S__PIProtocol::ser");
packet_ext->setDevice(ser);
}
}
if (type_rec == PIProtocol::Serial && ser != 0) {
ser->setDevice(device);
ser->setSpeed(speed);
devReceiverName = device;
devSenderName = device;
}
}
void PIProtocol::setReceiverAddress(const PIString & ip, int port, bool force) {
if (force) {
type_rec = PIProtocol::Ethernet;
if (eth == 0) {
eth = new PIEthernet();
eth->setName("__S__PIProtocol::eth");
packet_ext->setDevice(eth);
}
}
if (type_rec == PIProtocol::Ethernet && eth != 0) {
eth->setReadAddress(ip, port);
if (ip.trimmed().isEmpty()) devReceiverName = "no ip";
else devReceiverName = ip + ":" + PIString::fromNumber(port);
}
}
void PIProtocol::setSenderDevice(const PIString & device, PISerial::Speed speed, bool force) {
if (force) {
type_send = type_rec = PIProtocol::Serial;
if (ser == 0) ser = new PISerial();
ser->setName("__S__PIProtocol::ser");
}
if (type_send == PIProtocol::Serial && ser != 0) {
ser->setDevice(device);
ser->setSpeed(speed);
ser->open();
devSenderName = device;
}
}
void PIProtocol::setSenderAddress(const PIString & ip, int port, bool force) {
if (force) {
type_send = PIProtocol::Ethernet;
if (eth == 0) eth = new PIEthernet();
eth->setName("__S__PIProtocol::eth");
}
if (type_send == PIProtocol::Ethernet && eth != 0) {
eth->setSendAddress(ip, port);
if (ip.isEmpty()) devSenderName = "no ip";
else devSenderName = ip + ":" + PIString::fromNumber(port);
}
}
void PIProtocol::setSenderIP(const PIString & ip, bool force) {
if (force) {
type_send = PIProtocol::Ethernet;
if (eth == 0) eth = new PIEthernet();
}
if (type_send == PIProtocol::Ethernet && eth != 0) {
eth->setSendIP(ip);
if (ip.isEmpty()) devSenderName = "no ip";
else devSenderName = ip + ":" + PIString::fromNumber(eth->sendPort());
}
}
void PIProtocol::setSenderPort(int port, bool force) {
if (force) {
type_send = PIProtocol::Ethernet;
if (eth == 0) eth = new PIEthernet();
eth->setName("__S__PIProtocol::eth");
}
if (type_send == PIProtocol::Ethernet && eth != 0) {
eth->setSendPort(port);
if (eth->sendIP().isEmpty()) devSenderName = "no ip";
else devSenderName = eth->sendIP() + ":" + PIString::fromNumber(port);
}
}
void PIProtocol::setExpectedFrequency(float frequency) {
exp_freq = frequency;
changeDisconnectTimeout();
}
void PIProtocol::changeDisconnectTimeout() {
pckt_cnt_max = int(piRound(timeout_ * exp_freq));
if (pckt_cnt_max < 3) pckt_cnt_max = 3;
last_packets.resize(pckt_cnt_max);
}
void PIProtocol::startReceive(float exp_frequency) {
if (exp_frequency > 0.f) exp_freq = exp_frequency;
//if (type_rec == PIProtocol::Serial) ser->start();
//if (type_rec == PIProtocol::Ethernet) eth->start();
packet_ext->startThreadedRead();
msleep(1);
check_state();
if (exp_freq <= 0.f) return;
setExpectedFrequency(exp_freq);
diagTimer->start(1000. / exp_freq);
diag_tm.reset();
receiverStarted();
}
void PIProtocol::startSend(float frequency) {
//cout << "** start send " << send_freq << ", " << frequency << endl;
if (frequency > 0.f) send_freq = frequency;
msleep(1);
check_state();
if (send_freq <= 0.f) return;
sendTimer->start(1000. / send_freq);
diag_tm.reset();
senderStarted();
}
void PIProtocol::stopReceive() {
//if (type_rec == PIProtocol::Serial) ser->stop();
//if (type_rec == PIProtocol::Ethernet) eth->stop();
packet_ext->stop();
diagTimer->stop();
receiverStopped();
}
bool PIProtocol::receiveEvent(void * t, uchar * data, int size) {
PIProtocol * p = (PIProtocol * )t;
if (!p->receive(data, size)) return false;
p->work = true;
//p->lock();
if (p->validate()) {
p->received(true);
//p->unlock();
p->ifreq = p->diag_tm.elapsed_m();
if (p->ifreq > 0.) p->ifreq = 1000. / p->ifreq;
p->diag_tm.reset();
p->receive_count++;
p->packets_in_sec++;
p->bytes_in_sec += size;
p->cur_pckt = 1;
if (p->ret_func != 0) p->ret_func(p);
if (p->mp_owner != 0) PIMultiProtocolBase::receiveEvent(p->mp_owner, p, true, data, size);
return true;
}
p->received(false);
//p->unlock();
p->wrong_count++;
if (p->mp_owner != 0) PIMultiProtocolBase::receiveEvent(p->mp_owner, p, false, data, size);
return false;
}
void PIProtocol::diagEvent(void * t, int) {
PIProtocol * p = (PIProtocol * )t;
p->calc_freq();
p->calc_diag();
p->check_state();
if (p->ser != 0) p->missed_count = p->packet_ext->missedPackets();
}
void PIProtocol::secEvent(void * t, int ) {
PIProtocol * p = (PIProtocol * )t;
p->speedIn = PIString::readableSize(p->bytes_in_sec) + "/s";
p->speedOut = PIString::readableSize(p->bytes_out_sec) + "/s";
p->bytes_in_sec = p->bytes_out_sec = p->packets_in_sec = p->packets_out_sec = 0;
if (p->ser != 0) p->missed_count = p->packet_ext->missedPackets();
}
void PIProtocol::calc_diag() {
PIProtocol::Quality diag;
if (!work) {
diag = PIProtocol::Unknown;
return;
}
if (pckt_cnt < pckt_cnt_max) {
last_packets[pckt_cnt] = cur_pckt;
pckt_cnt++;
} else {
packets[(int)last_packets.back()]--;
if (!last_packets.isEmpty()) last_packets.pop_back();
last_packets.push_front(cur_pckt);
}
packets[(int)cur_pckt]++;
cur_pckt = 0;
float good_percents;
good_percents = (float)packets[1] / pckt_cnt * 100.f;
if (good_percents == 0.f) diag = PIProtocol::Failure;
else if (good_percents <= 20.f) diag = PIProtocol::Bad;
else if (good_percents > 20.f && good_percents <= 80.f) diag = PIProtocol::Average;
else diag = PIProtocol::Good;
if (diag != net_diag) {
qualityChanged(diag, net_diag);
net_diag = diag;
}
}
void PIProtocol::calc_freq() {
float tf;// = float(1000.f / diagTimer->elapsed_m());
tf = immediate_freq = ifreq;
ifreq = 0.f;
if (last_freq.size_s() >= pckt_cnt_max && last_freq.size_s() > 0) last_freq.pop_front();
last_freq.push_back(tf);
tf = last_freq[0];
for (uint i = 1; i < last_freq.size(); ++i)
tf += last_freq[i];
integral_freq = tf / last_freq.size();
}
void PIProtocol::check_state() {
if (type_rec == PIProtocol::Serial) {
if (ser != 0) {
if (ser->isOpened()) devReceiverState = "Opened";
else devReceiverState = "Not opened";
}
else devReceiverState = "Not exists";
}
if (type_rec == PIProtocol::Ethernet) {
if (eth != 0) {
if (eth->isOpened()) devReceiverState = "Opened";
else devReceiverState = "Not opened";
}
else devReceiverState = "Not exists";
}
if (type_send == PIProtocol::Serial) {
if (ser != 0) {
if (ser->isOpened()) devSenderState = "Opened";
else devSenderState = "Not opened";
}
else devSenderState = "Not exists";
}
if (type_send == PIProtocol::Ethernet) {
if (eth != 0) {
if (eth->isOpened()) devSenderState = "Opened";
else devSenderState = "Not opened";
}
else devSenderState = "Not exists";
}
}
void PIProtocol::send(const void * data, int size, bool direct) {
if (!direct) {
if (data == 0 || size == 0) return;
if (!aboutSend()) return;
}
if (type_send == PIProtocol::Serial)
if (ser->send(data, size)) {
send_count++;
packets_out_sec++;
bytes_out_sec += size;
}
if (type_send == PIProtocol::Ethernet)
if (eth->send(data, size)) {
send_count++;
packets_out_sec++;
bytes_out_sec += size;
}
}
void PIProtocol::send() {
//lock();
//memcpy(packet, sendDataPtr, sendDataSize);
//unlock();
if (!aboutSend()) return;
if (sendDataPtr == 0 || sendDataSize == 0) return;
if (type_send == PIProtocol::Serial)
if (ser->send(sendDataPtr, sendDataSize)) {
send_count++;
packets_out_sec++;
bytes_out_sec += sendDataSize;
}
if (type_send == PIProtocol::Ethernet)
if (eth->send(sendDataPtr, sendDataSize)) {
send_count++;
packets_out_sec++;
bytes_out_sec += sendDataSize;
}
}

238
src_main/io/piprotocol.h Executable file
View File

@@ -0,0 +1,238 @@
/*! \file piprotocol.h
* \brief Highly configurable from file I/O channel
*/
/*
PIP - Platform Independent Primitives
Protocol, input/output channel (COM, UDP)
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru, Bychkov Andrey wapmobil@gmail.com
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIPROTOCOL_H
#define PIPROTOCOL_H
#include "piserial.h"
#include "piethernet.h"
#include "pipacketextractor.h"
#include "pitimer.h"
#include "piconfig.h"
#include "pifile.h"
class PIProtocol; /// DEPRECATED
class PIP_EXPORT PIMultiProtocolBase: protected PIObject /// DEPRECATED
{
PIOBJECT_SUBCLASS(PIMultiProtocolBase, PIObject)
friend class PIProtocol;
public:
PIMultiProtocolBase() {;} /// DEPRECATED
virtual ~PIMultiProtocolBase() {;}
protected:
virtual void received(PIProtocol * prot, bool corrected, uchar * data, int size) {;}
private:
static void receiveEvent(PIMultiProtocolBase * p, PIProtocol * prot, bool corrected, uchar * data, int size) {p->mutex_receive.lock(); p->received(prot, corrected, data, size); p->mutex_receive.unlock();}
PIMutex mutex_receive;
};
typedef void (*ReceiveFunc)(void * );
/// events:
/// void receiverStarted()
/// void receiverStopped()
/// void senderStarted()
/// void senderStopped()
/// void received(bool validate_is_ok)
/// void qualityChanged(PIProtocol::Quality old_quality, PIProtocol::Quality new_quality)
///
/// handlers:
/// void startReceive(float exp_frequency = -1.f)
/// void stopReceive()
/// void startSend(float frequency = -1.f)
/// void stopSend()
/// void start()
/// void stop()
/// void send()
/// void send(const void * data, int size, bool direct = false)
class PIP_EXPORT PIProtocol: public PIObject /// DEPRECATED
{
PIOBJECT_SUBCLASS(PIProtocol, PIObject)
friend class PIMultiProtocolBase;
friend class PIMultiProtocol;
enum Type {None, Serial, Ethernet};
public:
//! Contructs an empty unconfigured protocol
PIProtocol(): PIObject() {init();} /// DEPRECATED
//! Contructs protocol configured from file "config", config file section "name"
PIProtocol(const PIString & config, const PIString & name, void * recHeaderPtr = 0, int recHeaderSize = 0,
void * recDataPtr = 0, int recDataSize = 0, void * sendDataPtr = 0, int sendDataSize = 0); // from config
virtual ~PIProtocol();
//! Connection quality
enum Quality {
Unknown /** Unknown, no one packet received yet */ = 1,
Failure /** No connection, no one correct packet received for last period */ = 2,
Bad /** Bad connection, correct packets received <= 20% */ = 3,
Average /** Average connection, correct packets received > 20% and <= 80% */ = 4,
Good /** Good connection, correct packets received > 80% */ = 5
};
EVENT_HANDLER0(void, startReceive) {startReceive(-1.f);}
EVENT_HANDLER1(void, startReceive, float, exp_frequency); // if "frequency = -1" used last passed value
EVENT_HANDLER0(void, stopReceive);
void setExpectedFrequency(float frequency); // for connection quality diagnostic
void setReceiverDevice(const PIString & device, PISerial::Speed speed, bool force = false); // for Serial
void setReceiverData(void * dataPtr, int dataSize) {this->dataPtr = (uchar * )dataPtr; this->dataSize = dataSize; packet_ext->setHeader(PIByteArray(headerPtr, headerSize)); packet_ext->setPayloadSize(dataSize); packet_ext->setPacketSize(dataSize);}
void setReceiverDataHeader(void * headerPtr, int headerSize) {this->headerPtr = (uchar * )headerPtr; this->headerSize = headerSize; packet_ext->setHeader(PIByteArray(headerPtr, headerSize)); packet_ext->setPayloadSize(dataSize); packet_ext->setPacketSize(dataSize);}
void setReceiverAddress(const PIString & ip, int port, bool force = false); // for Ethernet
void setReceiverParameters(PIFlags<PISerial::Parameters> parameters) {if (type_rec == PIProtocol::Serial || type_send == PIProtocol::Serial) ser->setParameters(parameters);} // for Serial
void setReceiveSlot(ReceiveFunc slot) {ret_func = slot;}
float expectedFrequency() const {return exp_freq;}
EVENT_HANDLER0(void, startSend) {startSend(-1.f);} // if "frequency = -1" used last passed value
EVENT_HANDLER1(void, startSend, float, frequency); // if "frequency = -1" used last passed value
EVENT_HANDLER0(void, stopSend) {sendTimer->stop(); senderStopped();}
void setSenderFrequency(float frequency) {send_freq = frequency;}
void setSenderDevice(const PIString & device, PISerial::Speed speed, bool force = false); // for Serial
void setSenderData(void * dataPtr, int dataSize) {sendDataPtr = (uchar * )dataPtr; sendDataSize = dataSize;}
void setSenderAddress(const PIString & ip, int port, bool force = false); // for Ethernet
void setSenderIP(const PIString & ip, bool force = false); // for Ethernet
void setSenderPort(int port, bool force = false); // for Ethernet
void setSenderParameters(PIFlags<PISerial::Parameters> parameters) {if (type_send == PIProtocol::Serial) ser->setParameters(parameters);} // for Serial
float senderFrequency() const {return send_freq;}
EVENT_HANDLER0(void, start) {startReceive(); startSend();}
EVENT_HANDLER0(void, stop) {stopReceive(); stopSend();}
EVENT_HANDLER0(void, send);
EVENT_HANDLER2(void, send, const void *, data, int, size) {send(data, size, false);}
EVENT_HANDLER3(void, send, const void *, data, int, size, bool, direct);
void setName(const PIString & name) {protName = name; PIObject::setName(name);}
PIString name() const {return protName;}
void setDisconnectTimeout(float timeout) {timeout_ = timeout; changeDisconnectTimeout();}
float disconnectTimeout() const {return timeout_;}
const float * disconnectTimeout_ptr() const {return &timeout_;}
float immediateFrequency() const {return immediate_freq;}
float integralFrequency() const {return integral_freq;}
const float * immediateFrequency_ptr() const {return &immediate_freq;}
const float * integralFrequency_ptr() const {return &integral_freq;}
ullong receiveCountPerSec() const {return packets_in_sec;}
const ullong * receiveCountPerSec_ptr() const {return &packets_in_sec;}
ullong sendCountPerSec() const {return packets_out_sec;}
const ullong * sendCountPerSec_ptr() const {return &packets_out_sec;}
ullong receiveBytesPerSec() const {return bytes_in_sec;}
const ullong * receiveBytesPerSec_ptr() const {return &bytes_in_sec;}
ullong sendBytesPerSec() const {return bytes_out_sec;}
const ullong * sendBytesPerSec_ptr() const {return &bytes_out_sec;}
ullong receiveCount() const {return receive_count;}
const ullong * receiveCount_ptr() const {return &receive_count;}
ullong wrongCount() const {return wrong_count;}
const ullong * wrongCount_ptr() const {return &wrong_count;}
ullong sendCount() const {return send_count;}
const ullong * sendCount_ptr() const {return &send_count;}
ullong missedCount() const {return missed_count;}
const ullong * missedCount_ptr() const {return &missed_count;}
PIProtocol::Quality quality() const {return net_diag;} // receive quality
const int * quality_ptr() const {return (int * )&net_diag;} // receive quality pointer
PIString receiverDeviceName() const {return devReceiverName;}
PIString senderDeviceName() const {return devSenderName;}
PIString receiverDeviceState() const {return devReceiverState;}
const PIString * receiverDeviceState_ptr() const {return &devReceiverState;}
PIString senderDeviceState() const {return devSenderState;}
const PIString * senderDeviceState_ptr() const {return &devSenderState;}
PIString receiveSpeed() const {return speedIn;}
const PIString * receiveSpeed_ptr() const {return &speedIn;}
PIString sendSpeed() const {return speedOut;}
const PIString * sendSpeed_ptr() const {return &speedOut;}
void * receiveData() {return dataPtr;}
void * sendData() {return sendDataPtr;}
PIPacketExtractor * packetExtractor() {return packet_ext;}
// PIByteArray lastHeader() {return packet_ext->lastHeader();}
EVENT0(receiverStarted)
EVENT0(receiverStopped)
EVENT0(senderStarted)
EVENT0(senderStopped)
EVENT1(received, bool, validate_is_ok)
EVENT2(qualityChanged, PIProtocol::Quality, new_quality, PIProtocol::Quality, old_quality)
protected:
virtual bool receive(uchar * data, int size) {if (dataPtr != 0) memcpy(dataPtr, data, size); return true;} // executed when raw data received, break if 'false' return
virtual bool validate() {return true;} // function for validate algorithm and save data from dataPtr to external struct
virtual bool headerValidate(uchar * src, uchar * rec, int size) {for (int i = 0; i < size; ++i) if (src[i] != rec[i]) return false; return true;} // function for validate header (COM-port and headerSize > 0)
virtual uint checksum_i(void * data, int size) { // function for checksum (uint)
uint c = 0;
for (int i = 0; i < size; ++i)
c += ((uchar*)data)[i];
return ~(c + 1);
}
virtual uchar checksum_c(void * data, int size) { // function for checksum (uchar)
uchar c = 0;
for (int i = 0; i < size; ++i)
c += ((uchar*)data)[i];
return ~(c + 1);
}
virtual bool aboutSend() {return true;} // executed before send data, if return 'false' then data is not sending
void init();
void init_sender(PIConfig::Entry & b, PIConfig::Entry & sb, const PIString & config);
void init_receiver(PIConfig::Entry & b, PIConfig::Entry & rb, const PIString & config);
void check_state();
void calc_freq();
void calc_diag();
PISerial * ser;
PIEthernet * eth;
uint dataSize, headerSize, sendDataSize;
uchar * dataPtr, * headerPtr, * sendDataPtr;
private:
static void sendEvent(void * e, int) {((PIProtocol * )e)->send();}
static bool receiveEvent(void * t, uchar * data, int size);
static bool headerValidateEvent(void * t, uchar * src, uchar * rec, int size) {return ((PIProtocol * )t)->headerValidate(src, rec, size);}
static void diagEvent(void * t, int);
static void secEvent(void * t, int);
void setMultiProtocolOwner(PIMultiProtocolBase * mp) {mp_owner = mp;}
PIMultiProtocolBase * multiProtocolOwner() const {return mp_owner;}
void changeDisconnectTimeout();
ReceiveFunc ret_func;
PIPacketExtractor * packet_ext;
PITimer * diagTimer, * sendTimer, * secTimer;
PITimeMeasurer diag_tm;
PIMultiProtocolBase * mp_owner;
PIProtocol::Type type_send, type_rec;
PIProtocol::Quality net_diag;
PIDeque<float> last_freq;
PIDeque<char> last_packets;
PIString protName, devReceiverName, devReceiverState, devSenderName, devSenderState, speedIn, speedOut;
bool work, new_mp_prot;
float exp_freq, send_freq, ifreq, immediate_freq, integral_freq, timeout_;
int packets[2], pckt_cnt, pckt_cnt_max;
char cur_pckt;
ullong wrong_count, receive_count, send_count, missed_count, packets_in_sec, packets_out_sec, bytes_in_sec, bytes_out_sec;
};
#endif // PIPROTOCOL_H

825
src_main/io/piserial.cpp Executable file
View File

@@ -0,0 +1,825 @@
/*
PIP - Platform Independent Primitives
COM
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru, Bychkov Andrey wapmobil@gmail.com
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, either version 3 of the License, or
(at your option) any later version.
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 "piincludes_p.h"
#include "piserial.h"
#include "piconfig.h"
#include "pidir.h"
#include <errno.h>
#ifdef WINDOWS
# include <winreg.h>
# define TIOCM_LE 1
# define TIOCM_DTR 4
# define TIOCM_RTS 7
# define TIOCM_CTS 8
# define TIOCM_ST 3
# define TIOCM_SR 2
# define TIOCM_CAR 1
# define TIOCM_RNG 9
# define TIOCM_DSR 6
# define B50 50
# define B75 75
# define B110 110
# define B300 300
# define B600 600
# define B1200 1200
# define B2400 2400
# define B4800 4800
# define B9600 9600
# define B14400 14400
# define B19200 19200
# define B38400 38400
# define B57600 57600
# define B115200 115200
# define B230400 230400
# define B460800 460800
# define B500000 500000
# define B576000 576000
# define B921600 921600
# define B1000000 1000000
# define B1152000 1152000
# define B1500000 1500000
# define B2000000 2000000
# define B2500000 2500000
# define B3000000 3000000
# define B3500000 3500000
# define B4000000 4000000
#else
# include <termios.h>
# include <fcntl.h>
# include <sys/ioctl.h>
# ifndef B50
# define B50 0000001
# endif
# ifndef B75
# define B75 0000002
# endif
# ifndef B230400
# define B230400 0010003
# endif
# ifndef B460800
# define B460800 0010004
# endif
# ifndef B500000
# define B500000 0010005
# endif
# ifndef B576000
# define B576000 0010006
# endif
# ifndef B921600
# define B921600 0010007
# endif
# ifndef B1000000
# define B1000000 0010010
# endif
# ifndef B1152000
# define B1152000 0010011
# endif
# ifndef B1500000
# define B1500000 0010012
# endif
# ifndef B2000000
# define B2000000 0010013
# endif
# ifndef B2500000
# define B2500000 0010014
# endif
# ifndef B3000000
# define B3000000 0010015
# endif
# ifndef B3500000
# define B3500000 0010016
# endif
# ifndef B4000000
# define B4000000 0010017
# endif
#endif
#ifndef CRTSCTS
# define CRTSCTS 020000000000
#endif
/*! \class PISerial
* \brief Serial device
*
* \section PISerial_sec0 Synopsis
* This class provide access to serial device, e.g. COM port. It can read,
* write, wait for write. There are several read and write functions.
*
*
*/
REGISTER_DEVICE(PISerial)
PRIVATE_DEFINITION_START(PISerial)
#ifdef WINDOWS
DCB desc, sdesc;
void * hCom;
DWORD readed, mask;
#else
termios desc, sdesc;
uint readed;
#endif
PRIVATE_DEFINITION_END(PISerial)
PISerial::PISerial(): PIIODevice("", ReadWrite) {
construct();
}
PISerial::PISerial(const PIString & device_, PISerial::Speed speed_, PIFlags<PISerial::Parameters> params_): PIIODevice(device_, ReadWrite) {
construct();
setPath(device_);
setSpeed(speed_);
setParameters(params_);
}
PISerial::~PISerial() {
closeDevice();
piMonitor.serials--;
}
void PISerial::construct() {
#ifdef WINDOWS
PRIVATE->hCom = 0;
#endif
fd = -1;
piMonitor.serials++;
setPriority(piHigh);
vtime = 10;
sending = false;
setParameters(0);
setSpeed(S115200);
setDataBitsCount(8);
}
void PISerial::setParameter(PISerial::Parameters parameter, bool on) {
PIFlags<Parameters> cp = (PIFlags<Parameters>)(property("parameters").toInt());
cp.setFlag(parameter, on);
setParameters(cp);
}
bool PISerial::isParameterSet(PISerial::Parameters parameter) const {
PIFlags<Parameters> cp = (PIFlags<Parameters>)(property("parameters").toInt());
return cp[parameter];
}
bool PISerial::setPin(int number, bool on) {
switch (number) {
case 1: return setCAR(on); break;
case 2: return setSR(on); break;
case 3: return setST(on); break;
case 4: return setDTR(on); break;
case 5:
piCoutObj << "Pin number 5 is ground";
return false;
case 6: return setDSR(on); break;
case 7: return setRTS(on); break;
case 8: return setCTS(on); break;
case 9: return setRNG(on); break;
default:
piCoutObj << "Pin number " << number << " doesn`t exists!";
return false;
}
return false;
}
bool PISerial::isPin(int number) const {
switch (number) {
case 1: return isCAR(); break;
case 2: return isSR(); break;
case 3: return isST(); break;
case 4: return isDTR(); break;
case 5: return false;
case 6: return isDSR(); break;
case 7: return isRTS(); break;
case 8: return isCTS(); break;
case 9: return isRNG(); break;
default:
piCoutObj << "Pin number " << number << " doesn`t exists!";
return false;
}
return false;
}
bool PISerial::setLE(bool on) {return setBit(TIOCM_LE, on, "LE");}
bool PISerial::setDTR(bool on) {return setBit(TIOCM_DTR, on, "DTR");}
bool PISerial::setRTS(bool on) {return setBit(TIOCM_RTS, on, "RTS");}
bool PISerial::setCTS(bool on) {return setBit(TIOCM_CTS, on, "CTS");}
bool PISerial::setST(bool on) {return setBit(TIOCM_ST, on, "ST");}
bool PISerial::setSR(bool on) {return setBit(TIOCM_SR, on, "SR");}
bool PISerial::setCAR(bool on) {return setBit(TIOCM_CAR, on, "CAR");}
bool PISerial::setRNG(bool on) {return setBit(TIOCM_RNG, on, "RNG");}
bool PISerial::setDSR(bool on) {return setBit(TIOCM_DSR, on, "DSR");}
bool PISerial::isLE() const {return isBit(TIOCM_LE, "LE");}
bool PISerial::isDTR() const {return isBit(TIOCM_DTR, "DTR");}
bool PISerial::isRTS() const {return isBit(TIOCM_RTS, "RTS");}
bool PISerial::isCTS() const {return isBit(TIOCM_CTS, "CTS");}
bool PISerial::isST() const {return isBit(TIOCM_ST, "ST");}
bool PISerial::isSR() const {return isBit(TIOCM_SR, "SR");}
bool PISerial::isCAR() const {return isBit(TIOCM_CAR, "CAR");}
bool PISerial::isRNG() const {return isBit(TIOCM_RNG, "RNG");}
bool PISerial::isDSR() const {return isBit(TIOCM_DSR, "DSR");}
bool PISerial::setBit(int bit, bool on, const PIString & bname) {
#ifndef WINDOWS
if (fd < 0) {
piCoutObj << "setBit" << bname << " error: \"" << path() << "\" is not opened!";
return false;
}
if (ioctl(fd, on ? TIOCMBIS : TIOCMBIC, &bit) < 0) {
piCoutObj << "setBit" << bname << " error: " << errorString();
return false;
}
return true;
#else
piCoutObj << "setBit" << bname << " doesn`t implemented on Windows, sorry :-(";
return false;
#endif
}
bool PISerial::isBit(int bit, const PIString & bname) const {
#ifndef WINDOWS
if (fd < 0) {
piCoutObj << "isBit" << bname << " error: \"" << path() << "\" is not opened!";
return false;
}
int ret = 0;
if (ioctl(fd, TIOCMGET, &ret) < 0)
piCoutObj << "isBit" << bname << " error: " << errorString();
return ret & bit;
#else
piCoutObj << "isBit" << bname << " doesn`t implemented on Windows, sorry :-(";
return false;
#endif
}
void PISerial::flush() {
#ifndef WINDOWS
if (fd != -1) tcflush(fd, TCIOFLUSH);
#endif
}
bool PISerial::closeDevice() {
if (isRunning()) {
stop();
PIThread::terminate();
}
if (fd != -1) {
#ifdef WINDOWS
SetCommState(PRIVATE->hCom, &PRIVATE->sdesc);
SetCommMask(PRIVATE->hCom, PRIVATE->mask);
// piCoutObj << "close" <<
CloseHandle(PRIVATE->hCom);
PRIVATE->hCom = 0;
#else
tcsetattr(fd, TCSANOW, &PRIVATE->sdesc);
::close(fd);
#endif
fd = -1;
}
return true;
}
int PISerial::convertSpeed(PISerial::Speed speed) {
switch (speed) {
case S50: return B50;
case S75: return B75;
case S110: return B110;
case S300: return B300;
case S600: return B600;
case S1200: return B1200;
case S2400: return B2400;
case S4800: return B4800;
case S9600: return B9600;
case S19200: return B19200;
case S38400: return B38400;
case S57600: return B57600;
case S115200: return B115200;
case S230400: return B230400;
case S460800: return B460800;
case S500000: return B500000;
case S576000: return B576000;
case S921600: return B921600;
case S1000000: return B1000000;
case S1152000: return B1152000;
case S1500000: return B1500000;
case S2000000: return B2000000;
case S2500000: return B2500000;
case S3000000: return B3000000;
case S3500000: return B3500000;
case S4000000: return B4000000;
default: break;
}
return B115200;
}
/** \brief Advanced read function
* \details Read to pointer "read_to" no more than "max_size" and no longer
* than "timeout_ms" milliseconds. If "timeout_ms" < 0 function will be
* wait forever until "max_size" will be readed. If size <= 0 function
* immediate returns \b false. For read data with unknown size use function
* \a readData().
* \returns \b True if readed bytes count = "max_size", else \b false
* \sa \a readData() */
bool PISerial::read(void * data, int size, double timeout_ms) {
if (data == 0 || size <= 0) return false;
int ret, all = 0;
if (timeout_ms > 0.) {
bool br = setOption(BlockingRead, false);
all = readDevice(data, 1);
tm_.reset();
while (all < size && tm_.elapsed_m() < timeout_ms) {
ret = readDevice(&((uchar * )data)[all], size - all);
if (ret > 0) all += ret;
else msleep(1);
}
setOption(BlockingRead, br);
received(data, all);
return (all == size);
} else {
bool br = setOption(BlockingRead, true);
all = readDevice(data, 1);
while (all < size) {
ret = readDevice(&((uchar * )data)[all], size - all);
if (ret > 0) all += ret;
}
setOption(BlockingRead, br);
received(data, all);
return (all == size);
}
return false;
}
/** \brief Advanced read function
* \details Read all or no more than "size" and no longer than
* "timeout_ms" milliseconds. If "timeout_ms" < 0 function will be
* wait forever until "size" will be readed. If "size" <= 0
* function will be read all until "timeout_ms" elaped. \n If size <= 0
* and "timeout_ms" <= 0 function immediate returns empty string.
* \n This function similar to \a readData() but returns data as string.
* \sa \a readData() */
PIString PISerial::read(int size, double timeout_ms) {
PIString str;
if (size <= 0 && timeout_ms <= 0.) return str;
int ret, all = 0;
uchar td[1024];
if (timeout_ms > 0.) {
bool br = setOption(BlockingRead, false);
tm_.reset();
if (size <= 0) {
while (tm_.elapsed_m() < timeout_ms) {
ret = readDevice(td, 1024);
if (ret <= 0) msleep(1);
else str << PIString((char*)td, ret);
}
} else {
while (all < size && tm_.elapsed_m() < timeout_ms) {
ret = readDevice(td, size - all);
if (ret <= 0) msleep(1);
else {
str << PIString((char*)td, ret);
all += ret;
}
}
}
setOption(BlockingRead, br);
} else {
bool br = setOption(BlockingRead, true);
all = readDevice(td, 1);
str << PIString((char*)td, all);
while (all < size) {
ret = readDevice(td, size - all);
if (ret <= 0) msleep(1);
else {
str << PIString((char*)td, ret);
all += ret;
}
}
setOption(BlockingRead, br);
}
received(str.data(), str.size_s());
return str;
}
/** \brief Advanced read function
* \details Read all or no more than "size" and no longer than
* "timeout_ms" milliseconds. If "timeout_ms" < 0 function will be
* wait forever until "size" will be readed. If "size" <= 0
* function will be read all until "timeout_ms" elaped. \n If size <= 0
* and "timeout_ms" <= 0 function immediate returns empty byte array.
* \n This function similar to \a read() but returns data as byte array.
* \sa \a read() */
PIByteArray PISerial::readData(int size, double timeout_ms) {
PIByteArray str;
if (size <= 0 && timeout_ms <= 0.) return str;
int ret, all = 0;
uchar td[1024];
if (timeout_ms > 0.) {
bool br = setOption(BlockingRead, false);
tm_.reset();
if (size <= 0) {
while (tm_.elapsed_m() < timeout_ms) {
ret = readDevice(td, 1024);
if (ret <= 0) msleep(1);
else str.append(td, ret);
}
} else {
while (all < size && tm_.elapsed_m() < timeout_ms) {
ret = readDevice(td, size - all);
if (ret <= 0) msleep(1);
else {
str.append(td, ret);
all += ret;
}
}
}
setOption(BlockingRead, br);
} else {
bool br = setOption(BlockingRead, true);
all = readDevice(td, 1);
str.append(td, all);
while (all < size) {
ret = readDevice(td, size - all);
if (ret <= 0) msleep(1);
else {
str.append(td, ret);
all += ret;
}
}
setOption(BlockingRead, br);
}
received(str.data(), str.size_s());
return str;
}
bool PISerial::openDevice() {
//piCout << "ser open" << path();
if (path().isEmpty()) return false;
#ifdef WINDOWS
DWORD ds = 0, sm = 0;
if (isReadable()) {ds |= GENERIC_READ; sm |= FILE_SHARE_READ;}
if (isWriteable()) {ds |= GENERIC_WRITE; sm |= FILE_SHARE_WRITE;}
PIString wp = "//./" + path();
PRIVATE->hCom = CreateFileA(wp.data(), ds, sm, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0);
if (PRIVATE->hCom == INVALID_HANDLE_VALUE) {
piCoutObj << "Unable to open \"" << path() << "\"";
fd = -1;
return false;
}
fd = 0;
#else
int om = 0;
switch (mode()) {
case PIIODevice::ReadOnly: om = O_RDONLY; break;
case PIIODevice::WriteOnly: om = O_WRONLY; break;
case PIIODevice::ReadWrite: om = O_RDWR; break;
}
//cout << "init ser " << path_ << " mode " << om << " param " << params << endl;
fd = ::open(path().data(), O_NOCTTY | om);
if (fd == -1) {
piCoutObj << "Unable to open \"" << path() << "\"";
return false;
}
tcgetattr(fd, &PRIVATE->desc);
PRIVATE->sdesc = PRIVATE->desc;
//piCoutObj << "Initialized " << path_;
#endif
applySettings();
return true;
}
void PISerial::applySettings() {
#ifdef WINDOWS
if (fd == -1) return;
setTimeouts();
GetCommMask(PRIVATE->hCom, &PRIVATE->mask);
SetCommMask(PRIVATE->hCom, EV_RXCHAR);
GetCommState(PRIVATE->hCom, &PRIVATE->sdesc);
// piCoutObj << PRIVATE->sdesc.fBinary << PRIVATE->sdesc.fAbortOnError << PRIVATE->sdesc.fDsrSensitivity << PRIVATE->sdesc.fDtrControl << PRIVATE->sdesc.fDummy2 << PRIVATE->sdesc.fErrorChar;
PRIVATE->desc = PRIVATE->sdesc;
PRIVATE->desc.DCBlength = sizeof(PRIVATE->desc);
PRIVATE->desc.BaudRate = convertSpeed(outSpeed());
if (dataBitsCount() >= 5 && dataBitsCount() <= 8)
PRIVATE->desc.ByteSize = dataBitsCount();
else
PRIVATE->desc.ByteSize = 8;
PIFlags<Parameters> params = parameters();
if (params[PISerial::ParityControl]) {
PRIVATE->desc.fParity = 1;
PRIVATE->desc.Parity = params[PISerial::ParityOdd] ? 1 : 2;
}
PRIVATE->desc.StopBits = params[PISerial::TwoStopBits] ? TWOSTOPBITS : ONESTOPBIT;
/*PRIVATE->desc.fOutxCtsFlow = 0;
PRIVATE->desc.fOutxDsrFlow = 0;
PRIVATE->desc.fDtrControl = 0;
PRIVATE->desc.fRtsControl = 0;
PRIVATE->desc.fInX = 0;
PRIVATE->desc.fOutX = 0;
PRIVATE->desc.fBinary = 1;
PRIVATE->desc.fAbortOnError = 0;
PRIVATE->desc.fNull = 0;*/
if (SetCommState(PRIVATE->hCom, &PRIVATE->desc) == -1) {
piCoutObj << "Unable to set comm state for \"" << path() << "\"";
return;
}
#else
if (fd == -1) return;
tcgetattr(fd, &PRIVATE->desc);
PRIVATE->desc.c_oflag = PRIVATE->desc.c_lflag = PRIVATE->desc.c_cflag = 0;
PRIVATE->desc.c_iflag = IGNBRK;
PRIVATE->desc.c_cflag = CLOCAL | HUPCL;
switch (dataBitsCount()) {
case 5: PRIVATE->desc.c_cflag |= (CSIZE & CS5); break;
case 6: PRIVATE->desc.c_cflag |= (CSIZE & CS6); break;
case 7: PRIVATE->desc.c_cflag |= (CSIZE & CS7); break;
case 8: default: PRIVATE->desc.c_cflag |= (CSIZE & CS8); break;
};
if (isReadable()) PRIVATE->desc.c_cflag |= CREAD;
PIFlags<Parameters> params = parameters();
if (params[PISerial::TwoStopBits]) PRIVATE->desc.c_cflag |= CSTOPB;
if (params[PISerial::ParityControl]) {
PRIVATE->desc.c_iflag |= INPCK;
PRIVATE->desc.c_cflag |= PARENB;
if (params[PISerial::ParityOdd]) PRIVATE->desc.c_cflag |= PARODD;
}
PRIVATE->desc.c_cc[VMIN] = 1;
PRIVATE->desc.c_cc[VTIME] = vtime;
cfsetispeed(&PRIVATE->desc, convertSpeed(inSpeed()));
cfsetospeed(&PRIVATE->desc, convertSpeed(outSpeed()));
tcflush(fd, TCIOFLUSH);
setTimeouts();
if (tcsetattr(fd, TCSANOW, &PRIVATE->desc) < 0) {
piCoutObj << "Can`t set attributes for \"" << path() << "\"";
return;
}
#endif
}
void PISerial::setTimeouts() {
#ifdef WINDOWS
COMMTIMEOUTS times;
times.ReadIntervalTimeout = isOptionSet(BlockingRead) ? vtime : MAXDWORD;
times.ReadTotalTimeoutConstant = isOptionSet(BlockingRead) ? 0 : 1;
times.ReadTotalTimeoutMultiplier = isOptionSet(BlockingRead) ? 0 : MAXDWORD;
times.WriteTotalTimeoutConstant = isOptionSet(BlockingWrite) ? 0 : 1;
times.WriteTotalTimeoutMultiplier = 0;
if (SetCommTimeouts(PRIVATE->hCom, &times) == -1)
piCoutObj << "Unable to set timeouts for \"" << path() << "\"";
#else
fcntl(fd, F_SETFL, isOptionSet(BlockingRead) ? 0 : O_NONBLOCK);
#endif
}
/** \brief Basic read function
* \details Read to pointer "read_to" no more than "max_size". If read is
* set to blocking this function will be wait at least one byte.
* \returns Readed bytes count
* \sa \a readData() */
int PISerial::readDevice(void * read_to, int max_size) {
#ifdef WINDOWS
if (!canRead()) return -1;
if (sending) return -1;
// piCoutObj << "com event ...";
//WaitCommEvent(PRIVATE->hCom, 0, 0);
//piCoutObj << "read ..." << PRIVATE->hCom;
ReadFile(PRIVATE->hCom, read_to, max_size, &PRIVATE->readed, 0);
//piCoutObj << "read ok" << PRIVATE->readed;
return PRIVATE->readed;
#else
if (!canRead()) return -1;
return ::read(fd, read_to, max_size);
#endif
}
int PISerial::writeDevice(const void * data, int max_size) {
if (fd == -1 || !canWrite()) {
//piCoutObj << "Can`t write to uninitialized COM";
return -1;
}
#ifdef WINDOWS
// if (block_write != wait) {
// block_write = wait;
// piCoutObj << "set timeout ...";
// setReadIsBlocking(block_read);
// piCoutObj << "set timeout ok";
// }
DWORD wrote;
// piCoutObj << "send ...";// << max_size;// << ": " << PIString((char*)data, max_size);
sending = true;
WriteFile(PRIVATE->hCom, data, max_size, &wrote, 0);
sending = false;
// piCoutObj << "send ok";// << wrote << " bytes in " << path();
#else
int wrote;
wrote = ::write(fd, data, max_size);
if (isOptionSet(BlockingWrite)) tcdrain(fd);
#endif
return (int)wrote;
//piCoutObj << "Error while sending";
}
bool PISerial::configureDevice(const void * e_main, const void * e_parent) {
PIConfig::Entry * em = (PIConfig::Entry * )e_main;
PIConfig::Entry * ep = (PIConfig::Entry * )e_parent;
setDevice(readDeviceSetting<PIString>("device", device(), em, ep));
setSpeed((PISerial::Speed)(readDeviceSetting<int>("speed", (int)outSpeed(), em, ep)));
setDataBitsCount(readDeviceSetting<int>("dataBitsCount", dataBitsCount(), em, ep));
setParameter(PISerial::ParityControl, readDeviceSetting<bool>("parityControl", isParameterSet(PISerial::ParityControl), em, ep));
setParameter(PISerial::ParityOdd, readDeviceSetting<bool>("parityOdd", isParameterSet(PISerial::ParityOdd), em, ep));
setParameter(PISerial::TwoStopBits, readDeviceSetting<bool>("twoStopBits", isParameterSet(PISerial::TwoStopBits), em, ep));
return true;
}
PIString PISerial::constructFullPathDevice() const {
PIString ret;
ret << path() << ":" << int(inSpeed()) << ":" << dataBitsCount();
if (parameters()[ParityControl]) {
if (parameters()[ParityOdd]) ret << ":O";
else ret << ":E";
} else ret << ":N";
if (parameters()[TwoStopBits]) ret << ":2";
else ret << ":1";
return ret;
}
void PISerial::configureFromFullPathDevice(const PIString & full_path) {
PIStringList pl = full_path.split(":");
for (int i = 0; i < pl.size_s(); ++i) {
PIString p(pl[i]);
switch (i) {
case 0: setProperty("path", p); break;
case 1: setProperty("outSpeed", p.toInt()); setProperty("inSpeed", p.toInt()); break;
case 2: setProperty("dataBitsCount", p.toInt()); break;
case 3:
p = p.toLowerCase();
if (p != "n") setParameter(ParityControl);
if (p == "o") setParameter(ParityOdd);
break;
case 4: if (p.toInt() == 2) setParameter(TwoStopBits); break;
}
}
applySettings();
}
PIVector<int> PISerial::availableSpeeds() {
PIVector<int> spds;
spds << 50 << 75 << 110 << 300 << 600 << 1200 << 2400 << 4800 <<
9600 << 19200 << 38400 << 57600 << 115200 << 1500000 <<
2000000 << 2500000 << 3000000 << 3500000 << 4000000;
return spds;
}
PIStringList PISerial::availableDevices(bool test) {
PIStringList dl;
#ifdef WINDOWS
HKEY key = 0;
RegOpenKey(HKEY_LOCAL_MACHINE, (LPCTSTR)"HARDWARE\\DEVICEMAP\\SERIALCOMM", &key);
if (key != 0) {
char name[1024], data[1024];
DWORD name_len = 1024, data_len = 1024, type = 0, index = 0;
LONG ret;
while ((ret = RegEnumValue(key, index, (LPTSTR)name, &name_len, NULL, &type, (uchar * )data, &data_len)) != ERROR_NO_MORE_ITEMS) {
dl << PIString(data);
index++;
}
RegCloseKey(key);
}
#else
# ifndef ANDROID
PIStringList prefixes;
# ifdef QNX
prefixes << "ser";
# else
prefixes << "ttyS" << "ttyO" << "ttyUSB" << "ttyACM" << "ttyGS"
<< "ttyMI" << "ttymxc" << "ttyAMA" << "rfcomm" << "ircomm";
# ifdef FREE_BSD
prefixes << "cu";
# endif
PIFile file_prefixes("/proc/tty/drivers", PIIODevice::ReadOnly);
if (file_prefixes.open()) {
PIString fc = file_prefixes.readAll(true), line, cpref;
PIStringList words;
file_prefixes.close();
while (!fc.isEmpty()) {
words.clear();
line = fc.takeLine();
if (line.isEmpty()) break;
while (!line.isEmpty())
words << line.takeWord();
if (words.size_s() < 2) break;
if (words.back() != "serial") continue;
cpref = words[1];
int li = cpref.findLast("/");
if (li > 0) cpref.cutLeft(li + 1);
prefixes << cpref;
}
prefixes.removeDuplicates();
}
# endif
PIDir dir("/dev");
PIVector<PIFile::FileInfo> de = dir.entries();
piForeachC (PIFile::FileInfo & e, de) { // TODO changes in FileInfo
piForeachC (PIString & p, prefixes) {
if (e.name().left(p.size_s()) != p) continue;
dl << e.path;
}
}
# endif
#endif
if (test) {
for (int i = 0; i < dl.size_s(); ++i) {
#ifdef WINDOWS
void * hComm = CreateFileA(dl[i].data(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0);
if (hComm == INVALID_HANDLE_VALUE) {
#else
int fd = ::open(dl[i].data(), O_NOCTTY | O_RDONLY);
if (fd == -1) {
#endif
dl.remove(i);
--i;
continue;
}
bool rok = true;
#ifdef WINDOWS
/*COMMTIMEOUTS times;
times.ReadIntervalTimeout = MAXDWORD;
times.ReadTotalTimeoutConstant = 0;
times.ReadTotalTimeoutMultiplier = 0;
times.WriteTotalTimeoutConstant = 1;
times.WriteTotalTimeoutMultiplier = 0;
SetCommTimeouts(hComm, &times);
if (ReadFile(hComm, &void_, 1, &readed_, 0) == 0)
rok = GetLastError() == ;*/
#else
int void_ = 0;
fcntl(fd, F_SETFL, O_NONBLOCK);
if (::read(fd, &void_, 1) == -1)
rok = errno != EIO;
#endif
if (!rok) {
dl.remove(i);
--i;
continue;
}
#ifdef WINDOWS
CloseHandle(hComm);
#else
::close(fd);
#endif
}
}
return dl;
}
void PISerial::optionsChanged() {
if (isOpened()) setTimeouts();
}

236
src_main/io/piserial.h Executable file
View File

@@ -0,0 +1,236 @@
/*! \file piserial.h
* \brief Serial device
*/
/*
PIP - Platform Independent Primitives
COM
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru, Bychkov Andrey wapmobil@gmail.com
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PISERIAL_H
#define PISERIAL_H
#include "pitimer.h"
#include "piiodevice.h"
class PIP_EXPORT PISerial: public PIIODevice
{
PIIODEVICE(PISerial)
public:
//! Contructs an empty %PISerial
explicit PISerial();
//! \brief Parameters of PISerial
enum Parameters {
ParityControl /*! Enable parity check and generate */ = 0x1,
ParityOdd /*! Parity is odd instead of even */ = 0x2,
TwoStopBits /*! Two stop bits instead of one */ = 0x4
};
//! \brief Speed of PISerial
enum Speed {
S50 /*! 50 baud */ = 50,
S75 /*! 75 baud */ = 75,
S110 /*! 110 baud */ = 110,
S300 /*! 300 baud */ = 300,
S600 /*! 600 baud */ = 600,
S1200 /*! 1200 baud */ = 1200,
S2400 /*! 2400 baud */ = 2400,
S4800 /*! 4800 baud */ = 4800,
S9600 /*! 9600 baud */ = 9600,
S19200 /*! 19200 baud */ = 19200,
S38400 /*! 38400 baud */ = 38400,
S57600 /*! 57600 baud */ = 57600,
S115200 /*! 115200 baud */ = 115200,
S230400 /*! 230400 baud */ = 230400,
S460800 /*! 460800 baud */ = 460800,
S500000 /*! 500000 baud */ = 500000,
S576000 /*! 576000 baud */ = 576000,
S921600 /*! 921600 baud */ = 921600,
S1000000 /*! 1000000 baud */ = 1000000,
S1152000 /*! 1152000 baud */ = 1152000,
S1500000 /*! 1500000 baud */ = 1500000,
S2000000 /*! 2000000 baud */ = 2000000,
S2500000 /*! 2500000 baud */ = 2500000,
S3000000 /*! 3000000 baud */ = 3000000,
S3500000 /*! 3500000 baud */ = 3500000,
S4000000 /*! 4000000 baud */ = 4000000
};
//! Contructs %PISerial with device name "device", speed "speed" and parameters "params"
explicit PISerial(const PIString & device, PISerial::Speed speed = S115200, PIFlags<PISerial::Parameters> params = 0);
~PISerial();
//! Set both input and output speed to "speed"
void setSpeed(PISerial::Speed speed) {setProperty("outSpeed", (int)speed); setProperty("inSpeed", (int)speed); applySettings();}
//! Set output speed to "speed"
void setOutSpeed(PISerial::Speed speed) {setProperty("outSpeed", (int)speed); applySettings();}
//! Set input speed to "speed"
void setInSpeed(PISerial::Speed speed) {setProperty("inSpeed", (int)speed); applySettings();}
//! Set device name to "dev"
void setDevice(const PIString & dev) {setPath(dev); if (isOpened()) {close(); open();};}
//! Set parameters to "parameters_"
void setParameters(PIFlags<PISerial::Parameters> parameters_) {setProperty("parameters", (int)parameters_); applySettings();}
//! Set parameter "parameter" to "on" state
void setParameter(PISerial::Parameters parameter, bool on = true);
//! Returns if parameter "parameter" is set
bool isParameterSet(PISerial::Parameters parameter) const;
//! Returns parameters
PIFlags<PISerial::Parameters> parameters() const {return (PIFlags<Parameters>)(property("parameters").toInt());}
//! Set data bits count. Valid range is from 5 to 8, befault is 8
void setDataBitsCount(int bits) {setProperty("dataBitsCount", bits); applySettings();}
//! Returns data bits count
int dataBitsCount() const {return property("dataBitsCount").toInt();}
//! Set pin number "number" to logic level "on". Valid numbers are 4 (DTR) and 7 (RTS)
bool setPin(int number, bool on);
//! Returns pin number "number" logic level. Valid numbers range is from 1 to 9
bool isPin(int number) const;
bool setLE(bool on); // useless function, just formally
bool setDTR(bool on);
bool setRTS(bool on);
bool setCTS(bool on); // useless function, just formally
bool setST(bool on); // useless function, just formally
bool setSR(bool on); // useless function, just formally
bool setCAR(bool on); // useless function, just formally
bool setRNG(bool on); // useless function, just formally
bool setDSR(bool on); // useless function, just formally
bool isLE() const;
bool isDTR() const;
bool isRTS() const;
bool isCTS() const;
bool isST() const;
bool isSR() const;
bool isCAR() const;
bool isRNG() const;
bool isDSR() const;
void setVTime(int t) {vtime = t; applySettings();}
//! Returns device name
PIString device() const {return path();}
//! Returns output speed
PISerial::Speed outSpeed() const {return (PISerial::Speed)(property("outSpeed").toInt());}
//! Returns input speed
PISerial::Speed inSpeed() const {return (PISerial::Speed)(property("inSpeed").toInt());}
int VTime() const {return vtime;}
//! Discard all buffered input and output data
void flush();
int read(void * read_to, int max_size) {return readDevice(read_to, max_size);}
bool read(void * read_to, int max_size, double timeout_ms);
PIString read(int size = -1, double timeout_ms = 1000.);
PIByteArray readData(int size = -1, double timeout_ms = 1000.);
//! \brief Write to device data "data" with maximum size "size" and wait for data written if "wait" is \b true.
//! \returns \b true if sended bytes count = "size"
bool send(const void * data, int size) {return (write(data, size) == size);}
/// NOTE: no reason to use this function, use PIString::toUtf8() or PIString::dataAscii(),lengthAscii() instead
//! \brief Write to device string "data" and wait for data written if "wait" is \b true.
//! \returns \b true if sended bytes count = size of string
//bool send(const PIString & data, bool wait = false) {return (write(data.data(), data.lengthAscii(), wait) == data.size_s());}
//! \brief Write to device byte array "data" and wait for data written if "wait" is \b true.
//! \returns \b true if sended bytes count = size of string
bool send(const PIByteArray & data) {return (write(data.data(), data.size_s()) == data.size_s());}
//! \brief Returns all available speeds for serial devices
static PIVector<int> availableSpeeds();
//! \brief Returns all available system devices. If "test" each device will be tried to open
static PIStringList availableDevices(bool test = false);
//! \ioparams
//! \{
#ifdef DOXYGEN
//! \brief device, default ""
string device;
//! \brief input/output speed, default 115200
int speed;
//! \brief dataBitsCount, default 8
int dataBitsCount;
//! \brief parityControl, default false
bool parityControl;
//! \brief parityOdd, default false
bool parityOdd;
//! \brief twoStopBits, default false
bool twoStopBits;
#endif
//! \}
protected:
PIString fullPathPrefix() const {return "ser";}
PIString constructFullPathDevice() const;
void configureFromFullPathDevice(const PIString & full_path);
bool configureDevice(const void * e_main, const void * e_parent = 0);
void optionsChanged();
int readDevice(void * read_to, int max_size);
int writeDevice(const void * data, int max_size);
//! Executes when any read function was successful. Default implementation does nothing
virtual void received(const void * data, int size) {;}
void construct();
void applySettings();
void setTimeouts();
int convertSpeed(PISerial::Speed speed);
bool setBit(int bit, bool on, const PIString & bname);
bool isBit(int bit, const PIString & bname) const;
bool openDevice();
bool closeDevice();
PRIVATE_DECLARATION
int fd, vtime;
bool sending;
PITimeMeasurer tm_;
};
#endif // PISERIAL_H

View File

@@ -0,0 +1,251 @@
/*
PIP - Platform Independent Primitives
File
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 "piincludes_p.h"
#include "pisharedmemory.h"
#ifdef WINDOWS
#endif
#if defined(QNX) || defined(ANDROID)
#endif
#ifdef MAC_OS
#endif
#ifdef LINUX
# include <fcntl.h>
# include <sys/stat.h>
# include <sys/mman.h>
#endif
/*! \class PISharedMemory
* \brief Shared memory
*
* \section PISharedMemory_sec0 Synopsis
* This class provide access to local file. You can manipulate
* binary content or use this class as text stream. To binary
* access there are function \a read(), \a write(), and many
* \a writeBinary() functions. For write variables to file in
* their text representation threr are many "<<" operators.
*
* \section PISharedMemory_sec1 Position
* Each opened file has a read/write position - logical position
* in the file content you read from or you write to. You can
* find out current position with function \a pos(). Function
* \a seek(llong position) move position to position "position",
* \a seekToBegin() move position to the begin of file,
* \a seekToEnd() move position to the end of file.
*
*/
//REGISTER_DEVICE(PISharedMemory);
PRIVATE_DEFINITION_START(PISharedMemory)
PIByteArray name;
#ifdef WINDOWS
HANDLE map;
void * data;
#endif
#ifdef LINUX
void * data;
bool owner;
#endif
PRIVATE_DEFINITION_END(PISharedMemory)
PISharedMemory::PISharedMemory(): PIIODevice() {
initPrivate();
dsize = -1;
}
PISharedMemory::PISharedMemory(const PIString & shm_name, int size, PIIODevice::DeviceMode mode): PIIODevice(shm_name, mode) {
initPrivate();
dsize = size;
if (!shm_name.isEmpty())
open();
}
PISharedMemory::PISharedMemory(const PISharedMemory & other) {
initPrivate();
dsize = other.dsize;
setPath(other.path());
mode_ = other.mode_;
}
bool PISharedMemory::openDevice() {
close();
#ifdef WINDOWS
DWORD m = PAGE_READWRITE;
if (!isWriteable()) m = PAGE_READONLY;
PRIVATE->name = ("PIP_SHM_" + path()).toByteArray();
PRIVATE->name.push_back(0);
PRIVATE->map = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, m, 0, (DWORD)dsize, (const char *)PRIVATE->name.data());
if (!PRIVATE->map) {
piCoutObj << "CreateFileMapping error," << errorString();
return false;
}
if (!isWriteable()) m = FILE_MAP_READ;
else m = FILE_MAP_ALL_ACCESS;
PRIVATE->data = MapViewOfFile(PRIVATE->map, m, 0, 0, dsize);
if (!PRIVATE->data) {
piCoutObj << "MapViewOfFile error," << errorString();
CloseHandle(PRIVATE->map);
return false;
}
//piCout << PRIVATE->map << PRIVATE->data;
#endif
#ifdef LINUX
int m = O_RDWR;
if (!isWriteable()) m = O_RDONLY;
PRIVATE->name = ("/pip_shm_" + path()).toByteArray();
PRIVATE->name.push_back(0);
int fd = shm_open((const char *)PRIVATE->name.data(), m, 0777);
//piCout << "try open" << PICoutManipulators::Hex << fd;
if (fd < 0) {
//piCoutObj << "shm_open error," << errorString();
fd = shm_open((const char *)PRIVATE->name.data(), m | O_CREAT, 0777);
//piCout << "try create" << PICoutManipulators::Hex << (m | O_CREAT) << fd;
if (fd < 0) {
piCoutObj << "shm_open error," << errorString();
return false;
}
PRIVATE->owner = true;
ftruncate(fd, dsize);
//piCout << "created" << fd;
}
m = PROT_WRITE;
if (!isWriteable()) m = PROT_READ;
PRIVATE->data = mmap(0, dsize, m, MAP_SHARED, fd, 0);
::close(fd);
if (!PRIVATE->data) {
piCoutObj << "mmap error," << errorString();
return false;
}
//piCout << "opened" << PRIVATE->data;
#endif
return true;
}
bool PISharedMemory::closeDevice() {
#ifdef WINDOWS
if (PRIVATE->data) UnmapViewOfFile(PRIVATE->data);
if (PRIVATE->map) CloseHandle(PRIVATE->map);
#endif
#ifdef LINUX
//piCout << "close" << PIString(PRIVATE->name) << PRIVATE->data;
if (PRIVATE->data) munmap(PRIVATE->data, dsize);
if (PRIVATE->owner) {
//if (!PRIVATE->name.isEmpty()) {
//piCout << "unlink" << PIString(PRIVATE->name);
shm_unlink((const char *)PRIVATE->name.data());
}
//if (fd > 0)
#endif
initPrivate();
return true;
}
void PISharedMemory::initPrivate() {
#ifdef WINDOWS
PRIVATE->map = 0;
PRIVATE->data = 0;
#endif
#ifdef LINUX
PRIVATE->data = 0;
PRIVATE->owner = false;
#endif
}
PIByteArray PISharedMemory::readAll() {
if (dsize <= 0) return PIByteArray();
#ifdef WINDOWS
if (!PRIVATE->data) return PIByteArray();
#endif
#ifdef LINUX
if (!PRIVATE->data) return PIByteArray();
#endif
PIByteArray a(dsize);
read(a.data(), a.size_s());
/*llong cp = pos();
if (forceRead) {
seekToBegin();
while (!isEnd())
a.push_back(readChar());
seek(cp);
return a;
}
llong s = size();
if (s < 0) return a;
a.resize(s);
s = readAll(a.data());
seek(cp);
if (s >= 0) a.resize(s);*/
return a;
}
llong PISharedMemory::size() const {
if (isClosed()) return -1;
return dsize;
}
int PISharedMemory::read(void * read_to, int max_size) {
return read(read_to, max_size, 0);
}
int PISharedMemory::read(void * read_to, int max_size, int offset) {
#ifdef WINDOWS
if (!PRIVATE->data) return -1;
CopyMemory(read_to, &(((char*)(PRIVATE->data))[offset]), max_size);
return max_size;
#endif
#ifdef LINUX
if (!PRIVATE->data) return -1;
memcpy(read_to, &(((char*)(PRIVATE->data))[offset]), max_size);
return max_size;
#endif
}
int PISharedMemory::write(const void * data, int max_size) {
return write(data, max_size, 0);
}
int PISharedMemory::write(const void * data, int max_size, int offset) {
#ifdef WINDOWS
if (!PRIVATE->data) return -1;
CopyMemory(&(((char*)(PRIVATE->data))[offset]), data, max_size);
return max_size;
#endif
#ifdef LINUX
if (!PRIVATE->data) return -1;
memcpy(&(((char*)(PRIVATE->data))[offset]), data, max_size);
return max_size;
#endif
}

View File

@@ -0,0 +1,87 @@
/*! \file pisharedmemory.h
* \brief Shared memory
*/
/*
PIP - Platform Independent Primitives
Shared Memory
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PISHAREDMEMORY_H
#define PISHAREDMEMORY_H
#include "piiodevice.h"
class PIP_EXPORT PISharedMemory: public PIIODevice
{
PIIODEVICE(PISharedMemory)
public:
explicit PISharedMemory();
//! Constructs a shared memory object with name "shm_name", size "size" and open mode "mode"
explicit PISharedMemory(const PIString & shm_name, int size, DeviceMode mode = ReadWrite);
PISharedMemory(const PISharedMemory & other);
virtual ~PISharedMemory() {close();}
//! Read all shared memory object content to byte array and return it
PIByteArray readAll();
//! Returns shared memory object size
llong size() const;
//! Returns if shared memory object is empty
bool isEmpty() const {return (size() <= 0);}
//! Read from shared memory object to "read_to" no more than "max_size" and return readed bytes count
int read(void * read_to, int max_size);
//! Read from shared memory object to "read_to" no more than "max_size" and return readed bytes count
int read(void * read_to, int max_size, int offset);
//! Write to shared memory object "data" with size "max_size" and return written bytes count
int write(const void * data, int max_size);
//! Write to shared memory object "data" with size "max_size" and return written bytes count
int write(const void * data, int max_size, int offset);
//! Write "data" to shared memory object
int write(const PIByteArray & data) {return write(data.data(), data.size_s());}
//! Write "data" to shared memory object
int write(const PIByteArray & data, int offset) {return write(data.data(), data.size_s(), offset);}
protected:
bool openDevice();
bool closeDevice();
int readDevice(void * read_to, int max_size) {return read(read_to, max_size, 0);}
int writeDevice(const void * data, int max_size) {return write(data, max_size, 0);}
private:
void initPrivate();
int dsize;
PRIVATE_DECLARATION
};
#endif // PISHAREDMEMORY_H

View File

@@ -0,0 +1,79 @@
/*
PIP - Platform Independent Primitives
PIIODevice that pass write to read
Copyright (C) 2017 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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 "pitransparentdevice.h"
/*! \class PITransparentDevice
* \brief PIIODevice that pass write to read
*
* \section PITransparentDevice_sec0 Synopsis
* This class pass all data from \a write() function to \a read().
* %PITransparentDevice contains internal queue and works in
* packets mode. If you write 3 different packets into this device,
* read will return this 3 packets.
*/
REGISTER_DEVICE(PITransparentDevice)
PITransparentDevice::PITransparentDevice() {
}
int PITransparentDevice::readDevice(void * read_to, int max_size) {
if (!canRead()) return -1;
que_mutex.lock();
if (que.isEmpty()) {
que_mutex.unlock();
return 0;
}
PIByteArray ba = que.dequeue();
que_mutex.unlock();
int ret = piMini(max_size, ba.size_s());
memcpy(read_to, ba.data(), ret);
return ret;
}
int PITransparentDevice::writeDevice(const void * data, int max_size) {
if (!canWrite()) return -1;
que_mutex.lock();
que.enqueue(PIByteArray(data, max_size));
que_mutex.unlock();
return max_size;
}
PIString PITransparentDevice::fullPathPrefix() const {
return PIStringAscii("tr");
}
bool PITransparentDevice::openDevice() {
return true;
}
bool PITransparentDevice::closeDevice() {
que_mutex.lock();
que.clear();
que_mutex.unlock();
return true;
}

View File

@@ -0,0 +1,51 @@
/*! \file pitransparentdevice.h
* \brief PIIODevice that pass write to read
*/
/*
PIP - Platform Independent Primitives
PIIODevice that pass write to read
Copyright (C) 2017 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PITRANSPARENTDEVICE_H
#define PITRANSPARENTDEVICE_H
#include "piiodevice.h"
class PIP_EXPORT PITransparentDevice: public PIIODevice
{
PIIODEVICE(PITransparentDevice)
public:
//! Contructs empty %PITransparentDevice
explicit PITransparentDevice();
~PITransparentDevice() {closeDevice();}
protected:
bool openDevice();
bool closeDevice();
int readDevice(void * read_to, int max_size);
int writeDevice(const void * data, int max_size);
PIString fullPathPrefix() const;
PIMutex que_mutex;
PIQueue<PIByteArray> que;
};
#endif // PITRANSPARENTDEVICE_H

153
src_main/io/piusb.h Executable file
View File

@@ -0,0 +1,153 @@
/*! \file piusb.h
* \brief USB device
*/
/*
PIP - Platform Independent Primitives
USB, based on libusb
Copyright (C) 2016 Ivan Pelipenko peri4ko@yandex.ru
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, either version 3 of the License, or
(at your option) any later version.
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/>.
*/
#ifndef PIUSB_H
#define PIUSB_H
#include "piiodevice.h"
struct usb_dev_handle;
class PIP_EXPORT PIUSB: public PIIODevice
{
PIIODEVICE(PIUSB)
public:
explicit PIUSB(ushort vid = 0, ushort pid = 0);
~PIUSB() {closeDevice();}
struct Endpoint {
Endpoint(uchar a = 0, uchar at = 0, ushort mps = 0) {address = a; attributes = at; max_packet_size = mps; parse();}
enum Direction {Write = 0, Read = 1};
enum TransferType {Control = 0, Isochronous = 1, Bulk = 2, Interrupt = 3};
enum SynchronisationType {NoSynchonisation= 0, Asynchronous = 2, Adaptive = 1, Synchronous = 3};
enum UsageType {DataEndpoint = 0, FeedbackEndpoint = 2, ExplicitFeedbackDataEndpoint = 1};
void parse();
bool isNull() const {return address == 0;}
uchar address;
uchar attributes;
ushort max_packet_size;
Direction direction;
TransferType transfer_type;
SynchronisationType synchronisation_type;
UsageType usage_type;
};
struct Interface {
Interface() {index = value_to_select = class_code = subclass_code = protocol_code = 0;}
uchar index;
uchar value_to_select;
ushort class_code;
ushort subclass_code;
ushort protocol_code;
PIVector<PIUSB::Endpoint> endpoints;
};
struct Configuration {
Configuration() {index = value_to_select = attributes = max_power = 0; self_powered = remote_wakeup = false;}
uchar index;
uchar value_to_select;
uchar attributes;
ushort max_power; // mA
bool self_powered;
bool remote_wakeup;
PIVector<PIUSB::Interface> interfaces;
};
struct Descriptor {
Descriptor() {memset(this, 0, sizeof(Descriptor));}
ushort usb_spec_number;
uchar device_class;
uchar device_subclass;
uchar device_protocol;
uchar max_packet_size;
ushort id_vendor;
ushort id_product;
ushort id_device_release;
uchar index_manufacturer;
uchar index_product;
uchar index_serial;
PIVector<PIUSB::Configuration> configurations;
};
const PIUSB::Descriptor & currentDescriptor() const {return desc_;}
const PIUSB::Configuration & currentConfiguration() const {return conf_;}
const PIUSB::Interface & currentInterface() const {return iface_;}
ushort vendorID() const {return vid_;}
ushort productID() const {return pid_;}
int deviceNumber() const {return property("deviceNumber").toInt();}
int timeoutRead() const {return property("timeoutRead").toInt();}
int timeoutWrite() const {return property("timeoutWrite").toInt();}
const PIUSB::Endpoint & endpointRead() const {return ep_read;}
const PIUSB::Endpoint & endpointWrite() const {return ep_write;}
const PIVector<PIUSB::Endpoint> & endpoints() const {return eps;}
PIVector<PIUSB::Endpoint> endpointsRead();
PIVector<PIUSB::Endpoint> endpointsWrite();
PIUSB::Endpoint getEndpointByAddress(uchar address);
void setVendorID(ushort vid) {vid_ = vid; setPath(PIString::fromNumber(vid_, 16).expandLeftTo(4, "0") + ":" + PIString::fromNumber(pid_, 16).expandLeftTo(4, "0"));}
void setProductID(ushort pid) {pid_ = pid; setPath(PIString::fromNumber(vid_, 16).expandLeftTo(4, "0") + ":" + PIString::fromNumber(pid_, 16).expandLeftTo(4, "0"));}
bool setConfiguration(uchar value);
bool setInterface(uchar value);
void setEndpointRead(const PIUSB::Endpoint & ep) {ep_read = ep;}
void setEndpointWrite(const PIUSB::Endpoint & ep) {ep_write = ep;}
void setDeviceNumber(int dn) {setProperty("deviceNumber", dn);}
void setTimeoutRead(int t) {setProperty("timeoutRead", t);}
void setTimeoutWrite(int t) {setProperty("timeoutWrite", t);}
int controlWrite(const void * data, int max_size);
void flush();
protected:
PIString fullPathPrefix() const {return "usb";}
bool configureDevice(const void * e_main, const void * e_parent = 0);
PIString constructFullPathDevice() const;
void configureFromFullPathDevice(const PIString & full_path);
int readDevice(void * read_to, int max_size);
int writeDevice(const void * data, int max_size);
//bool init();
bool openDevice();
bool closeDevice();
PIVector<PIUSB::Endpoint> eps;
ushort vid_, pid_;
int intefrace_, timeout_r, timeout_w;
int interface_claimed;
PIUSB::Endpoint ep_read, ep_write;
Descriptor desc_;
Configuration conf_;
Interface iface_;
usb_dev_handle * hdev;
};
PICout operator <<(PICout s, const PIUSB::Endpoint & v);
#endif // PIUSB_H