From: Mikko Rasa Date: Fri, 10 Jun 2011 18:08:51 +0000 (+0300) Subject: Merge branch 'io-master' X-Git-Url: http://git.tdb.fi/?p=libs%2Fcore.git;a=commitdiff_plain;h=c7afef88380ebebc8c2b04e48664d73281ec8848;hp=a268ee8d1990eca21155137d0245b2985579d064 Merge branch 'io-master' Conflicts: .gitignore Build Changelog.txt --- diff --git a/.gitignore b/.gitignore index 82b1d62..0c896d6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,6 @@ /grep /libmspcore.a /libmspcore.so -/libmspstrings.a -/libmspstrings.so /mspcore.pc /release /temp diff --git a/Build b/Build index e1f3fd7..cdd83c4 100644 --- a/Build +++ b/Build @@ -53,6 +53,12 @@ package "mspcore" install true; }; + headers "msp/io" + { + source "src/io"; + install true; + }; + library "mspcore" { source "source/core"; @@ -60,6 +66,7 @@ package "mspcore" source "source/time"; source "source/strings"; source "source/stringcodec"; + source "source/io"; install true; }; diff --git a/Changelog.txt b/Changelog.txt index a9d3619..498e9ff 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -29,4 +29,14 @@ 1.0 * First released version -($Id$) +== Changes from pre-2.0 mspio == + +1.1 +* Filter class makes adding filters easier +* Memory class enables I/O from/to memory buffers +* Console size can be queried +* Bugfixes + - Don't consider an empty set for poll an error + +1.0 +* First released version diff --git a/source/io/base.cpp b/source/io/base.cpp new file mode 100644 index 0000000..48de3ee --- /dev/null +++ b/source/io/base.cpp @@ -0,0 +1,61 @@ +#include "base.h" +#include "poll.h" + +using namespace std; + +namespace Msp { +namespace IO { + +Base::Base(): + mode(M_READ), + events(P_NONE), + eof_flag(false) +{ } + +Base::~Base() +{ + signal_deleted.emit(); +} + +bool Base::getline(string &line) +{ + line.clear(); + + if(eof_flag) + return false; + + while(1) + { + int c = get(); + if(c==-1 || c=='\n') + break; + line += c; + } + + return !eof_flag || !line.empty(); +} + +int Base::get() +{ + char c; + if(do_read(&c, 1)==0) + return -1; + return static_cast(c); +} + +void Base::set_events(PollEvent e) +{ + events = e; + signal_events_changed.emit(events); +} + +void Base::event(PollEvent ev) +{ + if(ev&P_INPUT) + signal_data_available.emit(); + + on_event(ev); +} + +} // namespace IO +} // namespace Msp diff --git a/source/io/base.h b/source/io/base.h new file mode 100644 index 0000000..774d034 --- /dev/null +++ b/source/io/base.h @@ -0,0 +1,121 @@ +#ifndef MSP_IO_BASE_H_ +#define MSP_IO_BASE_H_ + +#include +#include "mode.h" +#include "poll.h" +#include "types.h" + +namespace Msp { +namespace IO { + +/** +Common interface for all I/O objects. + +A derived class must call set_events(P_NONE) before it is destroyed to avoid +leaving stale pointers in an EventDispatcher. +*/ +class Base +{ +public: + /** Emitted when there is data available for reading. If all data is not + read, the signal is emitted again immediately. */ + sigc::signal signal_data_available; + + /** Emitted when there is no more data to be read. */ + sigc::signal signal_end_of_file; + + /** Emitted when there is a nonlinearity in I/O (such as a file being + seeked) and any data buffered by upper layers needs to be flushed. */ + sigc::signal signal_flush_required; + + /** Emitted when the I/O object has closed. */ + sigc::signal signal_closed; + + /** Emitted when the mask of interesting events changes. Mainly for use by + EventDispatcher. */ + sigc::signal signal_events_changed; + + /** Emitted when the object is deleted. Mainly for use by + EventDispatcher. */ + sigc::signal signal_deleted; + +protected: + Mode mode; + PollEvent events; + bool eof_flag; + + Base(); +private: + Base(const Base &); + Base &operator=(const Base &); +public: + virtual ~Base(); + + /** Sets blocking mode. When blocking is enabled, most operations won't + return until they can make progress. When blocking is disabled, these + operations may return immediately with a return code indicating that nothing + was done. + + Blocking is enabled by default. */ + virtual void set_block(bool) { } + + /** Returns the current mode flags. */ + Mode get_mode() const { return mode; } + +protected: + virtual unsigned do_write(const char *, unsigned) =0; + virtual unsigned do_read(char *, unsigned) =0; + +public: + /** Writes data from a buffer. Subject to blocking. Returns the number of + bytes written, which may be zero for a non-blockin operation. */ + unsigned write(const char *b, unsigned c) { return do_write(b, c); } + + /** Writes a string. This is identical to calling + write(s.data(), s.size()). */ + unsigned write(const std::string &s) { return do_write(s.data(), s.size()); } + + /** Writes a single character. This is identical to calling + write(&c, 1). */ + virtual unsigned put(char c) { return do_write(&c, 1); } + + /** Reads data into a buffer. Subject to blocking. Returns the number of + bytes read, which may be zero for a non-blocking operation. */ + unsigned read(char *b, unsigned c) { return do_read(b, c); } + + /** Reads characters up to the next linefeed or end-of-file. The linefeed + is not included in the line. Returns true if a line was successfully read, + false otherwise. */ + virtual bool getline(std::string &); + + /** Reads a single character. Returns -1 if no character was available due + to end-of-file or non-blocking operation. */ + virtual int get(); + + /** Returns the end-of-file flag. */ + bool eof() const { return eof_flag; } + +protected: + void set_events(PollEvent); + +public: + /** Returns a mask of the currently interesting events. Used by + EventDispatcher. */ + PollEvent get_events() const { return events; } + + /** Returns a handle for polling. Should throw if the object does not have + an event handle. */ + virtual Handle get_event_handle() =0; + + /** Notifies the object of an event. Used by EventDispatcher. */ + void event(PollEvent); + +protected: + virtual void on_event(PollEvent) { } +}; + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/buffered.cpp b/source/io/buffered.cpp new file mode 100644 index 0000000..f53d2fc --- /dev/null +++ b/source/io/buffered.cpp @@ -0,0 +1,188 @@ +#include +#include "buffered.h" +#include "except.h" + +using namespace std; + +namespace Msp { +namespace IO { + +Buffered::Buffered(Base &b, unsigned s): + below(b), + buf_size(s), + buf(new char[buf_size]), + begin(buf), + end(buf), + cur_op(M_NONE) +{ + mode = below.get_mode(); + below.signal_flush_required.connect(sigc::mem_fun(this, &Buffered::flush)); +} + +Buffered::~Buffered() +{ + try + { + flush(); + } + catch(...) + { } + + delete[] buf; +} + +void Buffered::flush() +{ + if(cur_op==M_WRITE) + { + unsigned used = end-begin; + if(used) + { + unsigned len = below.write(begin, used); + + begin=end = buf; + + if(len(end-begin), size); + memcpy(data, begin, len); + begin += len; + ret += len; + } + else + // Read the rest directly from the underlying object + ret += below.read(data, size); + + eof_flag = (below.eof() && begin==end); + + return ret; + } +} + +unsigned Buffered::put(char c) +{ + set_op(M_WRITE); + + if(end(*begin++); + + char c; + if(do_read(&c, 1)==0) + return -1; + return static_cast(c); +} + +void Buffered::set_op(Mode op) +{ + if(op!=cur_op) + flush(); + cur_op = op; +} + +unsigned Buffered::get_current_size() const +{ + return end-begin; +} + +Handle Buffered::get_event_handle() +{ + throw Exception("Buffered doesn't support events"); +} + +} // namespace IO +} // namespace Msp diff --git a/source/io/buffered.h b/source/io/buffered.h new file mode 100644 index 0000000..36d2a11 --- /dev/null +++ b/source/io/buffered.h @@ -0,0 +1,46 @@ +#ifndef MSP_IO_BUFFERED_H_ +#define MSP_IO_BUFFERED_H_ + +#include "base.h" + +namespace Msp { +namespace IO { + +class Buffered: public Base +{ +private: + Base &below; + unsigned buf_size; + char *buf; + char *begin; + char *end; + Mode cur_op; + +public: + Buffered(Base &, unsigned =8192); + ~Buffered(); + + void flush(); + +protected: + unsigned do_write(const char *, unsigned); + unsigned do_read(char *, unsigned); +public: + unsigned put(char); + + bool getline(std::string &); + int get(); + +private: + void set_op(Mode); +public: + Mode get_current_op() const { return cur_op; } + unsigned get_current_size() const; + + virtual Handle get_event_handle(); +}; + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/console.cpp b/source/io/console.cpp new file mode 100644 index 0000000..9db7737 --- /dev/null +++ b/source/io/console.cpp @@ -0,0 +1,187 @@ +#ifndef WIN32 +#include +#include +#include +#include +#endif +#include +#include "console.h" + +namespace { + +#ifndef WIN32 +termios orig_attr; +#endif + +} // namespace + +namespace Msp { +namespace IO { + +Console::Console(unsigned n) +{ + if(n>2) + throw InvalidParameterValue("Invalid parameter for Console::Console"); + + mode = (n==0 ? M_READ : M_WRITE); + +#ifdef WIN32 + switch(n) + { + case 0: handle = GetStdHandle(STD_INPUT_HANDLE); break; + case 1: handle = GetStdHandle(STD_OUTPUT_HANDLE); break; + case 2: handle = GetStdHandle(STD_ERROR_HANDLE); break; + } +#else + handle = n; + + if(handle==0) + tcgetattr(handle, &orig_attr); +#endif + + if(n==0) + set_events(P_INPUT); +} + +Console::~Console() +{ +#ifndef WIN32 + if(handle==0) + tcsetattr(handle, TCSADRAIN, &orig_attr); +#endif +} + +void Console::set_block(bool b) +{ +#ifdef WIN32 + // XXX Dunno how to do this in win32 + (void)b; +#else + int flags = fcntl(0, F_GETFL); + flags = (flags&~O_NONBLOCK) | (b?0:O_NONBLOCK); + fcntl(0, F_SETFL, flags); +#endif +} + +void Console::set_local_echo(bool e) +{ + if(!(mode&M_READ)) + throw InvalidState("Local echo can only be set on input console"); + +#ifdef WIN32 + DWORD m; + GetConsoleMode(handle, &m); + SetConsoleMode(handle, (m&~ENABLE_ECHO_INPUT) | (e?ENABLE_ECHO_INPUT:0)); +#else + termios t; + tcgetattr(0, &t); + t.c_lflag = (t.c_lflag&~ECHO) | (e?ECHO:0); + tcsetattr(0, TCSADRAIN, &t); +#endif +} + +void Console::set_line_buffer(bool l) +{ + if(!(mode&M_READ)) + throw InvalidState("Line buffering can only be set on input console"); + +#ifdef WIN32 + DWORD m; + GetConsoleMode(handle, &m); + SetConsoleMode(handle, (m&~ENABLE_LINE_INPUT) | (l?ENABLE_LINE_INPUT:0)); +#else + // XXX ICANON does more than just set line buffering, may need a bit more thought + termios t; + tcgetattr(0, &t); + t.c_lflag = (t.c_lflag&~ICANON) | (l?ICANON:0); + tcsetattr(0, TCSADRAIN, &t); +#endif +} + +void Console::get_size(unsigned &rows, unsigned &cols) +{ + if(!(mode&M_WRITE)) + throw InvalidState("Size can only be queried from an output console"); + +#ifdef WIN32 + // XXX Figure out how to do this + rows = 24; + cols = 80; +#else + struct winsize wsz; + ioctl(handle, TIOCGWINSZ, &wsz); + rows = wsz.ws_row; + cols = wsz.ws_col; +#endif +} + +unsigned Console::do_write(const char *buf, unsigned len) +{ + if(!(mode&M_WRITE)) + throw InvalidState("Console is not writable"); + +#ifdef WIN32 + DWORD ret; + if(!WriteFile(handle, buf, len, &ret, 0)) + throw SystemError("Writing to console failed", GetLastError()); +#else + int ret = ::write(handle, buf, len); + if(ret==-1) + throw SystemError("Writing to console failed", errno); +#endif + + return ret; +} + +unsigned Console::do_read(char *buf, unsigned len) +{ + if(!(mode&M_READ)) + throw InvalidState("Console is not readable"); + +#ifdef WIN32 + DWORD ret; + if(!ReadFile(handle, buf, len, &ret, 0)) + throw SystemError("Reading from console failed", GetLastError()); +#else + int ret = ::read(handle, buf, len); + if(ret==-1) + { + if(errno==EAGAIN) + return 0; + else + throw SystemError("Reading from console failed", errno); + } + else if(ret==0) + eof_flag = true; +#endif + + return ret; +} + +Handle Console::get_event_handle() +{ + return 0; +} + +Console &Console::instance(unsigned n) +{ + static Console in(0); + static Console out(1); + static Console err(2); + + switch(n) + { + case 0: return in; + case 1: return out; + case 2: return err; + } + + throw InvalidParameterValue("Unknown Console instance requested"); +} + +Console &cin = Console::instance(0); +Console &cout = Console::instance(1); +Console &cerr = Console::instance(2); + +} // namespace IO +} // namespace Msp diff --git a/source/io/console.h b/source/io/console.h new file mode 100644 index 0000000..3c0bb71 --- /dev/null +++ b/source/io/console.h @@ -0,0 +1,56 @@ +#ifndef MSP_IO_CONSOLE_H_ +#define MSP_IO_CONSOLE_H_ + +#include "base.h" + +namespace Msp { +namespace IO { + +/** +Provides access to standard input, output and error streams. This class can't +be instantiated directly - use one of the cin, cout and cerr references +instead. +*/ +class Console: public Base +{ +private: + Handle handle; + + Console(unsigned); +public: + ~Console(); + + virtual void set_block(bool); + + /** If local echo is enabled, characters will appear on the console as they + are typed. Can only be used on an input Console. */ + void set_local_echo(bool); + + /** If line buffering is enabled, input will only be available when a + newline is encountered. On some systems, this may also enable line editing. + Can only be used on an input Console. + */ + void set_line_buffer(bool); + + /** Retrieves the size of the Console. Can only be used on an output + Console. */ + void get_size(unsigned &rows, unsigned &cols); + +protected: + virtual unsigned do_write(const char *, unsigned); + virtual unsigned do_read(char *, unsigned); + +public: + virtual Handle get_event_handle(); + + static Console &instance(unsigned); +}; + +extern Console &cin; +extern Console &cout; +extern Console &cerr; + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/eventdispatcher.cpp b/source/io/eventdispatcher.cpp new file mode 100644 index 0000000..06a594f --- /dev/null +++ b/source/io/eventdispatcher.cpp @@ -0,0 +1,75 @@ +#include +#include "base.h" +#include "eventdispatcher.h" +#include "poll.h" + +namespace Msp { +namespace IO { + +EventDispatcher::EventDispatcher() +{ } + +void EventDispatcher::add(Base &obj) +{ + SlotMap::iterator i = objects.find(&obj); + if(i==objects.end()) + { + i = objects.insert(SlotMap::value_type(&obj, Slot(&obj))).first; + i->second.evch_conn = obj.signal_events_changed.connect(sigc::bind(sigc::mem_fun(this, &EventDispatcher::object_events_changed), &obj)); + i->second.del_conn = obj.signal_deleted.connect(sigc::bind(sigc::mem_fun(this, &EventDispatcher::object_deleted), &obj)); + + if(obj.get_events()) + poller.set_object(obj, obj.get_events()); + } +} + +void EventDispatcher::remove(Base &obj) +{ + SlotMap::iterator i = objects.find(&obj); + if(i!=objects.end()) + { + i->second.evch_conn.disconnect(); + i->second.del_conn.disconnect(); + objects.erase(i); + + poller.set_object(obj, P_NONE); + } +} + +void EventDispatcher::tick() +{ + if(objects.empty()) + return; + + if(poller.poll()>0) + dispatch(); +} + +void EventDispatcher::tick(const Time::TimeDelta &dt) +{ + if(objects.empty()) + return; + + if(poller.poll(dt)>0) + dispatch(); +} + +void EventDispatcher::object_events_changed(PollEvent ev, Base *obj) +{ + poller.set_object(*obj, ev); +} + +void EventDispatcher::object_deleted(Base *obj) +{ + remove(*obj); +} + +void EventDispatcher::dispatch() +{ + const Poller::SlotSeq &result = poller.get_result(); + for(Poller::SlotSeq::const_iterator i=result.begin(); i!=result.end(); ++i) + i->object->event(i->events); +} + +} // namespace IO +} // namespace Msp diff --git a/source/io/eventdispatcher.h b/source/io/eventdispatcher.h new file mode 100644 index 0000000..7e89d47 --- /dev/null +++ b/source/io/eventdispatcher.h @@ -0,0 +1,55 @@ +#ifndef EVENTDISPATCHER_H_ +#define EVENTDISPATCHER_H_ + +#include +#include +#include "poll.h" + +namespace Msp { +namespace IO { + +/** +Put your I/O objects inside one of these to get signaled when something happens +on some of them. +*/ +class EventDispatcher: public sigc::trackable +{ +private: + struct Slot + { + Base *obj; + sigc::connection evch_conn; + sigc::connection del_conn; + + Slot(Base *o): obj(o) { } + }; + + typedef std::map SlotMap; + + Poller poller; + SlotMap objects; + +public: + EventDispatcher(); + + void add(Base &); + void remove(Base &); + + /** Checks for and dispatches events. If there are no events available, + blocks until there are. */ + void tick(); + + /** Checks for and dispatches events. If there are no events available, + waits at most the specified time before returning. */ + void tick(const Time::TimeDelta &); + +private: + void object_events_changed(PollEvent, Base *); + void object_deleted(Base *); + void dispatch(); +}; + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/except.h b/source/io/except.h new file mode 100644 index 0000000..5770f64 --- /dev/null +++ b/source/io/except.h @@ -0,0 +1,22 @@ +#ifndef MSP_IO_EXCEPT_H_ +#define MSP_IO_EXCEPT_H_ + +#include + +namespace Msp { +namespace IO { + +class FileNotFound: public Exception +{ +public: + FileNotFound(const std::string &w_, const std::string &f): Exception(w_), filename(f) { } + const std::string &get_filename() { return filename; } + ~FileNotFound() throw() { } +private: + std::string filename; +}; + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/file.cpp b/source/io/file.cpp new file mode 100644 index 0000000..2990d5c --- /dev/null +++ b/source/io/file.cpp @@ -0,0 +1,242 @@ +#ifndef WIN32 +#include +#include +#include +#endif +#include +#include "except.h" +#include "file.h" + +using namespace std; + +namespace Msp { +namespace IO { + +File::File(const string &fn, Mode m, CreateMode cm) +{ + if(!(m&M_RDWR)) + throw InvalidParameterValue("Invalid read/write mode"); + if(cm&~(C_CREATE|C_TRUNCATE)) + throw InvalidParameterValue("Invalid create mode"); + + mode = m; + +#ifdef WIN32 + int flags = 0; + int create_flags = OPEN_EXISTING; + + if(mode&M_READ) + flags |= GENERIC_READ; + else if(mode&M_WRITE) + { + flags |= GENERIC_WRITE; + + switch(static_cast(cm)) + { + case C_NONE: create_flags = OPEN_EXISTING; break; + case C_CREATE: create_flags = OPEN_ALWAYS; break; + case C_TRUNCATE: create_flags = TRUNCATE_EXISTING; break; + case C_CREATE+C_TRUNCATE: create_flags = CREATE_ALWAYS; break; + } + } + + handle = CreateFile(fn.c_str(), flags, 0, 0, create_flags, FILE_ATTRIBUTE_NORMAL, 0); + if(handle==INVALID_HANDLE_VALUE) + { + int err = GetLastError(); + if(err==ERROR_FILE_NOT_FOUND) + throw FileNotFound("Can't find file "+fn, fn); + else + throw SystemError(format("Can't open file '%s'", fn), GetLastError()); + } +#else + int flags = 0; + switch(mode&M_RDWR) + { + case M_READ: flags |= O_RDONLY; break; + case M_WRITE: flags |= O_WRONLY; break; + case M_RDWR: flags |= O_RDWR; break; + default:; + } + + if(mode&M_WRITE) + { + if(cm&C_CREATE) + flags |= O_CREAT; + if(cm&C_TRUNCATE) + flags |= O_TRUNC; + } + if(mode&M_APPEND) + flags |= O_APPEND; + if(mode&M_NONBLOCK) + flags |= O_NONBLOCK; + + handle = ::open(fn.c_str(), flags, 0666); + if(handle==-1) + { + int err = errno; + if(err==ENOENT) + throw FileNotFound("Can't find file "+fn, fn); + else + throw SystemError(format("Can't open file '%s'", fn), err); + } +#endif + + set_events(P_INPUT); +} + +File::~File() +{ + close(); +} + +void File::close() +{ + if(handle==MSP_IO_INVALID_HANDLE) + return; + + set_events(P_NONE); + + signal_flush_required.emit(); + +#ifdef WIN32 + CloseHandle(handle); +#else + ::close(handle); +#endif + + handle = MSP_IO_INVALID_HANDLE; + signal_closed.emit(); +} + +void File::set_block(bool b) +{ + check_access(M_NONE); + + mode = (mode&~M_NONBLOCK); + if(b) + mode = (mode|M_NONBLOCK); +#ifndef WIN32 + int flags = fcntl(handle, F_GETFD); + fcntl(handle, F_SETFL, (flags&O_NONBLOCK)|(b?0:O_NONBLOCK)); +#endif +} + +unsigned File::do_write(const char *buf, unsigned size) +{ + check_access(M_WRITE); + + if(size==0) + return 0; + +#ifdef WIN32 + if(mode&M_APPEND) + seek(0, S_END); + DWORD ret; + if(WriteFile(handle, buf, size, &ret, 0)==0) + throw SystemError("Writing to file failed", GetLastError()); +#else + int ret = ::write(handle, buf, size); + if(ret==-1) + { + if(errno==EAGAIN) + return 0; + else + throw SystemError("Writing to file failed", errno); + } +#endif + + return ret; +} + +unsigned File::do_read(char *buf, unsigned size) +{ + check_access(M_READ); + + if(size==0) + return 0; + +#ifdef WIN32 + DWORD ret; + if(ReadFile(handle, buf, size, &ret, 0)==0) + throw SystemError("Reading from file failed", GetLastError()); +#else + int ret = ::read(handle, buf, size); + if(ret==-1) + { + if(errno==EAGAIN) + return 0; + else + throw SystemError("Reading from file failed", errno); + } +#endif + + if(ret==0) + { + eof_flag = true; + signal_end_of_file.emit(); + } + + return ret; +} + +void File::sync() +{ +#ifndef WIN32 + signal_flush_required.emit(); + + fsync(handle); +#endif +} + +int File::seek(int off, SeekType st) +{ + check_access(M_NONE); + + signal_flush_required.emit(); + + int type = sys_seek_type(st); +#ifdef WIN32 + DWORD ret = SetFilePointer(handle, off, 0, type); + if(ret==INVALID_SET_FILE_POINTER) + throw SystemError("Seek failed", GetLastError()); +#else + int ret = lseek(handle, off, type); + if(ret==-1) + throw SystemError("Seek failed", errno); +#endif + + eof_flag = false; + + return ret; +} + +int File::tell() const +{ + check_access(M_NONE); + +#ifdef WIN32 + DWORD ret = SetFilePointer(handle, 0, 0, FILE_CURRENT); + if(ret==INVALID_SET_FILE_POINTER) + throw SystemError("Tell failed", GetLastError()); +#else + int ret = lseek(handle, 0, SEEK_CUR); + if(ret==-1) + throw SystemError("Tell failed", errno); +#endif + + return ret; +} + +void File::check_access(Mode m) const +{ + if(handle==MSP_IO_INVALID_HANDLE) + throw InvalidState("File is not open"); + if(m==M_READ && !(mode&M_READ)) + throw InvalidState("File is not readable"); + if(m==M_WRITE && !(mode&M_WRITE)) + throw InvalidState("File is not writable"); +} + +} // namespace IO +} // namespace Msp diff --git a/source/io/file.h b/source/io/file.h new file mode 100644 index 0000000..05aaf95 --- /dev/null +++ b/source/io/file.h @@ -0,0 +1,77 @@ +#ifndef MSP_IO_FILE_H_ +#define MSP_IO_FILE_H_ + +#include +#include "base.h" +#include "buffered.h" +#include "filtered.h" +#include "seek.h" + +namespace Msp { +namespace IO { + +/** +A class for reading and writing files. + +Non-blocking mode is not supported on Win32. +*/ +class File: public Base +{ +public: + enum CreateMode + { + C_NONE = 0, + C_CREATE = 1, + C_TRUNCATE = 2 + }; + +private: + Handle handle; + +public: + /** Creates a new file object and opens it. If the create flag is set and + write access is requested and the file does exist, it is created. Otherwise + a missing file is an error. */ + File(const std::string &, Mode = M_READ, CreateMode = CreateMode(C_CREATE+C_TRUNCATE)); + virtual ~File(); + + /** Closes the file. Any attempt to access the file after this will cause + an exception to be thrown. */ + void close(); + + void set_block(bool); + +protected: + virtual unsigned do_write(const char *, unsigned); + virtual unsigned do_read(char *, unsigned); + +public: + virtual void sync(); + + /** Changes the read/write offset of the file. Returns the new offset. */ + virtual int seek(int, SeekType); + + /** Returns the current read/write offset of the file. */ + virtual int tell() const; + + virtual Handle get_event_handle() { return handle; } + +private: + void check_access(Mode) const; +}; + +inline File::CreateMode operator|(File::CreateMode m, File::CreateMode n) +{ return File::CreateMode(static_cast(m)|static_cast(n)); } + +inline File::CreateMode operator&(File::CreateMode m, File::CreateMode n) +{ return File::CreateMode(static_cast(m)&static_cast(n)); } + +inline File::CreateMode operator~(File::CreateMode m) +{ return File::CreateMode(~static_cast(m)); } + +typedef Filtered BufferedFile; + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/filtered.h b/source/io/filtered.h new file mode 100644 index 0000000..0d323ec --- /dev/null +++ b/source/io/filtered.h @@ -0,0 +1,66 @@ +#ifndef MSP_IO_FILTERED_H_ +#define MSP_IO_FILTERED_H_ + +namespace Msp { +namespace IO { + +template +class Filtered: public B +{ +private: + struct Activator + { + Filtered &f; + + Activator(Filtered &f_): f(f_) { f.active = true; } + ~Activator() { f.active = false; } + }; + + F filter; + bool active; + +public: + Filtered(): filter(*this), active(false) { } + ~Filtered() { active = true; } + + template + Filtered(A0 a0): B(a0), filter(*this), active(false) { } + + template + Filtered(A0 a0, A1 a1): B(a0, a1), filter(*this), active(false) { } + +protected: + virtual unsigned do_write(const char *b, unsigned s) + { + if(!active) + { + Activator a(*this); + return filter.write(b, s); + } + else + return B::do_write(b, s); + } + + virtual unsigned do_read(char *b, unsigned s) + { + if(!active) + { + Activator a(*this); + return filter.read(b, s); + } + else + return B::do_read(b, s); + } + +public: + virtual unsigned put(char c) { return filter.put(c); } + virtual bool getline(std::string &l) { return filter.getline(l); } + virtual int get() { return filter.get(); } + + F &get_filter() { return filter; } +}; + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/memory.cpp b/source/io/memory.cpp new file mode 100644 index 0000000..6ea130b --- /dev/null +++ b/source/io/memory.cpp @@ -0,0 +1,127 @@ +#include +#include +#include "except.h" +#include "memory.h" + +using namespace std; + +namespace Msp { +namespace IO { + +Memory::Memory(char *d, unsigned s) +{ + init(d, d+s, M_RDWR); +} + +Memory::Memory(char *b, char *e) +{ + init(b, e, M_RDWR); +} + +Memory::Memory(const char *cd, unsigned s) +{ + char *d = const_cast(cd); + init(d, d+s, M_READ); +} + +Memory::Memory(const char *b, const char *e) +{ + init(const_cast(b), const_cast(e), M_READ); +} + +void Memory::init(char *b, char *e, Mode m) +{ + begin = b; + end = e; + pos = begin; + mode = m; +} + +unsigned Memory::do_write(const char *buf, unsigned size) +{ + check_mode(M_WRITE); + + size = min(size, end-pos); + memcpy(pos, buf, size); + pos += size; + return size; +} + +unsigned Memory::do_read(char *buf, unsigned size) +{ + if(pos==end) + { + eof_flag = true; + return 0; + } + + size = min(size, end-pos); + memcpy(buf, pos, size); + pos += size; + return size; +} + +unsigned Memory::put(char c) +{ + check_mode(M_WRITE); + *pos++ = c; + return 1; +} + +bool Memory::getline(string &line) +{ + char *nl = find(pos, end, '\n'); + line.assign(pos, nl); + bool result = (nl!=pos); + pos = nl; + return result; +} + +int Memory::get() +{ + if(pos==end) + { + eof_flag = true; + return -1; + } + + return static_cast(*pos++); +} + +unsigned Memory::seek(int off, SeekType type) +{ + char *new_pos; + if(type==S_BEG) + new_pos = begin+off; + else if(type==S_CUR) + new_pos = pos+off; + else if(type==S_END) + new_pos = end+off; + else + throw InvalidParameterValue("Invalid seek type"); + + if(new_posend) + throw InvalidParameterValue("Invalid seek offset"); + + pos = new_pos; + return pos-begin; +} + +Handle Memory::get_event_handle() +{ + throw Exception("Memory doesn't support events"); +} + +void Memory::check_mode(Mode m) const +{ + if(m==M_WRITE) + { + if(!(mode&M_WRITE)) + throw InvalidState("Memory is not writable"); + if(pos==end) + throw InvalidState("Attempt to write past end of Memory"); + } +} + +} // namespace IO +} // namespace Msp diff --git a/source/io/memory.h b/source/io/memory.h new file mode 100644 index 0000000..ba6c4d8 --- /dev/null +++ b/source/io/memory.h @@ -0,0 +1,44 @@ +#ifndef MSP_IO_MEMORY_H_ +#define MSP_IO_MEMORY_H_ + +#include "base.h" +#include "seek.h" + +namespace Msp { +namespace IO { + +class Memory: public Base +{ +private: + char *begin; + char *end; + char *pos; + +public: + Memory(char *, unsigned); + Memory(char *, char *); + Memory(const char *, unsigned); + Memory(const char *, const char *); +private: + void init(char *, char *, Mode); + + virtual unsigned do_write(const char *, unsigned); + virtual unsigned do_read(char *, unsigned); +public: + virtual unsigned put(char); + virtual bool getline(std::string &); + virtual int get(); + + unsigned seek(int, SeekType); + unsigned tell() const { return pos-begin; } + + virtual Handle get_event_handle(); + +private: + void check_mode(Mode) const; +}; + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/mode.h b/source/io/mode.h new file mode 100644 index 0000000..5e103f2 --- /dev/null +++ b/source/io/mode.h @@ -0,0 +1,29 @@ +#ifndef MSP_IO_MODE_H_ +#define MSP_IO_MODE_H_ + +namespace Msp { +namespace IO { + +enum Mode +{ + M_NONE = 0, + M_READ = 1, + M_WRITE = 2, + M_RDWR = M_READ|M_WRITE, + M_APPEND = 4, + M_NONBLOCK = 8 +}; + +inline Mode operator|(Mode m, Mode n) +{ return Mode(static_cast(m)|static_cast(n)); } + +inline Mode operator&(Mode m, Mode n) +{ return Mode(static_cast(m)&static_cast(n)); } + +inline Mode operator~(Mode m) +{ return Mode(~static_cast(m)); } + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/pipe.cpp b/source/io/pipe.cpp new file mode 100644 index 0000000..b3735aa --- /dev/null +++ b/source/io/pipe.cpp @@ -0,0 +1,182 @@ +#ifndef WIN32 +#include +#include +#endif +#include +#include "pipe.h" + +using namespace std; + +namespace Msp { +namespace IO { + +Pipe::Pipe() +{ +#ifdef WIN32 + string name = format("\\\\.\\pipe\\%u.%p", GetCurrentProcessId(), this); + handle[0] = CreateNamedPipe(name.c_str(), PIPE_ACCESS_INBOUND|FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE, 1, 1024, 1024, 0, 0); + if(handle[0]==INVALID_HANDLE_VALUE) + throw SystemError("Unable to create pipe", GetLastError()); + + handle[1] = CreateFile(name.c_str(), GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0); + if(handle[1]==INVALID_HANDLE_VALUE) + { + unsigned err = GetLastError(); + CloseHandle(handle[0]); + throw SystemError("Unable to create pipe", err); + } + + overlapped = 0; + event = CreateEvent(0, true, false, 0); + buf_size = 1024; + buffer = new char[buf_size]; + buf_avail = 0; + buf_next = buffer; +#else + if(pipe(handle)==-1) + throw SystemError("Unable to create pipe", errno); +#endif + + set_events(P_INPUT); +} + +Pipe::~Pipe() +{ + close(); +} + +void Pipe::close() +{ + set_events(P_NONE); + + signal_flush_required.emit(); +#ifdef WIN32 + CloseHandle(handle[0]); + CloseHandle(handle[1]); +#else + ::close(handle[0]); + ::close(handle[1]); + signal_closed.emit(); +#endif +} + +void Pipe::set_block(bool b) +{ + mode = (mode&~M_NONBLOCK); + if(b) + mode = (mode|M_NONBLOCK); + +#ifndef WIN32 + int flags = fcntl(handle[0], F_GETFD); + fcntl(handle[0], F_SETFL, (flags&O_NONBLOCK)|(b?0:O_NONBLOCK)); + flags = fcntl(handle[1], F_GETFD); + fcntl(handle[1], F_SETFL, (flags&O_NONBLOCK)|(b?0:O_NONBLOCK)); +#endif +} + +unsigned Pipe::do_write(const char *buf, unsigned size) +{ + if(size==0) + return 0; + +#ifdef WIN32 + DWORD ret; + if(!WriteFile(handle[1], buf, size, &ret, 0)) + throw SystemError("Writing to pipe failed", GetLastError()); +#else + int ret = ::write(handle[1], buf, size); + if(ret==-1) + { + if(errno==EAGAIN) + return 0; + else + throw SystemError("Writing to pipe failed", errno); + } +#endif + + return ret; +} + +unsigned Pipe::do_read(char *buf, unsigned size) +{ + if(size==0) + return 0; + +#ifdef WIN32 + // Initiate overlapped read if needed + get_event_handle(); + + if(overlapped) + { + DWORD ret; + if(!GetOverlappedResult(handle[0], overlapped, &ret, !buf_avail)) + throw SystemError("Reading from pipe failed", GetLastError()); + else + { + buf_avail += ret; + delete overlapped; + overlapped = 0; + } + } + + unsigned ret = min(buf_avail, size); + memcpy(buf, buf_next, ret); + buf_next += ret; + buf_avail -= ret; + + // Initiate another overlapped read in case someone is polling us + get_event_handle(); +#else + int ret = ::read(handle[0], buf, size); + if(ret==-1) + { + if(errno==EAGAIN) + return 0; + else + throw SystemError("Reading from pipe failed", errno); + } +#endif + + if(ret==0) + { + eof_flag = true; + signal_end_of_file.emit(); + } + + return ret; +} + +Handle Pipe::get_event_handle() +{ +#ifdef WIN32 + if(!overlapped && !buf_avail) + { + overlapped = new OVERLAPPED; + memset(overlapped, 0, sizeof(OVERLAPPED)); + overlapped->hEvent = event; + + DWORD ret; + buf_next = buffer; + if(!ReadFile(handle[0], buffer, buf_size, &ret, overlapped)) + { + unsigned err = GetLastError(); + if(err!=ERROR_IO_PENDING) + throw SystemError("Failed to start an overlapped read", err); + } + else + { + buf_avail = ret; + delete overlapped; + overlapped = 0; + SetEvent(event); + } + } + + return event; +#else + return handle[0]; +#endif +} + +} // namespace IO +} // namespace Msp diff --git a/source/io/pipe.h b/source/io/pipe.h new file mode 100644 index 0000000..71edf7e --- /dev/null +++ b/source/io/pipe.h @@ -0,0 +1,41 @@ +#ifndef MSP_IO_PIPE_H_ +#define MSP_IO_PIPE_H_ + +#include "base.h" + +namespace Msp { +namespace IO { + +class Pipe: public Base +{ +private: + Handle handle[2]; +#ifdef WIN32 + OVERLAPPED *overlapped; + Handle event; + unsigned buf_size; + char *buffer; + unsigned buf_avail; + char *buf_next; +#endif + +public: + Pipe(); + ~Pipe(); + + void close(); + + void set_block(bool); + +protected: + virtual unsigned do_write(const char *, unsigned); + virtual unsigned do_read(char *, unsigned); + +public: + virtual Handle get_event_handle(); +}; + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/poll.cpp b/source/io/poll.cpp new file mode 100644 index 0000000..dce1912 --- /dev/null +++ b/source/io/poll.cpp @@ -0,0 +1,212 @@ +#include +#include +#include +#include "except.h" +#include "base.h" +#include "poll.h" + +namespace { + +using namespace Msp; +using namespace Msp::IO; + +inline int sys_poll_event(PollEvent event) +{ + int result = 0; + + if(event&~(P_INPUT|P_PRIO|P_OUTPUT)) + throw InvalidParameterValue("Invalid poll events"); + +#ifndef WIN32 + if(event&P_INPUT) + result |= POLLIN; + if(event&P_PRIO) + result |= POLLPRI; + if(event&P_OUTPUT) + result |= POLLOUT; +#endif + + return result; +} + +inline PollEvent poll_event_from_sys(int event) +{ + PollEvent result = P_NONE; + +#ifdef WIN32 + // Stop the compiler from complaining about unused parameter + event = event; +#else + if(event&POLLIN) + result = result|P_INPUT; + if(event&POLLPRI) + result = result|P_PRIO; + if(event&POLLOUT) + result = result|P_OUTPUT; + if(event&POLLERR) + result = result|P_ERROR; +#endif + + return result; +} + +inline PollEvent do_poll(Base &obj, PollEvent pe, int timeout) +{ +#ifdef WIN32 + if(timeout<0) + timeout = INFINITE; + + DWORD ret = WaitForSingleObject(obj.get_event_handle(), timeout); + if(ret==WAIT_OBJECT_0) + return pe; + else if(ret==WAIT_FAILED) + throw SystemError("Poll failed", GetLastError()); + + return P_NONE; +#else + pollfd pfd = {obj.get_event_handle(), sys_poll_event(pe), 0}; + + int ret = ::poll(&pfd, 1, timeout); + if(ret==-1) + { + if(errno==EINTR) + return P_NONE; + else + throw SystemError("Poll failed", errno); + } + + return poll_event_from_sys(pfd.revents); +#endif +} + +} + +namespace Msp { +namespace IO { + +Poller::Poller(): + pfd_dirty(false) +{ } + +void Poller::set_object(Base &obj, PollEvent ev) +{ + // Verify that the object has an event handle + if(ev) + obj.get_event_handle(); + + SlotMap::iterator i = objects.find(&obj); + if(i!=objects.end()) + { + if(ev) + i->second.events = ev; + else + objects.erase(i); + + pfd_dirty = true; + } + else if(ev) + { +#ifdef WIN32 + if(objects.size()>=MAXIMUM_WAIT_OBJECTS) + throw InvalidState("Maximum number of wait objects reached"); +#endif + objects.insert(SlotMap::value_type(&obj, Slot(&obj, ev))); + + pfd_dirty = true; + } +} + +int Poller::poll() +{ + return do_poll(-1); +} + +int Poller::poll(const Time::TimeDelta &timeout) +{ + if(timeout(timeout/Time::msec)); +} + +void Poller::rebuild_pfd() +{ + pfd.clear(); + + pollfd p; + + for(SlotMap::iterator i=objects.begin(); i!=objects.end(); ++i) + { + p.fd = i->second.object->get_event_handle(); +#ifndef WIN32 + p.events = sys_poll_event(i->second.events); +#endif + pfd.push_back(p); + } + + pfd_dirty = false; +} + +int Poller::do_poll(int timeout) +{ + if(pfd_dirty) + rebuild_pfd(); + + poll_result.clear(); + +#ifdef WIN32 + if(timeout<0) + timeout = INFINITE; + + DWORD ret = WaitForMultipleObjects(pfd.size(), &pfd.front().fd, false, timeout); + if(/*ret>=WAIT_OBJECT_0 &&*/ retsecond.object, i->second.events)); + + return 1; + } + else if(ret==WAIT_FAILED) + throw SystemError("Poll failed", GetLastError()); + + return 0; +#else + int ret = ::poll(&pfd.front(), pfd.size(), timeout); + if(ret==-1) + { + if(errno==EINTR) + return 0; + else + throw SystemError("Poll failed", errno); + } + + int n = ret; + SlotMap::iterator j = objects.begin(); + for(std::vector::iterator i=pfd.begin(); (i!=pfd.end() && n>0); ++i,++j) + if(i->revents) + { + poll_result.push_back(Slot(j->second.object, poll_event_from_sys(i->revents))); + --n; + } + + return ret; +#endif +} + + +PollEvent poll(Base &obj, PollEvent pe) +{ + return do_poll(obj, pe, -1); +} + +PollEvent poll(Base &obj, PollEvent pe, const Time::TimeDelta &timeout) +{ + if(timeout(timeout/Time::msec)); +} + +} // namespace IO +} // namespace Msp diff --git a/source/io/poll.h b/source/io/poll.h new file mode 100644 index 0000000..6367d8f --- /dev/null +++ b/source/io/poll.h @@ -0,0 +1,82 @@ +#ifndef MSP_IO_POLL_H_ +#define MSP_IO_POLL_H_ + +#ifndef WIN32 +#include +#endif +#include +#include +#include +#include +#include "types.h" + +namespace Msp { +namespace IO { + +class Base; + +enum PollEvent +{ + P_NONE = 0, + P_INPUT = 1, + P_PRIO = 2, + P_OUTPUT = 4, + P_ERROR = 8 +}; + +inline PollEvent operator|(PollEvent e, PollEvent f) +{ return PollEvent(static_cast(e)|static_cast(f)); } + +inline PollEvent operator&(PollEvent e, PollEvent f) +{ return PollEvent(static_cast(e)&static_cast(f)); } + +inline PollEvent operator~(PollEvent e) +{ return PollEvent(~static_cast(e)); } + + +class Poller +{ +public: + struct Slot + { + Base *object; + PollEvent events; + + Slot(Base *o, PollEvent e): object(o), events(e) { } + }; + + typedef std::list SlotSeq; +private: + typedef std::map SlotMap; + +#ifdef WIN32 + struct pollfd + { + Handle fd; + }; +#endif + + SlotMap objects; + std::vector pfd; + bool pfd_dirty; + SlotSeq poll_result; + + void rebuild_pfd(); + int do_poll(int); + +public: + Poller(); + + void set_object(Base &, PollEvent); + int poll(); + int poll(const Time::TimeDelta &); + const SlotSeq &get_result() const { return poll_result; } +}; + +PollEvent poll(Base &, PollEvent); +PollEvent poll(Base &, PollEvent, const Time::TimeDelta &); + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/print.h b/source/io/print.h new file mode 100644 index 0000000..65744cb --- /dev/null +++ b/source/io/print.h @@ -0,0 +1,66 @@ +#ifndef MSP_IO_PRINT_H_ +#define MSP_IO_PRINT_H_ + +#include +#include "base.h" +#include "console.h" + +namespace Msp { +namespace IO { + +/** +Writes a string to an I/O object. Same as o.write(f). Provided for +completeness with the other print functions. +*/ +inline unsigned print(Base &o, const std::string &f) +{ return o.write(f); } + +template +inline unsigned print(Base &o, const std::string &f, A1 a1) +{ return print(o, format(f, a1)); } + +template +inline unsigned print(Base &o, const std::string &f, A1 a1, A2 a2) +{ return print(o, format(f, a1, a2)); } + +template +inline unsigned print(Base &o, const std::string &f, A1 a1, A2 a2, A3 a3) +{ return print(o, format(f, a1, a2, a3)); } + +template +inline unsigned print(Base &o, const std::string &f, A1 a1, A2 a2, A3 a3, A4 a4) +{ return print(o, format(f, a1, a2, a3, a4)); } + +template +inline unsigned print(Base &o, const std::string &f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) +{ return print(o, format(f, a1, a2, a3, a4, a5)); } + +/* The following functions print to console */ + +inline unsigned print(const std::string &f) +{ return print(cout, f); } + +template +inline unsigned print(const std::string &f, A1 a1) +{ return print(cout, f, a1); } + +template +inline unsigned print(const std::string &f, A1 a1, A2 a2) +{ return print(cout, f, a1, a2); } + +template +inline unsigned print(const std::string &f, A1 a1, A2 a2, A3 a3) +{ return print(cout, f, a1, a2, a3); } + +template +inline unsigned print(const std::string &f, A1 a1, A2 a2, A3 a3, A4 a4) +{ return print(cout, f, a1, a2, a3, a4); } + +template +inline unsigned print(const std::string &f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) +{ return print(cout, f, a1, a2, a3, a4, a5); } + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/seek.cpp b/source/io/seek.cpp new file mode 100644 index 0000000..b10c5a0 --- /dev/null +++ b/source/io/seek.cpp @@ -0,0 +1,32 @@ +#ifdef WIN32 +#include +#endif +#include "except.h" +#include "seek.h" + +namespace Msp { +namespace IO { + +int sys_seek_type(SeekType st) +{ +#ifdef WIN32 + if(st==S_BEG) + return FILE_BEGIN; + else if(st==S_CUR) + return FILE_CURRENT; + else if(st==S_END) + return FILE_END; +#else + if(st==S_BEG) + return SEEK_SET; + else if(st==S_CUR) + return SEEK_CUR; + else if(st==S_END) + return SEEK_END; +#endif + + throw InvalidParameterValue("Invalid seek type"); +} + +} // namespace IO +} // namespace Msp diff --git a/source/io/seek.h b/source/io/seek.h new file mode 100644 index 0000000..3c6c331 --- /dev/null +++ b/source/io/seek.h @@ -0,0 +1,19 @@ +#ifndef MSP_IO_SEEK_H_ +#define MSP_IO_SEEK_H_ + +namespace Msp { +namespace IO { + +enum SeekType +{ + S_BEG, + S_CUR, + S_END +}; + +extern int sys_seek_type(SeekType); + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/serial.cpp b/source/io/serial.cpp new file mode 100644 index 0000000..ec6a198 --- /dev/null +++ b/source/io/serial.cpp @@ -0,0 +1,340 @@ +#ifdef WIN32 +#include +#else +#include +#include +#include +#endif +#include +#include "except.h" +#include "serial.h" + +using namespace std; + +namespace { + +using namespace Msp; +using namespace Msp::IO; + +#ifdef WIN32 +typedef DCB DeviceState; +#else +typedef termios DeviceState; +#endif + +void get_state(Handle handle, DeviceState &state) +{ +#ifdef WIN32 + GetCommState(handle, &state); +#else + tcgetattr(handle, &state); +#endif +} + +void set_state(Handle handle, DeviceState &state) +{ +#ifdef WIN32 + if(SetCommState(handle, &state)==0) + throw SystemError("Cannot set serial port parameters", GetLastError()); +#else + if(tcsetattr(handle, TCSADRAIN, &state)==-1) + throw SystemError("Cannot set serial port parameters", errno); +#endif +} + +void set_baud_rate(DeviceState &state, unsigned baud) +{ +#ifdef WIN32 + state.BaudRate = baud; +#else + speed_t speed; + switch(baud) + { + case 0: speed = B0; break; + case 50: speed = B50; break; + case 75: speed = B75; break; + case 110: speed = B110; break; + case 134: speed = B134; break; + case 150: speed = B150; break; + case 200: speed = B200; break; + case 300: speed = B300; break; + case 600: speed = B600; break; + case 1200: speed = B1200; break; + case 1800: speed = B1800; break; + case 2400: speed = B2400; break; + case 4800: speed = B4800; break; + case 9600: speed = B9600; break; + case 19200: speed = B19200; break; + case 38400: speed = B38400; break; + case 57600: speed = B57600; break; + case 115200: speed = B115200; break; + case 230400: speed = B230400; break; + default: throw InvalidParameterValue("Invalid baud rate"); + } + + cfsetospeed(&state, speed); + cfsetispeed(&state, speed); +#endif +} + +void set_data_bits(DeviceState &state, unsigned bits) +{ +#ifdef WIN32 + state.ByteSize = bits; +#else + tcflag_t flag; + switch(bits) + { + case 5: flag = CS5; break; + case 6: flag = CS6; break; + case 7: flag = CS7; break; + case 8: flag = CS8; break; + default: throw InvalidParameterValue("Invalid data bit count"); + } + + state.c_cflag = (state.c_cflag&~CSIZE)|flag; +#endif +} + +void set_parity(DeviceState &state, Serial::Parity par) +{ +#ifdef WIN32 + switch(par) + { + case Serial::NONE: state.Parity = NOPARITY; break; + case Serial::EVEN: state.Parity = EVENPARITY; break; + case Serial::ODD: state.Parity = ODDPARITY; break; + default: throw InvalidParameterValue("Invalid parity"); + } +#else + tcflag_t flag; + switch(par) + { + case Serial::NONE: flag = 0; break; + case Serial::EVEN: flag = PARENB; break; + case Serial::ODD: flag = PARENB|PARODD; break; + default: throw InvalidParameterValue("Invalid parity"); + } + + state.c_cflag = (state.c_cflag&~(PARENB|PARODD))|flag; +#endif +} + +void set_stop_bits(DeviceState &state, unsigned bits) +{ +#ifdef WIN32 + switch(bits) + { + case 1: state.StopBits = ONESTOPBIT; break; + case 2: state.StopBits = TWOSTOPBITS; break; + default: throw InvalidParameterValue("Invalid stop bit count"); + } +#else + tcflag_t flag; + switch(bits) + { + case 1: flag = 0; break; + case 2: flag = CSTOPB; break; + default: throw InvalidParameterValue("Invalid stop bit count"); + } + + state.c_cflag = (state.c_cflag&~CSTOPB)|flag; +#endif +} + +} + + +namespace Msp { +namespace IO { + +Serial::Serial(const string &descr) +{ + string::size_type comma = descr.find(','); + string port = descr.substr(0, comma); + +#ifdef WIN32 + port = "\\\\.\\"+port; + + handle = CreateFile(port.c_str(), GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + if(handle==INVALID_HANDLE_VALUE) + throw SystemError(format("Can't open serial port '%s'", port), GetLastError()); + mode = M_READ|M_WRITE; + + COMMTIMEOUTS timeouts; + timeouts.ReadIntervalTimeout = MAXDWORD; + timeouts.ReadTotalTimeoutMultiplier = MAXDWORD; + timeouts.ReadTotalTimeoutConstant = MAXDWORD-1; + timeouts.WriteTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 0; + SetCommTimeouts(handle, &timeouts); +#else + if(port.compare(0, 5, "/dev/")) + port = "/dev/"+port; + + handle = open(port.c_str(), O_RDWR); + if(handle==-1) + throw SystemError(format("Can't open serial port '%s'", port), errno); + mode = M_READ|M_WRITE; + + termios t; + tcgetattr(handle, &t); + t.c_lflag &= ~(ECHO|ICANON); + t.c_oflag &= ~OPOST; + tcsetattr(handle, TCSADRAIN, &t); +#endif + + if(comma!=string::npos) + { + try + { + set_parameters(descr.substr(comma+1)); + } + catch(...) + { + close(); + throw; + } + } + + set_events(P_INPUT); +} + +Serial::~Serial() +{ + close(); +} + +void Serial::close() +{ +#ifdef WIN32 + CloseHandle(handle); +#else + ::close(handle); +#endif +} + +void Serial::set_block(bool b) +{ + if(b) + mode = mode|M_NONBLOCK; + else + mode = mode&~M_NONBLOCK; + +#ifndef WIN32 + int flags = fcntl(handle, F_GETFD); + fcntl(handle, F_SETFL, (flags&O_NONBLOCK)|(b?0:O_NONBLOCK)); +#endif +} + +void Serial::set_baud_rate(unsigned rate) +{ + DeviceState state; + get_state(handle, state); + ::set_baud_rate(state, rate); + set_state(handle, state); +} + +void Serial::set_data_bits(unsigned bits) +{ + DeviceState state; + get_state(handle, state); + ::set_data_bits(state, bits); + set_state(handle, state); +} + +void Serial::set_parity(Parity par) +{ + DeviceState state; + get_state(handle, state); + ::set_parity(state, par); + set_state(handle, state); +} + +void Serial::set_stop_bits(unsigned bits) +{ + DeviceState state; + get_state(handle, state); + ::set_stop_bits(state, bits); + set_state(handle, state); +} + +void Serial::set_parameters(const string ¶ms) +{ + unsigned i; + for(i=0; i'8') + throw InvalidParameterValue("Invalid data bit count"); + if(params[i+2]!='N' && params[i+2]!='E' && params[i+2]!='O') + throw InvalidParameterValue("Invalid parity"); + if(params[i+3]!='1' && params[i+3]!='2') + throw InvalidParameterValue("Invalid stop bit count"); + + DeviceState state; + get_state(handle, state); + ::set_baud_rate(state, lexical_cast(params.substr(0, i))); + ::set_data_bits(state, params[i+1]-'0'); + ::set_parity(state, (params[i+2]=='E' ? EVEN : params[i+2]=='O' ? ODD : NONE)); + ::set_stop_bits(state, params[i+3]-'0'); + set_state(handle, state); +} + +unsigned Serial::do_write(const char *buf, unsigned size) +{ + if(size==0) + return 0; + +#ifdef WIN32 + DWORD ret; + if(WriteFile(handle, buf, size, &ret, 0)==0) + throw SystemError("Writing to serial port failed", GetLastError()); +#else + int ret = ::write(handle, buf, size); + if(ret==-1) + { + if(errno==EAGAIN) + return 0; + else + throw SystemError("Writing to serial port failed", errno); + } +#endif + + return ret; +} + +unsigned Serial::do_read(char *buf, unsigned size) +{ + if(size==0) + return 0; + +#ifdef WIN32 + DWORD ret; + if(ReadFile(handle, buf, size, &ret, 0)==0) + throw SystemError("Reading from serial port failed", GetLastError()); +#else + int ret = ::read(handle, buf, size); + if(ret==-1) + { + if(errno==EAGAIN) + return 0; + else + throw SystemError("Reading from serial port failed", errno); + } +#endif + + return ret; +} + +Handle Serial::get_event_handle() +{ +#ifdef WIN32 + throw Exception("Serial port events not supported on win32 yet"); +#else + return handle; +#endif +} + +} // namespace IO +} // namespace Msp diff --git a/source/io/serial.h b/source/io/serial.h new file mode 100644 index 0000000..069789c --- /dev/null +++ b/source/io/serial.h @@ -0,0 +1,49 @@ +#ifndef MSP_IO_SERIAL_H_ +#define MSP_IO_SERIAL_H_ + +#include "base.h" + +namespace Msp { +namespace IO { + +class Serial: public Base +{ +public: + enum Parity + { + NONE, + EVEN, + ODD + }; + +private: + Handle handle; + +public: + Serial(const std::string &); + virtual ~Serial(); + +private: + void close(); + +public: + virtual void set_block(bool); + + void set_baud_rate(unsigned); + void set_data_bits(unsigned); + void set_parity(Parity); + void set_stop_bits(unsigned); + void set_parameters(const std::string &); + +private: + virtual unsigned do_write(const char *, unsigned); + virtual unsigned do_read(char *, unsigned); + +public: + virtual Handle get_event_handle(); +}; + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/types.h b/source/io/types.h new file mode 100644 index 0000000..f18b7cb --- /dev/null +++ b/source/io/types.h @@ -0,0 +1,22 @@ +#ifndef MSP_IO_TYPES_H_ +#define MSP_IO_TYPES_H_ + +#ifdef WIN32 +#include +#endif + +namespace Msp { +namespace IO { + +#ifdef WIN32 +typedef HANDLE Handle; +#define MSP_IO_INVALID_HANDLE INVALID_HANDLE_VALUE +#else +typedef int Handle; +#define MSP_IO_INVALID_HANDLE -1 +#endif + +} // namespace IO +} // namespace Msp + +#endif diff --git a/source/io/utils.cpp b/source/io/utils.cpp new file mode 100644 index 0000000..eee51cc --- /dev/null +++ b/source/io/utils.cpp @@ -0,0 +1,17 @@ +#include "base.h" +#include "utils.h" + +namespace Msp { +namespace IO { + +unsigned read_all(Base &obj, char *buf, unsigned size) +{ + unsigned pos = 0; + while(pos