Initial revision
authorMikko Rasa <tdb@tdb.fi>
Tue, 12 Jun 2007 12:07:58 +0000 (12:07 +0000)
committerMikko Rasa <tdb@tdb.fi>
Tue, 12 Jun 2007 12:07:58 +0000 (12:07 +0000)
20 files changed:
Build [new file with mode: 0644]
source/base.cpp [new file with mode: 0644]
source/base.h [new file with mode: 0644]
source/buffered.cpp [new file with mode: 0644]
source/buffered.h [new file with mode: 0644]
source/error.h [new file with mode: 0644]
source/eventdispatcher.cpp [new file with mode: 0644]
source/eventdispatcher.h [new file with mode: 0644]
source/file.cpp [new file with mode: 0644]
source/file.h [new file with mode: 0644]
source/mode.h [new file with mode: 0644]
source/pipe.cpp [new file with mode: 0644]
source/pipe.h [new file with mode: 0644]
source/poll.cpp [new file with mode: 0644]
source/poll.h [new file with mode: 0644]
source/seek.cpp [new file with mode: 0644]
source/seek.h [new file with mode: 0644]
source/types.h [new file with mode: 0644]
source/utils.cpp [new file with mode: 0644]
source/utils.h [new file with mode: 0644]

diff --git a/Build b/Build
new file mode 100644 (file)
index 0000000..daf676f
--- /dev/null
+++ b/Build
@@ -0,0 +1,13 @@
+package "mspio"
+{
+       require "mspstrings";
+       require "mspcore";
+       require "sigc++-2.0";
+
+       library "mspio"
+       {
+               source "source";
+               install true;
+               install_headers "msp/io";
+       };
+};
diff --git a/source/base.cpp b/source/base.cpp
new file mode 100644 (file)
index 0000000..174bace
--- /dev/null
@@ -0,0 +1,70 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#include "base.h"
+#include "poll.h"
+
+using namespace std;
+
+#include <iostream>
+
+namespace Msp {
+namespace IO {
+
+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<unsigned char>(c);
+}
+
+void Base::event(PollEvent ev)
+{
+       if(ev&P_INPUT)
+               signal_data_available.emit();
+
+       on_event(ev);
+}
+
+Base::~Base()
+{
+       signal_deleted.emit();
+}
+
+Base::Base():
+       mode(M_READ),
+       events(P_NONE),
+       eof_flag(false)
+{ }
+
+void Base::set_events(PollEvent e)
+{
+       //cout<<"object "<<this<<" set_events "<<e<<'\n';
+       events=e;
+       signal_events_changed.emit(events);
+}
+
+} // namespace IO
+} // namespace Msp
diff --git a/source/base.h b/source/base.h
new file mode 100644 (file)
index 0000000..b4b8627
--- /dev/null
@@ -0,0 +1,161 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#ifndef MSP_IO_BASE_H_
+#define MSP_IO_BASE_H_
+
+#include <sigc++/sigc++.h>
+#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<void> signal_data_available;
+
+       /**
+       Emitted when there is no more data to be read.
+       */
+       sigc::signal<void> signal_end_of_file;
+
+       /**
+       Emitted when the I/O object is about to close.  Mainly intended for
+       buffering objects that need to flush their buffers at closing.
+       */
+       sigc::signal<void> signal_closing;
+
+       /**
+       Emitted when the I/O object has closed.
+       */
+       sigc::signal<void> signal_closed;
+
+       /**
+       Emitted when the mask of interesting events changes.  Mainly for use by
+       EventDispatcher.
+       */
+       sigc::signal<void, PollEvent> signal_events_changed;
+
+       /**
+       Emitted when the object is deleted.  Mainly for use by EventDispatcher.
+       */
+       sigc::signal<void> signal_deleted;
+
+       /**
+       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; }
+
+       /**
+       Writes data from a buffer.  Subject to blocking.
+
+       @param   b  Buffer to write from
+       @param   c  Number of bytes to write
+
+       @return  Number of bytes written
+       */
+       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.
+
+       @param   b  Buffer to read into
+       @param   c  Maximum number of bytes to read
+
+       @return  Number of bytes read
+       */
+       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.
+
+       @param   line  String to put the characters into
+
+       @return  Whether anything was read
+       */
+       virtual bool getline(std::string &);
+
+       /**
+       Reads a single character.
+
+       @return  A character, or -1 if none were available
+       */
+       virtual int get();
+
+       /**
+       Returns the end-of-file flag.
+       */
+       bool eof() const { return eof_flag; }
+
+       /**
+       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);
+
+       virtual ~Base();
+protected:
+       Mode mode;
+       PollEvent events;
+       bool eof_flag;
+
+       Base();
+       void             set_events(PollEvent);
+       virtual void     on_event(PollEvent) { }
+       virtual unsigned do_write(const char *, unsigned) =0;
+       virtual unsigned do_read(char *, unsigned) =0;
+private:
+       Base(const Base &);
+       Base &operator=(const Base &);
+};
+
+} // namespace IO
+} // namespace Msp
+
+#endif
diff --git a/source/buffered.cpp b/source/buffered.cpp
new file mode 100644 (file)
index 0000000..4cd5cb4
--- /dev/null
@@ -0,0 +1,204 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#include "buffered.h"
+#include "error.h"
+
+using namespace std;
+
+namespace Msp {
+namespace IO {
+
+Buffered::Buffered(Base &b, unsigned s):
+       below(b),
+       buf_size(s),
+       in_buf(new char[buf_size]),
+       in_ptr(in_buf),
+       in_avail(0),
+       out_buf(new char[buf_size]),
+       out_used(0)
+{
+       mode=below.get_mode();
+       below.signal_closing.connect(sigc::mem_fun(this, &Buffered::below_closing));
+}
+
+unsigned Buffered::put(char c)
+{
+       if(out_used<buf_size)
+       {
+               out_buf[out_used++]=c;
+               return 1;
+       }
+       else
+               return do_write(&c, 1);
+}
+
+void Buffered::flush()
+{
+       if(out_used==0)
+               return;
+
+       unsigned len=below.write(out_buf, out_used);
+       if(len<out_used)
+       {
+               memmove(out_buf, out_buf+len, out_used-len);
+               out_used-=len;
+               throw Exception("Couldn't flush all data");
+       }
+       out_used=0;
+}
+
+bool Buffered::getline(std::string &line)
+{
+       for(unsigned i=0; i<in_avail; ++i)
+               if(in_ptr[i]=='\n')
+               {
+                       line.assign(in_ptr, i);
+                       in_ptr+=i+1;
+                       in_avail-=i+1;
+                       return true;
+               }
+
+       return Base::getline(line);
+}
+
+int Buffered::get()
+{
+       if(in_avail>0)
+       {
+               --in_avail;
+               return *in_ptr++;
+       }
+
+       char c;
+       if(do_read(&c, 1)==0)
+               return -1;
+       return static_cast<unsigned char>(c);
+}
+
+Handle Buffered::get_event_handle()
+{
+       throw Exception("Buffered doesn't support events");
+}
+
+Buffered::~Buffered()
+{
+       try
+       {
+               flush();
+       }
+       catch(...)
+       { }
+
+       delete[] in_buf;
+       delete[] out_buf;
+}
+
+void Buffered::below_closing()
+{
+       flush();
+}
+
+unsigned Buffered::do_write(const char *buf, unsigned size)
+{
+       if(out_used+size<buf_size)
+       {
+               // All data fits in the buffer
+               memcpy(out_buf+out_used, buf, size);
+               out_used+=size;
+
+               return size;
+       }
+       else
+       {
+               int ret=0;
+               bool ok=true;
+
+               while(size>0)
+               {
+                       // XXX sub-obtimal - should try to write directly from input first
+                       // Fill the buffer and pass it on
+                       unsigned head=min(buf_size-out_used, size);
+
+                       memcpy(out_buf+out_used, buf, head);
+                       out_used+=head;
+
+                       buf+=head;
+                       size-=head;
+                       ret+=head;
+
+                       if(!ok) break;
+
+                       unsigned len=below.write(out_buf, out_used);
+
+                       ok=(len==out_used);
+                       if(ok)
+                               out_used=0;
+                       else
+                       {
+                               memmove(out_buf, out_buf+len, buf_size-len);
+                               out_used=buf_size-len;
+                       }
+               }
+
+               return ret;
+       }
+}
+
+unsigned Buffered::do_read(char *buf, unsigned size)
+{
+       if(size<=in_avail)
+       {
+               // The request can be served from the buffer
+               memcpy(buf, in_ptr, size);
+               in_ptr+=size;
+               in_avail-=size;
+
+               return size;
+       }
+       else
+       {
+               // Use whatever is left in the buffer
+               memcpy(buf, in_ptr, in_avail);
+
+               buf+=in_avail;
+               size-=in_avail;
+               int ret=in_avail;
+
+               in_ptr=in_buf;
+               in_avail=0;
+
+               if(size>=buf_size)
+                       ret+=below.read(buf, size);
+               else
+               {
+                       // Read more data into the buffer
+                       while(size>0)
+                       {
+                               in_avail=below.read(in_buf, buf_size);
+                               if(in_avail==0)
+                               {
+                                       eof_flag=true;
+                                       break;
+                               }
+
+                               unsigned head=min(size, in_avail);
+                               memcpy(buf, in_buf, head);
+                               buf+=head;
+                               size-=head;
+
+                               in_ptr=in_buf+head;
+                               in_avail-=head;
+                               ret+=head;
+                       }
+               }
+
+               return ret;
+       }
+}
+
+} // namespace IO
+} // namespace Msp
diff --git a/source/buffered.h b/source/buffered.h
new file mode 100644 (file)
index 0000000..b6584c1
--- /dev/null
@@ -0,0 +1,43 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#ifndef MSP_IO_BUFFERED_H_
+#define MSP_IO_BUFFERED_H_
+
+#include "base.h"
+
+namespace Msp {
+namespace IO {
+
+class Buffered: public Base
+{
+public:
+       Buffered(Base &, unsigned =8192);
+       unsigned put(char);
+       void flush();
+       bool getline(std::string &);
+       int  get();
+       int  tell() const;
+       Handle get_event_handle();
+       ~Buffered();
+private:
+       Base     &below;
+       unsigned buf_size;
+       char     *in_buf;
+       char     *in_ptr;
+       unsigned in_avail;
+       char     *out_buf;
+       unsigned out_used;
+
+       void  below_closing();
+       unsigned do_write(const char *, unsigned);
+       unsigned do_read(char *, unsigned);
+};
+
+} // namespace IO
+} // namespace Msp
+
+#endif
diff --git a/source/error.h b/source/error.h
new file mode 100644 (file)
index 0000000..8ebcf38
--- /dev/null
@@ -0,0 +1,34 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#ifndef MSP_IO_ERROR_H_
+#define MSP_IO_ERROR_H_
+
+#include <msp/core/error.h>
+
+namespace Msp {
+namespace IO {
+
+class Exception: public Msp::Exception
+{
+public:
+       Exception(const std::string &w_): Msp::Exception(w_) { }
+};
+
+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/eventdispatcher.cpp b/source/eventdispatcher.cpp
new file mode 100644 (file)
index 0000000..04147e9
--- /dev/null
@@ -0,0 +1,80 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#include <msp/time/units.h>
+#include "base.h"
+#include "eventdispatcher.h"
+#include "poll.h"
+
+#include <iostream>
+using namespace std;
+
+namespace Msp {
+namespace IO {
+
+EventDispatcher::EventDispatcher()
+{ }
+
+void EventDispatcher::add(Base &obj)
+{
+       //cout<<"evdisp add "<<&obj<<'\n';
+       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)
+{
+       //cout<<"evdisp remove "<<&obj<<'\n';
+       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(bool block)
+{
+       if(objects.empty())
+               return;
+
+       int ret;
+       if(block)
+               ret=poller.poll();
+       else
+               ret=poller.poll(Time::zero);
+
+       if(ret>0)
+       {
+               const Poller::SlotSeq &result=poller.get_result();
+               for(Poller::SlotSeq::const_iterator i=result.begin(); i!=result.end(); ++i)
+                       i->object->event(i->events);
+       }
+}
+
+void EventDispatcher::object_events_changed(PollEvent ev, Base *obj)
+{
+       poller.set_object(*obj, ev);
+}
+
+void EventDispatcher::object_deleted(Base *obj)
+{
+       remove(*obj);
+}
+
+} // namespace IO
+} // namespace Msp
diff --git a/source/eventdispatcher.h b/source/eventdispatcher.h
new file mode 100644 (file)
index 0000000..9bea6d2
--- /dev/null
@@ -0,0 +1,53 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#ifndef EVENTDISPATCHER_H_
+#define EVENTDISPATCHER_H_
+
+#include <sigc++/connection.h>
+#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:
+       EventDispatcher();
+       void add(Base &);
+       void remove(Base &);
+
+       /**
+       Checks for and dispatches events.  If block is true, will block until events
+       are available.
+       */
+       void tick(bool =true);
+private:
+       struct Slot
+       {
+               Base *obj;
+               sigc::connection evch_conn;
+               sigc::connection del_conn;
+
+               Slot(Base *o): obj(o) { }
+       };
+       typedef std::map<Base *, Slot> SlotMap;
+
+       Poller poller;
+       SlotMap objects;
+
+       void object_events_changed(PollEvent, Base *);
+       void object_deleted(Base *);
+};
+
+} // namespace IO
+} // namespace Msp
+
+#endif
diff --git a/source/file.cpp b/source/file.cpp
new file mode 100644 (file)
index 0000000..9b1e433
--- /dev/null
@@ -0,0 +1,282 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#ifndef WIN32
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+#include <msp/strings/formatter.h>
+#include "error.h"
+#include "file.h"
+
+using namespace std;
+
+namespace Msp {
+namespace IO {
+
+/**
+Creates a new file object and opens it.  If the
+create flag is true and write access is requested and the file does exist, it
+is created.  Otherwise a missing file is an error.
+
+@param   fn  Name of the file to open
+@param   m   Open mode
+@param   cm  Flags controlling creation of a new file
+*/
+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<int>(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)
+               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);
+}
+
+/**
+Closes the file.  Any attempt to access the file after this will cause an
+exception to be thrown.
+*/
+void File::close()
+{
+       if(handle==MSP_IO_INVALID_HANDLE)
+               return;
+
+       set_events(P_NONE);
+
+       signal_closing.emit();
+
+#ifdef WIN32
+       CloseHandle(handle);
+#else
+       ::close(handle);
+#endif
+
+       handle=MSP_IO_INVALID_HANDLE;
+       signal_closed.emit();
+}
+
+/**
+Sets the blocking state of the file.  If blocking is disabled, all operations
+will return immediately, even if they can't be fully completed.
+*/
+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
+}
+
+/**
+Seeks the file to the given byte offset.
+
+@param   off  Offset in bytes
+@param   st   Seek type
+
+@return  The resulting offset
+*/
+int File::seek(int off, SeekType st)
+{
+       check_access(M_NONE);
+
+       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;
+}
+
+/**
+Returns the current read/write offset of the file.
+*/
+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;
+}
+
+File::~File()
+{
+       close();
+}
+
+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");
+}
+
+/**
+Writes data from a buffer to the file.
+
+@param   buf   Buffer to write from.
+@param   size  Length of data to write.
+
+@return  The number of bytes written
+*/
+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;
+}
+
+void File::sync()
+{
+#ifndef WIN32
+       fsync(handle);
+#endif
+}
+
+/**
+Reads data from the file.
+
+@param   buf   Buffer to read data into.
+@param   size  Maximum size of data to read.
+
+@return  The number of bytes read, possibly zero
+*/
+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;
+}
+
+} // namespace IO
+} // namespace Msp
diff --git a/source/file.h b/source/file.h
new file mode 100644 (file)
index 0000000..49b596b
--- /dev/null
@@ -0,0 +1,67 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#ifndef MSP_IO_FILE_H_
+#define MSP_IO_FILE_H_
+
+#include <string>
+#include "base.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
+       };
+
+       File(const std::string &, Mode=M_READ, CreateMode =CreateMode(C_CREATE+C_TRUNCATE));
+
+       void close();
+
+       void set_block(bool);
+       void enable_events();
+
+       void sync();
+
+       int  seek(int, SeekType);
+       int  tell() const;
+
+       Handle get_event_handle() { return handle; }
+
+       ~File();
+private:
+       Handle handle;
+
+       void      check_access(Mode) const;
+       unsigned  do_write(const char *, unsigned);
+       unsigned  do_read(char *, unsigned);
+};
+
+inline File::CreateMode operator|(File::CreateMode m, File::CreateMode n)
+{ return File::CreateMode((int)m|(int)n); }
+
+inline File::CreateMode operator&(File::CreateMode m, File::CreateMode n)
+{ return File::CreateMode((int)m&(int)n); }
+
+inline File::CreateMode operator~(File::CreateMode m)
+{ return File::CreateMode(~(int)m); }
+
+} // namespace IO
+} // namespace Msp
+
+#endif
diff --git a/source/mode.h b/source/mode.h
new file mode 100644 (file)
index 0000000..1d944b1
--- /dev/null
@@ -0,0 +1,35 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#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((int)m|(int)n); }
+
+inline Mode operator&(Mode m, Mode n)
+{ return Mode((int)m&(int)n); }
+
+inline Mode operator~(Mode m)
+{ return Mode(~(int)m); }
+
+} // namespace IO
+} // namespace Msp
+
+#endif
diff --git a/source/pipe.cpp b/source/pipe.cpp
new file mode 100644 (file)
index 0000000..88dd462
--- /dev/null
@@ -0,0 +1,182 @@
+#ifndef WIN32
+#include <fcntl.h>
+#include <errno.h>
+#endif
+#include <msp/strings/formatter.h>
+#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);
+}
+
+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
+}
+
+void Pipe::close()
+{
+       set_events(P_NONE);
+
+       signal_closing.emit();
+#ifdef WIN32
+       CloseHandle(handle[0]);
+       CloseHandle(handle[1]);
+#else
+       ::close(handle[0]);
+       ::close(handle[1]);
+       signal_closed.emit();
+#endif
+}
+
+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
+}
+
+Pipe::~Pipe()
+{
+       close();
+}
+
+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;
+}
+
+} // namespace IO
+} // namespace Msp
diff --git a/source/pipe.h b/source/pipe.h
new file mode 100644 (file)
index 0000000..e8e1f53
--- /dev/null
@@ -0,0 +1,41 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#ifndef MSP_IO_PIPE_H_
+#define MSP_IO_PIPE_H_
+
+#include "base.h"
+
+namespace Msp {
+namespace IO {
+
+class Pipe: public Base
+{
+public:
+       Pipe();
+       void set_block(bool);
+       void close();
+       Handle get_event_handle();
+       ~Pipe();
+private:
+       Handle handle[2];
+#ifdef WIN32
+       OVERLAPPED *overlapped;
+       Handle     event;
+       unsigned   buf_size;
+       char       *buffer;
+       unsigned   buf_avail;
+       char       *buf_next;
+#endif
+
+       unsigned do_write(const char *, unsigned);
+       unsigned do_read(char *, unsigned);
+};
+
+} // namespace IO
+} // namespace Msp
+
+#endif
diff --git a/source/poll.cpp b/source/poll.cpp
new file mode 100644 (file)
index 0000000..f8f6c51
--- /dev/null
@@ -0,0 +1,223 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#include <errno.h>
+#include <msp/strings/formatter.h>
+#include <msp/time/units.h>
+#include "error.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();
+
+       //cout<<"poller set_object "<<&obj<<' '<<ev<<'\n';
+
+       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<Time::zero)
+               throw InvalidParameterValue("Invalid timeout");
+
+       return do_poll(static_cast<int>(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();
+
+       if(pfd.empty())
+               throw InvalidState("Nothing to poll");
+
+#ifdef WIN32
+       if(timeout<0)
+               timeout=INFINITE;
+
+       DWORD ret=WaitForMultipleObjects(pfd.size(), reinterpret_cast<HANDLE *>(&pfd.front()), false, timeout);
+       if(/*ret>=WAIT_OBJECT_0 &&*/ ret<WAIT_OBJECT_0+pfd.size())
+       {
+               SlotMap::iterator i=objects.begin();
+               advance(i, ret-WAIT_OBJECT_0);
+               poll_result.push_back(Slot(i->second.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<pollfd>::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<Time::zero)
+               throw InvalidParameterValue("Invalid timeout");
+
+       return do_poll(obj, pe, static_cast<int>(timeout/Time::msec));
+}
+
+} // namespace IO
+} // namespace Msp
diff --git a/source/poll.h b/source/poll.h
new file mode 100644 (file)
index 0000000..6ac6149
--- /dev/null
@@ -0,0 +1,84 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#ifndef MSP_IO_POLL_H_
+#define MSP_IO_POLL_H_
+
+#ifndef WIN32
+#include <poll.h>
+#endif
+#include <list>
+#include <map>
+#include <vector>
+#include <msp/time/timedelta.h>
+#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((int)e|(int)f); }
+
+inline PollEvent operator&(PollEvent e, PollEvent f)
+{ return PollEvent((int)e&(int)f); }
+
+inline PollEvent operator~(PollEvent e)
+{ return PollEvent(~(int)e); }
+
+class Poller
+{
+public:
+       struct Slot
+       {
+               Base *object;
+               PollEvent events;
+
+               Slot(Base *o, PollEvent e): object(o), events(e) { }
+       };
+       typedef std::list<Slot> SlotSeq;
+
+       Poller();
+       void set_object(Base &, PollEvent);
+       int  poll();
+       int  poll(const Time::TimeDelta &);
+       const SlotSeq &get_result() const { return poll_result; }
+private:
+       typedef std::map<Base *, Slot> SlotMap;
+
+#ifdef WIN32
+       struct pollfd
+       {
+               Handle fd;
+       };
+#endif
+
+       SlotMap objects;
+       std::vector<pollfd> pfd;
+       bool pfd_dirty;
+       SlotSeq poll_result;
+
+       void rebuild_pfd();
+       int do_poll(int);
+};
+
+PollEvent poll(Base &, PollEvent);
+PollEvent poll(Base &, PollEvent, const Time::TimeDelta &);
+
+} // namespace IO
+} // namespace Msp
+
+#endif
diff --git a/source/seek.cpp b/source/seek.cpp
new file mode 100644 (file)
index 0000000..f2d381e
--- /dev/null
@@ -0,0 +1,38 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#ifdef WIN32
+#include <windows.h>
+#endif
+#include <msp/core/error.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/seek.h b/source/seek.h
new file mode 100644 (file)
index 0000000..6d15437
--- /dev/null
@@ -0,0 +1,25 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#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/types.h b/source/types.h
new file mode 100644 (file)
index 0000000..bb8b923
--- /dev/null
@@ -0,0 +1,28 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#ifndef MSP_IO_TYPES_H_
+#define MSP_IO_TYPES_H_
+
+#ifdef WIN32
+#include <windows.h>
+#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/utils.cpp b/source/utils.cpp
new file mode 100644 (file)
index 0000000..8ce5bf3
--- /dev/null
@@ -0,0 +1,23 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#include "base.h"
+#include "utils.h"
+
+namespace Msp {
+namespace IO {
+
+unsigned read_all(Base &obj, char *buf, unsigned size)
+{
+       unsigned pos=0;
+       while(pos<size)
+               pos+=obj.read(buf+pos, size-pos);
+
+       return pos;
+}
+
+} // namespace IO
+} // namespace Msp
diff --git a/source/utils.h b/source/utils.h
new file mode 100644 (file)
index 0000000..fc51d45
--- /dev/null
@@ -0,0 +1,27 @@
+/* $Id$
+
+This file is part of libmspio
+Copyright © 2007 Mikko Rasa, Mikkosoft Productions
+Distributed under the LGPL
+*/
+#ifndef MSP_IO_UTILS_H_
+#define MSP_IO_UTILS_H_
+
+namespace Msp {
+namespace IO {
+
+class Base;
+
+/**
+Reads data from an object.  Does not return until the requested amount of data
+is read, regardless of the blocking mode of the object.
+
+Note: If the data is not immediately available and the object is in non-blocking
+mode, this function effectively becomes a busyloop until it can get more data.
+*/
+unsigned read_all(Base &, char *, unsigned);
+
+} // namespace IO
+} // namespace Msp
+
+#endif