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

This commit is contained in:
2018-09-27 10:11:21 +00:00
parent 9b92720b61
commit 08592aae9a
50 changed files with 70 additions and 19 deletions

View File

@@ -0,0 +1,670 @@
/*
PIP - Platform Independent Primitives
Class for write binary data to logfile, and read or playback this data
Copyright (C) 2018 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() && i.baseName().startsWith(filePrefix())) {
setPath(i.path);
piBreak;
}
}
}
}
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 (!rapid_start) 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;
}
bool PIBinaryLog::cutBinLog(const PIBinaryLog::BinLogInfo & src, const PIString & dst, int from, int to) {
PIBinaryLog slog;
if (!slog.open(src.path, PIIODevice::ReadOnly)) return false;
PIVector<int> ids = src.records.keys();
slog.seekTo(from);
PIBinaryLog dlog;
dlog.createNewFile(dst);
bool first = true;
BinLogRecord br;
PISystemTime st;
while (!slog.isEnd() && ((slog.pos() <= to) || to < 0)) {
br = slog.readRecord();
if (first) {
st = br.timestamp;
first = false;
}
if (ids.contains(br.id)) {
dlog.writeBinLog_raw(br.id, br.timestamp - st, br.data);
}
}
return true;
}
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;
}

View File

@@ -0,0 +1,355 @@
/*! \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) 2018 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 join binlog files
/// TODO: Create functions to insert and delete records
class PIP_EXPORT 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 id of last readed record
int lastReadedID() const {return lastrecord.id;}
//! Returns timestamp of last readed record
PISystemTime lastReadedTimestamp() const {return lastrecord.timestamp;}
#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;}
//! 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;}
//! \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)
//! Get binlog info and statistic
static BinLogInfo getLogInfo(const PIString & path);
static bool cutBinLog(const BinLogInfo & src, const PIString & dst, int from, int to);
protected:
PIString fullPathPrefix() const {return PIStringAscii("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);
DeviceInfoFlags deviceInfoFlags() const {return PIIODevice::Reliable;}
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

View File

@@ -0,0 +1,138 @@
#include "pican.h"
#include "piincludes_p.h"
#if !defined(WINDOWS) && !defined(MAC_OS)
# define PIP_CAN
#endif
#ifdef PIP_CAN
# include <sys/ioctl.h>
# include <net/if.h>
# include <linux/can.h>
# include <linux/can/raw.h>
# ifndef AF_CAN
# define AF_CAN 29
# endif
# ifndef PF_CAN
# define PF_CAN AF_CAN
# endif
#endif
//PRIVATE_DEFINITION_START(PICAN)
//#ifdef PIP_CAN
//int fd;
//spi_ioc_transfer spi_ioc_tr;
//#endif
//PRIVATE_DEFINITION_END(PICAN)
REGISTER_DEVICE(PICAN)
PICAN::PICAN(const PIString & path, PIIODevice::DeviceMode mode) : PIIODevice(path, mode) {
setThreadedReadBufferSize(256);
setPath(path);
can_id = 0;
}
bool PICAN::openDevice() {
#ifdef PIP_CAN
piCout << "PICAN open device" << path();
sock = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if(sock < 0){
piCoutObj << "Error! while opening socket";
return false;
}
ifreq ifr;
strcpy(ifr.ifr_name, path().dataAscii());
piCout << "PICAN try to get interface index...";
if(ioctl(sock, SIOCGIFINDEX, &ifr) < 0){
piCoutObj << "Error! while determin the interface ioctl";
return false;
}
// bind socket to all CAN interface
sockaddr_can addr;
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
piCout << "PICAN try to bind socket to interface" << ifr.ifr_ifindex;
if(bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0){
piCoutObj << "Error! while binding socket";
return false;
}
piCout << "PICAN Open OK!";
return true;
#else
piCoutObj << "PICAN not implemented on windows";
return false;
#endif
}
bool PICAN::closeDevice() {
#ifdef PIP_CAN
if (sock > 0) ::close(sock);
#endif
return true;
}
int PICAN::readDevice(void * read_to, int max_size) {
#ifdef PIP_CAN
//piCout << "PICAN read";
can_frame frame;
int ret = 0;
ret = ::read(sock, &frame, sizeof(can_frame));
if(ret < 0) {piCoutObj << "Error while read CAN frame " << ret; return -1;}
//piCoutObj << "receive CAN frame Id =" << frame.can_id;
memcpy(read_to, frame.data, piMini(frame.can_dlc, max_size));
return max_size;
#endif
return 0;
}
int PICAN::writeDevice(const void * data, int max_size) {
#ifdef PIP_CAN
//piCout << "PICAN write" << can_id << max_size;
if (max_size > 8) {piCoutObj << "Can't send CAN frame bigger than 8 bytes (requested " << max_size << ")!"; return -1;}
can_frame frame;
frame.can_id = can_id;
frame.can_dlc = max_size;
memcpy(frame.data, data, max_size);
int ret = 0;
ret = ::write(sock, &frame, sizeof(can_frame));
if(ret < 0) {piCoutObj << "Error while send CAN frame " << ret; return -1;}
return max_size;
#endif
return 0;
}
void PICAN::setCANID(int id) {
can_id = id;
}
int PICAN::CANID() const {
return can_id;
}
PIString PICAN::constructFullPathDevice() const {
PIString ret;
ret << path() << ":" << PIString::fromNumber(CANID(),16);
return ret;
}
void PICAN::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: setPath(p); break;
case 1: setCANID(p.toInt(16)); break;
default: break;
}
}
}

View File

@@ -0,0 +1,32 @@
#ifndef PICAN_H
#define PICAN_H
#include "piiodevice.h"
class PIP_EXPORT PICAN: public PIIODevice
{
PIIODEVICE(PICAN)
public:
explicit PICAN(const PIString & path = PIString(), PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);
~PICAN() {}
void setCANID(int id);
int CANID() const;
protected:
bool openDevice();
bool closeDevice();
int readDevice(void * read_to, int max_size);
int writeDevice(const void * data, int max_size);
PIString fullPathPrefix() const {return PIStringAscii("can");}
PIString constructFullPathDevice() const;
void configureFromFullPathDevice(const PIString & full_path);
DeviceInfoFlags deviceInfoFlags() const {return PIIODevice::Reliable;}
private:
int sock;
int can_id;
};
#endif // PICAN_H

831
src_main/io_devices/piconfig.cpp Executable file
View File

@@ -0,0 +1,831 @@
/*
PIP - Platform Independent Primitives
Config parser
Copyright (C) 2018 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;
}
#ifdef PIP_STD_IOSTREAM
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);
}
#endif
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);
}
#ifdef PIP_STD_IOSTREAM
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;
}
#endif

539
src_main/io_devices/piconfig.h Executable file
View File

@@ -0,0 +1,539 @@
/*! \file piconfig.h
* \brief Configuration parser and writer
*/
/*
PIP - Platform Independent Primitives
Configuration parser and writer
Copyright (C) 2018 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;
#ifdef PIP_STD_IOSTREAM
friend std::ostream & operator <<(std::ostream & s, const Branch & v);
#endif
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);}}
#ifdef PIP_STD_IOSTREAM
void coutt(std::ostream & s, const PIString & p) const {piForeachC (Entry * i, *this) i->coutt(s, p);}
#endif
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;}
#ifdef PIP_STD_IOSTREAM
void coutt(std::ostream & s, const PIString & p) const;
#endif
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;
};
#ifdef PIP_STD_IOSTREAM
std::ostream & operator <<(std::ostream & s, const PIConfig::Branch & v);
std::ostream & operator <<(std::ostream & s, const PIConfig::Entry & v);
#endif
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

474
src_main/io_devices/pidir.cpp Executable file
View File

@@ -0,0 +1,474 @@
/*
PIP - Platform Independent Primitives
Directory
Copyright (C) 2018 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;
if (path_[0] == separator) return true;
#ifdef WINDOWS
return (path_.mid(1, 1) == ":");
#endif
return false;
}
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.isEmpty()) {
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;
#ifndef WINDOWS
bool is_abs = isAbsolute();
#endif
if (withParents) {
PIStringList l = d.path().split(separator);
//piCout << l;
l.removeAll("");
//piCout << l;
PIString cdp;
piForeachC (PIString & i, l) {
if (!cdp.isEmpty()
#ifndef WINDOWS
|| is_abs
#endif
)
cdp += separator;
cdp += i;
//piCout << "dir" << cdp;
if (!isExists(cdp))
if (!makeDir(cdp))
return false;
}
/*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 << "check" << 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 << "make" << 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_devices/pidir.h Executable file
View File

@@ -0,0 +1,136 @@
/*! \file pidir.h
* \brief Local directory
*/
/*
PIP - Platform Independent Primitives
Directory
Copyright (C) 2018 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

1277
src_main/io_devices/piethernet.cpp Executable file
View File

@@ -0,0 +1,1277 @@
/*
PIP - Platform Independent Primitives
Ethernet, UDP/TCP Broadcast/Multicast
Copyright (C) 2018 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
PIEthernet::Address::Address(uint _ip, ushort _port) {
set(_ip, _port);
}
PIEthernet::Address::Address(const PIString & ip_port) {
set(ip_port);
}
PIEthernet::Address::Address(const PIString & _ip, ushort _port) {
set(_ip, _port);
}
PIString PIEthernet::Address::ipString() const {
PIString ret = PIString::fromNumber(ip_b[0]);
ret << "." << PIString::fromNumber(ip_b[1]);
ret << "." << PIString::fromNumber(ip_b[2]);
ret << "." << PIString::fromNumber(ip_b[3]);
return ret;
}
PIString PIEthernet::Address::toString() const {
return ipString() + ":" + PIString::fromNumber(port_);
}
void PIEthernet::Address::setIP(uint _ip) {
ip_ = _ip;
}
void PIEthernet::Address::setIP(const PIString & _ip) {
initIP(_ip);
}
void PIEthernet::Address::setPort(ushort _port) {
port_ = _port;
}
void PIEthernet::Address::set(const PIString & ip_port) {
PIString _ip; int p(0);
splitIPPort(ip_port, &_ip, &p);
port_ = p;
initIP(_ip);
}
void PIEthernet::Address::set(const PIString & _ip, ushort _port) {
initIP(_ip);
port_ = _port;
}
void PIEthernet::Address::set(uint _ip, ushort _port) {
ip_ = _ip;
port_ = _port;
}
void PIEthernet::Address::clear() {
ip_ = 0;
port_ = 0;
}
bool PIEthernet::Address::isNull() const {
return (ip_ == 0) && (port_ == 0);
}
PIEthernet::Address PIEthernet::Address::resolve(const PIString & host_port) {
PIString host; int port(0);
splitIPPort(host_port, &host, &port);
return resolve(host, port);
}
PIEthernet::Address PIEthernet::Address::resolve(const PIString & host, ushort port) {
Address ret(0, port);
hostent * he = gethostbyname(host.dataAscii());
if (!he)
return ret;
if (he->h_addr_list[0])
ret.setIP(*((uint*)(he->h_addr_list[0])));
return ret;
}
void PIEthernet::Address::splitIPPort(const PIString & ipp, PIString * _ip, int * _port) {
//piCout << "parse" << ipp;
int sp = ipp.findLast(":");
if (_ip != 0) *_ip = (sp >= 0 ? ipp.left(sp) : ipp);
if (_port != 0 && sp >= 0) *_port = ipp.right(ipp.length() - ipp.find(":") - 1).toInt();
}
void PIEthernet::Address::initIP(const PIString & _ip) {
ip_ = inet_addr(_ip.dataAscii());
}
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();
addr_r.set(ip_port);
setType(type_);
setParameters(params_);
if (type_ != UDP) init();
}
PIEthernet::PIEthernet(int sock_, PIString ip_port): PIIODevice("", ReadWrite) {
construct();
addr_s.set(ip_port);
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);
setOption(BlockingWrite);
piMonitor.ethernets++;
connected_ = connecting_ = listen_threaded = server_bounded = false;
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;
}
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));
}
PIEthernet::Address PIEthernet::applyMask(const PIEthernet::Address & ip, const PIEthernet::Address & mask) {
Address ret(ip);
ret.ip_ &= mask.ip_;
return ret;
}
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));
}
PIEthernet::Address PIEthernet::getBroadcast(const PIEthernet::Address & ip, const PIEthernet::Address & mask) {
Address ret(ip);
ret.ip_ |= ~mask.ip_;
return ret;
}
bool PIEthernet::openDevice() {
if (connected_) return true;
init();
if (sock == -1 || path().isEmpty()) return false;
addr_r.set(path());
//Address::splitIPPort(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(addr_r.port());
PIFlags<Parameters> params = parameters();
if (params[PIEthernet::Broadcast]) PRIVATE->addr_.sin_addr.s_addr = INADDR_ANY;
else PRIVATE->addr_.sin_addr.s_addr = addr_r.ip();
#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" << addr_r << "," << ethErrorString();
return false;
}
opened_ = true;
while (!mcast_queue.isEmpty())
joinMulticastGroup(mcast_queue.dequeue());
//cout << "!" << endl;
applyTimeouts();
applyOptInt(IPPROTO_IP, IP_TTL, TTL());
addr_lr.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();
bool ned = connected_;
connected_ = connecting_ = false;
if (ned) disconnected(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!";
addr_r.set(path());
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(addr_r.ipString());
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 = addr_r.ip();
/*#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();
addr_r.set(path());
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 = addr_r.ip();
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;
addr_r.set(path());
memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
PRIVATE->addr_.sin_port = htons(addr_r.port());
PRIVATE->addr_.sin_addr.s_addr = addr_r.ip();
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" << addr_r << "," << ethErrorString();
return false;
}
if (::listen(sock, 64) == -1) {
piCoutObj << "Can`t listen on"<< addr_r << "," << 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
addr_r.set(path());
memset(&PRIVATE->addr_, 0, sizeof(PRIVATE->addr_));
PRIVATE->addr_.sin_port = htons(addr_r.port());
PRIVATE->addr_.sin_addr.s_addr = addr_r.ip();
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" << addr_r << "," << 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) {
addr_lr.set(uint(PRIVATE->raddr_.sin_addr.s_addr), ntohs(PRIVATE->raddr_.sin_port));
//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(addr_s.port());
PRIVATE->addr_.sin_addr.s_addr = addr_s.ip();
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(addr_s.port());
/*if (params[PIEthernet::Broadcast]) PRIVATE->saddr_.sin_addr.s_addr = INADDR_BROADCAST;
else*/ PRIVATE->saddr_.sin_addr.s_addr = addr_s.ip();
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,
#ifndef WINDOWS
isOptionSet(BlockingWrite) ? 0 : MSG_DONTWAIT
#else
0
#endif
, (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_));
addr_r.set(path());
PRIVATE->addr_.sin_port = htons(addr_r.port());
PRIVATE->addr_.sin_addr.s_addr = addr_r.ip();
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" << addr_r << "," << 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;
}
PIIODevice::DeviceInfoFlags PIEthernet::deviceInfoFlags() const {
switch (type()) {
case UDP: return 0;
case TCP_Client:
case TCP_Server:
case TCP_SingleTCP: return Sequential | Reliable;
default: break;
}
return 0;
}
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;
}
PIEthernet::Address PIEthernet::interfaceAddress(const PIString & interface_) {
#ifdef WINDOWS
piCout << "[PIEthernet] Not implemented on Windows, use \"PIEthernet::allAddresses\" or \"PIEthernet::interfaces\" instead";
return Address();
#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 Address(uint(sa->sin_addr.s_addr));
#endif
}
PIVector<PIEthernet::Address> 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();
PIVector<Address> ret;
bool has_127 = false;
piForeachC (PIEthernet::Interface & i, il) {
if (i.address == "127.0.0.1") has_127 = true;
Address a(i.address);
if (a.ip() == 0) continue;
ret << a;
}
// piCout << "[PIEthernet::allAddresses]" << al;
if (!has_127) ret << Address("127.0.0.1");
return ret;
//#endif
}
// 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));
}

521
src_main/io_devices/piethernet.h Executable file
View File

@@ -0,0 +1,521 @@
/*! \file piethernet.h
* \brief Ethernet device
*/
/*
PIP - Platform Independent Primitives
Ethernet, UDP/TCP Broadcast/Multicast
Copyright (C) 2018 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"
#ifdef ANDROID
struct
#else
class
#endif
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
};
//! \brief IPv4 network address, IP and port
class Address {
friend class PIEthernet;
friend inline PIByteArray & operator <<(PIByteArray & s, const PIEthernet::Address & v);
friend inline PIByteArray & operator >>(PIByteArray & s, PIEthernet::Address & v);
public:
//! Contructs %Address with binary representation of IP and port
Address(uint ip = 0, ushort port = 0);
//! Contructs %Address with string representation "i.i.i.i:p"
Address(const PIString & ip_port);
//! Contructs %Address with IP string representation "i.i.i.i" and port
Address(const PIString & ip, ushort port);
//! Returns binary IP
uint ip() const {return ip_;}
//! Returns port
ushort port() const {return port_;}
//! Returns string IP
PIString ipString() const;
//! Returns string representation of IP and port "i.i.i.i:p"
PIString toString() const;
//! Set address IP
void setIP(uint ip);
//! Set address IP
void setIP(const PIString & ip);
//! Set address port
void setPort(ushort port);
//! Set address IP and port, "i.i.i.i:p"
void set(const PIString & ip_port);
//! Set address IP and port, "i.i.i.i"
void set(const PIString & ip, ushort port);
//! Set address binary IP and port
void set(uint ip, ushort port);
//! Set IP and port to 0
void clear();
//! Returns if IP and port is 0
bool isNull() const;
//! Resolve hostname "host:port" and return it address or null address on error
static Address resolve(const PIString & host_port);
//! Resolve hostname "host" with port "port" and return it address or null address on error
static Address resolve(const PIString & host, ushort port);
static void splitIPPort(const PIString & ipp, PIString * ip, int * port);
private:
void initIP(const PIString & _ip);
union {
uint ip_;
uchar ip_b[4];
};
ushort port_;
};
//! 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) {addr_r.set(ip, port); setPath(addr_r.toString());}
//! Set read address in format "i.i.i.i:p"
void setReadAddress(const PIString & ip_port) {addr_r.set(ip_port); setPath(addr_r.toString());}
//! Set read address
void setReadAddress(const Address & addr) {addr_r = addr; setPath(addr_r.toString());}
//! Set read IP
void setReadIP(const PIString & ip) {addr_r.setIP(ip); setPath(addr_r.toString());}
//! Set read port
void setReadPort(int port) {addr_r.setPort(port); setPath(addr_r.toString());}
//! Set send address
void setSendAddress(const PIString & ip, int port) {addr_s.set(ip, port);}
//! Set send address in format "i.i.i.i:p"
void setSendAddress(const PIString & ip_port) {addr_s.set(ip_port);}
//! Set send address
void setSendAddress(const Address & addr) {addr_s = addr;}
//! Set send IP
void setSendIP(const PIString & ip) {addr_s.setIP(ip);}
//! Set send port
void setSendPort(int port) {addr_s.setPort(port);}
//! Returns read address in format "i.i.i.i:p"
Address readAddress() const {return addr_r;}
//! Returns read IP
PIString readIP() const {return addr_r.ipString();}
//! Returns read port
int readPort() const {return addr_r.port();}
//! Returns send address in format "i.i.i.i:p"
Address sendAddress() const {return addr_s;}
//! Returns send IP
PIString sendIP() const {return addr_s.ipString();}
//! Returns send port
int sendPort() const {return addr_s.port();}
//! Returns address of last received UDP packet in format "i.i.i.i:p"
Address lastReadAddress() const {return addr_lr;}
//! Returns IP of last received UDP packet
PIString lastReadIP() const {return addr_lr.ipString();}
//! Returns port of last received UDP packet
int lastReadPort() const {return addr_lr.port();}
//! 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();}
//! Connect to TCP server with address "addr". Use only for TCP_Client
bool connect(const Address & addr) {setPath(addr.toString()); 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);}
//! Start listen for incoming TCP connections on address "addr". Use only for TCP_Server
bool listen(const Address & addr, bool threaded = false) {setReadAddress(addr); 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) {addr_s.set(ip, 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) {addr_s.set(ip_port); if (threaded) {writeThreaded(data, size); return true;} return send(data, size);}
//! Send data "data" with size "size" to address "addr"
bool send(const Address & addr, const void * data, int size, bool threaded = false) {addr_s = addr; 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) {addr_s.set(ip, 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) {addr_s.set(ip_port); if (threaded) {writeThreaded(data); return true;} return (write(data) == data.size_s());}
//! Send data "data" to address "addr" for UDP
bool send(const Address & addr, const PIByteArray & data, bool threaded = false) {addr_s = addr; 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 PIEthernet::Address interfaceAddress(const PIString & interface_);
//! Returns all system network IP addresses
static PIVector<PIEthernet::Address> allAddresses();
static PIString macFromBytes(const PIByteArray & mac);
static PIByteArray macToBytes(const PIString & mac);
static PIString applyMask(const PIString & ip, const PIString & mask);
static Address applyMask(const Address & ip, const Address & mask);
static PIString getBroadcast(const PIString & ip, const PIString & mask);
static Address getBroadcast(const Address & ip, const Address & 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);
DeviceInfoFlags deviceInfoFlags() const;
//! 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;
bool connected_, connecting_, listen_threaded, server_bounded;
mutable Address addr_r, addr_s, addr_lr;
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);}
inline PICout operator <<(PICout s, const PIEthernet::Address & v) {s.space(); s.setControl(0, true); s << "Address(" << v.toString() << ")"; s.restoreControl(); return s;}
inline bool operator ==(const PIEthernet::Address & v0, const PIEthernet::Address & v1) {return (v0.ip() == v1.ip() && v0.port() == v1.port());}
inline bool operator !=(const PIEthernet::Address & v0, const PIEthernet::Address & v1) {return (v0.ip() != v1.ip() || v0.port() != v1.port());}
inline PIByteArray & operator <<(PIByteArray & s, const PIEthernet::Address & v) {s << v.ip_ << v.port_; return s;}
inline PIByteArray & operator >>(PIByteArray & s, PIEthernet::Address & v) {s >> v.ip_ >> v.port_; return s;}
#endif // PIETHERNET_H

743
src_main/io_devices/pifile.cpp Executable file
View File

@@ -0,0 +1,743 @@
/*
PIP - Platform Independent Primitives
File
Copyright (C) 2018 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 <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
# ifdef CC_GCC
# define _fopen_call_ fopen64
# define _fseek_call_ fseeko64
# define _ftell_call_ ftello64
# else
# define _fopen_call_ fopen
# define _fseek_call_ fseek
# define _ftell_call_ ftell
# endif
# 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)
PRIVATE_DEFINITION_START(PIFile)
FILE * fd;
PRIVATE_DEFINITION_END(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;
if (!PIDir(ret).isExists()) return (PIStringAscii(".") + PIDir::separator);
return ret;
}
PIFile::PIFile(): PIIODevice() {
PRIVATE->fd = 0;
fdi = -1;
setPrecision(5);
}
PIFile::PIFile(const PIString & path, PIIODevice::DeviceMode mode): PIIODevice(path, mode) {
PRIVATE->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) {
// PRIVATE->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);
}
}
PRIVATE->fd = _fopen_call_(p.data(), strType(mode_).data());
//piCout << "fopen " << path() << ": " << strType(mode_).data() << PRIVATE->fd;
bool opened = (PRIVATE->fd != 0);
if (opened) {
fdi = fileno(PRIVATE->fd);
#ifndef WINDOWS
fcntl(fdi, F_SETFL, O_NONBLOCK);
#endif
_fseek_call_(PRIVATE->fd, 0, SEEK_SET);
clearerr(PRIVATE->fd);
}
//piCout << "open file" << PRIVATE->fd << opened_;
return opened;
}
bool PIFile::closeDevice() {
//piCout << "close file" << PRIVATE->fd << opened_;
if (isClosed() || PRIVATE->fd == 0) return true;
bool cs = (fclose(PRIVATE->fd) == 0);
if (cs) PRIVATE->fd = 0;
fdi = -1;
//piCout << "closed file" << PRIVATE->fd << opened_;
return cs;
}
PIString PIFile::readLine() {
PIByteArray str;
if (isClosed()) return str;
int cc;
while (!isEnd()) {
cc = fgetc(PRIVATE->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_(PRIVATE->fd, 0, SEEK_END); clearerr(PRIVATE->fd);
s = pos();
_fseek_call_(PRIVATE->fd, cp, SEEK_SET); clearerr(PRIVATE->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(PRIVATE->fd);
}
void PIFile::seek(llong position) {
if (isClosed()) return;
if (position == pos()) return;
_fseek_call_(PRIVATE->fd, position, SEEK_SET);
clearerr(PRIVATE->fd);
}
void PIFile::seekToBegin() {
if (isClosed()) return;
_fseek_call_(PRIVATE->fd, 0, SEEK_SET);
clearerr(PRIVATE->fd);
}
void PIFile::seekToEnd() {
if (isClosed()) return;
_fseek_call_(PRIVATE->fd, 0, SEEK_END);
clearerr(PRIVATE->fd);
}
void PIFile::seekToLine(llong line) {
if (isClosed()) return;
seekToBegin();
piForTimes(line) readLine();
clearerr(PRIVATE->fd);
}
char PIFile::readChar() {
return (char)fgetc(PRIVATE->fd);
}
void PIFile::setPath(const PIString & path) {
PIIODevice::setPath(path);
if (isOpened()) open();
}
llong PIFile::pos() const {
if (isClosed()) return -1;
return _ftell_call_(PRIVATE->fd);
}
bool PIFile::isEnd() const {
if (isClosed()) return true;
return (feof(PRIVATE->fd) || ferror(PRIVATE->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() && PRIVATE->fd != 0) ret = fprintf(PRIVATE->fd, ("%" + prec_str + "lf").data(), v);
return *this;
}
PIFile &PIFile::operator >>(double & v) {
if (canRead() && PRIVATE->fd != 0) ret = fscanf(PRIVATE->fd, "%lf", &v);
return *this;
}
PIFile &PIFile::operator >>(float & v) {
if (canRead() && PRIVATE->fd != 0) ret = fscanf(PRIVATE->fd, "%f", &v);
return *this;
}
PIFile &PIFile::operator >>(ullong & v) {
if (canRead() && PRIVATE->fd != 0) ret = fscanf(PRIVATE->fd, "%lln", &v);
return *this;
}
PIFile &PIFile::operator >>(ulong & v) {
if (canRead() && PRIVATE->fd != 0) ret = fscanf(PRIVATE->fd, "%ln", &v);
return *this;
}
PIFile &PIFile::operator >>(uint & v) {
if (canRead() && PRIVATE->fd != 0) ret = fscanf(PRIVATE->fd, "%n", &v);
return *this;
}
PIFile &PIFile::operator >>(ushort & v) {
if (canRead() && PRIVATE->fd != 0) ret = fscanf(PRIVATE->fd, "%hn", &v);
return *this;
}
PIFile &PIFile::operator >>(uchar & v) {
if (canRead() && PRIVATE->fd != 0) ret = fscanf(PRIVATE->fd, "%hhn", &v);
return *this;
}
PIFile &PIFile::operator >>(llong & v) {
if (canRead() && PRIVATE->fd != 0) ret = fscanf(PRIVATE->fd, "%lln", &v);
return *this;
}
PIFile &PIFile::operator >>(long & v) {
if (canRead() && PRIVATE->fd != 0) ret = fscanf(PRIVATE->fd, "%ln", &v);
return *this;
}
PIFile &PIFile::operator >>(int & v) {
if (canRead() && PRIVATE->fd != 0) ret = fscanf(PRIVATE->fd, "%n", &v);
return *this;
}
PIFile &PIFile::operator >>(short & v) {
if (canRead() && PRIVATE->fd != 0) ret = fscanf(PRIVATE->fd, "%hn", &v);
return *this;
}
PIFile &PIFile::operator >>(char & v) {
if (canRead() && PRIVATE->fd != 0) ret = fscanf(PRIVATE->fd, "%hhn", &v);
return *this;
}
PIFile &PIFile::operator <<(float v) {
if (canWrite() && PRIVATE->fd != 0) ret = fprintf(PRIVATE->fd, ("%" + prec_str + "f").data(), v);
return *this;
}
PIFile &PIFile::operator <<(ullong v) {
if (canWrite() && PRIVATE->fd != 0) ret = fprintf(PRIVATE->fd, "%llu", v);
return *this;
}
PIFile &PIFile::operator <<(ulong v) {
if (canWrite() && PRIVATE->fd != 0) ret = fprintf(PRIVATE->fd, "%lu", v);
return *this;
}
PIFile &PIFile::operator <<(uint v) {
if (canWrite() && PRIVATE->fd != 0) ret = fprintf(PRIVATE->fd, "%u", v);
return *this;
}
PIFile &PIFile::operator <<(ushort v) {
if (canWrite() && PRIVATE->fd != 0) ret = fprintf(PRIVATE->fd, "%hu", v);
return *this;
}
PIFile &PIFile::operator <<(uchar v) {
if (canWrite() && PRIVATE->fd != 0) ret = fprintf(PRIVATE->fd, "%u", int(v));
return *this;
}
PIFile &PIFile::operator <<(llong v) {
if (canWrite() && PRIVATE->fd != 0) ret = fprintf(PRIVATE->fd, "%lld", v);
return *this;
}
PIFile &PIFile::operator <<(long v) {
if (canWrite() && PRIVATE->fd != 0) ret = fprintf(PRIVATE->fd, "%ld", v);
return *this;
}
PIFile &PIFile::operator <<(int v) {
if (canWrite() && PRIVATE->fd != 0) ret = fprintf(PRIVATE->fd, "%d", v);
return *this;
}
PIFile &PIFile::operator <<(short v) {
if (canWrite() && PRIVATE->fd != 0) ret = fprintf(PRIVATE->fd, "%hd", v);
return *this;
}
PIFile &PIFile::operator <<(const PIByteArray & v) {
if (canWrite() && PRIVATE->fd != 0) write(v.data(), v.size());
return *this;
}
PIFile &PIFile::operator <<(const char v) {
if (canWrite() && PRIVATE->fd != 0) write(&v, 1);
return *this;
}
int PIFile::readDevice(void * read_to, int max_size) {
if (!canRead() || PRIVATE->fd == 0) return -1;
return fread(read_to, 1, max_size, PRIVATE->fd);
}
int PIFile::writeDevice(const void * data, int max_size) {
if (!canWrite() || PRIVATE->fd == 0) return -1;
return fwrite(data, 1, max_size, PRIVATE->fd);
}
PIFile &PIFile::operator <<(const PIString & v) {
if (canWrite() && PRIVATE->fd != 0)
*this << v.toCharset(defaultCharset());
return *this;
}
void PIFile::clear() {
close();
PRIVATE->fd = fopen(path().data(), "w");
if (PRIVATE->fd != 0) fclose(PRIVATE->fd);
PRIVATE->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;
}

323
src_main/io_devices/pifile.h Executable file
View File

@@ -0,0 +1,323 @@
/*! \file pifile.h
* \brief Local file
*/
/*
PIP - Platform Independent Primitives
File
Copyright (C) 2018 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 PIP_EXPORT FileInfo {
FileInfo() {size = 0; id_group = id_user = 0;}
enum PIP_EXPORT Flag {
File = 0x01,
Dir = 0x02,
Dot = 0x04,
DotDot = 0x08,
SymbolicLink = 0x10,
Hidden = 0x20
};
typedef PIFlags<FileInfo::Flag> Flags;
struct PIP_EXPORT 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 PIStringAscii("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();
DeviceInfoFlags deviceInfoFlags() const {return PIIODevice::Sequential | PIIODevice::Reliable;}
private:
PIString strType(const PIIODevice::DeviceMode type);
PRIVATE_DECLARATION
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,95 @@
/*
PIP - Platform Independent Primitives
PIIODevice wrapper around PIByteArray
Copyright (C) 2018 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,82 @@
/*! \file piiobytearray.h
* \brief PIIODevice wrapper around PIByteArray
*/
/*
PIP - Platform Independent Primitives
PIIODevice wrapper around PIByteArray
Copyright (C) 2018 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);
DeviceInfoFlags deviceInfoFlags() const {return PIIODevice::Sequential | PIIODevice::Reliable;}
ssize_t pos;
PIByteArray * data_;
};
#endif // PIIOBYTEARRAY_H

View File

@@ -0,0 +1,407 @@
/*
PIP - Platform Independent Primitives
Abstract input/output device
Copyright (C) 2018 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";
if (threadedReadBufferSize() == 0)
piCoutObj << "Warning: threadedReadBufferSize() == 0, read may be useless!";
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;
}

395
src_main/io_devices/piiodevice.h Executable file
View File

@@ -0,0 +1,395 @@
/*! \file piiodevice.h
* \brief Abstract input/output device
*/
/*
PIP - Platform Independent Primitives
Abstract input/output device
Copyright (C) 2018 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
};
//! \brief Characteristics of PIIODevice subclass
enum DeviceInfoFlag {
Sequential /*! Continuous channel */ = 0x01,
Reliable /*! Channel is safe */ = 0x02
};
typedef PIFlags<DeviceOption> DeviceOptions;
typedef PIFlags<DeviceInfoFlag> DeviceInfoFlags;
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);
//! Returns device characteristic flags
DeviceInfoFlags infoFlags() const {return deviceInfoFlags();}
//! 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); threadedReadBufferSizeChanged();}
//! 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_;}
bool open(DeviceMode _mode) {mode_ = _mode; if (!init_) init(); opened_ = openDevice(); if (opened_) opened(); return opened_;}
EVENT_HANDLER2(bool, open, const PIString &, _path, 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_HANDLER1(int, write, PIByteArray, data) {return writeDevice(data.data(), data.size_s());}
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() {;}
//! Reimplement to return correct \a DeviceInfoFlags. Default implementation returns 0
virtual DeviceInfoFlags deviceInfoFlags() const {return 0;}
//! Reimplement to apply new \a threadedReadBufferSize()
virtual void threadedReadBufferSizeChanged() {;}
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

View File

@@ -0,0 +1,38 @@
/*
PIP - Platform Independent Primitives
Module includes
Copyright (C) 2018 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 PIIODEVICESMODULE_H
#define PIIODEVICESMODULE_H
#include "pibinarylog.h"
#include "pican.h"
#include "piconfig.h"
#include "pidir.h"
#include "piethernet.h"
#include "pifile.h"
#include "piiobytearray.h"
#include "piiostring.h"
#include "pipeer.h"
#include "piserial.h"
#include "pisharedmemory.h"
#include "pispi.h"
#include "pitransparentdevice.h"
#include "piusb.h"
#endif // PIIODEVICESMODULE_H

View File

@@ -0,0 +1,25 @@
/*
PIP - Platform Independent Primitives
Module includes
Copyright (C) 2018 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 "piiodevicesmodule.h"
#endif // PIIOMODULE_H

View File

@@ -0,0 +1,102 @@
/*
PIP - Platform Independent Primitives
PIIODevice wrapper around PIString
Copyright (C) 2018 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);
}

View File

@@ -0,0 +1,85 @@
/*! \file piiostring.h
* \brief PIIODevice wrapper around PIString
*/
/*
PIP - Platform Independent Primitives
PIIODevice wrapper around PIString
Copyright (C) 2018 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);
DeviceInfoFlags deviceInfoFlags() const {return PIIODevice::Sequential | PIIODevice::Reliable;}
ssize_t pos;
PIString * str;
};
#endif // PIIOSTRING_H

View File

@@ -0,0 +1,21 @@
/*
PIP - Platform Independent Primitives
Multiprotocol
Copyright (C) 2018 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

View File

@@ -0,0 +1,93 @@
/*
PIP - Platform Independent Primitives
Multiprotocol
Copyright (C) 2018 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

1099
src_main/io_devices/pipeer.cpp Executable file
View File

@@ -0,0 +1,1099 @@
/*
PIP - Platform Independent Primitives
Peer - named I/O ethernet node
Copyright (C) 2018 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"
#include "pidatatransfer.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
class PIPeer::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;
};
PIPeer::PeerData::PeerData(const PIString & n): PIObject(n) {
dt_in.setPacketSize(_PIPEER_MSG_SIZE);
dt_out.setPacketSize(_PIPEER_MSG_SIZE);
dt_in.setCRCEnabled(false);
dt_out.setCRCEnabled(false);
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::PeerAddress::PeerAddress(const PIEthernet::Address & a, const PIEthernet::Address & m): address(a), netmask(m) {
ping = -1.;
wait_ping = false;
last_ping = PISystemTime::current(true);
}
int PIPeer::PeerInfo::ping() const {
int ret = -1;
piForeachC (PeerAddress & 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;
}
PIEthernet::Address PIPeer::PeerInfo::fastestAddress() const {
double mp = -1.;
PIEthernet::Address ret;
piForeachC (PeerAddress & 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::PeerAddress(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) {
PIByteArray ba = 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(ba);
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);
eth_mutex.lock();
// piCout << "dataRead lock";
if (type == 5) { // ping request
PIEthernet::Address 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::PeerAddress & a, pi->addresses) {
if (eth_send.send(a.address, sba))
diag_s.received(sba.size_s());
}
send_mutex.unlock();
}
}
}
eth_mutex.unlock();
return true;
}
if (type == 6) { // ping request
PIEthernet::Address 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::PeerAddress & 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;
eth_mutex.unlock();
return true;
}
}
}
eth_mutex.unlock();
return true;
}
// piCoutObj << "received data from" << from << "packet" << type;
if (type != 4) {
eth_mutex.unlock();
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();
eth_mutex.unlock();
return true;
}
if (pt == 1) {
peers_mutex.unlock();
eth_mutex.unlock();
dtReceived(from, pba);
return true;
}
if (pt == 2 || pt == 3) {
peers_mutex.unlock();
eth_mutex.unlock();
if (fp->_data)
fp->_data->receivedPacket(pt, pba);
return true;
}
peers_mutex.unlock();
eth_mutex.unlock();
return true;
}
PIMutexLocker plocker(peers_mutex);
PeerInfo * dp = quickestPeer(to);
if (dp == 0) {
//piCoutObj << "Can`t find peer \"" << to << "\"!";
eth_mutex.unlock();
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);
}
eth_mutex.unlock();
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::PeerAddress & ra(rpeer.addresses[z]);
for (int k = 0; k < peer.addresses.size_s(); ++k) {
PeerInfo::PeerAddress & 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;
PIEthernet::Address 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 << "sendMBcast 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::PeerAddress & 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...";
return true;
// 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);
// piCout << "syncPeers lock";
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);
// piCout << "reinit lock";
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();
PIVector<PIEthernet::Address> al = PIEthernet::allAddresses();
PIStringList sl;
piForeachC (PIEthernet::Address & a, al)
sl << a.ipString();
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);
}

215
src_main/io_devices/pipeer.h Executable file
View File

@@ -0,0 +1,215 @@
/*! \file pipeer.h
* \brief Peering net node
*/
/*
PIP - Platform Independent Primitives
Peer - named I/O ethernet node, forming self-organized peering network
Copyright (C) 2018 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"
class PIP_EXPORT PIPeer: public PIIODevice
{
PIIODEVICE(PIPeer)
private:
class PeerData;
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 PeerAddress {
PeerAddress(const PIEthernet::Address & a = PIEthernet::Address(), const PIEthernet::Address & m = PIEthernet::Address("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;}
PIEthernet::Address address;
PIEthernet::Address netmask;
double ping; // ms
bool wait_ping;
PISystemTime last_ping;
};
PIString name;
PIVector<PeerAddress> addresses;
int dist;
PIStringList neighbours;
bool isNeighbour() const {return dist == 0;}
int ping() const;
PIEthernet::Address 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)
bool lockedEth() const {return eth_mutex.isLocked();}
bool lockedPeers() const {return peers_mutex.isLocked();}
bool lockedMBcasts() const {return mc_mutex.isLocked();}
bool lockedSends() const {return send_mutex.isLocked();}
bool lockedMCSends() const {return send_mc_mutex.isLocked();}
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 PIStringAscii("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);
DeviceInfoFlags deviceInfoFlags() const {return PIIODevice::Reliable;}
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
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;
PIMutex mc_mutex, eth_mutex, peers_mutex, send_mutex, send_mc_mutex;
};
inline PICout operator <<(PICout c, const PIPeer::PeerInfo::PeerAddress & 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::PeerAddress & v) {s << v.address << v.netmask << v.ping; return s;}
inline PIByteArray & operator >>(PIByteArray & s, PIPeer::PeerInfo::PeerAddress & 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

View File

@@ -0,0 +1,718 @@
/*
PIP - Platform Independent Primitives
Protocol, input/output channel (COM, UDP)
Copyright (C) 2018 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 "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_devices/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) 2018 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 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

843
src_main/io_devices/piserial.cpp Executable file
View File

@@ -0,0 +1,843 @@
/*
PIP - Platform Independent Primitives
COM
Copyright (C) 2018 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 "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
#if defined(LINUX)
# include <linux/serial.h>
#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 << 230400 << 460800 <<
500000 << 576000 << 921600 << 1000000 << 1152000 << 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();
}
void PISerial::threadedReadBufferSizeChanged() {
if (!isOpened()) return;
#if defined(LINUX)
serial_struct ss;
ioctl(fd, TIOCGSERIAL, &ss);
//piCoutObj << "b" << ss.xmit_fifo_size;
ss.xmit_fifo_size = piMaxi(threadedReadBufferSize(), 4096);
ioctl(fd, TIOCSSERIAL, &ss);
//ioctl(fd, TIOCGSERIAL, &ss);
//piCoutObj << "a" << ss.xmit_fifo_size;
#endif
}

233
src_main/io_devices/piserial.h Executable file
View File

@@ -0,0 +1,233 @@
/*! \file piserial.h
* \brief Serial device
*/
/*
PIP - Platform Independent Primitives
COM
Copyright (C) 2018 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 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);}
//! \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 PIStringAscii("ser");}
PIString constructFullPathDevice() const;
void configureFromFullPathDevice(const PIString & full_path);
bool configureDevice(const void * e_main, const void * e_parent = 0);
void optionsChanged();
void threadedReadBufferSizeChanged();
int readDevice(void * read_to, int max_size);
int writeDevice(const void * data, int max_size);
DeviceInfoFlags deviceInfoFlags() const {return PIIODevice::Sequential;}
//! 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,262 @@
/*
PIP - Platform Independent Primitives
File
Copyright (C) 2018 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"
#if defined(LINUX) || defined(MAC_OS)
# define SHM_POSIX
#endif
#ifdef WINDOWS
#endif
#if defined(QNX) || defined(ANDROID)
#endif
#ifdef MAC_OS
//# include <fcntl.h>
#endif
#ifdef SHM_POSIX
# 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 SHM_POSIX
void * data;
bool owner;
#endif
PRIVATE_DEFINITION_END(PISharedMemory)
PISharedMemory::PISharedMemory(): PIIODevice() {
initPrivate();
dsize = 65536;
}
PISharedMemory::PISharedMemory(const PIString & shm_name, int size, PIIODevice::DeviceMode mode): PIIODevice(shm_name, mode) {
initPrivate();
dsize = size;
if (!shm_name.isEmpty())
open();
}
bool PISharedMemory::openDevice() {
close();
//piCoutObj << "try open" << path() << dsize;
#ifdef WINDOWS
PRIVATE->name = ("PIP_SHM_" + path()).toByteArray();
PRIVATE->name.push_back(0);
const char * nm = (const char *)PRIVATE->name.data();
PRIVATE->map = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, nm);
//piCoutObj << "open map =" << ullong(PRIVATE->map);
if (!PRIVATE->map) {
PRIVATE->map = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, (DWORD)dsize, nm);
//piCoutObj << "create map =" << ullong(PRIVATE->map);
if (!PRIVATE->map) {
piCoutObj << "CreateFileMapping error," << errorString();
return false;
}
}
PRIVATE->data = MapViewOfFile(PRIVATE->map, FILE_MAP_ALL_ACCESS, 0, 0, dsize);
if (!PRIVATE->data) {
CloseHandle(PRIVATE->map);
piCoutObj << "MapViewOfFile error," << errorString();
return false;
}
//piCout << PRIVATE->map << PRIVATE->data;
#endif
#ifdef SHM_POSIX
PRIVATE->name = ("/pip_shm_" + path()).toByteArray();
PRIVATE->name.push_back(0);
int fd = shm_open((const char *)PRIVATE->name.data(), O_RDWR, 0777);
//piCoutObj << "try open" << PICoutManipulators::Hex << fd;
if (fd < 0) {
//piCoutObj << "shm_open error," << errorString();
fd = shm_open((const char *)PRIVATE->name.data(), O_RDWR | O_CREAT, 0777);
//piCoutObj << "try create" << PICoutManipulators::Hex << (m | O_CREAT) << fd;
if (fd < 0) {
piCoutObj << "shm_open error," << errorString();
return false;
}
PRIVATE->owner = true;
//piCoutObj << "created" << fd;
}
if (fd >= 0)
ftruncate(fd, dsize);
PRIVATE->data = mmap(0, dsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
::close(fd);
if (PRIVATE->data == MAP_FAILED) {
piCoutObj << "mmap error," << errorString();
return false;
}
//piCoutObj << "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 SHM_POSIX
//piCoutObj << "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;
}
PIString PISharedMemory::constructFullPathDevice() const {
PIString ret;
ret << path() << ":" << dsize;
return ret;
}
void PISharedMemory::configureFromFullPathDevice(const PIString & full_path) {
initPrivate();
PIStringList pl = full_path.split(":");
for (int i = 0; i < pl.size_s(); ++i) {
PIString p(pl[i]);
switch (i) {
case 0: setPath(p); break;
case 1: dsize = p.toInt(); break;
}
}
}
void PISharedMemory::initPrivate() {
#ifdef WINDOWS
PRIVATE->map = 0;
PRIVATE->data = 0;
#endif
#ifdef SHM_POSIX
PRIVATE->data = 0;
PRIVATE->owner = false;
#endif
}
PIByteArray PISharedMemory::readAll() {
if (dsize <= 0) return PIByteArray();
#ifdef WINDOWS
if (!PRIVATE->data) return PIByteArray();
#endif
#ifdef SHM_POSIX
if (!PRIVATE->data) return PIByteArray();
#endif
PIByteArray a(dsize);
read(a.data(), a.size_s());
return a;
}
llong PISharedMemory::size() const {
if (isClosed()) return -1;
return dsize;
}
void PISharedMemory::setSize(llong s) {
bool o = isOpened();
if (o) close();
dsize = s;
if (o) open();
}
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 SHM_POSIX
if (!PRIVATE->data) return -1;
memcpy(read_to, &(((char*)(PRIVATE->data))[offset]), max_size);
return max_size;
#endif
return -1;
}
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 SHM_POSIX
if (!PRIVATE->data) return -1;
memcpy(&(((char*)(PRIVATE->data))[offset]), data, max_size);
return max_size;
#endif
return -1;
}

View File

@@ -0,0 +1,93 @@
/*! \file pisharedmemory.h
* \brief Shared memory
*/
/*
PIP - Platform Independent Primitives
Shared Memory
Copyright (C) 2018 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);
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;
//! Set shared memory object size
void setSize(llong s);
//! 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();
PIString fullPathPrefix() const {return PIStringAscii("shm");}
PIString constructFullPathDevice() const;
void configureFromFullPathDevice(const PIString & full_path);
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);}
DeviceInfoFlags deviceInfoFlags() const {return PIIODevice::Reliable;}
private:
void initPrivate();
int dsize;
PRIVATE_DECLARATION
};
#endif // PISHAREDMEMORY_H

View File

@@ -0,0 +1,147 @@
#include "pispi.h"
#include "piincludes_p.h"
#if !defined(WINDOWS) && !defined(MAC_OS)
# define PIP_SPI
#endif
#ifdef PIP_SPI
# include <fcntl.h>
# include <sys/ioctl.h>
# include <linux/spi/spidev.h>
#endif
PRIVATE_DEFINITION_START(PISPI)
#ifdef PIP_SPI
int fd;
spi_ioc_transfer spi_ioc_tr;
#endif
PRIVATE_DEFINITION_END(PISPI)
REGISTER_DEVICE(PISPI)
PISPI::PISPI(const PIString & path, uint speed, PIIODevice::DeviceMode mode) : PIIODevice(path, mode) {
setThreadedReadBufferSize(1024);
setPath(path);
setSpeed(speed);
setBits(8);
spi_mode = 0;
if (mode == ReadOnly)
piCoutObj << "error, SPI can't work in ReadOnly mode";
#ifdef PIP_SPI
PRIVATE->fd = 0;
#endif
}
void PISPI::setSpeed(uint speed_hz) {
spi_speed = speed_hz;
}
void PISPI::setBits(uchar bits) {
spi_bits = bits;
}
void PISPI::setParameter(PISPI::Parameters parameter, bool on) {
PIFlags<Parameters> cp = (PIFlags<Parameters>)spi_mode;
cp.setFlag(parameter, on);
spi_mode = (int)cp;
}
bool PISPI::isParameterSet(PISPI::Parameters parameter) const {
PIFlags<Parameters> cp = (PIFlags<Parameters>)spi_mode;
return cp[parameter];
}
bool PISPI::openDevice() {
#ifdef PIP_SPI
int ret = 0;
//piCoutObj << "open device" << path();
PRIVATE->fd = ::open(path().dataAscii(), O_RDWR);
if (PRIVATE->fd < 0) {piCoutObj << "can't open device";return false;}
//piCoutObj << "set mode" << spi_mode;
ret = ioctl(PRIVATE->fd, SPI_IOC_WR_MODE, &spi_mode);
if (ret == -1) {piCoutObj << "can't set spi write mode";return false;}
//piCoutObj << "set bits" << spi_bits;
ret = ioctl(PRIVATE->fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits);
if (ret == -1) {piCoutObj << "can't set bits per word";return false;}
//piCoutObj << "set speed" << spi_speed;
ret = ioctl(PRIVATE->fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed);
if (ret == -1) {piCoutObj << "can't set max write speed hz";return false;}
piCoutObj << "SPI open" << path() << "speed:" << spi_speed/1000 << "KHz" << "mode" << spi_mode << "bits" << spi_bits;
PRIVATE->spi_ioc_tr.delay_usecs = 0;
PRIVATE->spi_ioc_tr.speed_hz = 0;
PRIVATE->spi_ioc_tr.bits_per_word = spi_bits;
return true;
#else
piCoutObj << "PISPI not implemented on windows";
return false;
#endif
}
bool PISPI::closeDevice() {
#ifdef PIP_SPI
if (PRIVATE->fd) ::close(PRIVATE->fd);
#endif
return true;
}
int PISPI::readDevice(void * read_to, int max_size) {
int sz = piMini(recv_buf.size_s(), max_size);
memcpy(read_to, recv_buf.data(), sz);
recv_buf.resize(recv_buf.size_s() - sz);
return sz;
}
int PISPI::writeDevice(const void * data, int max_size) {
#ifdef PIP_SPI
if (max_size > 0) {
if (tx_buf.size_s() != max_size) {
tx_buf.resize(max_size);
rx_buf.resize(max_size);
PRIVATE->spi_ioc_tr.tx_buf = (ulong)(tx_buf.data());
PRIVATE->spi_ioc_tr.rx_buf = (ulong)(rx_buf.data());
PRIVATE->spi_ioc_tr.len = max_size;
}
memcpy(tx_buf.data(), data, max_size);
int ret;
//piCoutObj << "write" << max_size << tx_buf.size();
ret = ioctl(PRIVATE->fd, SPI_IOC_MESSAGE(1), &PRIVATE->spi_ioc_tr);
if (ret < 1) {piCoutObj << "can't send spi message" << ret; return -1;}
if (canRead()) recv_buf.append(rx_buf);
if (recv_buf.size_s() > threadedReadBufferSize()) recv_buf.resize(threadedReadBufferSize());
return max_size;
}
#endif
return 0;
}
PIString PISPI::constructFullPathDevice() const {
PIString ret;
ret << path() << ":" << int(speed()) << ":" << int(bits()) << ":" << (int)parameters();
return ret;
}
void PISPI::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: setPath(p); break;
case 1: setSpeed(p.toInt()); break;
case 2: setBits(p.toInt()); break;
case 3: setParameters(p.toInt()); break;
default: break;
}
}
}

View File

@@ -0,0 +1,59 @@
#ifndef PISPI_H
#define PISPI_H
#include "piiodevice.h"
class PIP_EXPORT PISPI: public PIIODevice
{
PIIODEVICE(PISPI)
public:
explicit PISPI(const PIString & path = PIString(), uint speed_hz = 1000000, PIIODevice::DeviceMode mode = PIIODevice::ReadWrite);
~PISPI() {}
//! \brief Parameters of PISPI
enum Parameters {
ClockInverse /*! SPI clk polarity control*/ = 0x1,
ClockPhaseShift /*! SPI clk phase control */ = 0x2,
};
void setSpeed(uint speed_hz);
uint speed() const {return spi_speed;}
void setBits(uchar bits = 8);
uchar bits() const {return spi_bits;}
//! Set parameters to "parameters_"
void setParameters(PIFlags<PISPI::Parameters> parameters_) {spi_mode = (int)parameters_;}
//! Set parameter "parameter" to "on" state
void setParameter(PISPI::Parameters parameter, bool on = true);
//! Returns if parameter "parameter" is set
bool isParameterSet(PISPI::Parameters parameter) const;
//! Returns parameters
PIFlags<PISPI::Parameters> parameters() const {return spi_mode;}
protected:
bool openDevice();
bool closeDevice();
int readDevice(void * read_to, int max_size);
int writeDevice(const void * data, int max_size);
PIString fullPathPrefix() const {return PIStringAscii("spi");}
PIString constructFullPathDevice() const;
void configureFromFullPathDevice(const PIString & full_path);
DeviceInfoFlags deviceInfoFlags() const {return PIIODevice::Sequential;}
private:
uint spi_speed;
uchar spi_mode;
uchar spi_bits;
PIByteArray tx_buf, rx_buf;
PIByteArray recv_buf;
PRIVATE_DECLARATION
};
#endif // PISPI_H

View File

@@ -0,0 +1,77 @@
/*
PIP - Platform Independent Primitives
PIIODevice that pass write to read
Copyright (C) 2018 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;
}
bool PITransparentDevice::openDevice() {
return true;
}
bool PITransparentDevice::closeDevice() {
que_mutex.lock();
que.clear();
que_mutex.unlock();
return true;
}

View File

@@ -0,0 +1,52 @@
/*! \file pitransparentdevice.h
* \brief PIIODevice that pass write to read
*/
/*
PIP - Platform Independent Primitives
PIIODevice that pass write to read
Copyright (C) 2018 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 {return PIStringAscii("tr");}
DeviceInfoFlags deviceInfoFlags() const {return PIIODevice::Reliable;}
PIMutex que_mutex;
PIQueue<PIByteArray> que;
};
#endif // PITRANSPARENTDEVICE_H

154
src_main/io_devices/piusb.h Executable file
View File

@@ -0,0 +1,154 @@
/*! \file piusb.h
* \brief USB device
*/
/*
PIP - Platform Independent Primitives
USB, based on libusb
Copyright (C) 2018 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 PIStringAscii("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();
DeviceInfoFlags deviceInfoFlags() const {return PIIODevice::Reliable;}
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